@plaudit/gutenberg-api-extensions 2.90.0 → 2.91.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 (44) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/blocks/common-native-property-constructors.js +20 -13
  3. package/dist/blocks/common-native-property-constructors.js.map +1 -1
  4. package/dist/blocks/data-controller/reducer.js +1 -4
  5. package/dist/blocks/data-controller/reducer.js.map +1 -1
  6. package/dist/blocks/data-controller/trigger-handlers.js +20 -11
  7. package/dist/blocks/data-controller/trigger-handlers.js.map +1 -1
  8. package/dist/blocks/data-controller/utils.js +9 -9
  9. package/dist/blocks/data-controller/utils.js.map +1 -1
  10. package/dist/blocks/data-controller-manager.d.ts +1 -1
  11. package/dist/blocks/data-controller-manager.js.map +1 -1
  12. package/dist/blocks/data-controller.d.ts +1 -1
  13. package/dist/blocks/data-controller.js.map +1 -1
  14. package/dist/blocks/hooks/useSuspendableOptions.js +1 -1
  15. package/dist/blocks/hooks/useSuspendableOptions.js.map +1 -1
  16. package/dist/blocks/simple-native-property-api.d.ts +1 -1
  17. package/dist/blocks/simple-native-property-impl.js +0 -1
  18. package/dist/blocks/simple-native-property-impl.js.map +1 -1
  19. package/dist/blocks/snp-data-store.d.ts +2 -2
  20. package/dist/blocks/snp-data-store.js +10 -15
  21. package/dist/blocks/snp-data-store.js.map +1 -1
  22. package/dist/controls/PickOne.js +1 -1
  23. package/dist/controls/PickOne.js.map +1 -1
  24. package/dist/index.d.ts +4 -2
  25. package/dist/index.js +4 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/lib/modified-fast-deep-equals.d.ts +4 -0
  28. package/dist/lib/modified-fast-deep-equals.js +91 -0
  29. package/dist/lib/modified-fast-deep-equals.js.map +1 -0
  30. package/package.json +2 -2
  31. package/src/blocks/common-native-property-constructors.tsx +20 -13
  32. package/src/blocks/data-controller/reducer.ts +1 -4
  33. package/src/blocks/data-controller/trigger-handlers.ts +22 -11
  34. package/src/blocks/data-controller/utils.ts +9 -9
  35. package/src/blocks/data-controller-manager.ts +2 -2
  36. package/src/blocks/data-controller.ts +2 -4
  37. package/src/blocks/hooks/useSuspendableOptions.ts +1 -1
  38. package/src/blocks/simple-native-property-api.ts +1 -1
  39. package/src/blocks/simple-native-property-impl.tsx +0 -1
  40. package/src/blocks/snp-data-store.ts +12 -15
  41. package/src/controls/PickOne.tsx +1 -0
  42. package/src/index.ts +5 -2
  43. package/src/lib/modified-fast-deep-equals.ts +91 -0
  44. package/styles/sortable-items-control.pcss +61 -55
@@ -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 => {
@@ -22,7 +22,7 @@ export interface DataStore {
22
22
 
23
23
  handlesProperty(name: string): boolean;
24
24
 
25
- addProperty(property: HydratedSimpleNativeProperty, writeThroughOnUndefined?: boolean): void;
25
+ addProperty(property: HydratedSimpleNativeProperty): void;
26
26
  }
27
27
  export type RawPath = (string|number)[];
28
28
  export type NodePath = [string, ...(string|number)[]];
@@ -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
  }
@@ -35,6 +35,7 @@ export function PickOneFromToggleGroup<T extends string>(props: PickOneFromToggl
35
35
  ? <ToggleGroupControlOption key={value} value={value} label={label} />
36
36
  : <ToggleGroupControlOptionIcon key={value} value={value} label={label.text} icon={label.icon} />)
37
37
  }
38
+ __nextHasNoMarginBottom
38
39
  />;
39
40
  }
