@wordpress-gcb/fields 0.2.1 → 0.2.2

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.
Files changed (91) hide show
  1. package/dist/conditional-logic.js +83 -0
  2. package/{src → dist}/control-context.js +3 -2
  3. package/{src → dist}/controls/MediaCapabilityGate.js +12 -8
  4. package/dist/controls/MediaPicker.js +149 -0
  5. package/dist/controls/MediaTriggerBadges.js +35 -0
  6. package/{src → dist}/controls/PopoverOrModal.js +49 -43
  7. package/dist/controls/SortableItem.js +126 -0
  8. package/dist/controls/button-group.js +46 -0
  9. package/dist/controls/checkbox-group.js +65 -0
  10. package/dist/controls/checkbox.js +15 -0
  11. package/dist/controls/code.js +24 -0
  12. package/dist/controls/color.js +241 -0
  13. package/dist/controls/date.js +55 -0
  14. package/dist/controls/datetime.js +61 -0
  15. package/dist/controls/email.js +17 -0
  16. package/dist/controls/file.js +163 -0
  17. package/dist/controls/gallery.js +371 -0
  18. package/dist/controls/google-map.js +143 -0
  19. package/dist/controls/heading-level.js +93 -0
  20. package/dist/controls/icon.js +292 -0
  21. package/dist/controls/image.js +360 -0
  22. package/dist/controls/index.js +88 -0
  23. package/dist/controls/message.js +86 -0
  24. package/dist/controls/number.js +19 -0
  25. package/dist/controls/oembed.js +42 -0
  26. package/{src → dist}/controls/page-link.js +1 -2
  27. package/dist/controls/post-object.js +913 -0
  28. package/dist/controls/radio.js +19 -0
  29. package/dist/controls/range.js +108 -0
  30. package/{src → dist}/controls/relationship.js +12 -7
  31. package/dist/controls/repeater.js +277 -0
  32. package/dist/controls/richtext.js +494 -0
  33. package/dist/controls/select.js +144 -0
  34. package/dist/controls/size.js +59 -0
  35. package/dist/controls/spacing.js +141 -0
  36. package/dist/controls/taxonomy.js +569 -0
  37. package/dist/controls/text.js +16 -0
  38. package/dist/controls/textarea.js +17 -0
  39. package/dist/controls/toggle-group.js +28 -0
  40. package/dist/controls/toggle.js +15 -0
  41. package/dist/controls/url.js +235 -0
  42. package/dist/controls/user.js +383 -0
  43. package/{src → dist}/controls/wysiwyg.js +1 -1
  44. package/{src → dist}/hooks/useTokens.js +25 -21
  45. package/{src → dist}/index.js +2 -8
  46. package/dist/inspector.js +163 -0
  47. package/{src → dist}/provider.js +18 -17
  48. package/dist/utils/map-utils.js +54 -0
  49. package/dist/utils/token-helper.js +396 -0
  50. package/{src → dist}/validation-context.js +4 -4
  51. package/package.json +20 -13
  52. package/src/conditional-logic.js +0 -77
  53. package/src/controls/MediaPicker.js +0 -139
  54. package/src/controls/MediaTriggerBadges.js +0 -31
  55. package/src/controls/SortableItem.js +0 -110
  56. package/src/controls/button-group.js +0 -49
  57. package/src/controls/checkbox-group.js +0 -55
  58. package/src/controls/checkbox.js +0 -13
  59. package/src/controls/code.js +0 -21
  60. package/src/controls/color.js +0 -235
  61. package/src/controls/date.js +0 -37
  62. package/src/controls/datetime.js +0 -54
  63. package/src/controls/email.js +0 -15
  64. package/src/controls/file.js +0 -134
  65. package/src/controls/gallery.js +0 -338
  66. package/src/controls/google-map.js +0 -117
  67. package/src/controls/heading-level.js +0 -99
  68. package/src/controls/icon.js +0 -301
  69. package/src/controls/image.js +0 -334
  70. package/src/controls/index.js +0 -95
  71. package/src/controls/message.js +0 -56
  72. package/src/controls/number.js +0 -17
  73. package/src/controls/oembed.js +0 -32
  74. package/src/controls/post-object.js +0 -788
  75. package/src/controls/radio.js +0 -18
  76. package/src/controls/range.js +0 -110
  77. package/src/controls/repeater.js +0 -290
  78. package/src/controls/richtext.js +0 -505
  79. package/src/controls/select.js +0 -141
  80. package/src/controls/size.js +0 -49
  81. package/src/controls/spacing.js +0 -141
  82. package/src/controls/taxonomy.js +0 -488
  83. package/src/controls/text.js +0 -14
  84. package/src/controls/textarea.js +0 -15
  85. package/src/controls/toggle-group.js +0 -34
  86. package/src/controls/toggle.js +0 -13
  87. package/src/controls/url.js +0 -164
  88. package/src/controls/user.js +0 -343
  89. package/src/inspector.js +0 -174
  90. package/src/utils/map-utils.js +0 -51
  91. package/src/utils/token-helper.js +0 -243
