@plaudit/gutenberg-api-extensions 2.95.0 → 2.97.0

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 (113) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/blocks/basic-custom-block-bindings-support.js +1 -1
  3. package/dist/blocks/basic-custom-block-bindings-support.js.map +1 -1
  4. package/dist/blocks/conditions.js.map +1 -1
  5. package/dist/blocks/problematic-blocks-blocker.js.map +1 -1
  6. package/dist/blocks/simple-block.d.ts +4 -4
  7. package/dist/blocks/simple-block.js +2 -2
  8. package/dist/blocks/simple-block.js.map +1 -1
  9. package/dist/lib/useful-types.d.ts +6 -7
  10. package/package.json +13 -15
  11. package/dist/lib/compat-types.d.ts +0 -34
  12. package/dist/lib/compat-types.js +0 -3
  13. package/dist/lib/compat-types.js.map +0 -1
  14. package/src/blocks/MoveError.ts +0 -7
  15. package/src/blocks/PathError.ts +0 -18
  16. package/src/blocks/SNPFlexibleItemsListComponent.tsx +0 -30
  17. package/src/blocks/SNPGroupComponent.tsx +0 -38
  18. package/src/blocks/SNPListComponent.tsx +0 -25
  19. package/src/blocks/SNPTreeContext.tsx +0 -13
  20. package/src/blocks/basic-custom-block-bindings-support.tsx +0 -248
  21. package/src/blocks/common-native-property-constructors.tsx +0 -927
  22. package/src/blocks/conditions.ts +0 -261
  23. package/src/blocks/csnp-api.ts +0 -221
  24. package/src/blocks/data-controller/actions.ts +0 -20
  25. package/src/blocks/data-controller/reducer.ts +0 -146
  26. package/src/blocks/data-controller/trigger-handlers.ts +0 -150
  27. package/src/blocks/data-controller/utils.ts +0 -415
  28. package/src/blocks/data-controller-manager.ts +0 -50
  29. package/src/blocks/data-controller.ts +0 -165
  30. package/src/blocks/hooks/built-in-suspendable-option-protocols/select.ts +0 -51
  31. package/src/blocks/hooks/built-in-suspendable-option-protocols/settings.ts +0 -70
  32. package/src/blocks/hooks/useSuspendableOptions.ts +0 -122
  33. package/src/blocks/index.ts +0 -23
  34. package/src/blocks/layered-styles-api.ts +0 -142
  35. package/src/blocks/layered-styles-impl.ts +0 -95
  36. package/src/blocks/layout/LaidOutProperty.tsx +0 -72
  37. package/src/blocks/layout/LaidOutPropertyRow.tsx +0 -28
  38. package/src/blocks/layout/NodeContext.tsx +0 -54
  39. package/src/blocks/layout/PanelRoot.tsx +0 -30
  40. package/src/blocks/layout/TabsRoot.tsx +0 -56
  41. package/src/blocks/layout/ToolsPanelContext.tsx +0 -22
  42. package/src/blocks/problematic-blocks-blocker.ts +0 -24
  43. package/src/blocks/problematic-variations-blocker.ts +0 -32
  44. package/src/blocks/shared-exportable-types.ts +0 -6
  45. package/src/blocks/shared-internal-types.ts +0 -18
  46. package/src/blocks/simple-block.tsx +0 -74
  47. package/src/blocks/simple-native-property-api.ts +0 -173
  48. package/src/blocks/simple-native-property-impl.tsx +0 -335
  49. package/src/blocks/simple-native-property-internal-shared.ts +0 -19
  50. package/src/blocks/snp-api.ts +0 -5
  51. package/src/blocks/snp-data-store.ts +0 -72
  52. package/src/blocks/utilities.ts +0 -66
  53. package/src/controls/AsynchronousFormTokenField.tsx +0 -86
  54. package/src/controls/BaseSortableItemsControl.tsx +0 -84
  55. package/src/controls/ExtendedFormTokenField.tsx +0 -144
  56. package/src/controls/ExtendedPostPicker.ts +0 -57
  57. package/src/controls/ExtendedRadioControl.tsx +0 -107
  58. package/src/controls/ExtendedTaxonomyPicker.tsx +0 -100
  59. package/src/controls/ExtendedTermPicker.tsx +0 -61
  60. package/src/controls/ExtendedTextareaControl.tsx +0 -65
  61. package/src/controls/ExtendedUserPicker.ts +0 -56
  62. package/src/controls/FileControl.tsx +0 -48
  63. package/src/controls/FullSizeToggleControl.tsx +0 -95
  64. package/src/controls/ImageControl.tsx +0 -143
  65. package/src/controls/InspectorPanel.tsx +0 -37
  66. package/src/controls/LazySuggestionsComboboxControl.tsx +0 -64
  67. package/src/controls/MultiSelectControl.tsx +0 -59
  68. package/src/controls/PickOne.tsx +0 -88
  69. package/src/controls/PromisableComponent.tsx +0 -56
  70. package/src/controls/ProperLinkControl.tsx +0 -98
  71. package/src/controls/SimpleToggle.tsx +0 -9
  72. package/src/controls/SortableFlexibleItemsControl.tsx +0 -37
  73. package/src/controls/SortableItemsControl.tsx +0 -22
  74. package/src/controls/basicNumericallyIdedItemPicker.tsx +0 -75
  75. package/src/controls/hooks/useImprovedTokenManager.ts +0 -163
  76. package/src/controls/hooks/useMultiSingleConversionLayer.ts +0 -17
  77. package/src/controls/hooks/useNonRenderingCounter.ts +0 -6
  78. package/src/controls/hooks/useOutputMemoizingFilter.ts +0 -16
  79. package/src/controls/hooks/useSortableItemsModel.ts +0 -196
  80. package/src/controls/hooks/useSuggestions.ts +0 -91
  81. package/src/controls/hooks/useTokenManager.ts +0 -177
  82. package/src/controls/index.ts +0 -24
  83. package/src/controls/shared.ts +0 -50
  84. package/src/controls/types.ts +0 -18
  85. package/src/editor/insert-sibling-or-child-block-shortcut.tsx +0 -60
  86. package/src/editor/install-insert-sole-allowed-block-shortcut-support.tsx +0 -51
  87. package/src/editor/simple-gutenberg-endpoints-api.ts +0 -31
  88. package/src/editor/simple-gutenberg-endpoints-impl.ts +0 -126
  89. package/src/index.ts +0 -30
  90. package/src/lib/compat-types.ts +0 -21
  91. package/src/lib/gutenberg-api-extensions-state/custom-block-bindings-support-logic.ts +0 -35
  92. package/src/lib/gutenberg-api-extensions-state/general-logic.ts +0 -41
  93. package/src/lib/gutenberg-api-extensions-state/layered-block-styles-logic.ts +0 -43
  94. package/src/lib/gutenberg-api-extensions-state/snp-logic.ts +0 -240
  95. package/src/lib/gutenberg-api-extensions-state.ts +0 -69
  96. package/src/lib/helpers.ts +0 -115
  97. package/src/lib/modified-fast-deep-equals.ts +0 -91
  98. package/src/lib/plaudit-icons/column-1.tsx +0 -6
  99. package/src/lib/plaudit-icons/column-2.tsx +0 -6
  100. package/src/lib/plaudit-icons/column-3.tsx +0 -6
  101. package/src/lib/plaudit-icons/placement-center.tsx +0 -3
  102. package/src/lib/plaudit-icons/placement-end.tsx +0 -3
  103. package/src/lib/plaudit-icons/placement-start.tsx +0 -3
  104. package/src/lib/plaudit-icons/placement-stretch.tsx +0 -3
  105. package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -4
  106. package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -3
  107. package/src/lib/plaudit-icons.ts +0 -13
  108. package/src/lib/sectioned-cache-store.ts +0 -120
  109. package/src/lib/suspense/promise-handlers.ts +0 -72
  110. package/src/lib/suspense.tsx +0 -18
  111. package/src/lib/useful-types.ts +0 -82
  112. package/src/schemas/README.md +0 -1
  113. package/src/schemas/plaudit-block-schema.json +0 -818
