@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.
- package/CHANGELOG.md +11 -0
- package/dist/blocks/common-native-property-constructors.js +20 -13
- package/dist/blocks/common-native-property-constructors.js.map +1 -1
- package/dist/blocks/data-controller/reducer.js +1 -4
- package/dist/blocks/data-controller/reducer.js.map +1 -1
- package/dist/blocks/data-controller/trigger-handlers.js +20 -11
- package/dist/blocks/data-controller/trigger-handlers.js.map +1 -1
- package/dist/blocks/data-controller/utils.js +9 -9
- package/dist/blocks/data-controller/utils.js.map +1 -1
- package/dist/blocks/data-controller-manager.d.ts +1 -1
- package/dist/blocks/data-controller-manager.js.map +1 -1
- package/dist/blocks/data-controller.d.ts +1 -1
- package/dist/blocks/data-controller.js.map +1 -1
- package/dist/blocks/hooks/useSuspendableOptions.js +1 -1
- package/dist/blocks/hooks/useSuspendableOptions.js.map +1 -1
- package/dist/blocks/simple-native-property-api.d.ts +1 -1
- package/dist/blocks/simple-native-property-impl.js +0 -1
- package/dist/blocks/simple-native-property-impl.js.map +1 -1
- package/dist/blocks/snp-data-store.d.ts +2 -2
- package/dist/blocks/snp-data-store.js +10 -15
- package/dist/blocks/snp-data-store.js.map +1 -1
- package/dist/controls/PickOne.js +1 -1
- package/dist/controls/PickOne.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/modified-fast-deep-equals.d.ts +4 -0
- package/dist/lib/modified-fast-deep-equals.js +91 -0
- package/dist/lib/modified-fast-deep-equals.js.map +1 -0
- package/package.json +2 -2
- package/src/blocks/common-native-property-constructors.tsx +20 -13
- package/src/blocks/data-controller/reducer.ts +1 -4
- package/src/blocks/data-controller/trigger-handlers.ts +22 -11
- package/src/blocks/data-controller/utils.ts +9 -9
- package/src/blocks/data-controller-manager.ts +2 -2
- package/src/blocks/data-controller.ts +2 -4
- package/src/blocks/hooks/useSuspendableOptions.ts +1 -1
- package/src/blocks/simple-native-property-api.ts +1 -1
- package/src/blocks/simple-native-property-impl.tsx +0 -1
- package/src/blocks/snp-data-store.ts +12 -15
- package/src/controls/PickOne.tsx +1 -0
- package/src/index.ts +5 -2
- package/src/lib/modified-fast-deep-equals.ts +91 -0
- 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
|
|
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
|
|
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 {
|
|
4
|
+
import {fastDeepEquals} from "../lib/modified-fast-deep-equals";
|
|
5
|
+
|
|
6
|
+
import type {UUID} from "./data-controller/utils";
|
|
5
7
|
import type {ActualBlockEditProps, BlockName} from "../lib/useful-types";
|
|
6
8
|
import type {DataStore, HydratedSimpleNativeProperty} from "./simple-native-property-api";
|
|
7
9
|
|
|
@@ -30,8 +32,14 @@ export abstract class SNPDataStore implements DataStore {
|
|
|
30
32
|
return this.attributeCache[attr] = existingValue;
|
|
31
33
|
}
|
|
32
34
|
setValue(attr: string, value: any) {
|
|
33
|
-
this.
|
|
34
|
-
|
|
35
|
+
const existingValue = this.getValue(attr);
|
|
36
|
+
if (!fastDeepEquals(existingValue, value)) {
|
|
37
|
+
this.attributeCache[attr] = value;
|
|
38
|
+
// WordPress doesn't handle saving and loading of empty objects consistently. As a result, we cannot safely write empty objects if the existing value is undefined
|
|
39
|
+
if (existingValue !== undefined || typeof value !== 'object' || !value || Object.entries(value).some(([, v]) => v !== undefined)) {
|
|
40
|
+
this.setAttribute(attr, value);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
35
43
|
}
|
|
36
44
|
handlesProperty(name: string): boolean {
|
|
37
45
|
return name in this.managedPropertyNames;
|
|
@@ -58,18 +66,7 @@ export abstract class SNPDataStore implements DataStore {
|
|
|
58
66
|
return attr in this.attributeCache;
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
addProperty(property: HydratedSimpleNativeProperty<{uuid: UUID}
|
|
62
|
-
if (this.managedPropertyNames[property.name]) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (this.getAttribute(property.name) === undefined) {
|
|
66
|
-
if (writeThroughOnUndefined) {
|
|
67
|
-
this.setValue(property.name, buildDefaultValueFromDefinition(property));
|
|
68
|
-
} else {
|
|
69
|
-
this.attributeCache[property.name] = buildDefaultValueFromDefinition(property);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
69
|
+
addProperty(property: HydratedSimpleNativeProperty<{uuid: UUID}>) {
|
|
73
70
|
this.managedPropertyNames[property.name] = true;
|
|
74
71
|
}
|
|
75
72
|
}
|
package/src/controls/PickOne.tsx
CHANGED
|
@@ -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,
|
|
3
|
-
import {registerStore,
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
10
|
+
&.plaudit-sortable-items-hidden {
|
|
11
|
+
height: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
14
15
|
|
|
15
|
-
.
|
|
16
|
-
|
|
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
|
-
.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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-
|
|
25
|
+
.plaudit-sortable-items-row {
|
|
37
26
|
display: grid;
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
44
|
+
.components-button {
|
|
45
|
+
height: 16px;
|
|
46
|
+
min-width: 16px;
|
|
47
47
|
padding: 0;
|
|
48
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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 {
|