@plaudit/gutenberg-api-extensions 2.94.2 → 2.96.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.
- package/CHANGELOG.md +12 -0
- package/dist/lib/useful-types.d.ts +1 -4
- package/package.json +4 -4
- package/src/blocks/MoveError.ts +0 -7
- package/src/blocks/PathError.ts +0 -18
- package/src/blocks/SNPFlexibleItemsListComponent.tsx +0 -30
- package/src/blocks/SNPGroupComponent.tsx +0 -38
- package/src/blocks/SNPListComponent.tsx +0 -25
- package/src/blocks/SNPTreeContext.tsx +0 -13
- package/src/blocks/basic-custom-block-bindings-support.tsx +0 -248
- package/src/blocks/common-native-property-constructors.tsx +0 -927
- package/src/blocks/conditions.ts +0 -261
- package/src/blocks/csnp-api.ts +0 -221
- package/src/blocks/data-controller/actions.ts +0 -20
- package/src/blocks/data-controller/reducer.ts +0 -146
- package/src/blocks/data-controller/trigger-handlers.ts +0 -150
- package/src/blocks/data-controller/utils.ts +0 -415
- package/src/blocks/data-controller-manager.ts +0 -50
- package/src/blocks/data-controller.ts +0 -165
- package/src/blocks/hooks/built-in-suspendable-option-protocols/select.ts +0 -51
- package/src/blocks/hooks/built-in-suspendable-option-protocols/settings.ts +0 -70
- package/src/blocks/hooks/useSuspendableOptions.ts +0 -122
- package/src/blocks/index.ts +0 -23
- package/src/blocks/layered-styles-api.ts +0 -142
- package/src/blocks/layered-styles-impl.ts +0 -95
- package/src/blocks/layout/LaidOutProperty.tsx +0 -72
- package/src/blocks/layout/LaidOutPropertyRow.tsx +0 -28
- package/src/blocks/layout/NodeContext.tsx +0 -54
- package/src/blocks/layout/PanelRoot.tsx +0 -30
- package/src/blocks/layout/TabsRoot.tsx +0 -56
- package/src/blocks/layout/ToolsPanelContext.tsx +0 -22
- package/src/blocks/problematic-blocks-blocker.ts +0 -24
- package/src/blocks/problematic-variations-blocker.ts +0 -32
- package/src/blocks/shared-exportable-types.ts +0 -6
- package/src/blocks/shared-internal-types.ts +0 -18
- package/src/blocks/simple-block.tsx +0 -74
- package/src/blocks/simple-native-property-api.ts +0 -173
- package/src/blocks/simple-native-property-impl.tsx +0 -335
- package/src/blocks/simple-native-property-internal-shared.ts +0 -19
- package/src/blocks/snp-api.ts +0 -5
- package/src/blocks/snp-data-store.ts +0 -72
- package/src/blocks/utilities.ts +0 -66
- package/src/controls/AsynchronousFormTokenField.tsx +0 -86
- package/src/controls/BaseSortableItemsControl.tsx +0 -84
- package/src/controls/ExtendedFormTokenField.tsx +0 -144
- package/src/controls/ExtendedPostPicker.ts +0 -57
- package/src/controls/ExtendedRadioControl.tsx +0 -107
- package/src/controls/ExtendedTaxonomyPicker.tsx +0 -100
- package/src/controls/ExtendedTermPicker.tsx +0 -61
- package/src/controls/ExtendedTextareaControl.tsx +0 -65
- package/src/controls/ExtendedUserPicker.ts +0 -56
- package/src/controls/FileControl.tsx +0 -48
- package/src/controls/FullSizeToggleControl.tsx +0 -95
- package/src/controls/ImageControl.tsx +0 -143
- package/src/controls/InspectorPanel.tsx +0 -37
- package/src/controls/LazySuggestionsComboboxControl.tsx +0 -64
- package/src/controls/MultiSelectControl.tsx +0 -59
- package/src/controls/PickOne.tsx +0 -88
- package/src/controls/PromisableComponent.tsx +0 -56
- package/src/controls/ProperLinkControl.tsx +0 -98
- package/src/controls/SimpleToggle.tsx +0 -9
- package/src/controls/SortableFlexibleItemsControl.tsx +0 -37
- package/src/controls/SortableItemsControl.tsx +0 -22
- package/src/controls/basicNumericallyIdedItemPicker.tsx +0 -75
- package/src/controls/hooks/useImprovedTokenManager.ts +0 -163
- package/src/controls/hooks/useMultiSingleConversionLayer.ts +0 -17
- package/src/controls/hooks/useNonRenderingCounter.ts +0 -6
- package/src/controls/hooks/useOutputMemoizingFilter.ts +0 -16
- package/src/controls/hooks/useSortableItemsModel.ts +0 -196
- package/src/controls/hooks/useSuggestions.ts +0 -91
- package/src/controls/hooks/useTokenManager.ts +0 -177
- package/src/controls/index.ts +0 -24
- package/src/controls/shared.ts +0 -50
- package/src/controls/types.ts +0 -18
- package/src/editor/insert-sibling-or-child-block-shortcut.tsx +0 -60
- package/src/editor/install-insert-sole-allowed-block-shortcut-support.tsx +0 -51
- package/src/editor/simple-gutenberg-endpoints-api.ts +0 -31
- package/src/editor/simple-gutenberg-endpoints-impl.ts +0 -126
- package/src/index.ts +0 -30
- package/src/lib/compat-types.ts +0 -21
- package/src/lib/gutenberg-api-extensions-state/custom-block-bindings-support-logic.ts +0 -35
- package/src/lib/gutenberg-api-extensions-state/general-logic.ts +0 -41
- package/src/lib/gutenberg-api-extensions-state/layered-block-styles-logic.ts +0 -43
- package/src/lib/gutenberg-api-extensions-state/snp-logic.ts +0 -240
- package/src/lib/gutenberg-api-extensions-state.ts +0 -69
- package/src/lib/helpers.ts +0 -115
- package/src/lib/modified-fast-deep-equals.ts +0 -91
- package/src/lib/plaudit-icons/column-1.tsx +0 -6
- package/src/lib/plaudit-icons/column-2.tsx +0 -6
- package/src/lib/plaudit-icons/column-3.tsx +0 -6
- package/src/lib/plaudit-icons/placement-center.tsx +0 -3
- package/src/lib/plaudit-icons/placement-end.tsx +0 -3
- package/src/lib/plaudit-icons/placement-start.tsx +0 -3
- package/src/lib/plaudit-icons/placement-stretch.tsx +0 -3
- package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -4
- package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -3
- package/src/lib/plaudit-icons.ts +0 -13
- package/src/lib/sectioned-cache-store.ts +0 -120
- package/src/lib/suspense/promise-handlers.ts +0 -72
- package/src/lib/suspense.tsx +0 -18
- package/src/lib/useful-types.ts +0 -82
- package/src/schemas/README.md +0 -1
- 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
|
-
}
|