PluginProbe
Extendify / 3.1.6
Extendify v3.1.6
3.2.1 3.2.0 3.1.6 3.1.5 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.6 3.0.5 3.0.4 trunk 0.1.0 0.10.0 0.10.1 0.10.2 0.11.0 0.11.1 0.2.0 0.3.0 0.3.1 0.4.0 0.5.0 0.6.0 All 127 releases
extendify / src / Shared / lib / vibe-globals.js

vibe-globals.js in Extendify 3.1.6, at src/Shared/lib/vibe-globals.js

123 lines 3.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { isObject } from '@shared/lib/utils';
2
3 const ownedSettings = ['layout', 'typography', 'custom'];
4 // Per-leaf, so clearing a heading size leaves fontFamily to the fonts layer.
5 // WordPress reads duotone per block name, never per variation, so blocks is owned here.
6 const ownedStyles = ['typography', 'elements', 'blocks'];
7
8 const leafPaths = (value, prefix) => {
9 // PHP serializes an empty object as []; as a leaf it would purge the branch.
10 if (Array.isArray(value) && value.length === 0) return [];
11 if (!isObject(value)) return [prefix];
12
13 const entries = Object.entries(value);
14 // Vibes ship `elements: {}` to mean none; as a leaf it would purge fonts too.
15 if (entries.length === 0) return [];
16
17 return entries.flatMap(([key, child]) => leafPaths(child, [...prefix, key]));
18 };
19
20 // The fonts and palette layers own these; a declaring payload is ignored.
21 const isExcludedPath = (section, path) => {
22 if (path[path.length - 1] === 'fontFamily') return true;
23 if (section !== 'settings') return false;
24 if (path[0] === 'color') return true;
25 return path[0] === 'typography' && path[1] === 'fontFamilies';
26 };
27
28 const collectOwnedPaths = (vibes, section, roots) => {
29 const paths = new Map();
30
31 for (const vibe of Object.values(vibes)) {
32 const source = vibe?.[section];
33 if (!isObject(source)) continue;
34
35 for (const key of roots) {
36 if (!(key in source)) continue;
37
38 for (const path of leafPaths(source[key], [key])) {
39 if (isExcludedPath(section, path)) continue;
40 paths.set(path.join('.'), path);
41 }
42 }
43 }
44
45 return [...paths.values()];
46 };
47
48 const valueAtPath = (source, path) =>
49 path.reduce(
50 (value, key) => (isObject(value) ? value[key] : undefined),
51 source,
52 );
53
54 const withoutPath = (source, [key, ...rest]) => {
55 if (!isObject(source) || !(key in source)) return source;
56
57 const updated = { ...source };
58
59 if (rest.length === 0) {
60 delete updated[key];
61 return updated;
62 }
63
64 const child = withoutPath(updated[key], rest);
65
66 if (isObject(child) && Object.keys(child).length === 0) {
67 delete updated[key];
68 } else {
69 updated[key] = child;
70 }
71
72 return updated;
73 };
74
75 const withPath = (source, [key, ...rest], value) => {
76 const updated = isObject(source) ? { ...source } : {};
77 updated[key] =
78 rest.length === 0 ? value : withPath(updated[key], rest, value);
79 return updated;
80 };
81
82 // Every vibe's leaves are purged first, so a switch leaves none of the last one behind.
83 // A vibe that declares nothing lets the theme's own theme.json surface again.
84 const applySection = (current, incoming, ownedPaths) => {
85 const purged = ownedPaths.reduce(
86 (section, path) => withoutPath(section, path),
87 current,
88 );
89
90 if (!isObject(incoming)) return purged;
91
92 return ownedPaths.reduce((section, path) => {
93 const value = valueAtPath(incoming, path);
94 return value === undefined ? section : withPath(section, path, value);
95 }, purged);
96 };
97
98 export const ownedSettingPaths = (vibes) =>
99 collectOwnedPaths(vibes, 'settings', ownedSettings);
100
101 export const ownedStylePaths = (vibes) =>
102 collectOwnedPaths(vibes, 'styles', ownedStyles);
103
104 export const applyVibeGlobals = ({ currentSettings, vibeSettings, vibes }) =>
105 applySection(currentSettings, vibeSettings, ownedSettingPaths(vibes));
106
107 export const applyVibeStyles = ({ currentStyles, vibeStyles, vibes }) =>
108 applySection(currentStyles, vibeStyles, ownedStylePaths(vibes));
109
110 // A fonts or colors write may not move vibe-owned leaves.
111 export const preserveVibeSettings = ({
112 mergedSettings,
113 currentSettings,
114 vibes,
115 }) => applySection(mergedSettings, currentSettings, ownedSettingPaths(vibes));
116
117 export const preserveVibeStyles = ({ mergedStyles, currentStyles, vibes }) =>
118 applySection(mergedStyles, currentStyles, ownedStylePaths(vibes));
119
120 // natural-1 is what the theme ships, so writing it would only restate the theme.
121 export const vibeGlobalsEntry = (vibes, selectedVibe) =>
122 selectedVibe === 'natural-1' ? undefined : vibes?.[selectedVibe];
123