@plaudit/gutenberg-api-extensions 2.90.1 → 2.91.1
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 +14 -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/common-native-property-constructors.js +18 -11
- package/dist/blocks/common-native-property-constructors.js.map +1 -1
- package/dist/blocks/data-controller/reducer.js +1 -4
- package/dist/blocks/data-controller/reducer.js.map +1 -1
- package/dist/blocks/data-controller/trigger-handlers.js +20 -11
- package/dist/blocks/data-controller/trigger-handlers.js.map +1 -1
- package/dist/blocks/data-controller/utils.js +9 -9
- package/dist/blocks/data-controller/utils.js.map +1 -1
- package/dist/blocks/data-controller-manager.d.ts +1 -1
- package/dist/blocks/data-controller-manager.js.map +1 -1
- package/dist/blocks/data-controller.d.ts +1 -1
- package/dist/blocks/data-controller.js.map +1 -1
- package/dist/blocks/hooks/useSuspendableOptions.js +1 -1
- package/dist/blocks/hooks/useSuspendableOptions.js.map +1 -1
- package/dist/blocks/simple-native-property-api.d.ts +2 -2
- package/dist/blocks/simple-native-property-api.js +3 -2
- package/dist/blocks/simple-native-property-api.js.map +1 -1
- package/dist/blocks/simple-native-property-impl.js +0 -1
- package/dist/blocks/simple-native-property-impl.js.map +1 -1
- package/dist/blocks/snp-data-store.d.ts +2 -2
- package/dist/blocks/snp-data-store.js +10 -15
- package/dist/blocks/snp-data-store.js.map +1 -1
- package/dist/controls/ExtendedTermPicker.js +3 -4
- package/dist/controls/ExtendedTermPicker.js.map +1 -1
- package/dist/controls/basicNumericallyIdedItemPicker.js +3 -4
- package/dist/controls/basicNumericallyIdedItemPicker.js.map +1 -1
- package/dist/index.d.ts +4 -8
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/modified-fast-deep-equals.d.ts +4 -0
- package/dist/lib/modified-fast-deep-equals.js +91 -0
- package/dist/lib/modified-fast-deep-equals.js.map +1 -0
- package/package.json +2 -2
- package/src/blocks/basic-custom-block-bindings-support.tsx +1 -1
- package/src/blocks/common-native-property-constructors.tsx +18 -12
- package/src/blocks/data-controller/reducer.ts +1 -4
- package/src/blocks/data-controller/trigger-handlers.ts +22 -11
- package/src/blocks/data-controller/utils.ts +9 -9
- package/src/blocks/data-controller-manager.ts +2 -2
- package/src/blocks/data-controller.ts +2 -4
- package/src/blocks/hooks/useSuspendableOptions.ts +1 -1
- package/src/blocks/simple-native-property-api.ts +5 -4
- package/src/blocks/simple-native-property-impl.tsx +0 -1
- package/src/blocks/snp-data-store.ts +12 -15
- package/src/controls/ExtendedTermPicker.tsx +3 -4
- package/src/controls/basicNumericallyIdedItemPicker.tsx +3 -4
- package/src/index.ts +5 -10
- package/src/lib/modified-fast-deep-equals.ts +91 -0
- package/styles/sortable-items-control.pcss +61 -55
|
@@ -449,13 +449,10 @@ function hydrateDesiccatedSimpleNativeProperty(
|
|
|
449
449
|
}
|
|
450
450
|
};
|
|
451
451
|
case "select":
|
|
452
|
-
registerPlainTextLabelRequiredMarker("select", " > .components-form-token-field .components-form-token-field__label");
|
|
453
452
|
return hydrateSelectProperty(config);
|
|
454
453
|
case "taxonomy":
|
|
455
|
-
registerPlainTextLabelRequiredMarker("taxonomy", " > .components-form-token-field .components-form-token-field__label");
|
|
456
454
|
return hydrateTaxonomyProperty(config);
|
|
457
455
|
case "term":
|
|
458
|
-
registerPlainTextLabelRequiredMarker("term", " > .components-form-token-field .components-form-token-field__label");
|
|
459
456
|
return hydrateTermProperty(config);
|
|
460
457
|
case "textarea":
|
|
461
458
|
return {
|
|
@@ -592,7 +589,7 @@ function NumberLikeListTextControl({listComponent, datum, itemType, onDatumChang
|
|
|
592
589
|
type PotentiallyExtendedRadioControlProps = CSNPControlComponentProps<RadioPropertyCSNPConfig, string>&SNPControlSlots;
|
|
593
590
|
function PotentiallyExtendedRadioControl({config, value, onChange, Label, Messages}: PotentiallyExtendedRadioControlProps) {
|
|
594
591
|
const unsuspendedOptions = useSuspendableOptions(config);
|
|
595
|
-
const options = useAsOptions(unsuspendedOptions, value);
|
|
592
|
+
const options = useAsOptions(unsuspendedOptions, value, undefined);
|
|
596
593
|
|
|
597
594
|
if (config.allowCustom) {
|
|
598
595
|
return <ExtendedRadioControl {...config.component} selected={value} onChange={onChange} options={options} label={config.label} help={config.help}
|
|
@@ -714,6 +711,7 @@ export function hydrateUserProperty(config: UserPropertyCSNPConfig): HydratedSim
|
|
|
714
711
|
}
|
|
715
712
|
|
|
716
713
|
export function hydrateTaxonomyProperty(config: TaxonomyPropertyCSNPConfig): HydratedSimpleNativeProperty {
|
|
714
|
+
registerPlainTextLabelRequiredMarker("taxonomy", " > .components-form-token-field .components-form-token-field__label");
|
|
717
715
|
if (config.multiple === false) {
|
|
718
716
|
return {
|
|
719
717
|
name: config.name,
|
|
@@ -766,6 +764,7 @@ export function hydrateTaxonomyProperty(config: TaxonomyPropertyCSNPConfig): Hyd
|
|
|
766
764
|
}
|
|
767
765
|
|
|
768
766
|
export function hydrateTermProperty(config: TermPropertyCSNPConfig): HydratedSimpleNativeProperty {
|
|
767
|
+
registerPlainTextLabelRequiredMarker("term", " > .components-form-token-field .components-form-token-field__label");
|
|
769
768
|
if (config.multiple === false) {
|
|
770
769
|
return {
|
|
771
770
|
name: config.name,
|
|
@@ -818,6 +817,7 @@ export function hydrateTermProperty(config: TermPropertyCSNPConfig): HydratedSim
|
|
|
818
817
|
}
|
|
819
818
|
|
|
820
819
|
function hydrateSelectProperty(config: SelectPropertyCSNPConfig): HydratedSimpleNativeProperty {
|
|
820
|
+
registerPlainTextLabelRequiredMarker("select", " > .components-form-token-field .components-form-token-field__label");
|
|
821
821
|
if (config.multiple) {
|
|
822
822
|
return {
|
|
823
823
|
name: config.name,
|
|
@@ -849,11 +849,12 @@ function hydrateSelectProperty(config: SelectPropertyCSNPConfig): HydratedSimple
|
|
|
849
849
|
transformer: config.transformer,
|
|
850
850
|
controlType: 'select',
|
|
851
851
|
control({value, onChange, slots: {Label, Messages}}) {
|
|
852
|
-
const
|
|
852
|
+
const currentValue = value ?? config.default;
|
|
853
|
+
const normalizedOptions = useAsOptions(useSuspendableOptions(config), currentValue, config.clearable ? '' : undefined);
|
|
853
854
|
return <>
|
|
854
855
|
<SelectControl
|
|
856
|
+
value={currentValue ?? ''} onChange={onChange} options={normalizedOptions} label={<Label />} help={config.help}
|
|
855
857
|
__nextHasNoMarginBottom __next40pxDefaultSize
|
|
856
|
-
value={value ?? config.default ?? ''} onChange={onChange} options={normalizedOptions} label={<Label />} help={config.help}
|
|
857
858
|
/>
|
|
858
859
|
<Messages />
|
|
859
860
|
</>;
|
|
@@ -862,14 +863,19 @@ function hydrateSelectProperty(config: SelectPropertyCSNPConfig): HydratedSimple
|
|
|
862
863
|
}
|
|
863
864
|
}
|
|
864
865
|
|
|
865
|
-
function useAsOptions<T extends string
|
|
866
|
+
function useAsOptions<T extends string>(options: PickableOptions<T>, currentValue: T|undefined|null, noSelectionValue: T|undefined) {
|
|
866
867
|
const normalizedOptions = useMemo(() => asOptions(options, noSelectionValue), [options, noSelectionValue]);
|
|
867
868
|
return useMemo(() => {
|
|
868
|
-
if (
|
|
869
|
-
if (
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
869
|
+
if (!normalizedOptions.find(opt => opt.value === currentValue)) {
|
|
870
|
+
if (currentValue !== undefined && currentValue !== null) {
|
|
871
|
+
const defaultValueItem = {value: currentValue, label: currentValue === "" ? "-- Default --" : currentValue.toString(), disabled: true};
|
|
872
|
+
if (noSelectionValue !== undefined && !normalizedOptions.some(opt => !opt.value)) {
|
|
873
|
+
return normalizedOptions.toSpliced(1, 0, defaultValueItem);
|
|
874
|
+
} else {
|
|
875
|
+
return [defaultValueItem, ...normalizedOptions];
|
|
876
|
+
}
|
|
877
|
+
} else if (noSelectionValue === undefined) {
|
|
878
|
+
return [{value: "", label: "-- Default --", disabled: true}, ...normalizedOptions];
|
|
873
879
|
}
|
|
874
880
|
}
|
|
875
881
|
return normalizedOptions;
|
|
@@ -30,10 +30,7 @@ export function buildReducer(blockClientId: string) {
|
|
|
30
30
|
}
|
|
31
31
|
for (const property of properties) {
|
|
32
32
|
for (const prop of Array.isArray(property) ? property : [property]) {
|
|
33
|
-
|
|
34
|
-
state.batchAddedPropertiesThatNeedWriteThrough.push(recordCloneableDefaultValueForNode(prop));
|
|
35
|
-
}
|
|
36
|
-
dataStore.addProperty(prop, !meta.inBatch);
|
|
33
|
+
dataStore.addProperty(prop);
|
|
37
34
|
state.dataStores.byProperty[prop.name] = dataStore;
|
|
38
35
|
state.treeRoot.children[prop.name] = buildNodeFromDataAndDefinition(dataStore.getValue(prop.name), prop);
|
|
39
36
|
}
|
|
@@ -15,7 +15,8 @@ import {
|
|
|
15
15
|
TreeMutatorResult,
|
|
16
16
|
walkToNode,
|
|
17
17
|
walkToNodeInValue,
|
|
18
|
-
writeValueToBackingDataStore
|
|
18
|
+
writeValueToBackingDataStore,
|
|
19
|
+
writeValueToBackingDataStoreWithCorrections,
|
|
19
20
|
} from "./utils";
|
|
20
21
|
|
|
21
22
|
type BasePayload = {[key in string]: any}&{type?: never, inBatch?: never};
|
|
@@ -107,21 +108,19 @@ function performConditionChecks(state: Draft<DCStoreState>) {
|
|
|
107
108
|
applyToTree(state.treeRoot, (node, path) => {
|
|
108
109
|
const condition = node.condition;
|
|
109
110
|
if (condition === undefined) {
|
|
111
|
+
if (node.rendered === undefined) {
|
|
112
|
+
if (getDataStoreAndCurrentValue(path, state).currentValue === undefined) {
|
|
113
|
+
// Restore from backup if we are transitioning from unrendered -> rendered
|
|
114
|
+
const stateNode = walkToNode(state.treeRoot, path);
|
|
115
|
+
writeValueToBackingDataStoreWithCorrections(path, state, restoreValueFromPotentialBackup(stateNode));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
110
118
|
node.rendered = true;
|
|
111
119
|
return TreeMutatorResult.CONTINUE;
|
|
112
120
|
} else {
|
|
113
121
|
const newRendered = testCondition(condition, propertyValueResolverBuilder(path));
|
|
114
122
|
if (newRendered !== node.rendered) {
|
|
115
|
-
const dataStore =
|
|
116
|
-
let currentValue: any;
|
|
117
|
-
try {
|
|
118
|
-
currentValue = walkToNodeInValue(dataStore.getValue(path[0]), path);
|
|
119
|
-
} catch (e) {
|
|
120
|
-
if (!(e instanceof PathError)) {
|
|
121
|
-
throw e;
|
|
122
|
-
}
|
|
123
|
-
currentValue = undefined;
|
|
124
|
-
}
|
|
123
|
+
const {dataStore, currentValue} = getDataStoreAndCurrentValue(path, state);
|
|
125
124
|
if (!newRendered) {
|
|
126
125
|
// Create a backup if we are transitioning from rendered -> unrendered
|
|
127
126
|
recordBackupForNode(node, currentValue);
|
|
@@ -137,3 +136,15 @@ function performConditionChecks(state: Draft<DCStoreState>) {
|
|
|
137
136
|
}
|
|
138
137
|
});
|
|
139
138
|
}
|
|
139
|
+
|
|
140
|
+
function getDataStoreAndCurrentValue(path: NodePath, state: Draft<DCStoreState>) {
|
|
141
|
+
const dataStore = getDataStore(path[0], state, true);
|
|
142
|
+
try {
|
|
143
|
+
return {dataStore, currentValue: walkToNodeInValue(dataStore.getValue(path[0]), path)} as const;
|
|
144
|
+
} catch (e) {
|
|
145
|
+
if (!(e instanceof PathError)) {
|
|
146
|
+
throw e;
|
|
147
|
+
}
|
|
148
|
+
return {dataStore, currentValue: undefined} as const;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -361,30 +361,30 @@ export function buildNodeFromDataAndDefinition(data: any, definition: HydratedSi
|
|
|
361
361
|
if (children) {
|
|
362
362
|
if (data === undefined || data === null) {
|
|
363
363
|
if (definition.type === 'array') {
|
|
364
|
-
return {
|
|
364
|
+
return {condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: []};
|
|
365
365
|
} else if (definition.type === 'object') {
|
|
366
366
|
if (Array.isArray(children)) {
|
|
367
367
|
return {
|
|
368
|
-
|
|
368
|
+
condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "",
|
|
369
369
|
children: Object.fromEntries(children.map(def => [def.name, buildNodeFromDataAndDefinition(undefined, def)]))
|
|
370
370
|
};
|
|
371
371
|
} else {
|
|
372
372
|
//TODO: We might need to throw an error here
|
|
373
|
-
return {
|
|
373
|
+
return {condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: {}};
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
} else if (typeof data === 'object') {
|
|
377
377
|
if (Array.isArray(data)) {
|
|
378
378
|
return {
|
|
379
|
-
|
|
379
|
+
condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: data.map(item => {
|
|
380
380
|
const typedChildren = Array.isArray(children) ? children : children[item.type];
|
|
381
381
|
if (typedChildren === undefined) {
|
|
382
382
|
//TODO: We might need to throw an error here
|
|
383
|
-
return {children: [],
|
|
383
|
+
return {children: [], validationError: ""};
|
|
384
384
|
}
|
|
385
385
|
return {
|
|
386
386
|
children: Object.fromEntries(typedChildren.map(def => [def.name, buildNodeFromDataAndDefinition(item[def.name], def)])),
|
|
387
|
-
|
|
387
|
+
validationError: ""
|
|
388
388
|
};
|
|
389
389
|
})
|
|
390
390
|
};
|
|
@@ -392,14 +392,14 @@ export function buildNodeFromDataAndDefinition(data: any, definition: HydratedSi
|
|
|
392
392
|
const typedChildren = Array.isArray(children) ? children : children[data.type];
|
|
393
393
|
if (typedChildren === undefined) {
|
|
394
394
|
//TODO: We might need to throw an error here
|
|
395
|
-
return {
|
|
395
|
+
return {condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: []};
|
|
396
396
|
}
|
|
397
397
|
return {
|
|
398
|
-
|
|
398
|
+
condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "",
|
|
399
399
|
children: Object.fromEntries(typedChildren.map(def => [def.name, buildNodeFromDataAndDefinition(data[def.name], def)]))
|
|
400
400
|
};
|
|
401
401
|
}
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
|
-
return {
|
|
404
|
+
return {condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: ""};
|
|
405
405
|
}
|
|
@@ -2,7 +2,7 @@ import type {DataController} from "./simple-native-property-api";
|
|
|
2
2
|
|
|
3
3
|
export type DataControllerManager = {
|
|
4
4
|
addDataController(blockClientId: string, dataController: DataController): void,
|
|
5
|
-
getDataController(blockClientId: string): DataController,
|
|
5
|
+
getDataController(blockClientId: string): DataController|undefined,
|
|
6
6
|
removeDataController(blockClientId: string): void,
|
|
7
7
|
notifyOfValidationErrorStatusChange(blockClientId: string, hasValidationError: boolean): void
|
|
8
8
|
}
|
|
@@ -46,5 +46,5 @@ export function createDataControllerManager(): DataControllerManager {
|
|
|
46
46
|
return styleSheet.replace(`.block-editor-list-view-tree tbody tr:is(${blockMarkers}) {background: var(--plaudit-gae-validation-error-bg) !important;}`);
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
-
} as DataControllerManagerInternals;
|
|
49
|
+
} satisfies DataControllerManagerInternals as DataControllerManagerInternals;
|
|
50
50
|
}
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {useMemo} from "@wordpress/element";
|
|
2
2
|
|
|
3
|
-
import {configureStore, Middleware, Tuple} from "@reduxjs/toolkit";
|
|
3
|
+
import {configureStore, type Middleware, Tuple} from "@reduxjs/toolkit";
|
|
4
4
|
|
|
5
5
|
import type {Dispatch} from "redux";
|
|
6
|
-
import type {ThunkDispatch} from "redux-thunk";
|
|
7
6
|
|
|
8
7
|
import {type Condition, resolveValueForCondition} from "./conditions";
|
|
9
8
|
import {PathError, PathErrorType} from "./PathError";
|
|
@@ -11,7 +10,7 @@ import type {DataController, DataStore, HydratedSimpleNativeProperty, NodePath}
|
|
|
11
10
|
|
|
12
11
|
import {actions, type DataControllerActions} from "./data-controller/actions";
|
|
13
12
|
import {buildReducer} from "./data-controller/reducer";
|
|
14
|
-
import {getDataStore, getOptionalValue, UUID, walkToNode} from "./data-controller/utils";
|
|
13
|
+
import {getDataStore, getOptionalValue, type UUID, walkToNode} from "./data-controller/utils";
|
|
15
14
|
|
|
16
15
|
type DescendantsWrapper = {[key: string]: DCNode}|DCNode[];
|
|
17
16
|
export type DCNode = {
|
|
@@ -30,7 +29,6 @@ export type DCStoreState = {
|
|
|
30
29
|
rerenderTrigger: number,
|
|
31
30
|
batchAddedPropertiesThatNeedWriteThrough: HydratedSimpleNativeProperty<{uuid: UUID}>[]
|
|
32
31
|
};
|
|
33
|
-
type DCThunkDispatch = ThunkDispatch<DCStoreState, unknown, DataControllerActions>;
|
|
34
32
|
|
|
35
33
|
export function useDataController(blockClientId: string): DataController {
|
|
36
34
|
return useMemo(() => {
|
|
@@ -24,7 +24,7 @@ export function useSuspendableOptions<T extends CSNPConfig&{options: PromisableP
|
|
|
24
24
|
selectorInfo.searchParams.has("attributes"), selectorInfo, blockClientId);
|
|
25
25
|
|
|
26
26
|
// This if/else statement is present because actually passing attributes through to the selectors represents a SEVERE relative performance hit.
|
|
27
|
-
// At a minimum, it causes at
|
|
27
|
+
// At a minimum, it causes at least one additional full execution pass, and, due to how data can be cached, that pass is almost guaranteed to turn into at least one full render.
|
|
28
28
|
let mapSelect: (...args: Parameters<MapSelect>) => [Record<string, any>|null, string|null];
|
|
29
29
|
if (usesBlockAttributes) {
|
|
30
30
|
mapSelect = wrappedSelect => {
|
|
@@ -5,8 +5,9 @@ import type {CSNPConfig} from "./csnp-api";
|
|
|
5
5
|
import type {Condition} from "./conditions";
|
|
6
6
|
import type {DCNode} from "./data-controller";
|
|
7
7
|
import {installGutenbergExtensions} from "../index";
|
|
8
|
-
import
|
|
8
|
+
import {store as apiExtensionsStore} from "../lib/gutenberg-api-extensions-state";
|
|
9
9
|
import type {HydratedLaidOutProperties} from "./simple-native-property-internal-shared";
|
|
10
|
+
import type {BlockName} from "../lib/useful-types";
|
|
10
11
|
|
|
11
12
|
import type {ReactElement, ReactNode} from "react";
|
|
12
13
|
|
|
@@ -22,7 +23,7 @@ export interface DataStore {
|
|
|
22
23
|
|
|
23
24
|
handlesProperty(name: string): boolean;
|
|
24
25
|
|
|
25
|
-
addProperty(property: HydratedSimpleNativeProperty
|
|
26
|
+
addProperty(property: HydratedSimpleNativeProperty): void;
|
|
26
27
|
}
|
|
27
28
|
export type RawPath = (string|number)[];
|
|
28
29
|
export type NodePath = [string, ...(string|number)[]];
|
|
@@ -121,10 +122,10 @@ export type SimpleNativeTab = {
|
|
|
121
122
|
export type PropertiesParameter = SimpleNativePanel|SimpleNativeTab|Array<LaidOutProperties[number]|SimpleNativePanel|SimpleNativeTab>;
|
|
122
123
|
|
|
123
124
|
export function registerSimpleNativeProperties(config: {block: BlockName|Array<BlockName>, properties: PropertiesParameter}) {
|
|
124
|
-
dispatch(
|
|
125
|
+
dispatch(apiExtensionsStore).addProperties(config.block, config.properties);
|
|
125
126
|
}
|
|
126
127
|
export function getSimpleNativeProperties(block: BlockName): Array<PDSimpleNativeProperty>|undefined {
|
|
127
|
-
const panelsAndTabs = select(
|
|
128
|
+
const panelsAndTabs = select(apiExtensionsStore).desiccatedProperties(block);
|
|
128
129
|
if (panelsAndTabs === undefined) {
|
|
129
130
|
return undefined;
|
|
130
131
|
}
|
|
@@ -276,7 +276,6 @@ function SNPPropsWrapper({blockSimpleNativePanelsAndTabs, blockEditProps, dataCo
|
|
|
276
276
|
}
|
|
277
277
|
dataController.checkConditions();
|
|
278
278
|
dataController.validateNodes();
|
|
279
|
-
dataController.commitBatchAddedProperties();
|
|
280
279
|
}, [blockSimpleNativePanelsAndTabs, dataController]); // We don't depend on blockEditProps here - that is handled by the following useEffect
|
|
281
280
|
|
|
282
281
|
// We have to reattach dataStores in the useMemo segment in order for Panel- and Tab-level conditions to work
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import {store as blockEditorStore} from "@wordpress/block-editor";
|
|
2
2
|
import {dispatch, select} from "@wordpress/data";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {fastDeepEquals} from "../lib/modified-fast-deep-equals";
|
|
5
|
+
|
|
6
|
+
import type {UUID} from "./data-controller/utils";
|
|
5
7
|
import type {ActualBlockEditProps, BlockName} from "../lib/useful-types";
|
|
6
8
|
import type {DataStore, HydratedSimpleNativeProperty} from "./simple-native-property-api";
|
|
7
9
|
|
|
@@ -30,8 +32,14 @@ export abstract class SNPDataStore implements DataStore {
|
|
|
30
32
|
return this.attributeCache[attr] = existingValue;
|
|
31
33
|
}
|
|
32
34
|
setValue(attr: string, value: any) {
|
|
33
|
-
this.
|
|
34
|
-
|
|
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
|
+
}
|
|
35
43
|
}
|
|
36
44
|
handlesProperty(name: string): boolean {
|
|
37
45
|
return name in this.managedPropertyNames;
|
|
@@ -58,18 +66,7 @@ export abstract class SNPDataStore implements DataStore {
|
|
|
58
66
|
return attr in this.attributeCache;
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
addProperty(property: HydratedSimpleNativeProperty<{uuid: UUID}
|
|
62
|
-
if (this.managedPropertyNames[property.name]) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (this.getAttribute(property.name) === undefined) {
|
|
66
|
-
if (writeThroughOnUndefined) {
|
|
67
|
-
this.setValue(property.name, buildDefaultValueFromDefinition(property));
|
|
68
|
-
} else {
|
|
69
|
-
this.attributeCache[property.name] = buildDefaultValueFromDefinition(property);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
69
|
+
addProperty(property: HydratedSimpleNativeProperty<{uuid: UUID}>) {
|
|
73
70
|
this.managedPropertyNames[property.name] = true;
|
|
74
71
|
}
|
|
75
72
|
}
|
|
@@ -5,6 +5,7 @@ import {useCallback} from "@wordpress/element";
|
|
|
5
5
|
import type {SNPControlSlots} from "../blocks";
|
|
6
6
|
import {ExtendedFormTokenField, unpackDisplayedTokenText, ValidationState} from "./ExtendedFormTokenField";
|
|
7
7
|
import {registerSimpleGutenbergApiEndpoint} from "../editor/simple-gutenberg-endpoints-api";
|
|
8
|
+
import {store as endpointsStore} from "../editor/simple-gutenberg-endpoints-impl";
|
|
8
9
|
import type {TokenItem} from "../lib/useful-types";
|
|
9
10
|
|
|
10
11
|
import type {ReactNode} from "react";
|
|
@@ -50,12 +51,10 @@ export function ExtendedTermPicker(props: ExtendedTermPickerProps) {
|
|
|
50
51
|
const {taxonomy} = props;
|
|
51
52
|
|
|
52
53
|
const suggestionQuery = useCallback((input: string, select: typeof dataSelect) => {
|
|
53
|
-
return select(
|
|
54
|
-
.get("plaudit-common.term-table-search-options", {search: input, taxonomy}) as TokenItem[]|undefined;
|
|
54
|
+
return select(endpointsStore).get("plaudit-common.term-table-search-options", {search: input, taxonomy}) as TokenItem[]|undefined;
|
|
55
55
|
}, [taxonomy]);
|
|
56
56
|
const validationQuery = useCallback((slugsBeingValidated: string[], select: typeof dataSelect) => {
|
|
57
|
-
return
|
|
58
|
-
.get("plaudit-common.term-table-search-validation", {slugs: slugsBeingValidated.join(','), taxonomy}) as TokenItem[]|undefined);
|
|
57
|
+
return select(endpointsStore).get("plaudit-common.term-table-search-validation", {slugs: slugsBeingValidated.join(','), taxonomy}) as TokenItem[]|undefined;
|
|
59
58
|
}, [taxonomy]);
|
|
60
59
|
|
|
61
60
|
return <ExtendedFormTokenField {...props} validationQuery={validationQuery} suggestionQuery={suggestionQuery} stringToTokenConverter={unpackDisplayedTokenText} />;
|
|
@@ -3,6 +3,7 @@ import {useCallback} from "@wordpress/element";
|
|
|
3
3
|
|
|
4
4
|
import type {SNPControlSlots} from "../blocks";
|
|
5
5
|
import {ExtendedFormTokenField, unpackDisplayedTokenText, ValidationState} from "./ExtendedFormTokenField";
|
|
6
|
+
import {store as endpointsStore} from "../editor/simple-gutenberg-endpoints-impl";
|
|
6
7
|
import {isNumeric} from "./shared";
|
|
7
8
|
import type {TokenItem} from "../lib/useful-types";
|
|
8
9
|
|
|
@@ -29,12 +30,10 @@ export function basicNumericallyIdedItemPicker(props: BasicNumericallyIdedItemPi
|
|
|
29
30
|
const {multiple, value, ...remainder} = props;
|
|
30
31
|
|
|
31
32
|
const suggestionQuery = useCallback((input: string, select: typeof dataSelect) => {
|
|
32
|
-
return select(
|
|
33
|
-
.get(endpoint + "-options", {search: input, ...queryProps}) as TokenItem[]|undefined;
|
|
33
|
+
return select(endpointsStore).get(endpoint + "-options", {search: input, ...queryProps}) as TokenItem[]|undefined;
|
|
34
34
|
}, [queryProps]);
|
|
35
35
|
const validationQuery = useCallback((idsBeingValidated: string[], select: typeof dataSelect) => {
|
|
36
|
-
return
|
|
37
|
-
.get(endpoint + "-validation", {ids: idsBeingValidated.join(','), ...queryProps}) as TokenItem[]|undefined);
|
|
36
|
+
return select(endpointsStore).get(endpoint + "-validation", {ids: idsBeingValidated.join(','), ...queryProps}) as TokenItem[]|undefined;
|
|
38
37
|
}, [queryProps]);
|
|
39
38
|
|
|
40
39
|
if (props.multiple === false) {
|
package/src/index.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import {installSimpleNativePropertiesSupport} from "./blocks/simple-native-property-impl";
|
|
2
|
-
import {installSimpleGutenbergApisSupport,
|
|
3
|
-
import {registerStore,
|
|
2
|
+
import {installSimpleGutenbergApisSupport, store as endpointsStore} from "./editor/simple-gutenberg-endpoints-impl";
|
|
3
|
+
import {registerStore, store as apiExtensionsStore} from "./lib/gutenberg-api-extensions-state";
|
|
4
4
|
|
|
5
5
|
export * from "./blocks";
|
|
6
6
|
export * from "./controls";
|
|
7
7
|
export * from "./editor/simple-gutenberg-endpoints-api";
|
|
8
8
|
export {TemporalLRUCache, useAdoptedStyleSheet} from "./lib/helpers";
|
|
9
|
+
export * from "./lib/modified-fast-deep-equals";
|
|
9
10
|
export * from "./lib/plaudit-icons";
|
|
10
11
|
export * as SectionedCacheStore from "./lib/sectioned-cache-store";
|
|
11
12
|
export * from "./lib/suspense";
|
|
12
13
|
export type * from "./lib/useful-types";
|
|
13
14
|
|
|
15
|
+
export {apiExtensionsStore, endpointsStore};
|
|
16
|
+
|
|
14
17
|
export function installGutenbergExtensions() {
|
|
15
18
|
if (!installGutenbergExtensions.called) {
|
|
16
19
|
installGutenbergExtensions.called = true;
|
|
@@ -22,11 +25,3 @@ export function installGutenbergExtensions() {
|
|
|
22
25
|
export namespace installGutenbergExtensions {
|
|
23
26
|
export let called: boolean = false;
|
|
24
27
|
}
|
|
25
|
-
|
|
26
|
-
declare module "@wordpress/data" {
|
|
27
|
-
function select(key: 'plaudit/gutenberg-api-extensions'): ReturnType<typeof select<typeof apiStore>>;
|
|
28
|
-
function dispatch(key: 'plaudit/gutenberg-api-extensions'): ReturnType<typeof dispatch<typeof apiStore>>;
|
|
29
|
-
|
|
30
|
-
function select(key: 'plaudit/simple-gutenberg-apis'): ReturnType<typeof select<typeof endpointStore>>;
|
|
31
|
-
function dispatch(key: 'plaudit/simple-gutenberg-apis'): ReturnType<typeof dispatch<typeof endpointStore>>;
|
|
32
|
-
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This is a modified copy of https://github.com/epoberezkin/fast-deep-equal that treats "not present" and "present but undefined" as equivalent
|
|
3
|
+
*/
|
|
4
|
+
export function fastDeepEquals(a: unknown, b: unknown) {
|
|
5
|
+
if (a === b) return true;
|
|
6
|
+
|
|
7
|
+
if (a && b && typeof a === 'object' && typeof b === 'object') {
|
|
8
|
+
if (a.constructor !== b.constructor) return false;
|
|
9
|
+
|
|
10
|
+
if (Array.isArray(a)) {
|
|
11
|
+
if (!Array.isArray(b)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const length = a.length;
|
|
16
|
+
if (length !== b.length) return false;
|
|
17
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
18
|
+
if (!fastDeepEquals(a[i], b[i])) return false;
|
|
19
|
+
}
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if (a instanceof Map) {
|
|
25
|
+
if (!(b instanceof Map)) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (const [key, value] of b.entries()) {
|
|
30
|
+
if (value !== undefined && !a.has(key)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (const [key, value] of a.entries()) {
|
|
35
|
+
if (value !== undefined && !b.has(key)) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
if (!fastDeepEquals(value, b.get(key))) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (a instanceof Set) {
|
|
46
|
+
if (!(b instanceof Set)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (a.size !== b.size) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
for (const key of a.keys()) {
|
|
53
|
+
if (!b.has(key)) return false;
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (a instanceof RegExp) {
|
|
59
|
+
return b instanceof RegExp && a.source === b.source && a.flags === b.flags;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
|
|
63
|
+
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
|
|
64
|
+
|
|
65
|
+
for (const [key, value] of Object.entries(b)) {
|
|
66
|
+
if (value !== undefined && !(key in a)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
for (const [key, value] of Object.entries(a)) {
|
|
71
|
+
if (key === '_owner' && "$$typeof" in a) {
|
|
72
|
+
// React-specific: avoid traversing React elements' _owner.
|
|
73
|
+
// _owner contains circular references
|
|
74
|
+
// and is not needed when comparing the actual elements (and not their owners)
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (value !== undefined) {
|
|
78
|
+
if (!fastDeepEquals(value, b[key as keyof typeof b])) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
} else if (key in b && b[key as keyof typeof b] !== undefined) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// true if both NaN, false otherwise
|
|
90
|
+
return a!==a && b!==b;
|
|
91
|
+
}
|