@@ -1,18 +0,0 @@
1
- import { RadioControl } from '@wordpress/components';
2
-
3
- export default function RadioField({ control, value, onChange }) {
4
- const options = (control.options || []).map((o) => ({
5
- label: o.label,
6
- value: String(o.value),
7
- }));
8
-
9
- return (
10
- <RadioControl
11
- label={control.label}
12
- help={control.helpText}
13
- selected={value != null ? String(value) : ''}
14
- options={options}
15
- onChange={onChange}
16
- />
17
- );
18
- }
@@ -1,110 +0,0 @@
1
- /**
2
- * RangeField — ported verbatim from the original.
3
- * Supports raw numeric ranges, legacy `map` configs, and `tokenGroup` binding.
4
- */
5
-
6
- import { __ } from '@wordpress/i18n';
7
- import { RangeControl, Spinner } from '@wordpress/components';
8
- import { parseMap, getTokenFromKey, getKeyFromToken, getMapKeys, mapToRangeMarks } from '../utils/map-utils';
9
- import { useTokens, getTokensByGroup, generateMapFromTokens } from '../hooks/useTokens';
10
-
11
- function RangeFieldImpl({
12
- label,
13
- value,
14
- onChange,
15
- min = 0,
16
- max = 100,
17
- step = 1,
18
- help,
19
- allowReset = true,
20
- resetFallbackValue,
21
- className = '',
22
- map,
23
- tokenGroup,
24
- defaultOptionKey,
25
- }) {
26
- const { tokens, loading } = useTokens();
27
-
28
- let normalizedMap = null;
29
- if (tokenGroup && tokens) {
30
- const groupTokens = getTokensByGroup(tokens, tokenGroup);
31
- if (groupTokens) {
32
- const generatedMap = generateMapFromTokens(groupTokens);
33
- normalizedMap = generatedMap ? parseMap(generatedMap) : null;
34
- }
35
- } else if (map) {
36
- normalizedMap = parseMap(map);
37
- }
38
-
39
- if (tokenGroup && loading) {
40
- return (
41
- <div style={{ padding: '12px 0' }}>
42
- <div style={{ fontWeight: 500, marginBottom: '8px' }}>{label}</div>
43
- <Spinner />
44
- </div>
45
- );
46
- }
47
-
48
- const allowedKeys = normalizedMap ? getMapKeys(normalizedMap) : null;
49
- const marks = normalizedMap ? mapToRangeMarks(normalizedMap) : null;
50
-
51
- if ((value === undefined || value === null || value === '') && defaultOptionKey && normalizedMap) {
52
- const defaultToken = getTokenFromKey(normalizedMap, defaultOptionKey);
53
- if (defaultToken && onChange) onChange(defaultToken);
54
- }
55
-
56
- let displayValue;
57
- if (normalizedMap && value) {
58
- displayValue = Number(getKeyFromToken(normalizedMap, value)) || value;
59
- } else if (typeof value === 'string' && !isNaN(value)) {
60
- displayValue = Number(value);
61
- } else {
62
- displayValue = value;
63
- }
64
-
65
- const handleChange = (newSliderValue) => {
66
- let finalValue = normalizedMap
67
- ? getTokenFromKey(normalizedMap, String(newSliderValue))
68
- : newSliderValue;
69
- if (onChange) onChange(finalValue);
70
- };
71
-
72
- const effectiveStep = normalizedMap && allowedKeys && allowedKeys.length > 1 ? 1 : step;
73
- const effectiveMin = normalizedMap && allowedKeys?.length ? Math.min(...allowedKeys) : min;
74
- const effectiveMax = normalizedMap && allowedKeys?.length ? Math.max(...allowedKeys) : max;
75
-
76
- return (
77
- <RangeControl
78
- label={label}
79
- value={displayValue}
80
- onChange={handleChange}
81
- min={effectiveMin}
82
- max={effectiveMax}
83
- step={effectiveStep}
84
- marks={marks}
85
- help={help}
86
- allowReset={allowReset}
87
- resetFallbackValue={resetFallbackValue}
88
- className={className}
89
- __nextHasNoMarginBottom
90
- __next40pxDefaultSize
91
- />
92
- );
93
- }
94
-
95
- export default function RangeField({ control, value, onChange }) {
96
- return (
97
- <RangeFieldImpl
98
- label={control.label}
99
- value={value}
100
- onChange={onChange}
101
- min={control.min ?? 0}
102
- max={control.max ?? 100}
103
- step={control.step ?? 1}
104
- help={control.helpText}
105
- map={control.map}
106
- tokenGroup={control.tokenGroup}
107
- defaultOptionKey={control.defaultOptionKey ?? control.default}
108
- />
109
- );
110
- }
@@ -1,290 +0,0 @@
1
- /**
2
- * Repeater Inspector control — multi-row form-of-forms.
3
- *
4
- * Distinct from the `gcb/repeater` block (which is an InnerBlocks-based
5
- * canvas surface). This control lives in the Inspector / post-fields /
6
- * options page, and stores an ARRAY OF OBJECTS — each object a "row"
7
- * shaped by the `fields` sub-config.
8
- *
9
- * {
10
- * attributeKey: 'social_links',
11
- * type: 'repeater',
12
- * label: 'Social links',
13
- * fields: [
14
- * { attributeKey: 'label', type: 'text', label: 'Label' },
15
- * { attributeKey: 'url', type: 'url', label: 'URL' },
16
- * ],
17
- * min: 0,
18
- * max: null, // null = unlimited
19
- * addButtonLabel: 'Add link',
20
- * collapsedTitle: 'label', // which sub-field to show in row header
21
- * }
22
- *
23
- * Stored:
24
- * [
25
- * { _id: 'r1', label: 'GitHub', url: 'https://...' },
26
- * { _id: 'r2', label: 'X', url: 'https://...' },
27
- * ]
28
- *
29
- * Each row carries a stable `_id` so React keys + dnd-kit don't lose
30
- * track during reorders. _id is generated on add and persisted with
31
- * the row.
32
- *
33
- * Sub-fields can be any registered control type — the row body uses
34
- * the same `controlComponents` registry as the top-level Inspector,
35
- * so adding a control type automatically makes it usable inside a
36
- * repeater.
37
- */
38
-
39
- import { __ } from '@wordpress/i18n';
40
- import { useEffect, useState } from '@wordpress/element';
41
- import { Button } from '@wordpress/components';
42
- import {
43
- DndContext,
44
- closestCenter,
45
- PointerSensor,
46
- useSensor,
47
- useSensors,
48
- } from '@dnd-kit/core';
49
- import {
50
- SortableContext,
51
- verticalListSortingStrategy,
52
- useSortable,
53
- arrayMove,
54
- } from '@dnd-kit/sortable';
55
- import { CSS } from '@dnd-kit/utilities';
56
- import { controlComponents } from './index';
57
-
58
- /**
59
- * cheap-enough unique id. Crypto.randomUUID would be better but is
60
- * unsupported in some Playground / older Safari builds we still want to
61
- * cover. Collision risk on a single repeater is negligible.
62
- */
63
- function newRowId() {
64
- return 'r' + Math.random().toString(36).slice(2, 10);
65
- }
66
-
67
- /**
68
- * Read `control.collapsedTitle` (a sub-field attributeKey) and pull the
69
- * matching value off the row. Falls back to the index-based "Item N"
70
- * label so empty rows are still identifiable.
71
- */
72
- function rowTitle(row, control, index) {
73
- const key = control.collapsedTitle;
74
- if (key && typeof row?.[key] === 'string' && row[key].trim() !== '') {
75
- return row[key];
76
- }
77
- return `${__('Item', 'gcblite')} ${index + 1}`;
78
- }
79
-
80
- function SortableRow({
81
- row,
82
- index,
83
- control,
84
- isOpen,
85
- onToggle,
86
- onUpdate,
87
- onRemove,
88
- attributes: parentAttrs,
89
- }) {
90
- const {
91
- attributes,
92
- listeners,
93
- setNodeRef,
94
- transform,
95
- transition,
96
- isDragging,
97
- } = useSortable({ id: row._id });
98
-
99
- const style = {
100
- transform: CSS.Transform.toString(transform),
101
- transition,
102
- opacity: isDragging ? 0.5 : 1,
103
- };
104
-
105
- return (
106
- <div ref={setNodeRef} style={style} className="gcb-repeater-row">
107
- <div className="gcb-repeater-row__header">
108
- <button
109
- type="button"
110
- {...attributes}
111
- {...listeners}
112
- className="gcb-repeater-row__handle"
113
- aria-label={__('Drag to reorder', 'gcblite')}
114
- onClick={(e) => e.preventDefault()}
115
- >
116
- <svg viewBox="0 0 20 20" width="12" height="12">
117
- <path d="M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z" />
118
- </svg>
119
- </button>
120
- <button
121
- type="button"
122
- className="gcb-repeater-row__title"
123
- onClick={onToggle}
124
- aria-expanded={isOpen}
125
- >
126
- <span className="gcb-repeater-row__caret" aria-hidden>
127
- {isOpen ? '▾' : '▸'}
128
- </span>
129
- <span className="gcb-repeater-row__title-text">
130
- {rowTitle(row, control, index)}
131
- </span>
132
- </button>
133
- <button
134
- type="button"
135
- className="gcb-repeater-row__remove"
136
- onClick={onRemove}
137
- aria-label={__('Remove row', 'gcblite')}
138
- >
139
- <svg viewBox="0 0 24 24" width="16" height="16">
140
- <path d="M12 13.06l3.712 3.713 1.061-1.06L13.061 12l3.712-3.712-1.06-1.06L12 10.938 8.288 7.227l-1.061 1.06L10.939 12l-3.712 3.712 1.06 1.061L12 13.061z" />
141
- </svg>
142
- </button>
143
- </div>
144
-
145
- {isOpen && (
146
- <div className="gcb-repeater-row__body">
147
- {(control.fields || []).map((sub) => {
148
- const SubComponent = controlComponents[sub.type];
149
- if (!SubComponent) {
150
- return (
151
- <div
152
- key={sub.attributeKey}
153
- style={{ padding: 8, background: '#fff3cd', border: '1px solid #ffeeba', marginBottom: 8 }}
154
- >
155
- <strong>{sub.label}</strong>:{' '}
156
- {__('unknown control type', 'gcblite')}{' '}
157
- <code>{sub.type}</code>
158
- </div>
159
- );
160
- }
161
- return (
162
- <SubComponent
163
- key={sub.attributeKey}
164
- control={sub}
165
- value={row?.[sub.attributeKey]}
166
- onChange={(next) =>
167
- onUpdate({ ...row, [sub.attributeKey]: next })
168
- }
169
- attributes={parentAttrs}
170
- />
171
- );
172
- })}
173
- </div>
174
- )}
175
- </div>
176
- );
177
- }
178
-
179
- export default function RepeaterField({ control, value, onChange, attributes }) {
180
- const rows = Array.isArray(value) ? value : [];
181
- const [openId, setOpenId] = useState(null);
182
-
183
- const min = typeof control.min === 'number' ? control.min : 0;
184
- const max =
185
- typeof control.max === 'number' && control.max > 0 ? control.max : null;
186
- const canAdd = max === null || rows.length < max;
187
- const canRemove = (i) => rows.length > min;
188
-
189
- const sensors = useSensors(
190
- useSensor(PointerSensor, { activationConstraint: { distance: 6 } })
191
- );
192
-
193
- const handleDragEnd = (event) => {
194
- const { active, over } = event;
195
- if (!over || active.id === over.id) return;
196
- const oldIndex = rows.findIndex((r) => r._id === active.id);
197
- const newIndex = rows.findIndex((r) => r._id === over.id);
198
- if (oldIndex < 0 || newIndex < 0) return;
199
- onChange(arrayMove(rows, oldIndex, newIndex));
200
- };
201
-
202
- const addRow = () => {
203
- const fresh = { _id: newRowId() };
204
- (control.fields || []).forEach((sub) => {
205
- if (Object.prototype.hasOwnProperty.call(sub, 'default')) {
206
- fresh[sub.attributeKey] = sub.default;
207
- }
208
- });
209
- const next = [...rows, fresh];
210
- onChange(next);
211
- setOpenId(fresh._id);
212
- };
213
-
214
- const removeRow = (rowId) => {
215
- onChange(rows.filter((r) => r._id !== rowId));
216
- if (openId === rowId) setOpenId(null);
217
- };
218
-
219
- const updateRow = (rowId, nextRow) => {
220
- onChange(rows.map((r) => (r._id === rowId ? nextRow : r)));
221
- };
222
-
223
- // Lazy backfill: rows authored before this control existed (or
224
- // imported from an export) may lack a stable _id. Patch in an
225
- // effect (not during render) so reorders work without warning.
226
- useEffect(() => {
227
- if (rows.some((r) => !r || !r._id)) {
228
- onChange(rows.map((r) => (r && r._id ? r : { ...r, _id: newRowId() })));
229
- }
230
- }, [rows, onChange]);
231
- const normalizedRows = rows.map((r) =>
232
- r && r._id ? r : { ...r, _id: 'pending-' + Math.random() }
233
- );
234
-
235
- return (
236
- <div className="components-base-control gcb-repeater-control">
237
- <div className="components-base-control__field">
238
- {control.label && (
239
- <label className="components-base-control__label">
240
- {control.label}
241
- </label>
242
- )}
243
-
244
- <DndContext
245
- sensors={sensors}
246
- collisionDetection={closestCenter}
247
- onDragEnd={handleDragEnd}
248
- >
249
- <SortableContext
250
- items={normalizedRows.map((r) => r._id)}
251
- strategy={verticalListSortingStrategy}
252
- >
253
- <div className="gcb-repeater-rows">
254
- {normalizedRows.map((row, index) => (
255
- <SortableRow
256
- key={row._id}
257
- row={row}
258
- index={index}
259
- control={control}
260
- isOpen={openId === row._id}
261
- onToggle={() =>
262
- setOpenId(openId === row._id ? null : row._id)
263
- }
264
- onUpdate={(next) => updateRow(row._id, next)}
265
- onRemove={() =>
266
- canRemove(index) ? removeRow(row._id) : null
267
- }
268
- attributes={attributes}
269
- />
270
- ))}
271
- </div>
272
- </SortableContext>
273
- </DndContext>
274
-
275
- {canAdd && (
276
- <Button
277
- variant="secondary"
278
- onClick={addRow}
279
- className="gcb-repeater__add"
280
- >
281
- {control.addButtonLabel || __('Add item', 'gcblite')}
282
- </Button>
283
- )}
284
- </div>
285
- {control.helpText && (
286
- <p className="components-base-control__help">{control.helpText}</p>
287
- )}
288
- </div>
289
- );
290
- }