@plaudit/gutenberg-api-extensions 2.90.1 → 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 (41) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/blocks/common-native-property-constructors.js +18 -11
  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/index.d.ts +4 -2
  23. package/dist/index.js +4 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/lib/modified-fast-deep-equals.d.ts +4 -0
  26. package/dist/lib/modified-fast-deep-equals.js +91 -0
  27. package/dist/lib/modified-fast-deep-equals.js.map +1 -0
  28. package/package.json +2 -2
  29. package/src/blocks/common-native-property-constructors.tsx +18 -12
  30. package/src/blocks/data-controller/reducer.ts +1 -4
  31. package/src/blocks/data-controller/trigger-handlers.ts +22 -11
  32. package/src/blocks/data-controller/utils.ts +9 -9
  33. package/src/blocks/data-controller-manager.ts +2 -2
  34. package/src/blocks/data-controller.ts +2 -4
  35. package/src/blocks/hooks/useSuspendableOptions.ts +1 -1
  36. package/src/blocks/simple-native-property-api.ts +1 -1
  37. package/src/blocks/simple-native-property-impl.tsx +0 -1
  38. package/src/blocks/snp-data-store.ts +12 -15
  39. package/src/index.ts +5 -2
  40. package/src/lib/modified-fast-deep-equals.ts +91 -0
  41. package/styles/sortable-items-control.pcss +61 -55
@@ -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
  }
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 {