@plaudit/gutenberg-api-extensions 2.82.0 → 2.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/blocks/SNPGroupComponent.js.map +1 -1
  3. package/dist/blocks/common-native-property-constructors.js +3 -2
  4. package/dist/blocks/common-native-property-constructors.js.map +1 -1
  5. package/dist/blocks/data-controller/actions.d.ts +135 -0
  6. package/dist/blocks/data-controller/actions.js +19 -0
  7. package/dist/blocks/data-controller/actions.js.map +1 -0
  8. package/dist/blocks/data-controller/reducer.d.ts +4 -0
  9. package/dist/blocks/data-controller/reducer.js +143 -0
  10. package/dist/blocks/data-controller/reducer.js.map +1 -0
  11. package/dist/blocks/data-controller/trigger-handlers.d.ts +140 -0
  12. package/dist/blocks/data-controller/trigger-handlers.js +117 -0
  13. package/dist/blocks/data-controller/trigger-handlers.js.map +1 -0
  14. package/dist/blocks/data-controller/utils.d.ts +43 -0
  15. package/dist/blocks/data-controller/utils.js +359 -0
  16. package/dist/blocks/data-controller/utils.js.map +1 -0
  17. package/dist/blocks/data-controller.d.ts +4 -37
  18. package/dist/blocks/data-controller.js +33 -604
  19. package/dist/blocks/data-controller.js.map +1 -1
  20. package/dist/blocks/layered-styles-impl.js +9 -8
  21. package/dist/blocks/layered-styles-impl.js.map +1 -1
  22. package/dist/blocks/layout/NodeContext.js +3 -3
  23. package/dist/blocks/layout/NodeContext.js.map +1 -1
  24. package/dist/blocks/simple-native-property-api.d.ts +10 -7
  25. package/dist/blocks/simple-native-property-api.js.map +1 -1
  26. package/dist/blocks/simple-native-property-impl.js +29 -24
  27. package/dist/blocks/simple-native-property-impl.js.map +1 -1
  28. package/dist/blocks/snp-data-store.d.ts +9 -6
  29. package/dist/blocks/snp-data-store.js +17 -12
  30. package/dist/blocks/snp-data-store.js.map +1 -1
  31. package/dist/controls/FullSizeToggleControl.d.ts +1 -1
  32. package/dist/controls/FullSizeToggleControl.js.map +1 -1
  33. package/dist/lib/gutenberg-api-extensions-state/layered-block-styles-logic.js +12 -12
  34. package/dist/lib/gutenberg-api-extensions-state/layered-block-styles-logic.js.map +1 -1
  35. package/dist/lib/gutenberg-api-extensions-state/snp-logic.js +2 -2
  36. package/dist/lib/gutenberg-api-extensions-state/snp-logic.js.map +1 -1
  37. package/package.json +7 -7
  38. package/simple-native-properties.md +1 -0
  39. package/src/blocks/SNPGroupComponent.tsx +1 -1
  40. package/src/blocks/common-native-property-constructors.tsx +3 -2
  41. package/src/blocks/data-controller/actions.ts +20 -0
  42. package/src/blocks/data-controller/reducer.ts +149 -0
  43. package/src/blocks/data-controller/trigger-handlers.ts +138 -0
  44. package/src/blocks/data-controller/utils.ts +339 -0
  45. package/src/blocks/data-controller.ts +38 -605
  46. package/src/blocks/layered-styles-impl.ts +10 -9
  47. package/src/blocks/layout/NodeContext.tsx +3 -3
  48. package/src/blocks/simple-native-property-api.ts +12 -10
  49. package/src/blocks/simple-native-property-impl.tsx +31 -24
  50. package/src/blocks/snp-data-store.ts +22 -13
  51. package/src/controls/FullSizeToggleControl.tsx +3 -3
  52. package/src/lib/gutenberg-api-extensions-state/layered-block-styles-logic.ts +12 -11
  53. package/src/lib/gutenberg-api-extensions-state/snp-logic.ts +2 -2
@@ -1,16 +1,19 @@
1
1
  import {useMemo} from "@wordpress/element";
2
- import {doAction} from "@wordpress/hooks";
3
2
 
4
- import {configureStore} from "@reduxjs/toolkit";
3
+ import {configureStore, Middleware, Tuple} from "@reduxjs/toolkit";
5
4
 
6
- import {produce} from "immer";
5
+ import type {Dispatch} from "redux";
6
+ import type {ThunkDispatch} from "redux-thunk";
7
7
 
8
- import {type Condition, resolveValueForCondition, testCondition} from "./conditions";
9
- import type {WordPressMetaActions} from "../lib/useful-types";
10
- import {MoveError} from "./MoveError";
8
+ import {type Condition, resolveValueForCondition} from "./conditions";
11
9
  import {PathError, PathErrorType} from "./PathError";
