@wordpress-gcb/fields 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conditional-logic.js +83 -0
- package/{src → dist}/control-context.js +3 -2
- package/{src → dist}/controls/MediaCapabilityGate.js +12 -8
- package/dist/controls/MediaPicker.js +149 -0
- package/dist/controls/MediaTriggerBadges.js +35 -0
- package/{src → dist}/controls/PopoverOrModal.js +49 -43
- package/dist/controls/SortableItem.js +126 -0
- package/dist/controls/button-group.js +46 -0
- package/dist/controls/checkbox-group.js +65 -0
- package/dist/controls/checkbox.js +15 -0
- package/dist/controls/code.js +24 -0
- package/dist/controls/color.js +249 -0
- package/dist/controls/date.js +55 -0
- package/dist/controls/datetime.js +61 -0
- package/dist/controls/email.js +17 -0
- package/dist/controls/file.js +163 -0
- package/dist/controls/gallery.js +371 -0
- package/dist/controls/google-map.js +143 -0
- package/dist/controls/heading-level.js +93 -0
- package/dist/controls/icon.js +292 -0
- package/dist/controls/image.js +360 -0
- package/dist/controls/index.js +88 -0
- package/dist/controls/message.js +86 -0
- package/dist/controls/number.js +19 -0
- package/dist/controls/oembed.js +42 -0
- package/{src → dist}/controls/page-link.js +1 -2
- package/dist/controls/post-object.js +913 -0
- package/dist/controls/radio.js +19 -0
- package/dist/controls/range.js +108 -0
- package/{src → dist}/controls/relationship.js +12 -7
- package/dist/controls/repeater.js +277 -0
- package/dist/controls/richtext.js +494 -0
- package/dist/controls/select.js +144 -0
- package/dist/controls/size.js +59 -0
- package/dist/controls/spacing.js +172 -0
- package/dist/controls/taxonomy.js +569 -0
- package/dist/controls/text.js +16 -0
- package/dist/controls/textarea.js +17 -0
- package/dist/controls/toggle-group.js +28 -0
- package/dist/controls/toggle.js +15 -0
- package/dist/controls/url.js +235 -0
- package/dist/controls/user.js +383 -0
- package/{src → dist}/controls/wysiwyg.js +1 -1
- package/{src → dist}/hooks/useTokens.js +25 -21
- package/{src → dist}/index.js +2 -8
- package/dist/inspector.js +163 -0
- package/{src → dist}/provider.js +18 -17
- package/dist/utils/map-utils.js +54 -0
- package/dist/utils/token-helper.js +396 -0
- package/{src → dist}/validation-context.js +4 -4
- package/package.json +20 -13
- package/src/conditional-logic.js +0 -77
- package/src/controls/MediaPicker.js +0 -139
- package/src/controls/MediaTriggerBadges.js +0 -31
- package/src/controls/SortableItem.js +0 -110
- package/src/controls/button-group.js +0 -49
- package/src/controls/checkbox-group.js +0 -55
- package/src/controls/checkbox.js +0 -13
- package/src/controls/code.js +0 -21
- package/src/controls/color.js +0 -235
- package/src/controls/date.js +0 -37
- package/src/controls/datetime.js +0 -54
- package/src/controls/email.js +0 -15
- package/src/controls/file.js +0 -134
- package/src/controls/gallery.js +0 -338
- package/src/controls/google-map.js +0 -117
- package/src/controls/heading-level.js +0 -99
- package/src/controls/icon.js +0 -301
- package/src/controls/image.js +0 -334
- package/src/controls/index.js +0 -95
- package/src/controls/message.js +0 -56
- package/src/controls/number.js +0 -17
- package/src/controls/oembed.js +0 -32
- package/src/controls/post-object.js +0 -788
- package/src/controls/radio.js +0 -18
- package/src/controls/range.js +0 -110
- package/src/controls/repeater.js +0 -290
- package/src/controls/richtext.js +0 -505
- package/src/controls/select.js +0 -141
- package/src/controls/size.js +0 -49
- package/src/controls/spacing.js +0 -141
- package/src/controls/taxonomy.js +0 -488
- package/src/controls/text.js +0 -14
- package/src/controls/textarea.js +0 -15
- package/src/controls/toggle-group.js +0 -34
- package/src/controls/toggle.js +0 -13
- package/src/controls/url.js +0 -164
- package/src/controls/user.js +0 -343
- package/src/inspector.js +0 -174
- package/src/utils/map-utils.js +0 -51
- package/src/utils/token-helper.js +0 -243
package/{src → dist}/index.js
RENAMED
|
@@ -16,13 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
// Injected-config boundary.
|
|
19
|
-
export {
|
|
20
|
-
GcbFieldsProvider,
|
|
21
|
-
GcbFieldsContext,
|
|
22
|
-
useGcbFieldsConfig,
|
|
23
|
-
useTokensConfig,
|
|
24
|
-
useGoogleMapsEnabled,
|
|
25
|
-
} from './provider';
|
|
19
|
+
export { GcbFieldsProvider, GcbFieldsContext, useGcbFieldsConfig, useTokensConfig, useGoogleMapsEnabled } from './provider';
|
|
26
20
|
|
|
27
21
|
// Inspector renderer + the control registry.
|
|
28
22
|
export { renderInspector } from './inspector';
|
|
@@ -35,4 +29,4 @@ export { ControlContext } from './control-context';
|
|
|
35
29
|
|
|
36
30
|
// Token helpers (for custom token-aware UI outside the standard controls).
|
|
37
31
|
export { useTokens, getTokensByGroup, generateMapFromTokens } from './hooks/useTokens';
|
|
38
|
-
export { getAllTokenGroups } from './utils/token-helper';
|
|
32
|
+
export { getAllTokenGroups } from './utils/token-helper';
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render an Inspector panel tree from a block's `gcb.controls` array.
|
|
3
|
+
*
|
|
4
|
+
* Controls are flat in the JSON; structure comes from `parentPanelId` references
|
|
5
|
+
* to `type: "group"` controls. We bucket controls under their group and emit one
|
|
6
|
+
* <PanelBody> per group, plus a default panel for anything ungrouped.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { PanelBody } from '@wordpress/components';
|
|
10
|
+
import { Fragment, useContext } from '@wordpress/element';
|
|
11
|
+
import { __ } from '@wordpress/i18n';
|
|
12
|
+
import { controlComponents } from './controls';
|
|
13
|
+
import { ValidationContext } from './validation-context';
|
|
14
|
+
import { STRUCTURAL_TYPES, shouldRender, panelsContainingErrors } from './conditional-logic';
|
|
15
|
+
|
|
16
|
+
// Re-export so existing callers keep working (post-fields.js imports
|
|
17
|
+
// shouldRender + panelsContainingErrors from './inspector').
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
+
export { shouldRender, panelsContainingErrors };
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {Array} controls
|
|
23
|
+
* @param {Object} attributes
|
|
24
|
+
* @param {Function} setAttributes
|
|
25
|
+
* @param {Object} [options]
|
|
26
|
+
* @param {boolean} [options.flatten] When true and no groups exist, render
|
|
27
|
+
* ungrouped controls flat (no outer "Settings" PanelBody). Used by the
|
|
28
|
+
* post-fields meta-box where the meta-box itself IS the panel — nesting
|
|
29
|
+
* another PanelBody would look like a redundant dropdown.
|
|
30
|
+
* @param {Set<string>} [options.forceOpenPanelIds] Panel ids to render
|
|
31
|
+
* with initialOpen=true. Used by the meta-box to auto-open any panel
|
|
32
|
+
* containing a field that just failed validation, so the user can see
|
|
33
|
+
* the offending field.
|
|
34
|
+
*/
|
|
35
|
+
export function renderInspector(controls, attributes, setAttributes, options = {}) {
|
|
36
|
+
const {
|
|
37
|
+
groups,
|
|
38
|
+
ungrouped
|
|
39
|
+
} = bucketControls(controls);
|
|
40
|
+
const flatten = options.flatten === true && groups.length === 0;
|
|
41
|
+
const forceOpen = options.forceOpenPanelIds || new Set();
|
|
42
|
+
return /*#__PURE__*/_jsxs(Fragment, {
|
|
43
|
+
children: [ungrouped.length > 0 && (flatten ? ungrouped.map(control => renderControl(control, attributes, setAttributes)) : /*#__PURE__*/_jsx(PanelBody, {
|
|
44
|
+
title: __('Settings', 'gcblite'),
|
|
45
|
+
initialOpen: true,
|
|
46
|
+
children: ungrouped.map(control => renderControl(control, attributes, setAttributes))
|
|
47
|
+
})), groups.map(({
|
|
48
|
+
group,
|
|
49
|
+
children
|
|
50
|
+
}) => /*#__PURE__*/_jsx(PanelBody
|
|
51
|
+
// Remount the panel when its forced-open status changes so
|
|
52
|
+
// the new initialOpen value is honoured. (PanelBody only
|
|
53
|
+
// reads initialOpen on mount.)
|
|
54
|
+
, {
|
|
55
|
+
title: group.label,
|
|
56
|
+
initialOpen: forceOpen.has(group.id),
|
|
57
|
+
children: children.map(control => renderControl(control, attributes, setAttributes))
|
|
58
|
+
}, `${group.id}:${forceOpen.has(group.id) ? 'open' : 'closed'}`))]
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function bucketControls(controls) {
|
|
62
|
+
const groupsById = new Map();
|
|
63
|
+
const groupOrder = [];
|
|
64
|
+
const ungrouped = [];
|
|
65
|
+
|
|
66
|
+
// First pass: register structural controls (group / panel / tools-panel)
|
|
67
|
+
// in declaration order.
|
|
68
|
+
controls.forEach(control => {
|
|
69
|
+
if (STRUCTURAL_TYPES.has(control.type) && control.id) {
|
|
70
|
+
groupsById.set(control.id, {
|
|
71
|
+
group: control,
|
|
72
|
+
children: []
|
|
73
|
+
});
|
|
74
|
+
groupOrder.push(control.id);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Second pass: assign each non-structural control to a panel or to ungrouped.
|
|
79
|
+
controls.forEach(control => {
|
|
80
|
+
if (STRUCTURAL_TYPES.has(control.type)) return;
|
|
81
|
+
const parentId = control.parentPanelId;
|
|
82
|
+
if (parentId && groupsById.has(parentId)) {
|
|
83
|
+
groupsById.get(parentId).children.push(control);
|
|
84
|
+
} else {
|
|
85
|
+
ungrouped.push(control);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
groups: groupOrder.map(id => groupsById.get(id)),
|
|
90
|
+
ungrouped
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function renderControl(control, attributes, setAttributes) {
|
|
94
|
+
if (!shouldRender(control, attributes)) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const Component = controlComponents[control.type];
|
|
98
|
+
if (!Component) {
|
|
99
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
100
|
+
style: {
|
|
101
|
+
padding: 8,
|
|
102
|
+
background: '#fff3cd',
|
|
103
|
+
border: '1px solid #ffeeba',
|
|
104
|
+
marginBottom: 8
|
|
105
|
+
},
|
|
106
|
+
children: [/*#__PURE__*/_jsx("strong", {
|
|
107
|
+
children: control.label
|
|
108
|
+
}), ": unknown control type ", /*#__PURE__*/_jsx("code", {
|
|
109
|
+
children: control.type
|
|
110
|
+
})]
|
|
111
|
+
}, control.id);
|
|
112
|
+
}
|
|
113
|
+
const value = attributes[control.attributeKey];
|
|
114
|
+
const onChange = next => setAttributes({
|
|
115
|
+
[control.attributeKey]: next
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Wrap each rendered control in a ValidationWrapper that overlays the
|
|
119
|
+
// required `*`, the inline error message, and the data-attribute used
|
|
120
|
+
// by the meta-box submit interceptor to scroll-into-view. The wrapper
|
|
121
|
+
// is invisible in surfaces without validation (sidebar default
|
|
122
|
+
// ValidationContext is { errors:{}, showErrors:false }) so blocks
|
|
123
|
+
// don't see any change.
|
|
124
|
+
return /*#__PURE__*/_jsx(ValidationWrapper, {
|
|
125
|
+
control: control,
|
|
126
|
+
children: /*#__PURE__*/_jsx(Component, {
|
|
127
|
+
control: control,
|
|
128
|
+
value: value,
|
|
129
|
+
onChange: onChange,
|
|
130
|
+
attributes: attributes
|
|
131
|
+
})
|
|
132
|
+
}, control.id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Per-field decorator: stamps data-gcblite-field for scroll-to-error,
|
|
137
|
+
* shows the required asterisk, and surfaces the inline error message
|
|
138
|
+
* once the host has flipped showErrors on.
|
|
139
|
+
*/
|
|
140
|
+
function ValidationWrapper({
|
|
141
|
+
control,
|
|
142
|
+
children
|
|
143
|
+
}) {
|
|
144
|
+
const {
|
|
145
|
+
errors,
|
|
146
|
+
showErrors
|
|
147
|
+
} = useContext(ValidationContext);
|
|
148
|
+
const key = control.attributeKey;
|
|
149
|
+
const required = !!control.validation?.required;
|
|
150
|
+
const errorMessage = showErrors && key ? errors[key] : null;
|
|
151
|
+
if (!required && !errorMessage && !key) {
|
|
152
|
+
return children;
|
|
153
|
+
}
|
|
154
|
+
return /*#__PURE__*/_jsxs("div", {
|
|
155
|
+
"data-gcblite-field": key || undefined,
|
|
156
|
+
className: ['gcblite-field', required ? 'gcblite-field--required' : '', errorMessage ? 'gcblite-field--has-error' : ''].filter(Boolean).join(' '),
|
|
157
|
+
children: [children, errorMessage && /*#__PURE__*/_jsx("p", {
|
|
158
|
+
className: "gcblite-field__error",
|
|
159
|
+
role: "alert",
|
|
160
|
+
children: errorMessage
|
|
161
|
+
})]
|
|
162
|
+
});
|
|
163
|
+
}
|
package/{src → dist}/provider.js
RENAMED
|
@@ -23,37 +23,38 @@
|
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
25
|
import { createContext, useContext } from '@wordpress/element';
|
|
26
|
-
|
|
26
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
27
27
|
export const GcbFieldsContext = createContext(null);
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
export function GcbFieldsProvider({
|
|
29
|
+
value,
|
|
30
|
+
children
|
|
31
|
+
}) {
|
|
32
|
+
return /*#__PURE__*/_jsx(GcbFieldsContext.Provider, {
|
|
33
|
+
value: value || {},
|
|
34
|
+
children: children
|
|
35
|
+
});
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
/** Legacy global fallback so the plugin keeps working before it injects config. */
|
|
38
39
|
function legacyGlobal() {
|
|
39
|
-
|
|
40
|
+
return typeof window !== 'undefined' && window.gcbLite || {};
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
/** Full resolved config (context first, window.gcbLite fallback). */
|
|
43
44
|
export function useGcbFieldsConfig() {
|
|
44
|
-
|
|
45
|
+
return useContext(GcbFieldsContext) || {};
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/** Design-token tree. */
|
|
48
49
|
export function useTokensConfig() {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const cfg = useContext(GcbFieldsContext) || {};
|
|
51
|
+
if (cfg.tokens !== undefined) return cfg.tokens || {};
|
|
52
|
+
return legacyGlobal().tokens || {};
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/** Whether the google-map control should enable Maps features. */
|
|
55
56
|
export function useGoogleMapsEnabled() {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
57
|
+
const cfg = useContext(GcbFieldsContext) || {};
|
|
58
|
+
if (cfg.googleMapsEnabled !== undefined) return !!cfg.googleMapsEnabled;
|
|
59
|
+
return !!legacyGlobal().googleMaps?.hasApiKey;
|
|
60
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map utilities for fields that translate user-friendly keys to backend tokens.
|
|
3
|
+
*
|
|
4
|
+
* simple: { sm: 'spacing-sm', md: 'spacing-md' }
|
|
5
|
+
* labelled:{ sm: { label: 'Small (16px)', token: 'spacing-sm' } }
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const parseMap = map => {
|
|
9
|
+
if (!map || typeof map !== 'object') return null;
|
|
10
|
+
const out = {};
|
|
11
|
+
Object.entries(map).forEach(([key, value]) => {
|
|
12
|
+
if (typeof value === 'string') {
|
|
13
|
+
out[key] = {
|
|
14
|
+
label: key,
|
|
15
|
+
token: value
|
|
16
|
+
};
|
|
17
|
+
} else if (typeof value === 'object' && value.token) {
|
|
18
|
+
out[key] = {
|
|
19
|
+
label: value.label || key,
|
|
20
|
+
token: value.token
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
25
|
+
};
|
|
26
|
+
export const getTokenFromKey = (m, key) => m && key ? m[key]?.token || null : null;
|
|
27
|
+
export const getKeyFromToken = (m, token) => {
|
|
28
|
+
if (!m || !token) return null;
|
|
29
|
+
const entry = Object.entries(m).find(([, v]) => v.token === token);
|
|
30
|
+
return entry ? entry[0] : null;
|
|
31
|
+
};
|
|
32
|
+
export const mapToOptions = m => {
|
|
33
|
+
if (!m) return [];
|
|
34
|
+
return Object.entries(m).map(([key, v]) => ({
|
|
35
|
+
label: v.label,
|
|
36
|
+
value: key
|
|
37
|
+
}));
|
|
38
|
+
};
|
|
39
|
+
export const getMapKeys = m => {
|
|
40
|
+
if (!m) return [];
|
|
41
|
+
return Object.keys(m).map(k => Number(k)).filter(k => !Number.isNaN(k)).sort((a, b) => a - b);
|
|
42
|
+
};
|
|
43
|
+
export const mapToRangeMarks = m => {
|
|
44
|
+
if (!m) return null;
|
|
45
|
+
return Object.entries(m).map(([key, v]) => ({
|
|
46
|
+
value: Number(key),
|
|
47
|
+
label: v.label
|
|
48
|
+
}));
|
|
49
|
+
};
|
|
50
|
+
export const isValidMapValue = (m, value) => {
|
|
51
|
+
if (!m || value === null || value === undefined) return false;
|
|
52
|
+
if (m[value]) return true;
|
|
53
|
+
return Object.values(m).some(e => e.token === value);
|
|
54
|
+
};
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token helpers — ported from the original GCB.
|
|
3
|
+
*
|
|
4
|
+
* Two layers of tokens:
|
|
5
|
+
* - Built-in groups (this file): hard-coded design system tokens that ship
|
|
6
|
+
* with the plugin. Useful as a fallback when a theme has no theme.json.
|
|
7
|
+
* - Theme groups (window.gcbLite.tokens): parsed from theme.json by PHP.
|
|
8
|
+
* Always merged in by getTokenGroupTokens() below — theme tokens win on
|
|
9
|
+
* a path collision.
|
|
10
|
+
*
|
|
11
|
+
* Token path syntax: "category:subKey" or "category:subKey:tokenKey".
|
|
12
|
+
* "spacing:scale" — the group of tokens
|
|
13
|
+
* "spacing:scale:30" — a specific token
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const builtInTokenGroups = {
|
|
17
|
+
color: {
|
|
18
|
+
label: 'Color',
|
|
19
|
+
children: {
|
|
20
|
+
palette: {
|
|
21
|
+
label: 'Palette',
|
|
22
|
+
tokens: [{
|
|
23
|
+
key: 'primary',
|
|
24
|
+
value: 'color-primary',
|
|
25
|
+
label: 'Primary'
|
|
26
|
+
}, {
|
|
27
|
+
key: 'secondary',
|
|
28
|
+
value: 'color-secondary',
|
|
29
|
+
label: 'Secondary'
|
|
30
|
+
}, {
|
|
31
|
+
key: 'accent',
|
|
32
|
+
value: 'color-accent',
|
|
33
|
+
label: 'Accent'
|
|
34
|
+
}, {
|
|
35
|
+
key: 'neutral',
|
|
36
|
+
value: 'color-neutral',
|
|
37
|
+
label: 'Neutral'
|
|
38
|
+
}, {
|
|
39
|
+
key: 'dark',
|
|
40
|
+
value: 'color-dark',
|
|
41
|
+
label: 'Dark'
|
|
42
|
+
}, {
|
|
43
|
+
key: 'light',
|
|
44
|
+
value: 'color-light',
|
|
45
|
+
label: 'Light'
|
|
46
|
+
}]
|
|
47
|
+
},
|
|
48
|
+
duotone: {
|
|
49
|
+
label: 'Duotone',
|
|
50
|
+
tokens: [{
|
|
51
|
+
key: 'blue',
|
|
52
|
+
value: 'duotone-blue',
|
|
53
|
+
label: 'Blue Duotone'
|
|
54
|
+
}, {
|
|
55
|
+
key: 'purple',
|
|
56
|
+
value: 'duotone-purple',
|
|
57
|
+
label: 'Purple Duotone'
|
|
58
|
+
}, {
|
|
59
|
+
key: 'green',
|
|
60
|
+
value: 'duotone-green',
|
|
61
|
+
label: 'Green Duotone'
|
|
62
|
+
}]
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
spacing: {
|
|
67
|
+
label: 'Spacing',
|
|
68
|
+
children: {
|
|
69
|
+
scale: {
|
|
70
|
+
label: 'Scale',
|
|
71
|
+
tokens: [{
|
|
72
|
+
key: '0',
|
|
73
|
+
value: 'spacing-none',
|
|
74
|
+
label: 'None (0)',
|
|
75
|
+
size: '0'
|
|
76
|
+
}, {
|
|
77
|
+
key: '10',
|
|
78
|
+
value: 'spacing-10',
|
|
79
|
+
label: 'Step 1 (0.25rem)',
|
|
80
|
+
size: '0.25rem'
|
|
81
|
+
}, {
|
|
82
|
+
key: '20',
|
|
83
|
+
value: 'spacing-20',
|
|
84
|
+
label: 'Step 2 (0.5rem)',
|
|
85
|
+
size: '0.5rem'
|
|
86
|
+
}, {
|
|
87
|
+
key: '30',
|
|
88
|
+
value: 'spacing-30',
|
|
89
|
+
label: 'Step 3 (1rem)',
|
|
90
|
+
size: '1rem'
|
|
91
|
+
}, {
|
|
92
|
+
key: '40',
|
|
93
|
+
value: 'spacing-40',
|
|
94
|
+
label: 'Step 4 (1.5rem)',
|
|
95
|
+
size: '1.5rem'
|
|
96
|
+
}, {
|
|
97
|
+
key: '50',
|
|
98
|
+
value: 'spacing-50',
|
|
99
|
+
label: 'Step 5 (2rem)',
|
|
100
|
+
size: '2rem'
|
|
101
|
+
}, {
|
|
102
|
+
key: '60',
|
|
103
|
+
value: 'spacing-60',
|
|
104
|
+
label: 'Step 6 (3rem)',
|
|
105
|
+
size: '3rem'
|
|
106
|
+
}, {
|
|
107
|
+
key: '70',
|
|
108
|
+
value: 'spacing-70',
|
|
109
|
+
label: 'Step 7 (4rem)',
|
|
110
|
+
size: '4rem'
|
|
111
|
+
}, {
|
|
112
|
+
key: '80',
|
|
113
|
+
value: 'spacing-80',
|
|
114
|
+
label: 'Step 8 (6rem)',
|
|
115
|
+
size: '6rem'
|
|
116
|
+
}]
|
|
117
|
+
},
|
|
118
|
+
presets: {
|
|
119
|
+
label: 'Presets',
|
|
120
|
+
tokens: [{
|
|
121
|
+
key: 'xs',
|
|
122
|
+
value: 'spacing-xs',
|
|
123
|
+
label: 'Extra Small (0.5rem)',
|
|
124
|
+
size: '0.5rem'
|
|
125
|
+
}, {
|
|
126
|
+
key: 'sm',
|
|
127
|
+
value: 'spacing-sm',
|
|
128
|
+
label: 'Small (1rem)',
|
|
129
|
+
size: '1rem'
|
|
130
|
+
}, {
|
|
131
|
+
key: 'md',
|
|
132
|
+
value: 'spacing-md',
|
|
133
|
+
label: 'Medium (1.5rem)',
|
|
134
|
+
size: '1.5rem'
|
|
135
|
+
}, {
|
|
136
|
+
key: 'lg',
|
|
137
|
+
value: 'spacing-lg',
|
|
138
|
+
label: 'Large (2rem)',
|
|
139
|
+
size: '2rem'
|
|
140
|
+
}, {
|
|
141
|
+
key: 'xl',
|
|
142
|
+
value: 'spacing-xl',
|
|
143
|
+
label: 'Extra Large (3rem)',
|
|
144
|
+
size: '3rem'
|
|
145
|
+
}, {
|
|
146
|
+
key: '2xl',
|
|
147
|
+
value: 'spacing-2xl',
|
|
148
|
+
label: '2X Large (4rem)',
|
|
149
|
+
size: '4rem'
|
|
150
|
+
}, {
|
|
151
|
+
key: '3xl',
|
|
152
|
+
value: 'spacing-3xl',
|
|
153
|
+
label: '3X Large (6rem)',
|
|
154
|
+
size: '6rem'
|
|
155
|
+
}]
|
|
156
|
+
},
|
|
157
|
+
semantic: {
|
|
158
|
+
label: 'Semantic',
|
|
159
|
+
tokens: [{
|
|
160
|
+
key: 'content',
|
|
161
|
+
value: 'spacing-content',
|
|
162
|
+
label: 'Content',
|
|
163
|
+
size: 'var(--wp--style--block-gap, 1.5rem)'
|
|
164
|
+
}, {
|
|
165
|
+
key: 'section',
|
|
166
|
+
value: 'spacing-section',
|
|
167
|
+
label: 'Section',
|
|
168
|
+
size: 'clamp(2rem, 5vw, 4rem)'
|
|
169
|
+
}, {
|
|
170
|
+
key: 'container',
|
|
171
|
+
value: 'spacing-container',
|
|
172
|
+
label: 'Container',
|
|
173
|
+
size: 'clamp(1rem, 3vw, 2rem)'
|
|
174
|
+
}]
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
typography: {
|
|
179
|
+
label: 'Typography',
|
|
180
|
+
children: {
|
|
181
|
+
fontSize: {
|
|
182
|
+
label: 'Font Sizes',
|
|
183
|
+
tokens: [{
|
|
184
|
+
key: 'xs',
|
|
185
|
+
value: 'text-xs',
|
|
186
|
+
label: 'Extra Small (12px)'
|
|
187
|
+
}, {
|
|
188
|
+
key: 'sm',
|
|
189
|
+
value: 'text-sm',
|
|
190
|
+
label: 'Small (14px)'
|
|
191
|
+
}, {
|
|
192
|
+
key: 'base',
|
|
193
|
+
value: 'text-base',
|
|
194
|
+
label: 'Base (16px)'
|
|
195
|
+
}, {
|
|
196
|
+
key: 'lg',
|
|
197
|
+
value: 'text-lg',
|
|
198
|
+
label: 'Large (18px)'
|
|
199
|
+
}, {
|
|
200
|
+
key: 'xl',
|
|
201
|
+
value: 'text-xl',
|
|
202
|
+
label: 'Extra Large (20px)'
|
|
203
|
+
}, {
|
|
204
|
+
key: '2xl',
|
|
205
|
+
value: 'text-2xl',
|
|
206
|
+
label: '2X Large (24px)'
|
|
207
|
+
}]
|
|
208
|
+
},
|
|
209
|
+
fontWeight: {
|
|
210
|
+
label: 'Font Weights',
|
|
211
|
+
tokens: [{
|
|
212
|
+
key: 'light',
|
|
213
|
+
value: 'font-light',
|
|
214
|
+
label: 'Light (300)'
|
|
215
|
+
}, {
|
|
216
|
+
key: 'normal',
|
|
217
|
+
value: 'font-normal',
|
|
218
|
+
label: 'Normal (400)'
|
|
219
|
+
}, {
|
|
220
|
+
key: 'medium',
|
|
221
|
+
value: 'font-medium',
|
|
222
|
+
label: 'Medium (500)'
|
|
223
|
+
}, {
|
|
224
|
+
key: 'semibold',
|
|
225
|
+
value: 'font-semibold',
|
|
226
|
+
label: 'Semibold (600)'
|
|
227
|
+
}, {
|
|
228
|
+
key: 'bold',
|
|
229
|
+
value: 'font-bold',
|
|
230
|
+
label: 'Bold (700)'
|
|
231
|
+
}]
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
sizing: {
|
|
236
|
+
label: 'Sizing',
|
|
237
|
+
children: {
|
|
238
|
+
containers: {
|
|
239
|
+
label: 'Container Widths',
|
|
240
|
+
tokens: [{
|
|
241
|
+
key: 'narrow',
|
|
242
|
+
value: 'container-narrow',
|
|
243
|
+
label: 'Narrow (600px)'
|
|
244
|
+
}, {
|
|
245
|
+
key: 'normal',
|
|
246
|
+
value: 'container-normal',
|
|
247
|
+
label: 'Normal (1200px)'
|
|
248
|
+
}, {
|
|
249
|
+
key: 'wide',
|
|
250
|
+
value: 'container-wide',
|
|
251
|
+
label: 'Wide (1600px)'
|
|
252
|
+
}, {
|
|
253
|
+
key: 'full',
|
|
254
|
+
value: 'container-full',
|
|
255
|
+
label: 'Full Width'
|
|
256
|
+
}]
|
|
257
|
+
},
|
|
258
|
+
borderRadius: {
|
|
259
|
+
label: 'Border Radius',
|
|
260
|
+
tokens: [{
|
|
261
|
+
key: 'none',
|
|
262
|
+
value: 'radius-none',
|
|
263
|
+
label: 'None'
|
|
264
|
+
}, {
|
|
265
|
+
key: 'sm',
|
|
266
|
+
value: 'radius-sm',
|
|
267
|
+
label: 'Small'
|
|
268
|
+
}, {
|
|
269
|
+
key: 'md',
|
|
270
|
+
value: 'radius-md',
|
|
271
|
+
label: 'Medium'
|
|
272
|
+
}, {
|
|
273
|
+
key: 'lg',
|
|
274
|
+
value: 'radius-lg',
|
|
275
|
+
label: 'Large'
|
|
276
|
+
}, {
|
|
277
|
+
key: 'full',
|
|
278
|
+
value: 'radius-full',
|
|
279
|
+
label: 'Full (Pill)'
|
|
280
|
+
}]
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get all token groups, merging built-ins with theme.json tokens.
|
|
288
|
+
* Theme tokens override built-ins on a path collision.
|
|
289
|
+
*
|
|
290
|
+
* @param {object} [themeTokens] Theme token tree. Defaults to the legacy
|
|
291
|
+
* `window.gcbLite.tokens` global so non-React callers (and the unmigrated
|
|
292
|
+
* plugin) keep working; the SDK's useTokens() hook passes the value from
|
|
293
|
+
* GcbFieldsProvider instead.
|
|
294
|
+
*/
|
|
295
|
+
export function getAllTokenGroups(themeTokens) {
|
|
296
|
+
if (themeTokens === undefined) {
|
|
297
|
+
themeTokens = typeof window !== 'undefined' && window.gcbLite?.tokens || {};
|
|
298
|
+
}
|
|
299
|
+
const merged = {
|
|
300
|
+
...builtInTokenGroups
|
|
301
|
+
};
|
|
302
|
+
Object.keys(themeTokens).forEach(category => {
|
|
303
|
+
if (merged[category]) {
|
|
304
|
+
merged[category] = {
|
|
305
|
+
...merged[category],
|
|
306
|
+
children: {
|
|
307
|
+
...(merged[category].children || {}),
|
|
308
|
+
...(themeTokens[category].children || {})
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
} else {
|
|
312
|
+
merged[category] = themeTokens[category];
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
return merged;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Resolve a token group path to its token list.
|
|
320
|
+
*
|
|
321
|
+
* @param {string} tokenGroup e.g. "spacing:scale", "custom:gap"
|
|
322
|
+
* @returns {Array|null}
|
|
323
|
+
*/
|
|
324
|
+
export function getTokenGroupTokens(tokenGroup) {
|
|
325
|
+
if (!tokenGroup || typeof tokenGroup !== 'string') return null;
|
|
326
|
+
const [categoryKey, subKey] = tokenGroup.split(':');
|
|
327
|
+
if (!categoryKey || !subKey) return null;
|
|
328
|
+
const all = getAllTokenGroups();
|
|
329
|
+
const category = all[categoryKey];
|
|
330
|
+
if (!category?.children) return null;
|
|
331
|
+
return category.children[subKey]?.tokens || null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Convert a token list to {label, value} options for a select/radio control.
|
|
336
|
+
*/
|
|
337
|
+
export function tokensToOptions(tokens) {
|
|
338
|
+
if (!Array.isArray(tokens)) return [];
|
|
339
|
+
return tokens.map(t => ({
|
|
340
|
+
label: t.label,
|
|
341
|
+
value: t.key
|
|
342
|
+
}));
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* For spacing tokens: resolve the raw size value (e.g. "1rem").
|
|
347
|
+
* Accepts a full path ("spacing:scale:30"), a partial ("scale:30"),
|
|
348
|
+
* or just a key ("30") — searches in priority order.
|
|
349
|
+
*/
|
|
350
|
+
export function getSpacingSize(tokenKey) {
|
|
351
|
+
if (!tokenKey) return null;
|
|
352
|
+
const parts = tokenKey.split(':');
|
|
353
|
+
let categoryKey, subKey, key;
|
|
354
|
+
if (parts.length === 3) {
|
|
355
|
+
[categoryKey, subKey, key] = parts;
|
|
356
|
+
} else if (parts.length === 2) {
|
|
357
|
+
categoryKey = 'spacing';
|
|
358
|
+
[subKey, key] = parts;
|
|
359
|
+
} else {
|
|
360
|
+
// Bare key — search every spacing subcategory.
|
|
361
|
+
const spacing = getAllTokenGroups().spacing;
|
|
362
|
+
if (spacing?.children) {
|
|
363
|
+
for (const subCat of Object.values(spacing.children)) {
|
|
364
|
+
const t = subCat.tokens?.find(tok => tok.key === tokenKey);
|
|
365
|
+
if (t?.size) return t.size;
|
|
366
|
+
if (t?.value) return t.value; // theme.json tokens use `value`
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
const cat = getAllTokenGroups()[categoryKey];
|
|
372
|
+
const tok = cat?.children?.[subKey]?.tokens?.find(t => t.key === key);
|
|
373
|
+
return tok?.size || tok?.value || null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Convert a spacing token to the WP CSS custom property.
|
|
378
|
+
* Falls back to var(--wp--preset--spacing--{key}) for unknown tokens.
|
|
379
|
+
*/
|
|
380
|
+
export function spacingTokenToCSSVar(tokenKey) {
|
|
381
|
+
if (!tokenKey) return null;
|
|
382
|
+
const parts = tokenKey.split(':');
|
|
383
|
+
const key = parts.length > 1 ? parts[parts.length - 1] : tokenKey;
|
|
384
|
+
return `var(--wp--preset--spacing--${key})`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Resolve a token to its cssVar, preferring an explicit cssVar property
|
|
389
|
+
* (theme.json tokens have one), falling back to the spacing convention.
|
|
390
|
+
*/
|
|
391
|
+
export function tokenToCSSVar(token) {
|
|
392
|
+
if (!token) return null;
|
|
393
|
+
if (token.cssVar) return token.cssVar;
|
|
394
|
+
if (token.slug) return `var(--wp--preset--spacing--${token.slug})`;
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { createContext } from '@wordpress/element';
|
|
15
|
-
|
|
16
15
|
export const ValidationContext = createContext({
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
errors: {},
|
|
17
|
+
// { attributeKey: 'human message' }
|
|
18
|
+
showErrors: false // becomes true after the first failed save attempt
|
|
19
|
+
});
|