@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.
- package/CHANGELOG.md +12 -0
- package/dist/blocks/basic-custom-block-bindings-support.js +1 -1
- package/dist/blocks/basic-custom-block-bindings-support.js.map +1 -1
- package/dist/blocks/conditions.js.map +1 -1
- package/dist/blocks/problematic-blocks-blocker.js.map +1 -1
- package/dist/blocks/simple-block.d.ts +4 -4
- package/dist/blocks/simple-block.js +2 -2
- package/dist/blocks/simple-block.js.map +1 -1
- package/dist/lib/useful-types.d.ts +6 -7
- package/package.json +13 -15
- package/dist/lib/compat-types.d.ts +0 -34
- package/dist/lib/compat-types.js +0 -3
- package/dist/lib/compat-types.js.map +0 -1
- 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,335 +0,0 @@
|
|
|
1
|
-
import {store as blockEditorStore} from "@wordpress/block-editor";
|
|
2
|
-
import {createHigherOrderComponent} from "@wordpress/compose";
|
|
3
|
-
import {dispatch, select} from "@wordpress/data";
|
|
4
|
-
import {useEffect, useMemo} from "@wordpress/element";
|
|
5
|
-
import {addAction, addFilter} from "@wordpress/hooks";
|
|
6
|
-
|
|
7
|
-
import {produce} from "immer";
|
|
8
|
-
|
|
9
|
-
import {useDataController, walkToNodeInValue} from "./data-controller";
|
|
10
|
-
import {DataControllerManager, createDataControllerManager} from "./data-controller-manager";
|
|
11
|
-
import {extractStyleLayerValueFromAttributes, LayeredStylesDataStore} from "./layered-styles-impl";
|
|
12
|
-
import {TabsRoot} from "./layout/TabsRoot";
|
|
13
|
-
import {PanelRoot} from "./layout/PanelRoot";
|
|
14
|
-
import {store as gutenbergApiExtensionsStore} from "../lib/gutenberg-api-extensions-state";
|
|
15
|
-
import {ActualBEPAttrs, ActualBlockEditProps, ActualBlockListBlockProps, isBlockJsonNativePropsConfig, RegisterBlockAttrs} from "../lib/useful-types";
|
|
16
|
-
import {PathError} from "./PathError";
|
|
17
|
-
import type {DataController, DataStore, HydratedSimpleNativeProperty, InspectorPanelGroup} from "./simple-native-property-api";
|
|
18
|
-
import type {HydratedSimpleNativePanel, PerBlockExtraPropTransforms, PreppedSimpleNativePanelsAndTabs} from "./simple-native-property-internal-shared";
|
|
19
|
-
import {SNPDataStore} from "./snp-data-store";
|
|
20
|
-
import {SNPTreeContext} from "./SNPTreeContext";
|
|
21
|
-
|
|
22
|
-
class AttributeDataStore extends SNPDataStore {
|
|
23
|
-
public getAttribute(name: string): any {
|
|
24
|
-
return this.getRawBlockAttributes()[name];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public setAttribute(name: string, value: any) {
|
|
28
|
-
this.setRawBlockAttributes(name === 'className' ? {className: value?.toString()} : {[name]: value});
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
type ActualBlockSaveProps = {[key: Exclude<string, 'className'|'style'>]: unknown|undefined, className?: string|undefined, style?: Record<string, unknown>};
|
|
33
|
-
|
|
34
|
-
export function installSimpleNativePropertiesSupport() {
|
|
35
|
-
if (installSimpleNativePropertiesSupport.called) {
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
installSimpleNativePropertiesSupport.called = true;
|
|
39
|
-
|
|
40
|
-
const dataControllerManager = createDataControllerManager();
|
|
41
|
-
|
|
42
|
-
const registeredBlocks = new Map<string, boolean>();
|
|
43
|
-
addFilter('blocks.registerBlockType', 'plaudit/gutenberg-api-extensions/simple-native-properties', (atts: RegisterBlockAttrs<Record<string, any>>) => {
|
|
44
|
-
let isPlauditNative: boolean;
|
|
45
|
-
if (registeredBlocks.has(atts.name)) {
|
|
46
|
-
isPlauditNative = registeredBlocks.get(atts.name)!;
|
|
47
|
-
} else {
|
|
48
|
-
dispatch(gutenbergApiExtensionsStore).recordBaselineBlockAttrs(atts);
|
|
49
|
-
if ((isPlauditNative = isBlockJsonNativePropsConfig(atts.plaudit)) && atts.plaudit.properties !== undefined
|
|
50
|
-
&& (Array.isArray(atts.plaudit.properties) ? atts.plaudit.properties.length : atts.plaudit.properties)
|
|
51
|
-
) {
|
|
52
|
-
dispatch(gutenbergApiExtensionsStore).addProperties(atts.name, atts.plaudit.properties);
|
|
53
|
-
}
|
|
54
|
-
registeredBlocks.set(atts.name, isPlauditNative);
|
|
55
|
-
}
|
|
56
|
-
//TODO: Figure out some way to re-register a block when its attributes are changed
|
|
57
|
-
const blockSimpleNativePanelsAndTabs = select(gutenbergApiExtensionsStore).preppedProperties(atts.name);
|
|
58
|
-
if (blockSimpleNativePanelsAndTabs) {
|
|
59
|
-
const extraPropTransforms = select(gutenbergApiExtensionsStore).extraPropTransforms(atts.name) ?? {};
|
|
60
|
-
const attsAttributes = {...atts.attributes};
|
|
61
|
-
if (isPlauditNative && Object.values(extraPropTransforms).some(cfg => cfg.styleProperty)) {
|
|
62
|
-
attsAttributes['__plaudit/computed-style-attr'] = {type: "object"};
|
|
63
|
-
}
|
|
64
|
-
const injectableProperties: Record<string, any> = {};
|
|
65
|
-
for (const panel of blockSimpleNativePanelsAndTabs.panels) {
|
|
66
|
-
attachSNPsInPanel(panel[1].properties, injectableProperties);
|
|
67
|
-
}
|
|
68
|
-
for (const tabGroup of Object.values(blockSimpleNativePanelsAndTabs.tabs)) {
|
|
69
|
-
for (const tab of Object.values(tabGroup[1])) {
|
|
70
|
-
for (const item of tab.items) {
|
|
71
|
-
if ('properties' in item) {
|
|
72
|
-
attachSNPsInPanel(item.properties, injectableProperties);
|
|
73
|
-
} else if (Array.isArray(item)) {
|
|
74
|
-
attachSNPsInPanel(item, injectableProperties);
|
|
75
|
-
} else {
|
|
76
|
-
attachSNP(item, injectableProperties);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
for (const [name, config] of Object.entries(injectableProperties)) {
|
|
82
|
-
attsAttributes[name] = config;
|
|
83
|
-
}
|
|
84
|
-
return {...atts, attributes: attsAttributes};
|
|
85
|
-
}
|
|
86
|
-
return atts;
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
addFilter('editor.BlockEdit', 'plaudit/gutenberg-api-extensions/simple-native-properties',
|
|
90
|
-
createHigherOrderComponent(BlockEdit => function SNPBlockEdit(blockEditProps: ActualBlockEditProps) {
|
|
91
|
-
const blockSimpleNativePanelsAndTabs = select(gutenbergApiExtensionsStore).preppedProperties(blockEditProps.name);
|
|
92
|
-
if (!blockSimpleNativePanelsAndTabs) {
|
|
93
|
-
return <BlockEdit {...blockEditProps} />;
|
|
94
|
-
}
|
|
95
|
-
if (select(blockEditorStore).getBlockParentsByBlockName(blockEditProps.clientId, "core/block")?.length) {
|
|
96
|
-
// If we have a core/block ancestor, then we can't be edited, and attempting to self-edit will cause a crash
|
|
97
|
-
return <BlockEdit {...blockEditProps} />;
|
|
98
|
-
}
|
|
99
|
-
return <DataControllerInitializationIsolator bep={blockEditProps} dcm={dataControllerManager} BlockEdit={BlockEdit} snpPanelsAndTabs={blockSimpleNativePanelsAndTabs} />;
|
|
100
|
-
}, 'plauditGutenbergApiExtensionsSimpleNativeProperties'));
|
|
101
|
-
|
|
102
|
-
addAction('plaudit.gutenbergApiExtensions.simpleNativeProperties.afterWrite', 'plaudit/gutenberg-api-extensions/simple-native-properties',
|
|
103
|
-
(value: unknown, definition: HydratedSimpleNativeProperty, dataStore: DataStore) => {
|
|
104
|
-
if ("styleProperty" in definition && definition.styleProperty) {
|
|
105
|
-
const computedStyleAttr: {[key: string]: { property: string, value: string, className: string }} = dataStore.getValue('__plaudit/computed-style-attr') ?? {};
|
|
106
|
-
const valueStr = value?.toString();
|
|
107
|
-
if (computedStyleAttr[definition.name]?.value !== valueStr) {
|
|
108
|
-
if (valueStr?.length) {
|
|
109
|
-
dataStore.setValue('__plaudit/computed-style-attr', {
|
|
110
|
-
...computedStyleAttr,
|
|
111
|
-
[definition.name]: {property: definition.styleProperty, value: valueStr, className: makeHasPropClassName(definition.name)}
|
|
112
|
-
});
|
|
113
|
-
} else {
|
|
114
|
-
const entries = Object.entries(computedStyleAttr)
|
|
115
|
-
.filter(([key]) => key !== definition.name);
|
|
116
|
-
dataStore.setValue('__plaudit/computed-style-attr', entries.length ? Object.fromEntries(entries) : undefined);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
addFilter('editor.BlockListBlock', 'plaudit/gutenberg-api-extensions/simple-native-properties',
|
|
123
|
-
createHigherOrderComponent(BlockListBlock => function SNPBlockListBlock(props: ActualBlockListBlockProps) {
|
|
124
|
-
const wrapperProps: {className?: string, style?: Record<string, unknown>} = {...props.wrapperProps};
|
|
125
|
-
const extraPropTransforms = select(gutenbergApiExtensionsStore).computedExtraPropTransforms(props.name);
|
|
126
|
-
if (!extraPropTransforms || !extraPropTransforms.appliesInEditor) {
|
|
127
|
-
const dataController = dataControllerManager.getDataController(props.clientId);
|
|
128
|
-
if (!dataController) {
|
|
129
|
-
return <BlockListBlock {...props} />;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const hasValidationError = dataController.hasValidationErrors();
|
|
133
|
-
dataControllerManager.notifyOfValidationErrorStatusChange(props.clientId, hasValidationError);
|
|
134
|
-
dataController.checkConditions(); // We don't want to wrap this in a useEffect because this function is already only called when necessary.
|
|
135
|
-
|
|
136
|
-
if (hasValidationError) {
|
|
137
|
-
//TODO: Should I set isValid?
|
|
138
|
-
wrapperProps.className = wrapperProps.className ? `${wrapperProps.className} has-validation-error` : "has-validation-error";
|
|
139
|
-
return <BlockListBlock {...props} wrapperProps={wrapperProps} />;
|
|
140
|
-
}
|
|
141
|
-
return <BlockListBlock {...props} />;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const dataController = dataControllerManager.getDataController(props.clientId);
|
|
145
|
-
if (!dataController) {
|
|
146
|
-
return <BlockListBlock
|
|
147
|
-
{...props}
|
|
148
|
-
wrapperProps={applyExtraPropTransforms(extraPropTransforms.transforms, props.attributes, wrapperProps)}
|
|
149
|
-
/>;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const hasValidationError = dataController.hasValidationErrors();
|
|
153
|
-
dataControllerManager.notifyOfValidationErrorStatusChange(props.clientId, hasValidationError);
|
|
154
|
-
dataController.checkConditions(); // We don't want to wrap this in a useEffect because this function is already only called when necessary.
|
|
155
|
-
|
|
156
|
-
if (hasValidationError) {
|
|
157
|
-
//TODO: Should I set isValid?
|
|
158
|
-
wrapperProps.className = wrapperProps.className ? `${wrapperProps.className} has-validation-error` : "has-validation-error";
|
|
159
|
-
}
|
|
160
|
-
return (
|
|
161
|
-
<BlockListBlock
|
|
162
|
-
{...props}
|
|
163
|
-
wrapperProps={applyExtraPropTransforms(extraPropTransforms.transforms, props.attributes, wrapperProps)}
|
|
164
|
-
/>
|
|
165
|
-
);
|
|
166
|
-
}, 'plauditGutenbergApiExtensionsSimpleNativeWrapperMarkup'));
|
|
167
|
-
|
|
168
|
-
addFilter('blocks.getSaveContent.extraProps', 'plaudit/gutenberg-api-extensions/simple-native-properties',
|
|
169
|
-
(props: ActualBlockSaveProps, blockType: RegisterBlockAttrs, attributes: ActualBEPAttrs) => {
|
|
170
|
-
if (isBlockJsonNativePropsConfig(blockType.plaudit)) {
|
|
171
|
-
return props;
|
|
172
|
-
}
|
|
173
|
-
const extraPropTransforms = select(gutenbergApiExtensionsStore).computedExtraPropTransforms(blockType.name);
|
|
174
|
-
|
|
175
|
-
return extraPropTransforms && extraPropTransforms.appliesInEditor ? produce(props, draft => {
|
|
176
|
-
applyExtraPropTransforms(extraPropTransforms.transforms, attributes, draft);
|
|
177
|
-
}) : props;
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
export namespace installSimpleNativePropertiesSupport {
|
|
181
|
-
export let called: boolean = false;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
type DataControllerInitializationIsolatorProps = {bep: ActualBlockEditProps, dcm: DataControllerManager, snpPanelsAndTabs: PreppedSimpleNativePanelsAndTabs, BlockEdit: any};
|
|
185
|
-
function DataControllerInitializationIsolator({bep, dcm, snpPanelsAndTabs, BlockEdit}: DataControllerInitializationIsolatorProps) {
|
|
186
|
-
const dataController = useDataController(bep.clientId);
|
|
187
|
-
useEffect(() => {
|
|
188
|
-
dcm.addDataController(bep.clientId, dataController);
|
|
189
|
-
return () => dcm.removeDataController(bep.clientId);
|
|
190
|
-
}, [dataController, bep.clientId]);
|
|
191
|
-
|
|
192
|
-
return <>
|
|
193
|
-
<BlockEdit {...bep} />
|
|
194
|
-
<SNPPropsWrapper blockEditProps={bep} blockSimpleNativePanelsAndTabs={snpPanelsAndTabs} dataController={dataController} />
|
|
195
|
-
</>;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function applyExtraPropTransforms(
|
|
199
|
-
extraPropTransforms: PerBlockExtraPropTransforms['transforms'], attributes: ActualBEPAttrs, draft: {className?: string, style?: Record<string, unknown>}
|
|
200
|
-
) {
|
|
201
|
-
for (const [propPath, propTransform] of extraPropTransforms) {
|
|
202
|
-
let value: any;
|
|
203
|
-
try {
|
|
204
|
-
value = propTransform.forLayeredStyles ? extractStyleLayerValueFromAttributes(attributes, propPath[0]) : walkToNodeInValue(attributes[propPath[0]], propPath);
|
|
205
|
-
} catch (e) {
|
|
206
|
-
if (e instanceof PathError) {
|
|
207
|
-
value = undefined;
|
|
208
|
-
} else {
|
|
209
|
-
console.error("Encountered an error while applying extra props transforms:", e);
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (propTransform.styleProperty) {
|
|
215
|
-
const hasPropNameClass = makeHasPropClassName(propTransform.propName);
|
|
216
|
-
if (typeof value === 'string' || typeof value === 'number') {
|
|
217
|
-
const styleValue = typeof value === 'number' ? value.toString() : value.trim();
|
|
218
|
-
if (styleValue.length) {
|
|
219
|
-
if (draft.style) {
|
|
220
|
-
draft.style[propTransform.styleProperty] = styleValue;
|
|
221
|
-
} else {
|
|
222
|
-
draft.style = {[propTransform.styleProperty]: styleValue};
|
|
223
|
-
}
|
|
224
|
-
if (!draft.className) {
|
|
225
|
-
draft.className = hasPropNameClass;
|
|
226
|
-
} else if (!draft.className.split(/\s+/).includes(hasPropNameClass)) {
|
|
227
|
-
draft.className = draft.className + " " + hasPropNameClass;
|
|
228
|
-
}
|
|
229
|
-
} else {
|
|
230
|
-
if (draft.style) {
|
|
231
|
-
delete draft.style[propTransform.styleProperty];
|
|
232
|
-
}
|
|
233
|
-
if (draft.className) {
|
|
234
|
-
draft.className = draft.className.split(/\s+/).filter(cn => cn !== hasPropNameClass).join(" ");
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
} else if (value === undefined || value === null) {
|
|
238
|
-
if (draft.style) {
|
|
239
|
-
delete draft.style[propTransform.styleProperty];
|
|
240
|
-
}
|
|
241
|
-
if (draft.className) {
|
|
242
|
-
draft.className = draft.className.split(/\s+/).filter(cn => cn !== hasPropNameClass).join(" ");
|
|
243
|
-
}
|
|
244
|
-
} else {
|
|
245
|
-
console.error(`Encountered a SNP that had a styleProperty set but evaluated to a ${typeof value}.`, "Prop path:", propPath.join("."));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return draft;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function makeDataStoreForPanelOrTab(
|
|
253
|
-
blockEditProps: ActualBlockEditProps, group: string|undefined, attributeCache: Record<string, any>, identifier: string, dataStoreTypeOverride?: 'layered-styles'|'attribute'
|
|
254
|
-
) {
|
|
255
|
-
const args = [blockEditProps, attributeCache, `${blockEditProps.clientId}-${identifier}`] as const;
|
|
256
|
-
if (dataStoreTypeOverride) {
|
|
257
|
-
return dataStoreTypeOverride === 'layered-styles' ? new LayeredStylesDataStore(...args) : new AttributeDataStore(...args);
|
|
258
|
-
}
|
|
259
|
-
return group === 'layered-styles' ? new LayeredStylesDataStore(...args) : new AttributeDataStore(...args);
|
|
260
|
-
}
|
|
261
|
-
type SNPPropsWrapperProps = {blockSimpleNativePanelsAndTabs: PreppedSimpleNativePanelsAndTabs, blockEditProps: ActualBlockEditProps, dataController: DataController};
|
|
262
|
-
function SNPPropsWrapper({blockSimpleNativePanelsAndTabs, blockEditProps, dataController}: SNPPropsWrapperProps) {
|
|
263
|
-
useMemo(() => {
|
|
264
|
-
const attributeCache = Object.create(null);
|
|
265
|
-
for (const [identifier, panel] of blockSimpleNativePanelsAndTabs.panels) {
|
|
266
|
-
const dataStore = makeDataStoreForPanelOrTab(blockEditProps, panel.group, attributeCache, identifier, panel.dataStoreTypeOverride);
|
|
267
|
-
dataController.addProperties(panel.properties, dataStore, true);
|
|
268
|
-
}
|
|
269
|
-
for (const [group, [identifier, tabs]] of Object.entries(blockSimpleNativePanelsAndTabs.tabs)) {
|
|
270
|
-
const dataStore = makeDataStoreForPanelOrTab(blockEditProps, group, attributeCache, identifier);
|
|
271
|
-
for (const tab of Object.values(tabs)) {
|
|
272
|
-
for (const panel of tab.items) {
|
|
273
|
-
dataController.addProperties(panel.properties, dataStore, true);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
dataController.checkConditions();
|
|
278
|
-
dataController.validateNodes();
|
|
279
|
-
}, [blockSimpleNativePanelsAndTabs, dataController]); // We don't depend on blockEditProps here - that is handled by the following useEffect
|
|
280
|
-
|
|
281
|
-
// We have to reattach dataStores in the useMemo segment in order for Panel- and Tab-level conditions to work
|
|
282
|
-
useMemo(() => {
|
|
283
|
-
const attributeCache = Object.create(null);
|
|
284
|
-
for (const dataStore of dataController.getAllDataStores()) {
|
|
285
|
-
dataStore.reattach(attributeCache);
|
|
286
|
-
}
|
|
287
|
-
}, [dataController.getAllDataStores(), blockEditProps]);
|
|
288
|
-
|
|
289
|
-
useEffect(() => {
|
|
290
|
-
//TODO: This is a temporary patch. The proper solution is to subscribe to the block-editor redux store and use that to determine when properties have changed.
|
|
291
|
-
if (dataController.getWasChangedByManagedControl()) {
|
|
292
|
-
dataController.markManagedControlChangeHandled();
|
|
293
|
-
} else{
|
|
294
|
-
dataController.checkConditions();
|
|
295
|
-
dataController.validateNodes();
|
|
296
|
-
}
|
|
297
|
-
}, [dataController.getAllDataStores(), blockEditProps]);
|
|
298
|
-
|
|
299
|
-
//TODO: Hook into block-focus change listener and run validation on focus change
|
|
300
|
-
return <SNPTreeContext.Provider value={dataController}>
|
|
301
|
-
{...blockSimpleNativePanelsAndTabs.panels.map(panel => <PanelRoot key={panel[0]} panel={panel[1]} />)}
|
|
302
|
-
{...Object.entries(blockSimpleNativePanelsAndTabs.tabs).map(
|
|
303
|
-
([group, tabs]) => <TabsRoot key={tabs[0]} tabs={tabs[1]} tabGroupName={group as InspectorPanelGroup} />)}
|
|
304
|
-
</SNPTreeContext.Provider>;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function attachSNP(property: HydratedSimpleNativeProperty, injectableProperties: Record<string, any>) {
|
|
308
|
-
injectableProperties[property.name] = {type: property.type, default: property.alwaysStore !== false ? undefined : property.default};
|
|
309
|
-
if ('enum' in property) {
|
|
310
|
-
injectableProperties[property.name].enum = property.enum;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function attachSNPsInPanel(properties: HydratedSimpleNativePanel['properties'], injectableProperties: Record<string, any>) {
|
|
315
|
-
for (const property of properties) {
|
|
316
|
-
if (Array.isArray(property)) {
|
|
317
|
-
for (const prop of property) {
|
|
318
|
-
attachSNP(prop, injectableProperties);
|
|
319
|
-
}
|
|
320
|
-
} else {
|
|
321
|
-
attachSNP(property, injectableProperties);
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
export function makeHasPropClassName(propName: string) {
|
|
327
|
-
return "has-" + kebabCase(propName);
|
|
328
|
-
}
|
|
329
|
-
const kebabCaseRegexes = [/([a-z])([A-Z])/g, /[\s_]+/g] as const;
|
|
330
|
-
export function kebabCase(value: string) {
|
|
331
|
-
return value
|
|
332
|
-
.replace(kebabCaseRegexes[0], "$1-$2")
|
|
333
|
-
.replace(kebabCaseRegexes[1], '-')
|
|
334
|
-
.toLowerCase();
|
|
335
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type {HydratedSimpleNativeProperty, InspectorPanelGroup, SimpleNativePanel, SimpleNativeTab} from "./simple-native-property-api";
|
|
2
|
-
|
|
3
|
-
export type ExtraPropTransformConfig = {propName: string, forLayeredStyles: boolean, styleProperty?: string};
|
|
4
|
-
export type ExtraPropTransformsConfig = {[key: string]: ExtraPropTransformConfig};
|
|
5
|
-
export type PerBlockExtraPropTransforms = {transforms: [[string, ...string[]], ExtraPropTransformConfig][], appliesInEditor: boolean};
|
|
6
|
-
|
|
7
|
-
export type HydratedSimpleNativeTabItem = Omit<HydratedSimpleNativePanel, 'group'>;
|
|
8
|
-
export type HydratedSimpleNativeTab = Omit<SimpleNativeTab, 'items'>&{items: HydratedSimpleNativeTabItem[]};
|
|
9
|
-
export type HydratedSimpleNativeTabGroup = {[tabTitle: string]: HydratedSimpleNativeTab};
|
|
10
|
-
export type HydratedSimpleNativePanel = Omit<SimpleNativePanel, 'properties'>&{properties: HydratedLaidOutProperties};
|
|
11
|
-
|
|
12
|
-
export type HydratedLaidOutProperties = (HydratedSimpleNativeProperty|HydratedSimpleNativeProperty[])[];
|
|
13
|
-
|
|
14
|
-
export type HydratedInspectorPanelGroupedTabs = {[key in InspectorPanelGroup]?: [string, HydratedSimpleNativeTabGroup]};
|
|
15
|
-
export type PreppedSimpleNativePanelsAndTabs = {panels: [string, HydratedSimpleNativePanel][], tabs: HydratedInspectorPanelGroupedTabs};
|
|
16
|
-
|
|
17
|
-
export function isRootLevelPanelGroup(panelGroup: string|undefined) {
|
|
18
|
-
return panelGroup === undefined || panelGroup === "default" || panelGroup === "advanced" || panelGroup === "settings" || panelGroup === "styles";
|
|
19
|
-
}
|
package/src/blocks/snp-api.ts
DELETED
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
export type {ActualBEPAttrs, ActualBlockEditProps, ActualBlockListBlockProps, ActualBlockSaveProps, BlockJsonNativePropsConfig, RegisterBlockAttrs, NonEmptyArray} from "../lib/useful-types";
|
|
2
|
-
export {isBlockJsonNativePropsConfig, arrayIsNotEmpty} from "../lib/useful-types";
|
|
3
|
-
export type {CSNPConfig} from "./csnp-api";
|
|
4
|
-
export type * from "./common-native-property-constructors";
|
|
5
|
-
export type * from "./simple-native-property-api";
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import {store as blockEditorStore} from "@wordpress/block-editor";
|
|
2
|
-
import {dispatch, select} from "@wordpress/data";
|
|
3
|
-
|
|
4
|
-
import {fastDeepEquals} from "../lib/modified-fast-deep-equals";
|
|
5
|
-
|
|
6
|
-
import type {UUID} from "./data-controller/utils";
|
|
7
|
-
import type {ActualBlockEditProps, BlockName} from "../lib/useful-types";
|
|
8
|
-
import type {DataStore, HydratedSimpleNativeProperty} from "./simple-native-property-api";
|
|
9
|
-
|
|
10
|
-
export abstract class SNPDataStore implements DataStore {
|
|
11
|
-
private readonly managedPropertyNames: {[propertyName in string]: true};
|
|
12
|
-
|
|
13
|
-
protected readonly blockClientId: string;
|
|
14
|
-
protected readonly blockName: BlockName;
|
|
15
|
-
|
|
16
|
-
public constructor(
|
|
17
|
-
explicitBlockEditProps: ActualBlockEditProps,
|
|
18
|
-
private attributeCache: Record<string, any>,
|
|
19
|
-
public readonly id: string
|
|
20
|
-
) {
|
|
21
|
-
this.managedPropertyNames = Object.create(null);
|
|
22
|
-
|
|
23
|
-
this.blockClientId = explicitBlockEditProps.clientId;
|
|
24
|
-
this.blockName = explicitBlockEditProps.name;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
getValue(attr: string): any {
|
|
28
|
-
if (attr in this.attributeCache) {
|
|
29
|
-
return this.attributeCache[attr];
|
|
30
|
-
}
|
|
31
|
-
const existingValue = this.getAttribute(attr);
|
|
32
|
-
return this.attributeCache[attr] = existingValue;
|
|
33
|
-
}
|
|
34
|
-
setValue(attr: string, value: any) {
|
|
35
|
-
const existingValue = this.getValue(attr);
|
|
36
|
-
if (!fastDeepEquals(existingValue, value)) {
|
|
37
|
-
this.attributeCache[attr] = value;
|
|
38
|
-
// WordPress doesn't handle saving and loading of empty objects consistently. As a result, we cannot safely write empty objects if the existing value is undefined
|
|
39
|
-
if (existingValue !== undefined || typeof value !== 'object' || !value || Object.entries(value).some(([, v]) => v !== undefined)) {
|
|
40
|
-
this.setAttribute(attr, value);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
handlesProperty(name: string): boolean {
|
|
45
|
-
return name in this.managedPropertyNames;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
reattach(attributeCache: Record<string, any>) {
|
|
49
|
-
this.attributeCache = attributeCache;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
protected abstract getAttribute(name: string): any;
|
|
53
|
-
protected abstract setAttribute(name: string, value: any): void;
|
|
54
|
-
|
|
55
|
-
protected getRawBlockAttributes() {
|
|
56
|
-
return select(blockEditorStore).getBlockAttributes(this.blockClientId) ?? {};
|
|
57
|
-
}
|
|
58
|
-
protected setRawBlockAttributes(attributes: Record<string, any>) {
|
|
59
|
-
dispatch(blockEditorStore).updateBlockAttributes!(this.blockClientId, attributes);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
protected getCachedValue(attr: string) {
|
|
63
|
-
return this.attributeCache[attr];
|
|
64
|
-
}
|
|
65
|
-
protected hasCachedValue(attr: string) {
|
|
66
|
-
return attr in this.attributeCache;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
addProperty(property: HydratedSimpleNativeProperty<{uuid: UUID}>) {
|
|
70
|
-
this.managedPropertyNames[property.name] = true;
|
|
71
|
-
}
|
|
72
|
-
}
|
package/src/blocks/utilities.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import {type BlockStyle, type BlockVariation, registerBlockStyle, registerBlockVariation, unregisterBlockStyle, unregisterBlockVariation} from "@wordpress/blocks";
|
|
2
|
-
import domReady from "@wordpress/dom-ready";
|
|
3
|
-
|
|
4
|
-
import type {PairNormalizedPickableOptions} from "../controls";
|
|
5
|
-
|
|
6
|
-
export function registerBlockStyles(blockName: string|string[], ...blockStyles: BlockStyle[]) {
|
|
7
|
-
domReady(() => {
|
|
8
|
-
forStringOrStrings(blockName, bn => {
|
|
9
|
-
for (const blockStyle of blockStyles) {
|
|
10
|
-
registerBlockStyle(bn, blockStyle);
|
|
11
|
-
}
|
|
12
|
-
});
|
|
13
|
-
if (Array.isArray(blockName)) {
|
|
14
|
-
for (const bn of blockName) {
|
|
15
|
-
for (const blockStyle of blockStyles) {
|
|
16
|
-
registerBlockStyle(bn, blockStyle);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
} else {
|
|
20
|
-
for (const blockStyle of blockStyles) {
|
|
21
|
-
registerBlockStyle(blockName, blockStyle);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
export function unregisterBlockStyles(blockName: string|string[], ...blockStyleNames: string[]) {
|
|
27
|
-
domReady(() => {
|
|
28
|
-
forStringOrStrings(blockName, bn => {
|
|
29
|
-
for (const blockStyleName of blockStyleNames) {
|
|
30
|
-
unregisterBlockStyle(bn, blockStyleName);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function registerBlockVariations(blockName: string, ...blockVariations: BlockVariation[]) {
|
|
37
|
-
domReady(() => {
|
|
38
|
-
forStringOrStrings(blockName, bn => registerBlockVariation(bn, blockVariations));
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
export function unregisterBlockVariations(blockName: string, ...blockVariationNames: string[]) {
|
|
42
|
-
domReady(() => {
|
|
43
|
-
forStringOrStrings(blockName, bn => unregisterBlockVariation(bn, blockVariationNames));
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function convertToSelectControlOptions(response: unknown): PairNormalizedPickableOptions<string> {
|
|
48
|
-
if (response === null || response === undefined) {
|
|
49
|
-
return [];
|
|
50
|
-
} else if (typeof response === 'object') {
|
|
51
|
-
return (Array.isArray(response) ? response : Object.entries(response)).toSorted(([a], [b]) => a.localeCompare(b));
|
|
52
|
-
} else {
|
|
53
|
-
console.warn("An invalid response type was passed to convertToSelectControlOptions:", response);
|
|
54
|
-
return [[String(response), String(response)]];
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function forStringOrStrings(stringOrStrings: string|string[], action: (string: string) => void) {
|
|
59
|
-
if (Array.isArray(stringOrStrings)) {
|
|
60
|
-
for (const string of stringOrStrings) {
|
|
61
|
-
action(string);
|
|
62
|
-
}
|
|
63
|
-
} else {
|
|
64
|
-
action(stringOrStrings);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import {Spinner, FormTokenField} from '@wordpress/components';
|
|
2
|
-
import {useCallback} from "@wordpress/element";
|
|
3
|
-
import {__} from "@wordpress/i18n";
|
|
4
|
-
|
|
5
|
-
import type {SNPControlSlots} from "../blocks";
|
|
6
|
-
import {useMultiSingleConversionLayer} from "./hooks/useMultiSingleConversionLayer";
|
|
7
|
-
import {useSuggestions} from "./hooks/useSuggestions";
|
|
8
|
-
import {useTokenManager} from "./hooks/useTokenManager";
|
|
9
|
-
import type {TokenItem} from "../lib/useful-types";
|
|
10
|
-
|
|
11
|
-
// The strange values correspond to the literals that are expected by TokenItem.status, which allows the assignment code to be cleaner
|
|
12
|
-
export const enum ValidationState {
|
|
13
|
-
Valid = 'success',
|
|
14
|
-
Invalid = 'error',
|
|
15
|
-
Validating = 'validating',
|
|
16
|
-
Waiting = 'waiting'
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
type AsynchronousFormTokenFieldPropsBase = {
|
|
20
|
-
label?: string;
|
|
21
|
-
help?: string;
|
|
22
|
-
|
|
23
|
-
validationQuery(tokens: string[]): Promise<Array<TokenItem>>;
|
|
24
|
-
suggestionQuery(input: string): Promise<Array<string>>;
|
|
25
|
-
makeTokenFromSuggestion(suggestion: string): TokenItem;
|
|
26
|
-
validator?: (value: string) => boolean;
|
|
27
|
-
expandOnFocus?: boolean;
|
|
28
|
-
}&Partial<Pick<SNPControlSlots, 'Messages'>>;
|
|
29
|
-
type AsynchronousFormTokenFieldPropsSingle = AsynchronousFormTokenFieldPropsBase&{
|
|
30
|
-
value?: string;
|
|
31
|
-
onChange: (value: string) => void;
|
|
32
|
-
multiple: false;
|
|
33
|
-
};
|
|
34
|
-
type AsynchronousFormTokenFieldPropsMultiple = AsynchronousFormTokenFieldPropsBase&{
|
|
35
|
-
value?: string[];
|
|
36
|
-
onChange: (value: string[]) => void;
|
|
37
|
-
multiple?: true|undefined;
|
|
38
|
-
};
|
|
39
|
-
export type AsynchronousFormTokenFieldProps = AsynchronousFormTokenFieldPropsSingle|AsynchronousFormTokenFieldPropsMultiple;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* @deprecated use ExtendedFormTokenField instead
|
|
43
|
-
*/
|
|
44
|
-
export function AsynchronousFormTokenField(props: AsynchronousFormTokenFieldProps) {
|
|
45
|
-
const {help, expandOnFocus = false} = props;
|
|
46
|
-
|
|
47
|
-
const {value, setValue} = useMultiSingleConversionLayer(props.value, props.onChange, props.makeTokenFromSuggestion, props.multiple);
|
|
48
|
-
|
|
49
|
-
const {
|
|
50
|
-
currentTokens, dispatchValueUpdate, hasValidationError, isInitializing, isValidating, tokenTitleMappings
|
|
51
|
-
} = useTokenManager(value, setValue, props.validationQuery, props.makeTokenFromSuggestion);
|
|
52
|
-
const {
|
|
53
|
-
hasLoadingError, isLoading, suggestions, input, setInput
|
|
54
|
-
} = useSuggestions(undefined, {expandOnFocus, getSuggestions: props.suggestionQuery});
|
|
55
|
-
|
|
56
|
-
const displayTransform = useCallback((token: string) => tokenTitleMappings[token] ?? token, [tokenTitleMappings]);
|
|
57
|
-
const onFocus = useCallback(() => setInput(input), [input]);
|
|
58
|
-
|
|
59
|
-
if (isInitializing) {
|
|
60
|
-
return <Spinner />;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
//TODO: If focus is in field but user hasn't started typing, show a message telling them to start typing
|
|
64
|
-
return <>
|
|
65
|
-
<FormTokenField
|
|
66
|
-
__next40pxDefaultSize
|
|
67
|
-
value={currentTokens}
|
|
68
|
-
label={props.label}
|
|
69
|
-
placeholder="Start typing to see suggestions"
|
|
70
|
-
suggestions={suggestions}
|
|
71
|
-
onChange={dispatchValueUpdate}
|
|
72
|
-
maxLength={props.multiple === false ? 1 : undefined}
|
|
73
|
-
__experimentalValidateInput={props.validator}
|
|
74
|
-
__experimentalAutoSelectFirstMatch={true}
|
|
75
|
-
__experimentalExpandOnFocus={expandOnFocus}
|
|
76
|
-
displayTransform={displayTransform}
|
|
77
|
-
onInputChange={setInput}
|
|
78
|
-
onFocus={onFocus}
|
|
79
|
-
/>
|
|
80
|
-
{help && <div><span className="components-form-token-field__help">{help}</span></div>}
|
|
81
|
-
{hasLoadingError && <div><Spinner /><span className="components-form-token-field__help">{__("An Error Occurred While Loading Suggestions")}</span></div>}
|
|
82
|
-
{isLoading && <div><Spinner /><span className="components-form-token-field__help">{__("Updating Suggestions")}</span></div>}
|
|
83
|
-
{hasValidationError && <div><Spinner /><span className="components-form-token-field__help">{__("An Error Occurred While Validating")}</span></div>}
|
|
84
|
-
{isValidating && <div><Spinner /><span className="components-form-token-field__help">{__("Validating")}</span></div>}
|
|
85
|
-
</>;
|
|
86
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import {BaseControl, Button, useBaseControlProps} from "@wordpress/components";
|
|
2
|
-
import {useCallback} from "@wordpress/element";
|
|
3
|
-
import {dragHandle, Icon} from "@wordpress/icons";
|
|
4
|
-
|
|
5
|
-
import {Reorder, useDragControls} from "framer-motion";
|
|
6
|
-
import type {PointerEvent, ReactNode} from "react";
|
|
7
|
-
|
|
8
|
-
import {ProvidedSlot} from "../blocks";
|
|
9
|
-
import {IndexedItemActionsMaker, KeyedValue, useSortableItemsModel} from "./hooks/useSortableItemsModel";
|
|
10
|
-
|
|
11
|
-
export type BaseSortableItemsControlProps<D> = Omit<Parameters<typeof useBaseControlProps>[0], 'children'|'id'>&{
|
|
12
|
-
id?: string,
|
|
13
|
-
value: D[]|undefined,
|
|
14
|
-
onAdd?: (index: number, value: D) => void,
|
|
15
|
-
onChange?: (value: D[]) => void,
|
|
16
|
-
onRemove?: (index: number) => void,
|
|
17
|
-
onReorder?: (oldIndex: number, newIndex: number) => void,
|
|
18
|
-
children: (datum: D, onDatumChange: (datum: D) => void, index: number) => ReactNode,
|
|
19
|
-
buttonsArea: (props: {addHandler: (added: D, index?: number) => void, disabled: boolean}) => ReactNode,
|
|
20
|
-
min?: number,
|
|
21
|
-
max?: number,
|
|
22
|
-
emptyValue?: D,
|
|
23
|
-
Label?: ProvidedSlot, Messages?: ProvidedSlot
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
export function BaseSortableItemsControl<D>({
|
|
27
|
-
value, onChange, onRemove, onReorder, onAdd, children: makeChild, min, max, label, Label, Messages, buttonsArea, emptyValue, ...wrapperProps
|
|
28
|
-
}: BaseSortableItemsControlProps<D>) {
|
|
29
|
-
const {add: addHandler, commit, makeIndexedItemActions, keyedValues} = useSortableItemsModel({onAdd, onChange, onRemove, onReorder, emptyValue, value});
|
|
30
|
-
|
|
31
|
-
const listLength = value?.length ?? 0;
|
|
32
|
-
const addDisabled = max !== undefined && listLength >= max;
|
|
33
|
-
const removeDisabled = min !== undefined && listLength <= min;
|
|
34
|
-
const {baseControlProps, controlProps} = useBaseControlProps({...wrapperProps, label: Label ? <Label /> : label});
|
|
35
|
-
|
|
36
|
-
return <BaseControl {...baseControlProps} __nextHasNoMarginBottom>
|
|
37
|
-
<div {...controlProps} className="plaudit-sortable-items-container">
|
|
38
|
-
<Reorder.Group
|
|
39
|
-
axis="y" onReorder={commit} values={keyedValues}
|
|
40
|
-
children={keyedValues.map((datum, index) => <SortableItemsListNode
|
|
41
|
-
addDisabled={addDisabled} datum={datum} index={index} key={datum.key} listLength={listLength}
|
|
42
|
-
makeChild={makeChild} makeIndexedItemActions={makeIndexedItemActions} removeDisabled={removeDisabled}
|
|
43
|
-
/>)}
|
|
44
|
-
/>
|
|
45
|
-
</div>
|
|
46
|
-
{buttonsArea({addHandler, disabled: addDisabled})}
|
|
47
|
-
{Messages && <Messages />}
|
|
48
|
-
</BaseControl>;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
type SortableItemsListNodeProps<D> = {
|
|
52
|
-
addDisabled: boolean, datum: KeyedValue<D>, index: number, listLength: number,
|
|
53
|
-
makeChild: BaseSortableItemsControlProps<D>['children'], removeDisabled: boolean,
|
|
54
|
-
makeIndexedItemActions: IndexedItemActionsMaker<D>
|
|
55
|
-
};
|
|
56
|
-
function SortableItemsListNode<D>({datum, index, listLength, makeChild, makeIndexedItemActions, removeDisabled}: SortableItemsListNodeProps<D>) {
|
|
57
|
-
const {remove, moveUp, moveDown, change} = makeIndexedItemActions(index, datum.key);
|
|
58
|
-
//TODO: If used, implement addBefore and addAfter the same way as moveUp and moveDown
|
|
59
|
-
|
|
60
|
-
const dragControls = useDragControls();
|
|
61
|
-
const dragStartHandler = useCallback((event: PointerEvent) => dragControls.start(event), [dragControls]);
|
|
62
|
-
|
|
63
|
-
return <Reorder.Item className="plaudit-sortable-items-row" data-index={index} dragListener={false} dragControls={dragControls} layout="position" value={datum}>
|
|
64
|
-
<div className="plaudit-sortable-items-ordering-controls">
|
|
65
|
-
<div className="plaudit-sortable-items-ordering-control">
|
|
66
|
-
<Icon icon={dragHandle} onPointerDown={dragStartHandler} />
|
|
67
|
-
</div>
|
|
68
|
-
<div className="plaudit-sortable-items-ordering-control">
|
|
69
|
-
<Button icon="arrow-up" aria-label="Move Up" disabled={index < 1} onClick={moveUp} __next40pxDefaultSize accessibleWhenDisabled />
|
|
70
|
-
<Button icon="arrow-down" aria-label="Move Down" disabled={index >= listLength - 1} onClick={moveDown} __next40pxDefaultSize accessibleWhenDisabled />
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
<div className="plaudit-sortable-items-inputs plaudit-sortable-items-padded">
|
|
74
|
-
{makeChild(datum.value, change, index)}
|
|
75
|
-
</div>
|
|
76
|
-
<div className="plaudit-sortable-items-presence-controls plaudit-sortable-items-padded">
|
|
77
|
-
{/*addEmptyHandlers && index === 0 &&
|
|
78
|
-
<Button icon="insert" className="insert-before" disabled={addDisabled} onClick={addEmptyHandlers[0]} aria-label="Insert Before"/>*/}
|
|
79
|
-
<Button icon="remove" disabled={removeDisabled} onClick={remove} aria-label="Remove" __next40pxDefaultSize accessibleWhenDisabled />
|
|
80
|
-
{/*addEmptyHandlers && <Button icon="insert" className="insert-after" disabled={addDisabled} onClick={addEmptyHandlers[1]}
|
|
81
|
-
aria-label={index < listLength - 1 ? "Insert Between" : "Insert After"}/>*/}
|
|
82
|
-
</div>
|
|
83
|
-
</Reorder.Item>;
|
|
84
|
-
}
|