@@ -1,927 +0,0 @@
1
- import {
2
- BaseControl,
3
- ColorPalette,
4
- DatePicker,
5
- DateTimePicker,
6
- Disabled,
7
- RadioControl,
8
- RangeControl,
9
- SelectControl,
10
- TextControl,
11
- ToggleControl,
12
- useBaseControlProps,
13
- __experimentalToggleGroupControl as ToggleGroupControl
14
- } from "@wordpress/components";
15
- import {dispatch, select} from "@wordpress/data";
16
- import {useCallback, useMemo, useRef} from "@wordpress/element";
17
- import {applyFilters} from "@wordpress/hooks";
18
-
19
- import {
20
- ExtendedPostPicker, ExtendedRadioControl, ImageControl, type ImageData,
21
- ExtendedTextareaControl, MultiSelectControl, type Dict,
22
- ProperLinkControl, SortableItemsControl, type PickableOptions,
23
- FullSizeToggleControl, ToggleGroupControlOptionWithOptionalIcon, ExtendedTaxonomyPicker, ExtendedTermPicker, ExtendedUserPicker, FileControl
24
- } from "../controls";
25
- import {normalizePickableOptionsToObjects, normalizePickableOptionsToPairs} from "../controls/shared";
26
- import {recordCloneableDefaultValueForNode} from "./data-controller/utils";
27
- import {useSuspendableOptions} from "./hooks/useSuspendableOptions";
28
- import {makeToolsPanelContextValue, ToolsPanelContext} from "./layout/ToolsPanelContext";
29
- import {store as gutenbergApiExtensionsStore} from "../lib/gutenberg-api-extensions-state";
30
- import {clone} from "../lib/helpers";
31
- import {arrayIsNotEmpty, BlockName} from "../lib/useful-types";
32
- import type {CSNPControlComponentProps} from "./shared-internal-types";
33
- import type {GenericSimpleNativeProperty, HydratedSimpleNativeProperty, PDSimpleNativeProperty, SNPControlProps, SNPControlSlots} from "./simple-native-property-api";
34
- import {SNPFlexibleItemsListComponent} from "./SNPFlexibleItemsListComponent";
35
- import {SNPGroupComponent} from "./SNPGroupComponent";
36
- import {SNPListComponent} from "./SNPListComponent";
37
-
38
- import type {ComponentPropsWithoutRef, CSSProperties} from "react";
39
- import {
40
- CSNPConfig, FlexibleItemsListPropertyConfig, getPropType, isObjectListPropertyConfig, isStringValuedToggleGroupCSNPConfig,
41
- ObjectListPropertyConfig, PostPropertyCSNPConfig, RadioPropertyCSNPConfig, SelectPropertyCSNPConfig, ShareableSNPElements,
42
- TaxonomyPropertyCSNPConfig, TermPropertyCSNPConfig, UserPropertyCSNPConfig
43
- } from "./csnp-api";
44
-
45
- export type OutsideHydrationInfo = {forLayeredStyles: boolean, blockName: BlockName};
46
- export function hydrateSimpleNativeProperty(
47
- config: PDSimpleNativeProperty, parentPath: string, outsideHydrationInfo: OutsideHydrationInfo, parentTypes: string[]
48
- ): HydratedSimpleNativeProperty {
49
- const propPath = parentPath.length !== 0 ? parentPath + "." + config.name : config.name;
50
- if (parentPath.length === 0) {
51
- config = injectBlockSourcedDefault(config, outsideHydrationInfo.blockName);
52
- }
53
-
54
- config = applyFilters('plaudit.gutenbergApiExtensions.simpleNativeProperties.field', config, outsideHydrationInfo.blockName, propPath) as typeof config;
55
- const res = recordCloneableDefaultValueForNode(typeof config.control === 'string'
56
- ? hydrateDesiccatedSimpleNativeProperty(config, propPath, outsideHydrationInfo, parentTypes) : config);
57
- validateAndRecordPotentialStyleProperty(res, propPath, outsideHydrationInfo, parentTypes);
58
- return res;
59
- }
60
- function validateAndRecordPotentialStyleProperty(
61
- config: HydratedSimpleNativeProperty, propPath: string, {forLayeredStyles, blockName}: OutsideHydrationInfo, parentTypes: string[]
62
- ) {
63
- if ("styleProperty" in config && config.styleProperty !== undefined) {
64
- if (parentTypes.includes('array')) {
65
- console.error(`A SNP was configured with a styleProperty, but it is within an array-type SNP. This is unsupported`, "SNP in error:", config);
66
- } else {
67
- dispatch(gutenbergApiExtensionsStore)
68
- .addOrUpdateExtraPropTransform(blockName, propPath, {propName: config.name, forLayeredStyles, styleProperty: config.styleProperty});
69
- }
70
- }
71
- }
72
- function injectBlockSourcedDefault(config: PDSimpleNativeProperty, blockName: BlockName): typeof config {
73
- if (config.default === undefined) {
74
- const existingAttr = select(gutenbergApiExtensionsStore).baselineBlockAttrs(blockName)?.attributes?.[config.name];
75
- if (existingAttr && typeof existingAttr === 'object' && 'default' in existingAttr) {
76
- if (existingAttr.default !== undefined) {
77
- const expectedPropType = getPropType(config);
78
- if (expectedPropType === 'array' ? Array.isArray(existingAttr.default) : (typeof existingAttr.default) === expectedPropType) {
79
- return {...config, default: clone<any>(existingAttr.default)};
80
- } else {
81
- console.error('Encountered a preexisting default value on a block that does not overlap with the datatype for an SNP');
82
- }
83
- }
84
- }
85
- }
86
- return config;
87
- }
88
-
89
- type PassthroughPropsType = Pick<PDSimpleNativeProperty, 'condition'|'name'|'label'|'required'>&{controlType: string, styleProperty?: string};
90
- function hydrateDesiccatedSimpleNativeProperty(
91
- config: CSNPConfig, propPath: string, outsideHydrationInfo: OutsideHydrationInfo, parentTypes: string[]
92
- ): HydratedSimpleNativeProperty {
93
- const passthroughProps: PassthroughPropsType = {
94
- condition: config.condition, label: config.label, name: config.name, required: config.required, controlType: config.control,
95
- styleProperty: config.styleProperty
96
- };
97
- switch (config.control) {
98
- case "colorPalette":
99
- return {
100
- type: 'string',
101
- default: config.default,
102
- alwaysStore: config.alwaysStore,
103
- ...passthroughProps,
104
- validator: config.validator,
105
- transformer: config.transformer,
106
- control({value, onChange, slots: {Label, Messages}}) {
107
- const resolvedOptions = useSuspendableOptions(config);
108
- const {colors, valueToColor, colorToValue} = useMemo(() => {
109
- const normalizedOptions = normalizePickableOptionsToObjects(resolvedOptions);
110
- const options = normalizedOptions.map(opt => {
111
- if (hasDefinedKey(opt, 'color') && CSS.supports('color', opt.color)) {
112
- return opt;
113
- } else if (CSS.supports('color', opt.value)) {
114
- return {...opt, color: opt.value};
115
- } else if (CSS.supports('color', '#' + opt.value)) {
116
- return {...opt, color: '#' + opt.value};
117
- } else {
118
- console.error(`The color property was ${opt.color ? "invalid" : "not set"} and value could not be converted to a valid CSS color.`,
119
- "The color will be displayed as transparent.", "Option in error:", opt);
120
- return {...opt, color: 'transparent'};
121
- }
122
- });
123
-
124
- return {
125
- colors: options.map(({value, label = value, color}) => ({color, name: label})),
126
- valueToColor: Object.fromEntries(options.map(opt => [opt.value, opt.color])) as Dict<CSSProperties['color']>,
127
- colorToValue: Object.fromEntries(options.map(opt => [opt.color, opt.value])) as Record<NonNullable<CSSProperties['color']>, string>
128
- };
129
- }, [resolvedOptions]);
130
-
131
- const safeOnChange = useCallback((v: string|undefined) => onChange(v ? colorToValue[v] : undefined), [colorToValue, onChange]);
132
-
133
- const {baseControlProps, controlProps} = useBaseControlProps({label: <Label />, help: config.help});
134
- return <BaseControl __nextHasNoMarginBottom {...baseControlProps}>
135
- <ColorPalette
136
- {...config.component}
137
- {...controlProps}
138
- value={value ? valueToColor[value] : undefined}
139
- onChange={safeOnChange}
140
- colors={colors}
141
- clearable={config.clearable ?? false}
142
- disableCustomColors={config.allowCustom !== true}
143
- __experimentalIsRenderedInSidebar={true}
144
- />
145
- <Messages />
146
- </BaseControl>;
147
- }
148
- };
149
- case "constant":
150
- return {
151
- type: config.type as any,
152
- default: config.default,
153
- alwaysStore: true,
154
- ...passthroughProps,
155
- validator: config.validator,
156
- transformer: config.transformer as any,
157
- control({value, slots: {Label, Messages}}: SNPControlProps<any>) {
158
- const {current: voidChangeHandler} = useRef(() => {});
159
- return <Disabled>
160
- <TextControl __nextHasNoMarginBottom __next40pxDefaultSize {...config.component} value={value} onChange={voidChangeHandler} label={<Label />} help={config.help} disabled={true} />
161
- <Messages />
162
- </Disabled>;
163
- }
164
- };
165
- case "date":
166
- return {
167
- type: 'number',
168
- default: config.default,
169
- alwaysStore: config.alwaysStore,
170
- ...passthroughProps,
171
- validator: config.validator,
172
- transformer: config.transformer,
173
- control({value, onChange, slots: {Label, Messages}}) {
174
- const safeOnChange = useCallback((v: string|null) => onChange(v === null ? undefined : new Date(v).getTime()), [onChange]);
175
- return <BaseControl __nextHasNoMarginBottom id="" label={<Label />} help={config.help}>
176
- {config.time
177
- ? <DateTimePicker {...config.component} currentDate={value ?? null} onChange={safeOnChange} />
178
- : <DatePicker {...config.component} currentDate={value ?? null} onChange={safeOnChange} />
179
- }
180
- <Messages />
181
- </BaseControl>;
182
- }
183
- }
184
- case "file":
185
- return {
186
- type: 'number',
187
- alwaysStore: config.alwaysStore,
188
- default: config.default,
189
- ...passthroughProps,
190
- validator: config.validator,
191
- transformer: config.transformer,
192
- control({value, onChange, slots: {Label}}) {
193
- return <FileControl label={<Label />} help={config.help} onChange={onChange} value={value} allowedTypes={config.allowedTypes} />;
194
- }
195
- };
196
- case "group": {
197
- registerPlainTextLabelRequiredMarker("post", " > .components-tools-panel > .components-tools-panel-header > *");
198
- const currentTypes = parentTypes.toSpliced(parentTypes.length - 1, 0, 'object');
199
- const hydratedProperties = (config.fields ?? [])
200
- .map(property => hydrateSimpleNativeProperty(property, propPath, outsideHydrationInfo, currentTypes));
201
- if (config.default !== undefined) {
202
- if (parentTypes.length !== 0) {
203
- console.error("Non-root groups should not have default values set.",
204
- "The resulting behavior is undefined and almost guaranteed to break.");
205
- } else if (hydratedProperties.some(prop => prop.name in config.default!)) {
206
- console.error("Groups should not have default values that conflict with their contained fields set.",
207
- "The resulting behavior is undefined and almost guaranteed to break.");
208
- }
209
- }
210
- const actualDefaultValues: {[key in string]: unknown} = {};
211
- if (config.interface === 'toolsPanel') {
212
- for (const hydratedProperty of hydratedProperties) {
213
- actualDefaultValues[hydratedProperty.name] = hydratedProperty.default;
214
- hydratedProperty.default = undefined;
215
- recordCloneableDefaultValueForNode(hydratedProperty, true);
216
- }
217
- }
218
- return {
219
- type: 'object',
220
- alwaysStore: config.alwaysStore,
221
- default: config.default ?? {},
222
- children: hydratedProperties,
223
- branching: config.interface === 'toolsPanel',
224
- ...passthroughProps,
225
- validator: config.validator,
226
- transformer: config.transformer,
227
- control({value, onChange, slots}: SNPControlProps<Dict<any>>) {
228
- return <ToolsPanelContext.Provider value={makeToolsPanelContextValue(actualDefaultValues)}>
229
- <SNPGroupComponent config={config} hydratedProperties={hydratedProperties} onChange={onChange} value={value} {...slots} />
230
- </ToolsPanelContext.Provider>;
231
- }
232
- };
233
- }
234
- case "image":
235
- return {
236
- type: 'object',
237
- alwaysStore: config.alwaysStore,
238
- default: undefined,
239
- ...passthroughProps,
240
- validator: config.validator,
241
- transformer: config.transformer,
242
- control({value, onChange, slots}: SNPControlProps<ImageData>) {
243
- return <ImageControl includeFocalPointPicker={config.includeFocalPointPicker} storage={config.storage} label={config.label} help={config.help} onChange={onChange} value={value} {...slots} />;
244
- }
245
- } as GenericSimpleNativeProperty<ImageData, 'object'>;
246
- case "link":
247
- return {
248
- type: 'object',
249
- default: config.default,
250
- alwaysStore: config.alwaysStore,
251
- ...passthroughProps,
252
- validator: config.validator,
253
- transformer: config.transformer,
254
- control({value, onChange, slots: {Label, Messages}}) {
255
- return <>
256
- <ProperLinkControl
257
- {...config.component}
258
- hasRichPreviews
259
- hasTextControl
260
- help={config.help}
261
- label={<Label />}
262
- onChange={onChange}
263
- settings={config.settings === false || config.settings === undefined ? false : (config.settings === true ? ProperLinkControl.DEFAULT_LINK_SETTINGS : config.settings)}
264
- value={value}
265
- />
266
- <Messages />
267
- </>;
268
- }
269
- };
270
- case "list": {
271
- const sharedProps: ShareableSNPElements<'array'> = {...passthroughProps, type: 'array', alwaysStore: config.alwaysStore};
272
- switch (config.itemType) {
273
- case undefined:
274
- case "string":
275
- return {
276
- ...sharedProps,
277
- default: config.default,
278
- type: 'array',
279
- validator: config.validator,
280
- transformer: config.transformer,
281
- control({value, onChange, slots}) {
282
- const children = useCallback((datum: any, onDatumChange: (datum: any) => void) => {
283
- return <TextControl {...config.listComponent} __nextHasNoMarginBottom __next40pxDefaultSize value={datum} onChange={onDatumChange} />;
284
- }, [config.listComponent]);
285
- return <SortableItemsControl {...config.component} {...slots}
286
- value={value} onChange={onChange} min={config.min} max={config.max} label={config.label}
287
- help={config.help} emptyValue={config.emptyValue ?? ''} children={children}
288
- />;
289
- }
290
- };
291
- case "float":
292
- case "int":
293
- case "integer":
294
- return {
295
- ...sharedProps,
296
- default: config.default,
297
- type: 'array',
298
- validator: config.validator,
299
- transformer: config.transformer,
300
- control({value, onChange, slots}) {
301
- const children = useCallback((datum: any, onDatumChange: (datum: any) => void) => {
302
- return <NumberLikeListTextControl listComponent={config.listComponent} datum={datum} itemType={config.itemType}
303
- onDatumChange={onDatumChange} />;
304
- }, [config.listComponent, config.itemType]);
305
- return <SortableItemsControl {...config.component} {...slots}
306
- value={value} onChange={onChange} min={config.min} max={config.max} label={config.label}
307
- help={config.help} emptyValue={config.emptyValue ?? 0} children={children}
308
- />;
309
- }
310
- };
311
- default: {
312
- const currentTypes = parentTypes.toSpliced(parentTypes.length - 1, 0, 'array');
313
- if (isObjectListPropertyConfig(config)) {
314
- const hydratedProperties = config.itemType
315
- .map(property => hydrateSimpleNativeProperty(property, propPath, outsideHydrationInfo, currentTypes));
316
- const def = config.default ?? [];
317
- return {
318
- ...sharedProps,
319
- children: hydratedProperties,
320
- default: def, // This type-checks, but directly inlining the value doesn't
321
- validator: config.validator,
322
- transformer: config.transformer,
323
- control({value, onChange, slots}) {
324
- return <SNPListComponent config={config as ObjectListPropertyConfig} onChange={onChange} value={value}
325
- hydratedProperties={hydratedProperties} {...slots} />;
326
- }
327
- }
328
- } else {
329
- const sharedProperties = config.sharedProperties;
330
- const completedItemTypes = sharedProperties?.length
331
- ? Object.fromEntries(Object.entries(config.itemType)
332
- .map(([key, v]) => [key, {...v, properties: [...sharedProperties, ...v.properties]}]))
333
- : config.itemType;
334
- const availableTypes = Object.entries(completedItemTypes)
335
- .map(([value, {label}]) => ({value, label}));
336
- if (!arrayIsNotEmpty(availableTypes)) {
337
- return invalidConfigMessageControl(config, passthroughProps, "Flexible Items lists must defined at least one item type.");
338
- }
339
-
340
- const invalidFlexibleItemDefinition = Object.values(completedItemTypes)
341
- .find(({properties}) => properties.some(property => property.name === 'type'));
342
- if (invalidFlexibleItemDefinition) {
343
- return invalidConfigMessageControl(config, passthroughProps, "Flexible item definitions CANNOT include a property named 'type'");
344
- }
345
- const hydratedProperties = Object.fromEntries(
346
- Object.entries(completedItemTypes)
347
- .map(([itemType, {properties}]) => {
348
- return [itemType, properties
349
- .map(property => hydrateSimpleNativeProperty(property, propPath, outsideHydrationInfo, currentTypes))]
350
- })
351
- );
352
- const emptyValues = Object.fromEntries(
353
- Object.entries(completedItemTypes).map(([itemType, {emptyValue}]) => {
354
- return [itemType, {...emptyValue, type: itemType}];
355
- }));
356
- const def = config.default ?? [];
357
- return {
358
- ...sharedProps,
359
- children: hydratedProperties,
360
- default: def, // This type-checks, but directly inlining the value doesn't
361
- validator: config.validator,
362
- transformer: config.transformer,
363
- control({value, onChange, slots}) {
364
- return <SNPFlexibleItemsListComponent
365
- config={config as FlexibleItemsListPropertyConfig} onChange={onChange} value={value} emptyValues={emptyValues}
366
- hydratedProperties={hydratedProperties} availableTypes={availableTypes} {...slots}
367
- />;
368
- }
369
- }
370
- }
371
- }
372
- }
373
- }
374
- case "message":
375
- return {
376
- type: 'string',
377
- default: config.default,
378
- alwaysStore: false,
379
- ...passthroughProps,
380
- validator: config.validator,
381
- transformer: config.transformer as any,
382
- control({value}: SNPControlProps<string>) {
383
- const lines = useMemo(() => {
384
- if (value && /\r?\n/.exec(value)) {
385
- // eslint-disable-next-line react/jsx-key -- random <br /> elements don't need a key
386
- return value.split(/\r?\n/g).flatMap(line => [<br />, line]).slice(1);
387
- }
388
- return value;
389
- }, [value]);
390
- return <Disabled children={lines} />;
391
- }
392
- };
393
- case "number":
394
- return {
395
- type: 'number',
396
- default: config.default,
397
- alwaysStore: config.alwaysStore,
398
- ...passthroughProps,
399
- validator: config.validator,
400
- transformer: config.transformer,
401
- control({value, onChange, slots: {Label, Messages}}) {
402
- const safeOnChange = useCallback((v: string) => onChange(float ? parseFloat(v) : parseInt(v)), [onChange]);
403
- const float = config.float || config.step === 'any'
404
- || (config.min !== undefined && !Number.isInteger(config.min))
405
- || (config.max !== undefined && !Number.isInteger(config.max))
406
- || (config.step !== undefined && !Number.isInteger(config.step));
407
- return <>
408
- <TextControl {...config.component}
409
- __nextHasNoMarginBottom __next40pxDefaultSize
410
- type="number" value={value ?? ''} min={config.min} max={config.max} step={config.step ?? (float ? "any" : undefined)}
411
- onChange={safeOnChange} label={<Label />} help={config.help} placeholder={config.placeholder}
412
- />
413
- <Messages />
414
- </>;
415
- }
416
- };
417
- case "post":
418
- registerPlainTextLabelRequiredMarker("post", " > .components-form-token-field .components-form-token-field__label");
419
- return hydratePostProperty(config);
420
- case "radio":
421
- return {
422
- type: 'string',
423
- enum: config.enum,
424
- default: config.default,
425
- alwaysStore: config.alwaysStore,
426
- ...passthroughProps,
427
- validator: config.validator,
428
- transformer: config.transformer,
429
- control({value, onChange, slots}) {
430
- return <PotentiallyExtendedRadioControl config={config} onChange={onChange} value={value} {...slots} />;
431
- }
432
- };
433
- case "range":
434
- registerPlainTextLabelRequiredMarker("range", " > .components-range-control > .components-base-control__field > .components-base-control__label");
435
- return {
436
- type: 'number',
437
- enum: config.enum,
438
- default: config.default,
439
- alwaysStore: config.alwaysStore,
440
- ...passthroughProps,
441
- validator: config.validator,
442
- transformer: config.transformer,
443
- control({value, onChange, slots: {Messages}}) {
444
- return <>
445
- <RangeControl
446
- {...config.component} value={value} onChange={onChange} min={config.min} max={config.max} label={config.label}
447
- step={config.step} help={config.help} __nextHasNoMarginBottom __next40pxDefaultSize
448
- />
449
- <Messages />
450
- </>;
451
- }
452
- };
453
- case "select":
454
- return hydrateSelectProperty(config);
455
- case "taxonomy":
456
- return hydrateTaxonomyProperty(config);
457
- case "term":
458
- return hydrateTermProperty(config);
459
- case "textarea":
460
- return {
461
- type: 'string',
462
- default: config.default,
463
- alwaysStore: config.alwaysStore,
464
- ...passthroughProps,
465
- validator: config.validator,
466
- transformer: config.transformer,
467
- control({value, onChange, slots}) {
468
- return <ExtendedTextareaControl {...config} value={value} onChange={onChange} {...slots} />;
469
- }
470
- };
471
- case "text":
472
- return {
473
- type: 'string',
474
- default: config.default,
475
- alwaysStore: config.alwaysStore,
476
- ...passthroughProps,
477
- validator: config.validator,
478
- transformer: config.transformer,
479
- control({value, onChange, slots: {Label, Messages}}) {
480
- return <>
481
- <TextControl {...config.component} __nextHasNoMarginBottom __next40pxDefaultSize value={value ?? ''}
482
- onChange={onChange} label={<Label />} help={config.help} placeholder={config.placeholder} />
483
- <Messages />
484
- </>;
485
- }
486
- };
487
- case "toggle":
488
- registerPlainTextLabelRequiredMarker("toggle", ToggleGroupControlLabelSelector);
489
- return {
490
- type: 'boolean',
491
- default: config.default,
492
- alwaysStore: config.alwaysStore,
493
- ...passthroughProps,
494
- validator: config.validator,
495
- transformer: config.transformer,
496
- control({value, onChange, slots: {Label, Messages}}) {
497
- const switchCfg = useMemo(() => config.switch ?? {}, [config.switch]);
498
- if (switchCfg.small === true) {
499
- return <>
500
- <ToggleControl {...config.component as Partial<ComponentPropsWithoutRef<typeof ToggleControl>>}
501
- __nextHasNoMarginBottom checked={value} onChange={onChange} label={<Label />} help={config.help} />
502
- <Messages />
503
- </>;
504
- } else {
505
- return <>
506
- <FullSizeToggleControl
507
- componentConfig={config.component as Partial<ComponentPropsWithoutRef<typeof ToggleGroupControl>>} label={config.label} help={config.help}
508
- onChange={onChange} switchCfg={switchCfg} value={value} />
509
- <Messages />
510
- </>;
511
- }
512
- }
513
- };
514
- case "toggleGroup":
515
- registerPlainTextLabelRequiredMarker("toggleGroup", ToggleGroupControlLabelSelector);
516
- if (isStringValuedToggleGroupCSNPConfig(config)) {
517
- return {
518
- type: 'string',
519
- default: config.default,
520
- alwaysStore: config.alwaysStore,
521
- ...passthroughProps,
522
- validator: config.validator,
523
- transformer: config.transformer,
524
- control({value, onChange, slots: {Messages}}) {
525
- const unsuspendedOptions = useSuspendableOptions(config);
526
- const options = useMemo(() => {
527
- return normalizePickableOptionsToPairs(unsuspendedOptions);
528
- }, [unsuspendedOptions]);
529
- const safeOnChange = useCallback((v: string|number|undefined) => onChange(v?.toString()), [onChange]);
530
- return <>
531
- <ToggleGroupControl
532
- {...config.component} value={value} onChange={safeOnChange} label={config.label} help={config.help} isBlock isDeselectable={config.clearable ?? false}
533
- children={options.map(([value, label]) => {
534
- return <ToggleGroupControlOptionWithOptionalIcon key={value} value={value} label={label}/>;
535
- })}
536
- __nextHasNoMarginBottom
537
- __next40pxDefaultSize
538
- />
539
- <Messages />
540
- </>;
541
- }
542
- };
543
- } else {
544
- return {
545
- type: 'number',
546
- default: config.default,
547
- alwaysStore: config.alwaysStore,
548
- ...passthroughProps,
549
- validator: config.validator,
550
- transformer: config.transformer,
551
- control({value, onChange, slots: {Messages}}) {
552
- const unsuspendedOptions = useSuspendableOptions(config);
553
- const options = useMemo(() => {
554
- return normalizePickableOptionsToPairs(unsuspendedOptions);
555
- }, [unsuspendedOptions]);
556
- const safeOnChange = useCallback(
557
- (v: string|number|undefined) => onChange(v === undefined || typeof v === 'number' ? v : (v.includes('.') ? parseFloat(v) : parseInt(v))),
558
- [onChange]);
559
- return <>
560
- <ToggleGroupControl
561
- {...config.component} value={value} label={config.label} help={config.help} isBlock isDeselectable={config.clearable ?? false}
562
- onChange={safeOnChange} __nextHasNoMarginBottom __next40pxDefaultSize
563
- >
564
- {...options.map(([value, label]) =>
565
- (<ToggleGroupControlOptionWithOptionalIcon key={value} value={value} label={label} />))}
566
- </ToggleGroupControl>
567
- <Messages />
568
- </>;
569
- }
570
- };
571
- }
572
- case "user":
573
- registerPlainTextLabelRequiredMarker("user", " > .components-form-token-field .components-form-token-field__label");
574
- return hydrateUserProperty(config);
575
- default:
576
- console.error("Invalid control name:", (config as any).control, "\nFalling back to a minimally-configured text control.");
577
- return hydrateDesiccatedSimpleNativeProperty({
578
- control: "text",
579
- name: (config as any).name,
580
- label: (config as any).label
581
- }, propPath, outsideHydrationInfo, parentTypes);
582
- }
583
- }
584
- const ToggleGroupControlLabelSelector: string = " > .components-base-control > .components-base-control__field:has(> [data-wp-component='ToggleGroupControl']) .components-base-control__label";
585
-
586
- type NumberLikeListTextControlProps = {listComponent?: ComponentPropsWithoutRef<typeof TextControl>, datum: string, itemType: string, onDatumChange(value: any): void};
587
- function NumberLikeListTextControl({listComponent, datum, itemType, onDatumChange}: NumberLikeListTextControlProps) {
588
- const onChange = useCallback((v: string) => onDatumChange(itemType !== 'float' ? parseInt(v) : parseFloat(v)), [itemType, onDatumChange])
589
- return <TextControl {...listComponent} __nextHasNoMarginBottom __next40pxDefaultSize type="number" value={datum} onChange={onChange} />
590
- }
591
-
592
- type PotentiallyExtendedRadioControlProps = CSNPControlComponentProps<RadioPropertyCSNPConfig, string>&SNPControlSlots;
593
- function PotentiallyExtendedRadioControl({config, value, onChange, Label, Messages}: PotentiallyExtendedRadioControlProps) {
594
- const unsuspendedOptions = useSuspendableOptions(config);
595
- const options = useAsOptions(unsuspendedOptions, value, undefined);
596
-
597
- if (config.allowCustom) {
598
- return <ExtendedRadioControl {...config.component} selected={value} onChange={onChange} options={options} label={config.label} help={config.help}
599
- allowCustom={true} Label={Label} Messages={Messages} />;
600
- } else {
601
- return <>
602
- <RadioControl {...config.component} selected={value} onChange={onChange} options={options} label={Label ? <Label /> : config.label} help={config.help} />
603
- <Messages />
604
- </>;
605
- }
606
- }
607
-
608
- export function hydratePostProperty(config: PostPropertyCSNPConfig): HydratedSimpleNativeProperty {
609
- if (config.multiple) {
610
- return {
611
- name: config.name,
612
- type: 'array',
613
- default: config.default,
614
- condition: config.condition,
615
- alwaysStore: config.alwaysStore,
616
- required: config.required,
617
- validator: config.validator,
618
- transformer: config.transformer,
619
- controlType: 'post',
620
- control({value, onChange, slots: {Messages}}: SNPControlProps<number[]>) {
621
- const safeOnChange = useCallback((v: string[]) => onChange(v.map(v => parseInt(v))), [onChange]);
622
- const safeValue = useMemo(() => value?.map(v => v.toString()), [value]);
623
- return <ExtendedPostPicker
624
- {...config.component}
625
- multiple={true}
626
- label={config.label}
627
- help={config.help}
628
- value={safeValue}
629
- onChange={safeOnChange}
630
- postTypes={config.postTypes}
631
- Messages={Messages}
632
- />;
633
- }
634
- };
635
- } else {
636
- return {
637
- name: config.name,
638
- type: 'number',
639
- default: config.default,
640
- condition: config.condition,
641
- alwaysStore: config.alwaysStore,
642
- required: config.required,
643
- validator: config.validator,
644
- transformer: config.transformer,
645
- controlType: 'post',
646
- control({value, onChange, slots: {Messages}}: SNPControlProps<number>) {
647
- return <ExtendedPostPicker
648
- {...config.component}
649
- multiple={false}
650
- label={config.label}
651
- help={config.help}
652
- value={value?.toString()}
653
- onChange={useCallback(v => onChange(parseInt(v)), [onChange])}
654
- postTypes={config.postTypes}
655
- Messages={Messages}
656
- />;
657
- }
658
- };
659
- }
660
- }
661
-
662
- export function hydrateUserProperty(config: UserPropertyCSNPConfig): HydratedSimpleNativeProperty {
663
- if (config.multiple) {
664
- return {
665
- name: config.name,
666
- type: 'array',
667
- default: config.default,
668
- condition: config.condition,
669
- alwaysStore: config.alwaysStore,
670
- required: config.required,
671
- validator: config.validator,
672
- transformer: config.transformer,
673
- controlType: 'user',
674
- control({value, onChange, slots: {Messages}}: SNPControlProps<number[]>) {
675
- const safeOnChange = useCallback((v: string[]) => onChange(v.map(v => parseInt(v))), [onChange]);
676
- const safeValue = useMemo(() => value?.map(v => v.toString()), [value]);
677
- return <ExtendedUserPicker
678
- {...config.component}
679
- multiple={true}
680
- label={config.label}
681
- help={config.help}
682
- value={safeValue}
683
- onChange={safeOnChange}
684
- userRoles={config.userRoles}
685
- Messages={Messages}
686
- />;
687
- }
688
- };
689
- } else {
690
- return {
691
- name: config.name,
692
- type: 'number',
693
- default: config.default,
694
- condition: config.condition,
695
- alwaysStore: config.alwaysStore,
696
- required: config.required,
697
- validator: config.validator,
698
- transformer: config.transformer,
699
- controlType: 'user',
700
- control({value, onChange, slots: {Messages}}: SNPControlProps<number>) {
701
- return <ExtendedUserPicker
702
- {...config.component}
703
- multiple={false}
704
- label={config.label}
705
- help={config.help}
706
- value={value?.toString()}
707
- onChange={useCallback(v => onChange(parseInt(v)), [onChange])}
708
- userRoles={config.userRoles}
709
- Messages={Messages}
710
- />;
711
- }
712
- };
713
- }
714
- }
715
-
716
- export function hydrateTaxonomyProperty(config: TaxonomyPropertyCSNPConfig): HydratedSimpleNativeProperty {
717
- registerPlainTextLabelRequiredMarker("taxonomy", " > .components-form-token-field .components-form-token-field__label");
718
- if (config.multiple === false) {
719
- return {
720
- name: config.name,
721
- type: 'string',
722
- default: config.default,
723
- condition: config.condition,
724
- alwaysStore: config.alwaysStore,
725
- required: config.required,
726
- validator: config.validator,
727
- transformer: config.transformer,
728
- controlType: 'taxonomy',
729
- control({value, onChange, slots: {Messages}}) {
730
- return <ExtendedTaxonomyPicker
731
- {...config.component}
732
- multiple={false}
733
- label={config.label}
734
- help={config.help}
735
- value={value}
736
- visibility={config.visibility}
737
- onChange={onChange}
738
- Messages={Messages}
739
- />;
740
- }
741
- };
742
- } else {
743
- return {
744
- name: config.name,
745
- type: 'array',
746
- default: config.default,
747
- condition: config.condition,
748
- alwaysStore: config.alwaysStore,
749
- required: config.required,
750
- validator: config.validator,
751
- transformer: config.transformer,
752
- controlType: 'taxonomy',
753
- control({value, onChange, slots: {Messages}}: SNPControlProps<string[]>) {
754
- return <ExtendedTaxonomyPicker
755
- {...config.component}
756
- multiple={true}
757
- label={config.label}
758
- help={config.help}
759
- value={value}
760
- visibility={config.visibility}
761
- onChange={onChange}
762
- Messages={Messages}
763
- />;
764
- }
765
- };
766
- }
767
- }
768
-
769
- export function hydrateTermProperty(config: TermPropertyCSNPConfig): HydratedSimpleNativeProperty {
770
- registerPlainTextLabelRequiredMarker("term", " > .components-form-token-field .components-form-token-field__label");
771
- if (config.multiple === false) {
772
- return {
773
- name: config.name,
774
- type: 'string',
775
- default: config.default,
776
- condition: config.condition,
777
- alwaysStore: config.alwaysStore,
778
- required: config.required,
779
- validator: config.validator,
780
- transformer: config.transformer,
781
- controlType: 'taxonomy',
782
- control({value, onChange, slots: {Messages}}) {
783
- return <ExtendedTermPicker
784
- {...config.component}
785
- multiple={false}
786
- label={config.label}
787
- help={config.help}
788
- value={value}
789
- taxonomy={config.taxonomy}
790
- onChange={onChange}
791
- Messages={Messages}
792
- />;
793
- }
794
- };
795
- } else {
796
- return {
797
- name: config.name,
798
- type: 'array',
799
- default: config.default,
800
- condition: config.condition,
801
- alwaysStore: config.alwaysStore,
802
- required: config.required,
803
- validator: config.validator,
804
- transformer: config.transformer,
805
- controlType: 'taxonomy',
806
- control({value, onChange, slots: {Messages}}: SNPControlProps<string[]>) {
807
- return <ExtendedTermPicker
808
- {...config.component}
809
- multiple={true}
810
- label={config.label}
811
- help={config.help}
812
- value={value}
813
- taxonomy={config.taxonomy}
814
- onChange={onChange}
815
- Messages={Messages}
816
- />;
817
- }
818
- };
819
- }
820
- }
821
-
822
- function hydrateSelectProperty(config: SelectPropertyCSNPConfig): HydratedSimpleNativeProperty {
823
- registerPlainTextLabelRequiredMarker("select", " > .components-form-token-field .components-form-token-field__label");
824
- if (config.multiple) {
825
- return {
826
- name: config.name,
827
- label: config.label,
828
- type: 'array',
829
- default: config.default,
830
- condition: config.condition,
831
- alwaysStore: config.alwaysStore,
832
- required: config.required,
833
- validator: config.validator,
834
- transformer: config.transformer,
835
- controlType: 'select',
836
- control({value, onChange, slots: {Messages}}) {
837
- return <MultiSelectControl value={value ?? config.default} onChange={onChange} options={useSuspendableOptions(config)} label={config.label}
838
- help={config.help} expandOnFocus={config.expandOnFocus} Messages={Messages} maxLength={config.maxLength} />;
839
- }
840
- };
841
- } else {
842
- return {
843
- name: config.name,
844
- label: config.label,
845
- type: 'string',
846
- enum: config.enum,
847
- default: config.default,
848
- condition: config.condition,
849
- alwaysStore: config.alwaysStore,
850
- required: config.required,
851
- validator: config.validator,
852
- transformer: config.transformer,
853
- controlType: 'select',
854
- control({value, onChange, slots: {Label, Messages}}) {
855
- const currentValue = value ?? config.default;
856
- const normalizedOptions = useAsOptions(useSuspendableOptions(config), currentValue, config.clearable ? '' : undefined);
857
- return <>
858
- <SelectControl
859
- value={currentValue ?? ''} onChange={onChange} options={normalizedOptions} label={<Label />} help={config.help}
860
- __nextHasNoMarginBottom __next40pxDefaultSize
861
- />
862
- <Messages />
863
- </>;
864
- }
865
- };
866
- }
867
- }
868
-
869
- function useAsOptions<T extends string>(options: PickableOptions<T>, currentValue: T|undefined|null, noSelectionValue: T|undefined) {
870
- const normalizedOptions = useMemo(() => asOptions(options, noSelectionValue), [options, noSelectionValue]);
871
- return useMemo(() => {
872
- if (!normalizedOptions.find(opt => opt.value === currentValue)) {
873
- if (currentValue !== undefined && currentValue !== null) {
874
- const defaultValueItem = {value: currentValue, label: currentValue === "" ? "-- Default --" : currentValue.toString(), disabled: true};
875
- if (noSelectionValue !== undefined && !normalizedOptions.some(opt => !opt.value)) {
876
- return normalizedOptions.toSpliced(1, 0, defaultValueItem);
877
- } else {
878
- return [defaultValueItem, ...normalizedOptions];
879
- }
880
- } else if (noSelectionValue === undefined) {
881
- return [{value: "", label: "-- Default --", disabled: true}, ...normalizedOptions];
882
- }
883
- }
884
- return normalizedOptions;
885
- }, [normalizedOptions, currentValue]);
886
- }
887
- function asOptions<T extends string|number>(options: PickableOptions<T>, noSelectionValue?: T) {
888
- const res: Array<{ value: T, label: string, disabled?: boolean }> = normalizePickableOptionsToPairs(options)
889
- .map(opt => ({value: opt[0], label: (typeof opt[1] === 'string' ? opt[1] : opt[1].text) ?? opt[0].toString()}));
890
- if (noSelectionValue !== undefined && !res.some(opt => !opt.value)) {
891
- res.splice(0, 0, {value: noSelectionValue, label: "-- No Selection --"});
892
- }
893
- return res;
894
- }
895
-
896
- function hasDefinedKey<O extends object, K extends keyof O>(obj: O, key: K): obj is O&Required<Pick<O, K>> {
897
- return obj[key] !== undefined;
898
- }
899
-
900
- function invalidConfigMessageControl(config: CSNPConfig, passthroughProps: PassthroughPropsType, message: string) {
901
- return {
902
- type: 'string',
903
- default: config.default,
904
- alwaysStore: true,
905
- ...passthroughProps,
906
- validator: config.validator,
907
- transformer: config.transformer,
908
- control() {
909
- return <Disabled>
910
- <p>Invalid configuration for {config.label}: {message}</p>
911
- </Disabled>;
912
- }
913
- } as any;
914
- }
915
-
916
- export function registerPlainTextLabelRequiredMarker(componentType: string, labelSelectorFragment: string) {
917
- let loadedStyles: { componentTypes: Set<string>, sheet: CSSStyleSheet }|undefined = (window as any).plauditGutenbergExtensionsPlainTextLabelCSSLoaded;
918
- if (loadedStyles === undefined) {
919
- (window as any).plauditGutenbergExtensionsPlainTextLabelCSSLoaded = loadedStyles = {componentTypes: new Set(), sheet: new CSSStyleSheet()};
920
- document.adoptedStyleSheets.push(loadedStyles.sheet);
921
- }
922
- if (loadedStyles.componentTypes.has(componentType)) {
923
- return;
924
- }
925
- loadedStyles.componentTypes.add(componentType);
926
- loadedStyles.sheet.insertRule(`.plaudit-snp-laid-out-property.required[data-plaudit-snp-control-type='${componentType}']${labelSelectorFragment}:after {content:" *";color: red;}`);
927
- }