12
- import type {DataController, DataStore, HydratedSimpleNativeProperty, NodePath, RawPath} from "./simple-native-property-api";
13
- import {buildDefaultValueFromDefinition, type HydratedLaidOutProperties} from "./simple-native-property-internal-shared";
10
+ import type {DataController, DataStore, HydratedSimpleNativeProperty, NodePath} from "./simple-native-property-api";
11
+
12
+ import {actions, DataControllerActions} from "./data-controller/actions";
13
+ import {buildReducer} from "./data-controller/reducer";
14
+ import {getDataStore, getOptionalValue, walkToNode} from "./data-controller/utils";
15
+
16
+ export type {DataControllerActions} from "./data-controller/actions";
14
17
 
15
18
  type DescendantsWrapper = {[key: string]: DCNode}|DCNode[];
16
19
  export type DCNode = {
@@ -22,213 +25,25 @@ export type DCNode = {
22
25
  validationError?: string
23
26
  };
24
27
  export type DCStoreState = {
25
- treeRoot: DCNode&{children: {[key: string]: DCNode}}, dataStores: {byProperty: {[key: string]: DataStore}, list: DataStore[]}, blockClientId: string,
28
+ treeRoot: Omit<DCNode, 'children'>&{children: {[key: string]: DCNode}},
29
+ dataStores: {byProperty: {[key: string]: DataStore}, list: DataStore[]},
30
+ blockClientId: string,
26
31
  changedByManagedControl: boolean,
27
32
  rerenderTrigger: number,
28
33
  batchAddedPropertiesThatNeedWriteThrough: HydratedSimpleNativeProperty[]
29
34
  };
30
-
31
- type GlobalDCAction<T extends string, P extends {[key: Exclude<string, 'type'|'inBatch'>]: any} = {}> = {type: T, inBatch?: boolean}&P;
32
- type DCAction<T extends string, P extends {[key: Exclude<string, 'type'|'path'|'inBatch'>]: any} = {}> = {type: T, path: NodePath, inBatch?: boolean}&P;
33
- export type DataControllerActions = DCAction<"writeValue", {value?: any}>|DCAction<"validateNode">|GlobalDCAction<"validateNodes">|GlobalDCAction<"checkConditions">
34
- |DCAction<"addNode", {value?: any, subtype?: string}>|DCAction<"removeNode">|DCAction<"moveNode", {from: number, to: number}|{from: string, to: string}>
35
- |GlobalDCAction<"addProperties", {properties: HydratedLaidOutProperties, dataStore: DataStore}>|GlobalDCAction<"markManagedControlChangeHandled">
36
- |GlobalDCAction<"commitBatchAddedProperties">;
35
+ type DCThunkDispatch = ThunkDispatch<DCStoreState, unknown, DataControllerActions>;
37
36
 
38
37
  export function useDataController(blockClientId: string): DataController {
39
38
  return useMemo(() => {
40
- const initialState: DCStoreState = {
41
- treeRoot: {children: {}, rendered: true}, dataStores: {byProperty: {}, list: []}, blockClientId, changedByManagedControl: false, rerenderTrigger: 0,
42
- batchAddedPropertiesThatNeedWriteThrough: []
43
- };
44
-
45
- const store = configureStore<DCStoreState, DataControllerActions|WordPressMetaActions, []>({
46
- reducer(state = initialState, action) {
47
- switch (action.type) {
48
- case "addProperties":
49
- return produce(state, (draft: typeof state) => {
50
- draft.changedByManagedControl = true;
51
- if (!draft.dataStores.list.includes(action.dataStore)) {
52
- draft.dataStores.list.push(action.dataStore);
53
- }
54
- for (const property of action.properties) {
55
- if (Array.isArray(property)) {
56
- for (const prop of property) {
57
- if (action.dataStore.getValue(prop.name) === undefined && action.inBatch) {
58
- draft.batchAddedPropertiesThatNeedWriteThrough.push(prop);
59
- }
60
- action.dataStore.addProperty(prop, !action.inBatch);
61
- draft.dataStores.byProperty[prop.name] = action.dataStore;
62
- draft.treeRoot.children[prop.name] = buildNodeFromDataAndDefinition(action.dataStore.getValue(prop.name), prop);
63
- }
64
- } else {
65
- if (action.dataStore.getValue(property.name) === undefined && action.inBatch) {
66
- draft.batchAddedPropertiesThatNeedWriteThrough.push(property);
67
- }
68
- action.dataStore.addProperty(property, !action.inBatch);
69
- draft.dataStores.byProperty[property.name] = action.dataStore;
70
- draft.treeRoot.children[property.name] = buildNodeFromDataAndDefinition(action.dataStore.getValue(property.name), property);
71
- }
72
- }
73
- if (!action.inBatch) {
74
- performConditionChecks(draft, state, blockClientId);
75
- }
76
- });
77
- case "checkConditions":
78
- return produce(state, (draft: typeof state) => performConditionChecks(draft, state, blockClientId));
79
- case "validateNodes":
80
- return produce(state, (draft: typeof state) => {
81
- let hasChanged = false;
82
- applyToTree(draft.treeRoot, (node, path) => {
83
- if (!node.rendered) {
84
- return TreeMutatorResult.SKIP_DESCENDANTS;
85
- }
86
-
87
- if (validateFoundNode(node, path, draft)) {
88
- hasChanged = true;
89
- }
90
- return TreeMutatorResult.CONTINUE;
91
- });
92
- if (hasChanged) {
93
- draft.rerenderTrigger = state.rerenderTrigger + 1;
94
- }
95
- });
96
- case "markManagedControlChangeHandled":
97
- return produce(state, (draft: typeof state) => {
98
- draft.changedByManagedControl = false;
99
- });
100
- case "commitBatchAddedProperties":
101
- return produce(state, (draft: typeof state) => {
102
- draft.batchAddedPropertiesThatNeedWriteThrough = [];
103
- for (const batchAddedProperty of state.batchAddedPropertiesThatNeedWriteThrough) {
104
- const batchAddedPropertyState = state.treeRoot.children[batchAddedProperty.name];
105
- if (batchAddedPropertyState?.rendered) {
106
- writeValueToBackingDataStoreWithCorrections([batchAddedProperty.name], state, buildDefaultValueFromDefinition(batchAddedProperty));
107
- }
108
- }
109
- });
110
- }
111
-
112
- if (!('path' in action)) {
113
- return state; // This accounts for WordPress' meta actions
114
- }
115
- const {path, type} = action;
116
- switch (type) {
117
- case "addNode":
118
- const parentDefinition = resolveParentNodeDefinition(state.treeRoot, path);
119
- if (!parentDefinition) {
120
- throw new Error("Cannot add children to a node without a definition");
121
- }
122
- const nodeNameOrIndex = path[path.length - 1];
123
- let parentDefinitionChildren: HydratedSimpleNativeProperty[]|undefined;
124
- if (Array.isArray(parentDefinition.children) || parentDefinition.children === undefined) {
125
- parentDefinitionChildren = parentDefinition.children;
126
- } else {
127
- const subtype = action.value?.type ?? action.subtype;
128
- if (!subtype || !parentDefinition.children[subtype]) {
129
- throw new PathError("Unable to locate the definition of the named node because its type could not be resolved.", path);
130
- }
131
- parentDefinitionChildren = parentDefinition.children[subtype];
132
- }
133
-
134
- if (typeof nodeNameOrIndex === 'string') {
135
- const definition = parentDefinitionChildren?.find(definition => definition.name === nodeNameOrIndex);
136
- if (definition === undefined) {
137
- throw new PathError("Unable to locate the definition of the named node.", path);
138
- }
139
- const data = populateValueBasedOnDefinition(action.value, parentDefinition);
140
- const builtNode = buildNodeFromDataAndDefinition(data, definition);
141
- return produce(state, (draft: typeof state) => {
142
- draft.changedByManagedControl = true;
143
- const parentNode = walkToNode(draft.treeRoot, path.slice(0, path.length - 1));
144
- if (parentNode.children === undefined) {
145
- parentNode.children = {[nodeNameOrIndex]: builtNode};
146
- } else {
147
- if (Array.isArray(parentNode.children)) {
148
- throw new PathError("Encountered a node with indexed descendants while expecting one with string-keyed descendants.", path);
149
- }
150
- parentNode.children[nodeNameOrIndex] = builtNode;
151
- }
152
- writeValueToBackingDataStoreWithCorrections(path, state, data);
153
- if (!action.inBatch) {
154
- performConditionChecks(draft, state, blockClientId);
155
- }
156
- });
157
- } else if (nodeNameOrIndex === undefined) {
158
- throw new PathError("Encountered an undefined name in a path.", path);
159
- } else {
160
- const data = populateValueBasedOnDefinition(action.value, parentDefinition);
161
- const builtNode = buildNodeFromDataAndDefinition(data, parentDefinition);
162
- return produce(state, (draft: typeof state) => {
163
- draft.changedByManagedControl = true;
164
- const parentNode = walkToNode(draft.treeRoot, path.slice(0, path.length - 1));
165
- if (parentNode.children === undefined) {
166
- parentNode.children = [];
167
- parentNode.children[nodeNameOrIndex] = builtNode;
168
- } else {
169
- if (!Array.isArray(parentNode.children)) {
170
- throw new PathError("Encountered a node with string-keyed descendants while expecting one with indexed descendants.", path);
171
- }
172
- parentNode.children[nodeNameOrIndex] = builtNode;
173
- }
174
- writeValueToBackingDataStoreWithCorrections(path, state, data);
175
- if (!action.inBatch) {
176
- performConditionChecks(draft, state, blockClientId);
177
- }
178
- });
179
- }
180
- case "removeNode":
181
- return produce(state, (draft: typeof state) => {
182
- draft.changedByManagedControl = true;
183
- const parentNode = walkToNode(draft.treeRoot, path.slice(0, path.length - 1));
184
- removeFromParentNode(parentNode.children, path);
185
- removeValueFromBackingDataStore(getDataStore(path[0], state), path);
186
- if (!action.inBatch) {
187
- performConditionChecks(draft, state, blockClientId);
188
- }
189
- });
190
- case "moveNode":
191
- // If from and to are the same, then this is a no-op, so we can just skip it
192
- if (action.from === action.to) {
193
- return state;
194
- }
195
- return produce(state, (draft: typeof state) => {
196
- draft.changedByManagedControl = true;
197
- moveNodeWithinParent(walkToNode(draft.treeRoot, path).children, path, action);
198
- const dataStore = getDataStore(path[0], state);
199
- dataStore.setValue(path[0], produce(dataStore.getValue(path[0]), (dsDraft: any) => {
200
- moveNodeWithinParent(walkToNodeInValue(dsDraft, path), path, action);
201
- }));
202
- if (!action.inBatch) {
203
- performConditionChecks(draft, state, blockClientId);
204
- }
205
- });
206
- case "validateNode":
207
- return produce(state, (draft: typeof state) => validateNode(path, draft));
208
- case "writeValue":
209
- writeValueToBackingDataStoreWithCorrections(path, state, action.value);
210
- if (!action.inBatch) {
211
- return produce(state, (draft: typeof state) => {
212
- draft.changedByManagedControl = true;
213
- validateNode(path, draft);
214
- performConditionChecks(draft, state, blockClientId);
215
- });
216
- }
217
- return state;
218
- }
219
- return state; // This accounts for WordPress' meta actions
220
- }
221
- });
222
-
39
+ const reducer = buildReducer(blockClientId);
40
+ const store = configureStore<DCStoreState, DataControllerActions, Tuple<ReadonlyArray<Middleware<{}, DCStoreState, Dispatch<DataControllerActions>>>>>({reducer});
223
41
  function getDataStoreImpl(property: string, required: true): DataStore;
224
42
  function getDataStoreImpl(property: string, required?: undefined|false): DataStore|undefined;
225
43
  function getDataStoreImpl(property: string, required?: boolean): DataStore|undefined {
226
44
  return required ? getDataStore(property, store.getState(), true) : getDataStore(property, store.getState(), false);
227
45
  }
228
46
  const dataController: DataController = {
229
- dispatch(action) {
230
- return store.dispatch(action);
231
- },
232
47
  getDataStore: getDataStoreImpl,
233
48
  getAllDataStores() {
234
49
  return store.getState().dataStores.list;
@@ -240,29 +55,32 @@ export function useDataController(blockClientId: string): DataController {
240
55
  return getOptionalValue(path, store.getState());
241
56
  },
242
57
  setValue(path, value) {
243
- store.dispatch({type: "writeValue", path, value});
58
+ store.dispatch(actions.node.write({path, value}));
244
59
  },
245
60
  addProperties(properties, dataStore, inBatch) {
246
- store.dispatch({type: "addProperties", properties, dataStore, inBatch});
61
+ store.dispatch(actions.addProperties({properties, dataStore}, inBatch));
247
62
  },
248
63
  addNode(path, value) {
249
64
  try {
250
- store.dispatch({type: "addNode", path, value});
65
+ store.dispatch(actions.node.add({path, value}));
251
66
  } catch (e) {
252
67
  if (e instanceof PathError && e.errorType === PathErrorType.UNEXPECTED_LEAF) {
253
68
  for (let i = e.errorIndex; i < path.length - 1; i++) {
254
- store.dispatch({type: "addNode", path: path.slice(0, i) as NodePath, inBatch: true});
69
+ store.dispatch(actions.node.add({path: path.slice(0, i) as NodePath, value}, true));
255
70
  }
256
- store.dispatch({type: "addNode", path, value});
71
+ store.dispatch(actions.node.add({path, value}));
257
72
  }
258
73
  throw e;
259
74
  }
260
75
  },
261
76
  removeNode(path) {
262
- store.dispatch({type: "removeNode", path});
77
+ store.dispatch(actions.node.remove({path}));
263
78
  },
264
79
  moveNode(path, indices) {
265
- store.dispatch({type: "moveNode", path, ...indices});
80
+ store.dispatch(actions.node.move({path, ...indices}));
81
+ },
82
+ validateNode(path) {
83
+ store.dispatch(actions.node.validate({path}));
266
84
  },
267
85
  hasRenderedNode(properties) {
268
86
  const rootNodes = store.getState().treeRoot.children;
@@ -310,7 +128,16 @@ export function useDataController(blockClientId: string): DataController {
310
128
  return store.getState().changedByManagedControl;
311
129
  },
312
130
  markManagedControlChangeHandled() {
313
- store.dispatch({type: "markManagedControlChangeHandled"});
131
+ store.dispatch(actions.markManagedControlChangeHandled({}));
132
+ },
133
+ checkConditions() {
134
+ store.dispatch(actions.checkConditions({}));
135
+ },
136
+ validateNodes() {
137
+ store.dispatch(actions.validateNodes({}));
138
+ },
139
+ commitBatchAddedProperties() {
140
+ store.dispatch(actions.commitBatchAddedProperties({}));
314
141
  },
315
142
  blockClientId
316
143
  };
@@ -318,17 +145,6 @@ export function useDataController(blockClientId: string): DataController {
318
145
  }, [blockClientId]);
319
146
  }
320
147
 
321
- function getOptionalValue(path: NodePath, state: DCStoreState) {
322
- try {
323
- return walkToNodeInValue(getDataStore(path[0], state).getValue(path[0]), path);
324
- } catch (e) {
325
- if (e instanceof PathError) {
326
- return undefined;
327
- }
328
- throw e;
329
- }
330
- }
331
-
332
148
  function testForValidationErrors(node: DCNode|undefined) {
333
149
  if (!node || !node.rendered) {
334
150
  return false;
@@ -350,387 +166,4 @@ function testForValidationErrors(node: DCNode|undefined) {
350
166
  return false;
351
167
  }
352
168
 
353
- function validateNode(path: NodePath, draft: DCStoreState) {
354
- if (validateFoundNode(walkToNode(draft.treeRoot, path), path, draft)) {
355
- draft.rerenderTrigger = draft.rerenderTrigger + 1;
356
- }
357
- }
358
-
359
- function removeFromParentNode(parentNode: any|undefined, path: RawPath) {
360
- if (typeof parentNode !== 'object' || parentNode === undefined || parentNode === null) {
361
- throw new PathError("Encountered a non-existent node while expecting one.", path);
362
- }
363
- const nodeName = path[path.length - 1];
364
- if (typeof nodeName === 'string') {
365
- if (Array.isArray(parentNode)) {
366
- throw new PathError(
367
- "Encountered a node with indexed descendants while expecting one with string-keyed descendants.",
368
- path, path.length - 1);
369
- }
370
- delete parentNode[nodeName];
371
- } else if (nodeName === undefined) {
372
- throw new PathError("Encountered an undefined name in a path.", path);
373
- } else {
374
- if (!Array.isArray(parentNode)) {
375
- throw new PathError(
376
- "Encountered a node with string-keyed descendants while expecting one with indexed descendants.",
377
- path, path.length - 1);
378
- }
379
- parentNode.splice(nodeName, 1);
380
- }
381
- }
382
-
383
- function moveNodeWithinParent(parentNode: any|undefined|null, path: RawPath, move: {from: number, to: number}|{from: string, to: string}) {
384
- if (typeof parentNode !== 'object' || parentNode === undefined || parentNode === null) {
385
- throw new PathError("Encountered a non-existent node while expecting one.", path);
386
- }
387
- if (isNumericMove(move)) {
388
- if (!Array.isArray(parentNode)) {
389
- throw new MoveError("Attempted to move a child of a node with string-keyed descendants by index", path, move);
390
- }
391
- if (move.from > move.to) {
392
- parentNode.splice(move.to, 0, ...parentNode.splice(move.from, 1));
393
- } else { //We know that
394
- parentNode.splice(move.to - 1, 0, ...parentNode.splice(move.from, 1));
395
- }
396
- } else {
397
- if (Array.isArray(parentNode)) {
398
- throw new MoveError("Attempted to move a child of a node with indexed descendants by string-key", path, move);
399
- }
400
- if (parentNode[move.to] !== undefined) {
401
- throw new MoveError("Attempted to move a string-keyed child to the same key as another", path, move);
402
- }
403
- }
404
- }
405
- function isNumericMove(move: {from: number, to: number}|{from: string, to: string}): move is {from: number, to: number} {
406
- return typeof move.from === 'number' && typeof move.to === 'number';
407
- }
408
-
409
- function resolveParentNodeDefinition(root: DCNode, path: RawPath): HydratedSimpleNativeProperty|undefined {
410
- if (path.length < 2) {
411
- throw new PathError("Unable to get the parent node of a path with less than two nodes", path, path.length - 1, PathErrorType.INSUFFICIENT_LENGTH);
412
- }
413
- return walkToNode(root, path.slice(0, path.length - (typeof path[path.length - 2] === 'number' ? 2 : 1))).definition;
414
- }
415
-
416
- function populateValueBasedOnDefinition(value: any, definition: HydratedSimpleNativeProperty|undefined): any {
417
- const typedChildren = Array.isArray(definition?.children) ? definition.children : definition?.children?.[value?.type];
418
- if (!typedChildren?.length) {
419
- return value ?? buildDefaultValueFromDefinition(definition);
420
- } else if (value === null || value === undefined) {
421
- return buildDefaultValueFromDefinition(definition);
422
- } else if (typeof value !== 'object') {
423
- return value;
424
- } else if (Array.isArray(value)) {
425
- return value;
426
- }
427
- return {...value, ...Object.fromEntries(typedChildren.map(def => [def.name, populateValueBasedOnDefinition(value[def.name], def)]))};
428
- }
429
-
430
- function walkToNode(root: DCNode, path: RawPath): DCNode {
431
- let current = root;
432
- for (let i = 0; i < path.length; i++) {
433
- current = descendIntoDCNodeChild(current, path, i);
434
- }
435
- return current;
436
- }
437
- function descendIntoDCNodeChild(current: DCNode|undefined, path: RawPath, i: number): DCNode {
438
- if (current?.children === undefined) {
439
- throw new PathError("Encountered a leaf node while expecting one with descendants.", path, i, PathErrorType.UNEXPECTED_LEAF);
440
- }
441
- const pathNode = path[i];
442
- if (typeof pathNode === 'string') {
443
- if (Array.isArray(current.children)) {
444
- throw new PathError("Encountered a node with indexed descendants while expecting one with string-keyed descendants.", path, i);
445
- }
446
- current = current.children[pathNode];
447
- } else if (pathNode === undefined) {
448
- throw new PathError("Encountered an undefined name in a path.", path, i);
449
- } else {
450
- if (!Array.isArray(current.children)) {
451
- throw new PathError("Encountered a node with string-keyed descendants while expecting one with indexed descendants.", path, i);
452
- }
453
- current = current.children[pathNode];
454
- }
455
- if (current === undefined) {
456
- throw new PathError("Encountered a non-existent node while expecting one.", path, i, PathErrorType.UNEXPECTED_LEAF);
457
- }
458
- return current;
459
- }
460
-
461
- type PropertyValueResolverBuilder = (path: RawPath) => (propertyPath: RawPath) => any;
462
- function performConditionChecks(draft: DCStoreState, state: DCStoreState, blockClientId: string) {
463
- const propertyValueResolverBuilder: PropertyValueResolverBuilder =
464
- path => propertyPath => resolveValueForCondition(propertyPath, path, blockClientId,
465
- property => getDataStore(property, draft, false));
466
- applyToTree(draft.treeRoot, (node, path) => {
467
- const condition = node.condition;
468
- if (condition === undefined) {
469
- node.rendered = true;
470
- return TreeMutatorResult.CONTINUE;
471
- } else {
472
- const newRendered = testCondition(condition, propertyValueResolverBuilder(path));
473
- if (newRendered !== node.rendered) {
474
- const dataStore = getDataStore(path[0], state);
475
- let currentValue: any;
476
- try {
477
- currentValue = walkToNodeInValue(dataStore.getValue(path[0]), path);
478
- } catch (e) {
479
- if (!(e instanceof PathError)) {
480
- throw e;
481
- }
482
- currentValue = undefined;
483
- }
484
- if (!newRendered) {
485
- // Create a backup if we are transitioning from rendered -> unrendered
486
- node.backup = currentValue;
487
- removeValueFromBackingDataStore(dataStore, path);
488
- } else if (currentValue === undefined) {
489
- // Restore from backup if we are transitioning from unrendered -> rendered
490
- const stateNode = walkToNode(state.treeRoot, path);
491
- writeValueToBackingDataStore(dataStore, path, stateNode.backup ?? buildDefaultValueFromDefinition(stateNode.definition));
492
- }
493
- node.rendered = newRendered;
494
- }
495
- return node.rendered ? TreeMutatorResult.CONTINUE : TreeMutatorResult.SKIP_DESCENDANTS;
496
- }
497
- });
498
- }
499
-
500
- /**
501
- * @return true iff the validation state changed
502
- */
503
- function validateFoundNode(node: DCNode, path: NodePath, draft: DCStoreState) {
504
- const definition = node.definition;
505
- if (definition) {
506
- let validationError: string;
507
- const value = getOptionalValue(path, draft);
508
- if (definition.required && !value) {
509
- validationError = `"${definition.label || definition.name}" is required.`;
510
- } else {
511
- validationError = definition.validator?.(value) ?? "";
512
- }
513
- if (node.validationError !== validationError) {
514
- node.validationError = validationError;
515
- return true;
516
- }
517
- }
518
- return false;
519
- }
520
-
521
- function getDataStore(property: string, state: DCStoreState, throwOnError: false): DataStore|undefined;
522
- function getDataStore(property: string, state: DCStoreState, throwOnError?: undefined|true): DataStore;
523
- function getDataStore(property: string, state: DCStoreState, throwOnError = true): DataStore|undefined {
524
- let dataStore = state.dataStores.byProperty[property];
525
- if (dataStore) {
526
- return dataStore;
527
- }
528
- dataStore = state.dataStores.list.find(dataStore => dataStore.handlesProperty(property));
529
- if (dataStore) {
530
- console.warn(`Unable to locate the DataStore for ${property} via the byProperty map, but did find a DataStore that handles that property. This should not be possible.`);
531
- } else if (throwOnError) {
532
- throw new Error(`Unable to resolve the dataStore for ${property} on block ${state.blockClientId}`);
533
- }
534
- return dataStore;
535
- }
536
-
537
- function removeValueFromBackingDataStore(dataStore: DataStore, path: NodePath) {
538
- const dsValue = dataStore.getValue(path[0]);
539
- if (path.length === 1) {
540
- dataStore.setValue(path[0], undefined);
541
- } else {
542
- dataStore.setValue(path[0], produce(dsValue, (dsValueDraft: any) => {
543
- const nodeParent = walkToNodeInValue(dsValueDraft, path.slice(0, path.length - 1));
544
- const nodeName = path[path.length - 1];
545
- if (typeof nodeName === 'string') {
546
- delete nodeParent[nodeName];
547
- } else if (nodeName === undefined) {
548
- throw new PathError("Encountered an undefined name in a path.", path);
549
- } else {
550
- (nodeParent as any[]).splice(nodeName, 1);
551
- }
552
- }));
553
- }
554
- }
555
- function writeValueToBackingDataStoreWithCorrections(path: NodePath, state: DCStoreState, value: any) {
556
- if (typeof value === 'number' && Number.isNaN(value)) {
557
- value = undefined; // This ensures that NaN's don't get stored
558
- }
559
- const rootDCNode = state.treeRoot.children[path[0]];
560
- if (rootDCNode === undefined) {
561
- throw new PathError("Encountered a path pointing to a non-existent root node.", path, 0);
562
- }
563
- const dataStore = getDataStore(path[0], state);
564
-
565
- // This path both handles scalar values and ensures that the root "value" of Groups and Lists are initialized before writing the actual attribute
566
- // In order to avoid accidentally overwriting group data when lazily adding items (i.e. via a ToolsPanel instance), we skip this code path if the group's root value has already been initialized
567
- if (path.length === 1 || (value === undefined && dataStore.getValue(path[0]) === undefined)) {
568
- const writtenValue = value ?? buildDefaultValueFromDefinition(rootDCNode.definition, path.length === 1);
569
- dataStore.setValue(path[0], writtenValue);
570
- if (rootDCNode.definition) {
571
- doAction('plaudit.gutenbergApiExtensions.simpleNativeProperties.afterWrite', writtenValue, rootDCNode.definition, dataStore);
572
- }
573
- if (path.length === 1) {
574
- return;
575
- }
576
- }
577
-
578
- let closestUsableDefinition = rootDCNode.definition;
579
- let lastWrittenValue: unknown;
580
- dataStore.setValue(path[0], produce(dataStore.getValue(path[0]), (dsValueDraft: any) => {
581
- let currentDSValue = dsValueDraft;
582
- let currentDCNode = rootDCNode;
583
- for (let i = 1; i < path.length - 1; i++) {
584
- const nodeName = path[i];
585
- currentDCNode = descendIntoDCNodeChild(currentDCNode, path, i);
586
- if (typeof nodeName === 'string') {
587
- //We only update the definition being used when we descend into a new node proper
588
- closestUsableDefinition = currentDCNode.definition;
589
- } else if (nodeName === undefined) {
590
- throw new PathError("Encountered an undefined name in a path.", path, i);
591
- }
592
-
593
- let nextDSValue = descendIntoDSValueNodeChild(currentDSValue, path, i);
594
- if (nextDSValue === undefined) {
595
- lastWrittenValue = currentDSValue[nodeName] = buildDefaultValueFromDefinition(closestUsableDefinition);
596
- currentDSValue = descendIntoDSValueNodeChild(currentDSValue, path, i);
597
- if (currentDSValue === undefined) {
598
- throw new PathError("Encountered a leaf node while expecting one with descendants.", path, i, PathErrorType.UNEXPECTED_LEAF);
599
- }
600
- } else {
601
- currentDSValue = nextDSValue;
602
- }
603
- }
604
- const nodeName = path[path.length - 1];
605
- if (nodeName === undefined) {
606
- throw new PathError("Encountered an undefined name in a path.", path);
607
- }
608
- lastWrittenValue = currentDSValue[nodeName] = value;
609
- }));
610
- if (closestUsableDefinition) {
611
- doAction('plaudit.gutenbergApiExtensions.simpleNativeProperties.afterWrite', lastWrittenValue, closestUsableDefinition, dataStore);
612
- }
613
- }
614
- function writeValueToBackingDataStore(dataStore: DataStore, path: NodePath, value: any) {
615
- if (typeof value === 'number' && Number.isNaN(value)) {
616
- value = undefined; // This ensures that NaN's don't get stored
617
- }
618
- const dsValue = dataStore.getValue(path[0]);
619
- if (path.length === 1) {
620
- dataStore.setValue(path[0], value);
621
- } else {
622
- dataStore.setValue(path[0], produce(dsValue, (dsValueDraft: any) => {
623
- const nodeParent = walkToNodeInValue(dsValueDraft, path.slice(0, path.length - 1));
624
- const nodeName = path[path.length - 1];
625
- if (nodeName === undefined) {
626
- throw new PathError("Encountered an undefined name in a path.", path);
627
- }
628
- nodeParent[nodeName] = value;
629
- }));
630
- }
631
- }
632
-
633
- /**
634
- * @param dsValue a value from a {@link DataStore}
635
- * @param path this is expected to be the entire path (including the node that was used to get {@link dsValue}
636
- */
637
- export function walkToNodeInValue(dsValue: any, path: RawPath) {
638
- let current = dsValue;
639
- for (let i = 1; i < path.length; i++) {
640
- if (current === undefined) {
641
- throw new PathError("Encountered a leaf node while expecting one with descendants.", path, i, PathErrorType.UNEXPECTED_LEAF);
642
- }
643
- current = descendIntoDSValueNodeChild(current, path, i);
644
- }
645
- return current;
646
- }
647
- function descendIntoDSValueNodeChild(current: any, path: RawPath, i: number) {
648
- const pathNode = path[i];
649
- if (typeof pathNode === 'string') {
650
- if (Array.isArray(current)) {
651
- throw new PathError("Encountered a node with indexed descendants while expecting one with string-keyed descendants.", path, i);
652
- }
653
- current = current[pathNode];
654
- } else if (pathNode === undefined) {
655
- throw new PathError("Encountered an undefined name in a path.", path, i);
656
- } else {
657
- if (!Array.isArray(current)) {
658
- throw new PathError("Encountered a node with string-keyed descendants while expecting one with indexed descendants.", path, i);
659
- }
660
- current = current[pathNode];
661
- }
662
- return current;
663
- }
664
-
665
- enum TreeMutatorResult {
666
- STOP, CONTINUE, SKIP_DESCENDANTS
667
- }
668
- function applyToTree(tree: DCNode, mutator: (node: DCNode, path: NodePath) => TreeMutatorResult, path: RawPath = []) {
669
- if (tree.children === undefined) {
670
- return TreeMutatorResult.CONTINUE;
671
- }
672
-
673
- const steps: Array<[NodePath, DCNode]> = Array.isArray(tree.children)
674
- ? tree.children.map((node, index) => [path.toSpliced(path.length, 0, index) as NodePath, node])
675
- : Object.entries(tree.children).map(([name, node]) => [path.toSpliced(path.length, 0, name) as NodePath, node]);
676
-
677
- for (const [nodePath, node] of steps) {
678
- const mutatorRes = mutator(node, nodePath);
679
- if (!mutatorRes) {
680
- return TreeMutatorResult.STOP;
681
- } else if (mutatorRes === TreeMutatorResult.CONTINUE) {
682
- if (!applyToTree(node, mutator, nodePath)) {
683
- return TreeMutatorResult.STOP;
684
- }
685
- }
686
- }
687
- return TreeMutatorResult.CONTINUE;
688
- }
689
-
690
- function buildNodeFromDataAndDefinition(data: any, definition: HydratedSimpleNativeProperty): DCNode {
691
- const children = definition.children;
692
- if (children) {
693
- if (data === undefined || data === null) {
694
- if (definition.type === 'array') {
695
- return {rendered: true, condition: definition.condition, definition, validationError: "", children: []};
696
- } else if (definition.type === 'object') {
697
- if (Array.isArray(children)) {
698
- return {
699
- rendered: true, condition: definition.condition, definition, validationError: "",
700
- children: Object.fromEntries(children.map(def => [def.name, buildNodeFromDataAndDefinition(undefined, def)]))
701
- };
702
- } else {
703
- //TODO: We might need to throw an error here
704
- return {rendered: true, condition: definition.condition, definition, validationError: "", children: {}};
705
- }
706
- }
707
- } else if (typeof data === 'object') {
708
- if (Array.isArray(data)) {
709
- return {
710
- rendered: true, condition: definition.condition, definition, validationError: "", children: data.map(item => {
711
- const typedChildren = Array.isArray(children) ? children : children[item.type];
712
- if (typedChildren === undefined) {
713
- //TODO: We might need to throw an error here
714
- return {children: [], rendered: true, validationError: ""};
715
- }
716
- return {
717
- children: Object.fromEntries(typedChildren.map(def => [def.name, buildNodeFromDataAndDefinition(item[def.name], def)])),
718
- rendered: true, validationError: ""
719
- };
720
- })
721
- };
722
- } else {
723
- const typedChildren = Array.isArray(children) ? children : children[data.type];
724
- if (typedChildren === undefined) {
725
- //TODO: We might need to throw an error here
726
- return {rendered: true, condition: definition.condition, definition, validationError: "", children: []};
727
- }
728
- return {
729
- rendered: true, condition: definition.condition, definition, validationError: "",
730
- children: Object.fromEntries(typedChildren.map(def => [def.name, buildNodeFromDataAndDefinition(data[def.name], def)]))
731
- };
732
- }
733
- }
734
- }
735
- return {rendered: true, condition: definition.condition, definition, validationError: ""};
736
- }
169
+ export {walkToNodeInValue} from "./data-controller/utils";