@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
@@ -0,0 +1,19 @@
1
+ import { RadioControl } from '@wordpress/components';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ export default function RadioField({
4
+ control,
5
+ value,
6
+ onChange
7
+ }) {
8
+ const options = (control.options || []).map(o => ({
9
+ label: o.label,
10
+ value: String(o.value)
11
+ }));
12
+ return /*#__PURE__*/_jsx(RadioControl, {
13
+ label: control.label,
14
+ help: control.helpText,
15
+ selected: value != null ? String(value) : '',
16
+ options: options,
17
+ onChange: onChange
18
+ });
19
+ }
@@ -0,0 +1,108 @@
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
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
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 {
27
+ tokens,
28
+ loading
29
+ } = useTokens();
30
+ let normalizedMap = null;
31
+ if (tokenGroup && tokens) {
32
+ const groupTokens = getTokensByGroup(tokens, tokenGroup);
33
+ if (groupTokens) {
34
+ const generatedMap = generateMapFromTokens(groupTokens);
35
+ normalizedMap = generatedMap ? parseMap(generatedMap) : null;
36
+ }
37
+ } else if (map) {
38
+ normalizedMap = parseMap(map);
39
+ }
40
+ if (tokenGroup && loading) {
41
+ return /*#__PURE__*/_jsxs("div", {
42
+ style: {
43
+ padding: '12px 0'
44
+ },
45
+ children: [/*#__PURE__*/_jsx("div", {
46
+ style: {
47
+ fontWeight: 500,
48
+ marginBottom: '8px'
49
+ },
50
+ children: label
51
+ }), /*#__PURE__*/_jsx(Spinner, {})]
52
+ });
53
+ }
54
+ const allowedKeys = normalizedMap ? getMapKeys(normalizedMap) : null;
55
+ const marks = normalizedMap ? mapToRangeMarks(normalizedMap) : null;
56
+ if ((value === undefined || value === null || value === '') && defaultOptionKey && normalizedMap) {
57
+ const defaultToken = getTokenFromKey(normalizedMap, defaultOptionKey);
58
+ if (defaultToken && onChange) onChange(defaultToken);
59
+ }
60
+ let displayValue;
61
+ if (normalizedMap && value) {
62
+ displayValue = Number(getKeyFromToken(normalizedMap, value)) || value;
63
+ } else if (typeof value === 'string' && !isNaN(value)) {
64
+ displayValue = Number(value);
65
+ } else {
66
+ displayValue = value;
67
+ }
68
+ const handleChange = newSliderValue => {
69
+ let finalValue = normalizedMap ? getTokenFromKey(normalizedMap, String(newSliderValue)) : newSliderValue;
70
+ if (onChange) onChange(finalValue);
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
+ return /*#__PURE__*/_jsx(RangeControl, {
76
+ label: label,
77
+ value: displayValue,
78
+ onChange: handleChange,
79
+ min: effectiveMin,
80
+ max: effectiveMax,
81
+ step: effectiveStep,
82
+ marks: marks,
83
+ help: help,
84
+ allowReset: allowReset,
85
+ resetFallbackValue: resetFallbackValue,
86
+ className: className,
87
+ __nextHasNoMarginBottom: true,
88
+ __next40pxDefaultSize: true
89
+ });
90
+ }
91
+ export default function RangeField({
92
+ control,
93
+ value,
94
+ onChange
95
+ }) {
96
+ return /*#__PURE__*/_jsx(RangeFieldImpl, {
97
+ label: control.label,
98
+ value: value,
99
+ onChange: onChange,
100
+ min: control.min ?? 0,
101
+ max: control.max ?? 100,
102
+ step: control.step ?? 1,
103
+ help: control.helpText,
104
+ map: control.map,
105
+ tokenGroup: control.tokenGroup,
106
+ defaultOptionKey: control.defaultOptionKey ?? control.default
107
+ });
108
+ }
@@ -4,11 +4,16 @@
4
4
  * multi-select mode, which handles the same use case.
5
5
  */
6
6
  import PostObjectField from './post-object';