40
41
 
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 endpointStore} from "./editor/simple-gutenberg-endpoints-impl";
3
+ import {registerStore, store as apiStore} 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 {apiStore, endpointStore};
16
+
14
17
  export function installGutenbergExtensions() {
15
18
  if (!installGutenbergExtensions.called) {
16
19
  installGutenbergExtensions.called = true;
@@ -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
+ }
@@ -1,71 +1,77 @@
1
- .plaudit-sortable-items-container .drop-zone {
2
- background-color: #ccc;
3
- transition-property: height, padding;
4
- transition-duration: 250ms;
5
- transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
6
- overflow: hidden;
7
- height: 20px;
8
- }
1
+ .plaudit-sortable-items-container {
2
+ .drop-zone {
3
+ background-color: #ccc;
4
+ transition-property: height, padding;
5
+ transition-duration: 250ms;
6
+ transition-timing-function: cubic-bezier(0.075, 0.82, 0.165, 1);
7
+ overflow: hidden;
8
+ height: 20px;
9
9
 
10
- .plaudit-sortable-items-container .drop-zone.plaudit-sortable-items-hidden {
11
- height: 0;
12
- padding: 0;
13
- }
10
+ &.plaudit-sortable-items-hidden {
11
+ height: 0;
12
+ padding: 0;
13
+ }
14
+ }
14
15
 
15
- .plaudit-sortable-items-container .dragging {
16
- display: none !important;
17
- }
18
- .plaudit-sortable-items-container .floating {
19
- position: fixed;
20
- box-shadow: 5px 5px 10px rgba(0,0,0,0.5);
21
- }
16
+ .dragging {
17
+ display: none !important;
18
+ }
22
19
 
23
- .plaudit-sortable-items-container .plaudit-sortable-items-row {
24
- display: grid;
25
- grid-template-areas: "HANDLE FIELD CONTROL";
26
- grid-template-columns: auto 1fr auto;
27
- border: 1px solid #ccc;
28
- background: #fff;
29
- }
30
- .plaudit-sortable-items-container .plaudit-sortable-items-ordering-controls {
31
- display: flex;
32
- flex-direction: row;
33
- gap: 0;
34
- padding-right: 6px;
20
+ .floating {
21
+ position: fixed;
22
+ box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.5);
23
+ }
35
24
 
36
- .plaudit-sortable-items-ordering-control {
25
+ .plaudit-sortable-items-row {
37
26
  display: grid;
38
- place-content: center;
39
- line-height: 1;
27
+ grid-template-areas: "HANDLE FIELD CONTROL";
28
+ grid-template-columns: auto 1fr auto;
29
+ border: 1px solid #ccc;
30
+ background: #fff;
31
+ }
40
32
 
41
- .components-button {
42
- height: 16px;
43
- min-width: 16px;
44
- padding: 0;
33
+ .plaudit-sortable-items-ordering-controls {
34
+ display: flex;
35
+ flex-direction: row;
36
+ gap: 0;
37
+ padding-right: 6px;
38
+
39
+ .plaudit-sortable-items-ordering-control {
40
+ display: grid;
41
+ place-content: center;
42
+ line-height: 1;
45
43
 
46
- span.dashIcon {
44
+ .components-button {
45
+ height: 16px;
46
+ min-width: 16px;
47
47
  padding: 0;
48
- width: 16px;
48
+
49
+ span.dashIcon {
50
+ padding: 0;
51
+ width: 16px;
52
+ }
49
53
  }
50
54
  }
51
55
  }
52
- }
53
- .plaudit-sortable-items-container .plaudit-sortable-items-presence-controls {
54
- display: flex;
55
- flex-direction: column;
56
- margin-top: -4px;
57
56
 
58
- button[aria-label="Insert After"] {
59
- position: absolute;
60
- top: 100%;
61
- left: 50%;
62
- width: 30px;
63
- height: 30px;
64
- transform: translate(-50%, -50%);
57
+ .plaudit-sortable-items-presence-controls {
58
+ display: flex;
59
+ flex-direction: column;
60
+ margin-top: -4px;
61
+
62
+ button[aria-label="Insert After"] {
63
+ position: absolute;
64
+ top: 100%;
65
+ left: 50%;
66
+ width: 30px;
67
+ height: 30px;
68
+ transform: translate(-50%, -50%);
69
+ }
70
+ }
71
+
72
+ .plaudit-sortable-items-padded {
73
+ padding-block: 8px;
65
74
  }
66
- }
67
- .plaudit-sortable-items-container .plaudit-sortable-items-padded {
68
- padding-block-start: 8px;
69
75
  }
70
76
 
71
77
  .plaudit-sortable-items-buttons.flexible-items {