@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.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/blocks/basic-custom-block-bindings-support.js +1 -1
  3. package/dist/blocks/basic-custom-block-bindings-support.js.map +1 -1
  4. package/dist/blocks/common-native-property-constructors.js +18 -11
  5. package/dist/blocks/common-native-property-constructors.js.map +1 -1
  6. package/dist/blocks/data-controller/reducer.js +1 -4
  7. package/dist/blocks/data-controller/reducer.js.map +1 -1
  8. package/dist/blocks/data-controller/trigger-handlers.js +20 -11
  9. package/dist/blocks/data-controller/trigger-handlers.js.map +1 -1
  10. package/dist/blocks/data-controller/utils.js +9 -9
  11. package/dist/blocks/data-controller/utils.js.map +1 -1
  12. package/dist/blocks/data-controller-manager.d.ts +1 -1
  13. package/dist/blocks/data-controller-manager.js.map +1 -1
  14. package/dist/blocks/data-controller.d.ts +1 -1
  15. package/dist/blocks/data-controller.js.map +1 -1
  16. package/dist/blocks/hooks/useSuspendableOptions.js +1 -1
  17. package/dist/blocks/hooks/useSuspendableOptions.js.map +1 -1
  18. package/dist/blocks/simple-native-property-api.d.ts +2 -2
  19. package/dist/blocks/simple-native-property-api.js +3 -2
  20. package/dist/blocks/simple-native-property-api.js.map +1 -1
  21. package/dist/blocks/simple-native-property-impl.js +0 -1
  22. package/dist/blocks/simple-native-property-impl.js.map +1 -1
  23. package/dist/blocks/snp-data-store.d.ts +2 -2
  24. package/dist/blocks/snp-data-store.js +10 -15
  25. package/dist/blocks/snp-data-store.js.map +1 -1
  26. package/dist/controls/ExtendedTermPicker.js +3 -4
  27. package/dist/controls/ExtendedTermPicker.js.map +1 -1
  28. package/dist/controls/basicNumericallyIdedItemPicker.js +3 -4
  29. package/dist/controls/basicNumericallyIdedItemPicker.js.map +1 -1
  30. package/dist/index.d.ts +4 -8
  31. package/dist/index.js +4 -1
  32. package/dist/index.js.map +1 -1
  33. package/dist/lib/modified-fast-deep-equals.d.ts +4 -0
  34. package/dist/lib/modified-fast-deep-equals.js +91 -0
  35. package/dist/lib/modified-fast-deep-equals.js.map +1 -0
  36. package/package.json +2 -2
  37. package/src/blocks/basic-custom-block-bindings-support.tsx +1 -1
  38. package/src/blocks/common-native-property-constructors.tsx +18 -12
  39. package/src/blocks/data-controller/reducer.ts +1 -4
  40. package/src/blocks/data-controller/trigger-handlers.ts +22 -11
  41. package/src/blocks/data-controller/utils.ts +9 -9
  42. package/src/blocks/data-controller-manager.ts +2 -2
  43. package/src/blocks/data-controller.ts +2 -4
  44. package/src/blocks/hooks/useSuspendableOptions.ts +1 -1
  45. package/src/blocks/simple-native-property-api.ts +5 -4
  46. package/src/blocks/simple-native-property-impl.tsx +0 -1
  47. package/src/blocks/snp-data-store.ts +12 -15
  48. package/src/controls/ExtendedTermPicker.tsx +3 -4
  49. package/src/controls/basicNumericallyIdedItemPicker.tsx +3 -4
  50. package/src/index.ts +5 -10
  51. package/src/lib/modified-fast-deep-equals.ts +91 -0
  52. 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 normalizedOptions = useAsOptions(useSuspendableOptions(config), value, config.clearable ? '' : undefined);
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|number>(options: PickableOptions<T>, currentValue: T|undefined|null, noSelectionValue?: T) {
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 ((currentValue !== undefined && currentValue !== null) && !normalizedOptions.find(opt => opt.value === currentValue)) {
869
- if (noSelectionValue !== undefined && !normalizedOptions.some(opt => !opt.value)) {
870
- return [normalizedOptions[0]!, {value: currentValue, label: currentValue.toString(), disabled: true}, ...normalizedOptions.slice(1)];
871
- } else {
872
- return [{value: currentValue, label: currentValue.toString(), disabled: true}, ...normalizedOptions];
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
- if (dataStore.getValue(prop.name) === undefined && meta.inBatch) {
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 = getDataStore(path[0], state, true);
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 {rendered: true, condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: []};
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
- rendered: true, condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "",
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 {rendered: true, condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: {}};
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
- rendered: true, condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: data.map(item => {
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: [], rendered: true, validationError: ""};
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
- rendered: true, validationError: ""
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 {rendered: true, condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: []};
395
+ return {condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "", children: []};
396
396
  }
397
397
  return {
398
- rendered: true, condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: "",
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 {rendered: true, condition: definition.condition, definition: recordCloneableDefaultValueForNode(definition), validationError: ""};
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 leat 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.
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 type {BlockName} from "../lib/useful-types";
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, writeThroughOnUndefined?: boolean): void;
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('plaudit/gutenberg-api-extensions').addProperties(config.block, config.properties);
125
+ dispatch(apiExtensionsStore).addProperties(config.block, config.properties);
125
126
  }
126
127
  export function getSimpleNativeProperties(block: BlockName): Array<PDSimpleNativeProperty>|undefined {
127
- const panelsAndTabs = select('plaudit/gutenberg-api-extensions').desiccatedProperties(block);
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 {buildDefaultValueFromDefinition, UUID} from "./data-controller/utils";
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.attributeCache[attr] = value;
34
- return this.setAttribute(attr, value);
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}>, writeThroughOnUndefined = true) {
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('plaudit/simple-gutenberg-apis')
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 (select('plaudit/simple-gutenberg-apis')
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('plaudit/simple-gutenberg-apis')
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 (select('plaudit/simple-gutenberg-apis')
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, type store as endpointStore} from "./editor/simple-gutenberg-endpoints-impl";
3
- import {registerStore, type store as apiStore} from "./lib/gutenberg-api-extensions-state";
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
+ }