7
-
7
+ import { jsx as _jsx } from "react/jsx-runtime";
8
8
  export default function RelationshipField(props) {
9
- const merged = {
10
- ...props,
11
- control: { ...props.control, multiple: true },
12
- };
13
- return <PostObjectField {...merged} />;
14
- }
9
+ const merged = {
10
+ ...props,
11
+ control: {
12
+ ...props.control,
13
+ multiple: true
14
+ }
15
+ };
16
+ return /*#__PURE__*/_jsx(PostObjectField, {
17
+ ...merged
18
+ });
19
+ }
@@ -0,0 +1,277 @@
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 { DndContext, closestCenter, PointerSensor, useSensor, useSensors } from '@dnd-kit/core';
43
+ import { SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from '@dnd-kit/sortable';
44
+ import { CSS } from '@dnd-kit/utilities';
45
+ import { controlComponents } from './index';
46
+
47
+ /**
48
+ * cheap-enough unique id. Crypto.randomUUID would be better but is
49
+ * unsupported in some Playground / older Safari builds we still want to
50
+ * cover. Collision risk on a single repeater is negligible.
51
+ */
52
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
53
+ function newRowId() {
54
+ return 'r' + Math.random().toString(36).slice(2, 10);
55
+ }
56
+
57
+ /**
58
+ * Read `control.collapsedTitle` (a sub-field attributeKey) and pull the
59
+ * matching value off the row. Falls back to the index-based "Item N"
60
+ * label so empty rows are still identifiable.
61
+ */
62
+ function rowTitle(row, control, index) {
63
+ const key = control.collapsedTitle;
64
+ if (key && typeof row?.[key] === 'string' && row[key].trim() !== '') {
65
+ return row[key];
66
+ }
67
+ return `${__('Item', 'gcblite')} ${index + 1}`;
68
+ }
69
+ function SortableRow({
70
+ row,
71
+ index,
72
+ control,
73
+ isOpen,
74
+ onToggle,
75
+ onUpdate,
76
+ onRemove,
77
+ attributes: parentAttrs
78
+ }) {
79
+ const {
80
+ attributes,
81
+ listeners,
82
+ setNodeRef,
83
+ transform,
84
+ transition,
85
+ isDragging
86
+ } = useSortable({
87
+ id: row._id
88
+ });
89
+ const style = {
90
+ transform: CSS.Transform.toString(transform),
91
+ transition,
92
+ opacity: isDragging ? 0.5 : 1
93
+ };
94
+ return /*#__PURE__*/_jsxs("div", {
95
+ ref: setNodeRef,
96
+ style: style,
97
+ className: "gcb-repeater-row",
98
+ children: [/*#__PURE__*/_jsxs("div", {
99
+ className: "gcb-repeater-row__header",
100
+ children: [/*#__PURE__*/_jsx("button", {
101
+ type: "button",
102
+ ...attributes,
103
+ ...listeners,
104
+ className: "gcb-repeater-row__handle",
105
+ "aria-label": __('Drag to reorder', 'gcblite'),
106
+ onClick: e => e.preventDefault(),
107
+ children: /*#__PURE__*/_jsx("svg", {
108
+ viewBox: "0 0 20 20",
109
+ width: "12",
110
+ height: "12",
111
+ children: /*#__PURE__*/_jsx("path", {
112
+ 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"
113
+ })
114
+ })
115
+ }), /*#__PURE__*/_jsxs("button", {
116
+ type: "button",
117
+ className: "gcb-repeater-row__title",
118
+ onClick: onToggle,
119
+ "aria-expanded": isOpen,
120
+ children: [/*#__PURE__*/_jsx("span", {
121
+ className: "gcb-repeater-row__caret",
122
+ "aria-hidden": true,
123
+ children: isOpen ? '▾' : '▸'
124
+ }), /*#__PURE__*/_jsx("span", {
125
+ className: "gcb-repeater-row__title-text",
126
+ children: rowTitle(row, control, index)
127
+ })]
128
+ }), /*#__PURE__*/_jsx("button", {
129
+ type: "button",
130
+ className: "gcb-repeater-row__remove",
131
+ onClick: onRemove,
132
+ "aria-label": __('Remove row', 'gcblite'),
133
+ children: /*#__PURE__*/_jsx("svg", {
134
+ viewBox: "0 0 24 24",
135
+ width: "16",
136
+ height: "16",
137
+ children: /*#__PURE__*/_jsx("path", {
138
+ 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"
139
+ })
140
+ })
141
+ })]
142
+ }), isOpen && /*#__PURE__*/_jsx("div", {
143
+ className: "gcb-repeater-row__body",
144
+ children: (control.fields || []).map(sub => {
145
+ const SubComponent = controlComponents[sub.type];
146
+ if (!SubComponent) {
147
+ return /*#__PURE__*/_jsxs("div", {
148
+ style: {
149
+ padding: 8,
150
+ background: '#fff3cd',
151
+ border: '1px solid #ffeeba',
152
+ marginBottom: 8
153
+ },
154
+ children: [/*#__PURE__*/_jsx("strong", {
155
+ children: sub.label
156
+ }), ":", ' ', __('unknown control type', 'gcblite'), ' ', /*#__PURE__*/_jsx("code", {
157
+ children: sub.type
158
+ })]
159
+ }, sub.attributeKey);
160
+ }
161
+ return /*#__PURE__*/_jsx(SubComponent, {
162
+ control: sub,
163
+ value: row?.[sub.attributeKey],
164
+ onChange: next => onUpdate({
165
+ ...row,
166
+ [sub.attributeKey]: next
167
+ }),
168
+ attributes: parentAttrs
169
+ }, sub.attributeKey);
170
+ })
171
+ })]
172
+ });
173
+ }
174
+ export default function RepeaterField({
175
+ control,
176
+ value,
177
+ onChange,
178
+ attributes
179
+ }) {
180
+ const rows = Array.isArray(value) ? value : [];
181
+ const [openId, setOpenId] = useState(null);
182
+ const min = typeof control.min === 'number' ? control.min : 0;
183
+ const max = typeof control.max === 'number' && control.max > 0 ? control.max : null;
184
+ const canAdd = max === null || rows.length < max;
185
+ const canRemove = i => rows.length > min;
186
+ const sensors = useSensors(useSensor(PointerSensor, {
187
+ activationConstraint: {
188
+ distance: 6
189
+ }
190
+ }));
191
+ const handleDragEnd = event => {
192
+ const {
193
+ active,
194
+ over
195
+ } = event;
196
+ if (!over || active.id === over.id) return;
197
+ const oldIndex = rows.findIndex(r => r._id === active.id);
198
+ const newIndex = rows.findIndex(r => r._id === over.id);
199
+ if (oldIndex < 0 || newIndex < 0) return;
200
+ onChange(arrayMove(rows, oldIndex, newIndex));
201
+ };
202
+ const addRow = () => {
203
+ const fresh = {
204
+ _id: newRowId()
205
+ };
206
+ (control.fields || []).forEach(sub => {
207
+ if (Object.prototype.hasOwnProperty.call(sub, 'default')) {
208
+ fresh[sub.attributeKey] = sub.default;
209
+ }
210
+ });
211
+ const next = [...rows, fresh];
212
+ onChange(next);
213
+ setOpenId(fresh._id);
214
+ };
215
+ const removeRow = rowId => {
216
+ onChange(rows.filter(r => r._id !== rowId));
217
+ if (openId === rowId) setOpenId(null);
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 : {
229
+ ...r,
230
+ _id: newRowId()
231
+ }));
232
+ }
233
+ }, [rows, onChange]);
234
+ const normalizedRows = rows.map(r => r && r._id ? r : {
235
+ ...r,
236
+ _id: 'pending-' + Math.random()
237
+ });
238
+ return /*#__PURE__*/_jsxs("div", {
239
+ className: "components-base-control gcb-repeater-control",
240
+ children: [/*#__PURE__*/_jsxs("div", {
241
+ className: "components-base-control__field",
242
+ children: [control.label && /*#__PURE__*/_jsx("label", {
243
+ className: "components-base-control__label",
244
+ children: control.label
245
+ }), /*#__PURE__*/_jsx(DndContext, {
246
+ sensors: sensors,
247
+ collisionDetection: closestCenter,
248
+ onDragEnd: handleDragEnd,
249
+ children: /*#__PURE__*/_jsx(SortableContext, {
250
+ items: normalizedRows.map(r => r._id),
251
+ strategy: verticalListSortingStrategy,
252
+ children: /*#__PURE__*/_jsx("div", {
253
+ className: "gcb-repeater-rows",
254
+ children: normalizedRows.map((row, index) => /*#__PURE__*/_jsx(SortableRow, {
255
+ row: row,
256
+ index: index,
257
+ control: control,
258
+ isOpen: openId === row._id,
259
+ onToggle: () => setOpenId(openId === row._id ? null : row._id),
260
+ onUpdate: next => updateRow(row._id, next),
261
+ onRemove: () => canRemove(index) ? removeRow(row._id) : null,
262
+ attributes: attributes
263
+ }, row._id))
264
+ })
265
+ })
266
+ }), canAdd && /*#__PURE__*/_jsx(Button, {
267
+ variant: "secondary",
268
+ onClick: addRow,
269
+ className: "gcb-repeater__add",
270
+ children: control.addButtonLabel || __('Add item', 'gcblite')
271
+ })]
272
+ }), control.helpText && /*#__PURE__*/_jsx("p", {
273
+ className: "components-base-control__help",
274
+ children: control.helpText
275
+ })]
276
+ });
277
+ }