@plaudit/gutenberg-api-extensions 2.95.0 → 2.97.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 +12 -0
- package/dist/blocks/basic-custom-block-bindings-support.js +1 -1
- package/dist/blocks/basic-custom-block-bindings-support.js.map +1 -1
- package/dist/blocks/conditions.js.map +1 -1
- package/dist/blocks/problematic-blocks-blocker.js.map +1 -1
- package/dist/blocks/simple-block.d.ts +4 -4
- package/dist/blocks/simple-block.js +2 -2
- package/dist/blocks/simple-block.js.map +1 -1
- package/dist/lib/useful-types.d.ts +6 -7
- package/package.json +13 -15
- package/dist/lib/compat-types.d.ts +0 -34
- package/dist/lib/compat-types.js +0 -3
- package/dist/lib/compat-types.js.map +0 -1
- package/src/blocks/MoveError.ts +0 -7
- package/src/blocks/PathError.ts +0 -18
- package/src/blocks/SNPFlexibleItemsListComponent.tsx +0 -30
- package/src/blocks/SNPGroupComponent.tsx +0 -38
- package/src/blocks/SNPListComponent.tsx +0 -25
- package/src/blocks/SNPTreeContext.tsx +0 -13
- package/src/blocks/basic-custom-block-bindings-support.tsx +0 -248
- package/src/blocks/common-native-property-constructors.tsx +0 -927
- package/src/blocks/conditions.ts +0 -261
- package/src/blocks/csnp-api.ts +0 -221
- package/src/blocks/data-controller/actions.ts +0 -20
- package/src/blocks/data-controller/reducer.ts +0 -146
- package/src/blocks/data-controller/trigger-handlers.ts +0 -150
- package/src/blocks/data-controller/utils.ts +0 -415
- package/src/blocks/data-controller-manager.ts +0 -50
- package/src/blocks/data-controller.ts +0 -165
- package/src/blocks/hooks/built-in-suspendable-option-protocols/select.ts +0 -51
- package/src/blocks/hooks/built-in-suspendable-option-protocols/settings.ts +0 -70
- package/src/blocks/hooks/useSuspendableOptions.ts +0 -122
- package/src/blocks/index.ts +0 -23
- package/src/blocks/layered-styles-api.ts +0 -142
- package/src/blocks/layered-styles-impl.ts +0 -95
- package/src/blocks/layout/LaidOutProperty.tsx +0 -72
- package/src/blocks/layout/LaidOutPropertyRow.tsx +0 -28
- package/src/blocks/layout/NodeContext.tsx +0 -54
- package/src/blocks/layout/PanelRoot.tsx +0 -30
- package/src/blocks/layout/TabsRoot.tsx +0 -56
- package/src/blocks/layout/ToolsPanelContext.tsx +0 -22
- package/src/blocks/problematic-blocks-blocker.ts +0 -24
- package/src/blocks/problematic-variations-blocker.ts +0 -32
- package/src/blocks/shared-exportable-types.ts +0 -6
- package/src/blocks/shared-internal-types.ts +0 -18
- package/src/blocks/simple-block.tsx +0 -74
- package/src/blocks/simple-native-property-api.ts +0 -173
- package/src/blocks/simple-native-property-impl.tsx +0 -335
- package/src/blocks/simple-native-property-internal-shared.ts +0 -19
- package/src/blocks/snp-api.ts +0 -5
- package/src/blocks/snp-data-store.ts +0 -72
- package/src/blocks/utilities.ts +0 -66
- package/src/controls/AsynchronousFormTokenField.tsx +0 -86
- package/src/controls/BaseSortableItemsControl.tsx +0 -84
- package/src/controls/ExtendedFormTokenField.tsx +0 -144
- package/src/controls/ExtendedPostPicker.ts +0 -57
- package/src/controls/ExtendedRadioControl.tsx +0 -107
- package/src/controls/ExtendedTaxonomyPicker.tsx +0 -100
- package/src/controls/ExtendedTermPicker.tsx +0 -61
- package/src/controls/ExtendedTextareaControl.tsx +0 -65
- package/src/controls/ExtendedUserPicker.ts +0 -56
- package/src/controls/FileControl.tsx +0 -48
- package/src/controls/FullSizeToggleControl.tsx +0 -95
- package/src/controls/ImageControl.tsx +0 -143
- package/src/controls/InspectorPanel.tsx +0 -37
- package/src/controls/LazySuggestionsComboboxControl.tsx +0 -64
- package/src/controls/MultiSelectControl.tsx +0 -59
- package/src/controls/PickOne.tsx +0 -88
- package/src/controls/PromisableComponent.tsx +0 -56
- package/src/controls/ProperLinkControl.tsx +0 -98
- package/src/controls/SimpleToggle.tsx +0 -9
- package/src/controls/SortableFlexibleItemsControl.tsx +0 -37
- package/src/controls/SortableItemsControl.tsx +0 -22
- package/src/controls/basicNumericallyIdedItemPicker.tsx +0 -75
- package/src/controls/hooks/useImprovedTokenManager.ts +0 -163
- package/src/controls/hooks/useMultiSingleConversionLayer.ts +0 -17
- package/src/controls/hooks/useNonRenderingCounter.ts +0 -6
- package/src/controls/hooks/useOutputMemoizingFilter.ts +0 -16
- package/src/controls/hooks/useSortableItemsModel.ts +0 -196
- package/src/controls/hooks/useSuggestions.ts +0 -91
- package/src/controls/hooks/useTokenManager.ts +0 -177
- package/src/controls/index.ts +0 -24
- package/src/controls/shared.ts +0 -50
- package/src/controls/types.ts +0 -18
- package/src/editor/insert-sibling-or-child-block-shortcut.tsx +0 -60
- package/src/editor/install-insert-sole-allowed-block-shortcut-support.tsx +0 -51
- package/src/editor/simple-gutenberg-endpoints-api.ts +0 -31
- package/src/editor/simple-gutenberg-endpoints-impl.ts +0 -126
- package/src/index.ts +0 -30
- package/src/lib/compat-types.ts +0 -21
- package/src/lib/gutenberg-api-extensions-state/custom-block-bindings-support-logic.ts +0 -35
- package/src/lib/gutenberg-api-extensions-state/general-logic.ts +0 -41
- package/src/lib/gutenberg-api-extensions-state/layered-block-styles-logic.ts +0 -43
- package/src/lib/gutenberg-api-extensions-state/snp-logic.ts +0 -240
- package/src/lib/gutenberg-api-extensions-state.ts +0 -69
- package/src/lib/helpers.ts +0 -115
- package/src/lib/modified-fast-deep-equals.ts +0 -91
- package/src/lib/plaudit-icons/column-1.tsx +0 -6
- package/src/lib/plaudit-icons/column-2.tsx +0 -6
- package/src/lib/plaudit-icons/column-3.tsx +0 -6
- package/src/lib/plaudit-icons/placement-center.tsx +0 -3
- package/src/lib/plaudit-icons/placement-end.tsx +0 -3
- package/src/lib/plaudit-icons/placement-start.tsx +0 -3
- package/src/lib/plaudit-icons/placement-stretch.tsx +0 -3
- package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -4
- package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -3
- package/src/lib/plaudit-icons.ts +0 -13
- package/src/lib/sectioned-cache-store.ts +0 -120
- package/src/lib/suspense/promise-handlers.ts +0 -72
- package/src/lib/suspense.tsx +0 -18
- package/src/lib/useful-types.ts +0 -82
- package/src/schemas/README.md +0 -1
- package/src/schemas/plaudit-block-schema.json +0 -818
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import {Button, SelectControl} from "@wordpress/components";
|
|
2
|
-
import {useCallback, useState} from "@wordpress/element";
|
|
3
|
-
|
|
4
|
-
import {BaseSortableItemsControl, type BaseSortableItemsControlProps} from "./BaseSortableItemsControl";
|
|
5
|
-
import type {NonEmptyArray} from "../lib/useful-types";
|
|
6
|
-
import type {FlexibleItem} from "./types";
|
|
7
|
-
|
|
8
|
-
import type {ReactNode} from "react";
|
|
9
|
-
|
|
10
|
-
type SortableFlexibleItemsControlSpecificProps<T extends string> = {
|
|
11
|
-
availableTypes: NonEmptyArray<{label: string, value: T}>,
|
|
12
|
-
emptyValueBuilder: (type: T) => FlexibleItem<T>
|
|
13
|
-
};
|
|
14
|
-
export type SortableFlexibleItemsControlProps<T extends string> = Omit<BaseSortableItemsControlProps<FlexibleItem<T>>, 'buttonsArea'>&SortableFlexibleItemsControlSpecificProps<T>;
|
|
15
|
-
export function SortableFlexibleItemsControl<T extends string>({availableTypes, emptyValueBuilder, ...baseProps}: SortableFlexibleItemsControlProps<T>) {
|
|
16
|
-
const buttonsArea = useCallback<BaseSortableItemsControlProps<FlexibleItem<T>>['buttonsArea']>(
|
|
17
|
-
// Unfortunately, the typedefs for React memo components don't allow us to properly check the emptyValueBuilder function
|
|
18
|
-
args => <ButtonsArea {...args} availableTypes={availableTypes} emptyValueBuilder={emptyValueBuilder} />, [availableTypes, emptyValueBuilder]);
|
|
19
|
-
return <BaseSortableItemsControl {...baseProps} buttonsArea={buttonsArea} />;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
type ButtonsAreaProps<T extends string> = Parameters<BaseSortableItemsControlProps<FlexibleItem<T>>['buttonsArea']>[0]&SortableFlexibleItemsControlSpecificProps<T>;
|
|
23
|
-
function ButtonsArea<T extends string>({addHandler, disabled, availableTypes, emptyValueBuilder}: ButtonsAreaProps<T>): ReactNode {
|
|
24
|
-
const [selectedType, setSelectedType] = useState(availableTypes[0].value);
|
|
25
|
-
return <div className="plaudit-sortable-items-buttons flexible-items">
|
|
26
|
-
<Button icon="insert" disabled={disabled} onClick={() => addHandler(emptyValueBuilder(selectedType))} __next40pxDefaultSize accessibleWhenDisabled>Add</Button>
|
|
27
|
-
<SelectControl
|
|
28
|
-
__next40pxDefaultSize
|
|
29
|
-
__nextHasNoMarginBottom
|
|
30
|
-
label="Type"
|
|
31
|
-
hideLabelFromVision={true}
|
|
32
|
-
value={selectedType}
|
|
33
|
-
options={availableTypes}
|
|
34
|
-
onChange={setSelectedType}
|
|
35
|
-
/>
|
|
36
|
-
</div>;
|
|
37
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import {Button} from "@wordpress/components";
|
|
2
|
-
import {useCallback} from "@wordpress/element";
|
|
3
|
-
|
|
4
|
-
import {BaseSortableItemsControl, type BaseSortableItemsControlProps} from "./BaseSortableItemsControl";
|
|
5
|
-
|
|
6
|
-
export type SortableItemsControlProps<D> = Omit<BaseSortableItemsControlProps<D>, 'buttonsArea'|'emptyValue'>&{emptyValue: D, addRowText?: string};
|
|
7
|
-
export function SortableItemsControl<D>({emptyValue, addRowText, ...baseProps}: SortableItemsControlProps<D>) {
|
|
8
|
-
const buttonsArea = useCallback<BaseSortableItemsControlProps<D>['buttonsArea']>(
|
|
9
|
-
args => <ButtonsArea {...args} emptyValue={emptyValue} addRowText={addRowText} />, [emptyValue, addRowText]);
|
|
10
|
-
return <BaseSortableItemsControl {...baseProps} buttonsArea={buttonsArea} emptyValue={emptyValue} />;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
type ButtonsAreaProps<D = any> = Parameters<BaseSortableItemsControlProps<D>['buttonsArea']>[0]&{emptyValue: D, addRowText?: string};
|
|
14
|
-
function ButtonsArea<D>({addHandler, disabled, emptyValue, addRowText}: ButtonsAreaProps<D>) {
|
|
15
|
-
return <div className="plaudit-sortable-items-buttons">
|
|
16
|
-
<Button
|
|
17
|
-
icon="insert" disabled={disabled}
|
|
18
|
-
onClick={() => addHandler(typeof emptyValue === 'object' ? {...emptyValue} : emptyValue)}
|
|
19
|
-
__next40pxDefaultSize accessibleWhenDisabled
|
|
20
|
-
>{addRowText ?? "Add Row"}</Button>
|
|
21
|
-
</div>
|
|
22
|
-
}
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import {select as dataSelect} from "@wordpress/data";
|
|
2
|
-
import {useCallback} from "@wordpress/element";
|
|
3
|
-
|
|
4
|
-
import type {SNPControlSlots} from "../blocks";
|
|
5
|
-
import {ExtendedFormTokenField, unpackDisplayedTokenText, ValidationState} from "./ExtendedFormTokenField";
|
|
6
|
-
import {store as endpointsStore} from "../editor/simple-gutenberg-endpoints-impl";
|
|
7
|
-
import {isNumeric} from "./shared";
|
|
8
|
-
import type {TokenItem} from "../lib/useful-types";
|
|
9
|
-
|
|
10
|
-
import type {ReactNode} from "react";
|
|
11
|
-
|
|
12
|
-
type BasicNumericallyIdedItemPickerPropsBase = {
|
|
13
|
-
label?: string;
|
|
14
|
-
help?: ReactNode;
|
|
15
|
-
placeholder?: string;
|
|
16
|
-
}&Partial<Pick<SNPControlSlots, 'Messages'>>;
|
|
17
|
-
type BasicNumericallyIdedItemPickerPropsSingle = BasicNumericallyIdedItemPickerPropsBase&{
|
|
18
|
-
onChange(value: string): void;
|
|
19
|
-
value?: string;
|
|
20
|
-
multiple: false;
|
|
21
|
-
};
|
|
22
|
-
type BasicNumericallyIdedItemPickerPropsMultiple = BasicNumericallyIdedItemPickerPropsBase&{
|
|
23
|
-
onChange(value: string[]): void;
|
|
24
|
-
value?: string[];
|
|
25
|
-
multiple?: true|undefined;
|
|
26
|
-
};
|
|
27
|
-
export type BasicNumericallyIdedItemPickerProps = BasicNumericallyIdedItemPickerPropsSingle|BasicNumericallyIdedItemPickerPropsMultiple;
|
|
28
|
-
|
|
29
|
-
export function basicNumericallyIdedItemPicker(props: BasicNumericallyIdedItemPickerProps, endpoint: string, queryProps: {[key: string]: any}) {
|
|
30
|
-
const {multiple, value, ...remainder} = props;
|
|
31
|
-
|
|
32
|
-
const suggestionQuery = useCallback((input: string, select: typeof dataSelect) => {
|
|
33
|
-
return select(endpointsStore).get(endpoint + "-options", {search: input, ...queryProps}) as TokenItem[]|undefined;
|
|
34
|
-
}, [queryProps]);
|
|
35
|
-
const validationQuery = useCallback((idsBeingValidated: string[], select: typeof dataSelect) => {
|
|
36
|
-
return select(endpointsStore).get(endpoint + "-validation", {ids: idsBeingValidated.join(','), ...queryProps}) as TokenItem[]|undefined;
|
|
37
|
-
}, [queryProps]);
|
|
38
|
-
|
|
39
|
-
if (props.multiple === false) {
|
|
40
|
-
return <ExtendedFormTokenField
|
|
41
|
-
{...remainder}
|
|
42
|
-
value={props.value}
|
|
43
|
-
multiple={false}
|
|
44
|
-
onChange={props.onChange}
|
|
45
|
-
stringToTokenConverter={makeTokenFromSuggestion}
|
|
46
|
-
suggestionQuery={suggestionQuery}
|
|
47
|
-
validationQuery={validationQuery}
|
|
48
|
-
validator={validator}
|
|
49
|
-
/>;
|
|
50
|
-
} else {
|
|
51
|
-
return <ExtendedFormTokenField
|
|
52
|
-
{...remainder}
|
|
53
|
-
value={props.value}
|
|
54
|
-
multiple={true}
|
|
55
|
-
onChange={props.onChange}
|
|
56
|
-
stringToTokenConverter={makeTokenFromSuggestion}
|
|
57
|
-
suggestionQuery={suggestionQuery}
|
|
58
|
-
validationQuery={validationQuery}
|
|
59
|
-
validator={validator}
|
|
60
|
-
/>;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function makeTokenFromSuggestion(token: string): TokenItem {
|
|
65
|
-
if (isNumeric(token)) {
|
|
66
|
-
return {value: token, status: ValidationState.Validating};
|
|
67
|
-
} else {
|
|
68
|
-
const {value, title} = unpackDisplayedTokenText(token);
|
|
69
|
-
return {value, status: isNumeric(value) ? ValidationState.Validating : ValidationState.Invalid, title};
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function validator(token: string) {
|
|
74
|
-
return /\(#([0-9]+)\)$/.exec(token)?.[1] !== undefined;
|
|
75
|
-
}
|
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import {type MapSelect, useSelect} from "@wordpress/data";
|
|
2
|
-
import {useEffect, useMemo, useRef, useState} from "@wordpress/element";
|
|
3
|
-
|
|
4
|
-
import {potentialWpErrorToString} from "../../lib/helpers";
|
|
5
|
-
import type {TokenItem} from "../../lib/useful-types";
|
|
6
|
-
|
|
7
|
-
export function packDisplayTokenText(token: TokenItem) {
|
|
8
|
-
return `${token.title || token.value} (#${token.value})`;
|
|
9
|
-
}
|
|
10
|
-
export function unpackDisplayedTokenText(token: string) {
|
|
11
|
-
const result = /(.+) \(#([^)]+)\)$/.exec(token);
|
|
12
|
-
return {title: result?.[1], value: result?.[2] ?? token};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// The strange values correspond to the literals that are expected by TokenItem.status, which allows the assignment code to be cleaner
|
|
16
|
-
export const enum ValidationState {
|
|
17
|
-
Valid = 'success',
|
|
18
|
-
Invalid = 'error',
|
|
19
|
-
Validating = 'validating'
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export type TokenManagementCallbacks = {
|
|
23
|
-
validationQuery(tokens: string[], ...args: Parameters<MapSelect>): TokenItem[]|undefined;
|
|
24
|
-
suggestionQuery(input: string, ...args: Parameters<MapSelect>): TokenItem[]|undefined;
|
|
25
|
-
stringToTokenConverter(suggestion: string): TokenItem;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type ImprovedTokenManagerReturnType = {
|
|
29
|
-
currentTokens: TokenItem[];
|
|
30
|
-
isValidating: boolean;
|
|
31
|
-
updateComponentValue: (value: (((prevState: (string | TokenItem)[]) => (string|TokenItem)[])|(string|TokenItem)[])) => void;
|
|
32
|
-
tokenStatusCache: Map<TokenItem["value"], TokenItem["status"]>;
|
|
33
|
-
thrownValidationError?: string;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
export function useImprovedTokenManager(
|
|
37
|
-
initialValue: (string|TokenItem)[]|undefined,
|
|
38
|
-
writeBackingValue: (value: TokenItem[]) => void,
|
|
39
|
-
validationQuery: TokenManagementCallbacks['validationQuery'],
|
|
40
|
-
makeTokenFromString: TokenManagementCallbacks['stringToTokenConverter'],
|
|
41
|
-
tokenTitleCache: Map<TokenItem['value'], TokenItem['title']>
|
|
42
|
-
): ImprovedTokenManagerReturnType {
|
|
43
|
-
const [componentValue, updateComponentValue] = useState<(string|TokenItem)[]>(
|
|
44
|
-
() => initialValue?.map(item => typeof item === 'string' ? makeTokenFromString(item) : item) ?? []);
|
|
45
|
-
|
|
46
|
-
const tokenStatusCacheRef = useRef<Map<TokenItem['value'], TokenItem['status']>|undefined>(undefined);
|
|
47
|
-
if (tokenStatusCacheRef.current === undefined) {
|
|
48
|
-
tokenStatusCacheRef.current = new Map();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const convertedTokens = useTokenConverter(componentValue, makeTokenFromString, tokenStatusCacheRef.current);
|
|
52
|
-
const {isValidating, validatedTokens, thrownValidationError} = useTokenValidator(convertedTokens, validationQuery, tokenStatusCacheRef.current)
|
|
53
|
-
const titledTokens = useTokenTitleApplier(validatedTokens, tokenTitleCache);
|
|
54
|
-
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
if (!isValidating) {
|
|
57
|
-
writeBackingValue(titledTokens);
|
|
58
|
-
}
|
|
59
|
-
}, [titledTokens, isValidating]);
|
|
60
|
-
|
|
61
|
-
return {currentTokens: titledTokens, isValidating, updateComponentValue, tokenStatusCache: tokenStatusCacheRef.current, thrownValidationError};
|
|
62
|
-
}
|
|
63
|
-
function syncTokenTitles(token: TokenItem, cache: Map<TokenItem['value'], TokenItem['title']>): TokenItem {
|
|
64
|
-
return {...token, title: cache.get(token.value) ?? addTokenTitleToCache(token, cache) ?? packDisplayTokenText(token)};
|
|
65
|
-
}
|
|
66
|
-
function addTokenTitleToCache(token: TokenItem, cache: Map<TokenItem['value'], TokenItem['title']>) {
|
|
67
|
-
let label = token.title?.trim();
|
|
68
|
-
if (!label) {
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const valueSuffix = `(#${token.value})`;
|
|
72
|
-
if (label.endsWith(valueSuffix)) {
|
|
73
|
-
label = label.substring(0, label.length - valueSuffix.length).trim();
|
|
74
|
-
}
|
|
75
|
-
const res = `${label.length > 30 ? `${label.substring(0, 30).trim()}…` : label} ${valueSuffix}`;
|
|
76
|
-
cache.set(token.value, res);
|
|
77
|
-
return res;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function isAllValidTokens(arr: (string|TokenItem)[]): arr is (Omit<TokenItem, 'title'|'status'>&Required<Pick<TokenItem, 'title'|'status'>>)[] {
|
|
81
|
-
return arr.every(token => typeof token !== 'string' && token.status !== undefined && token.status !== ValidationState.Validating && token.title !== undefined);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function useTokenConverter(
|
|
85
|
-
componentValue: (string|TokenItem)[], makeTokenFromString: (token: string) => TokenItem, tokenStatusCache: Map<TokenItem['value'], TokenItem['status']>
|
|
86
|
-
) {
|
|
87
|
-
const priorComponentValue = useRef<(string|TokenItem)[]>([]);
|
|
88
|
-
const alreadyConvertedTokens = useRef<TokenItem[]>([]);
|
|
89
|
-
if (priorComponentValue.current === componentValue) {
|
|
90
|
-
return alreadyConvertedTokens.current;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const newlyConvertedTokens = isAllValidTokens(componentValue) ? componentValue
|
|
94
|
-
: componentValue.map(token => {
|
|
95
|
-
if (typeof token === 'string') {
|
|
96
|
-
const res = makeTokenFromString(token);
|
|
97
|
-
if (res.status === undefined) {
|
|
98
|
-
res.status = tokenStatusCache.get(res.value);
|
|
99
|
-
}
|
|
100
|
-
return res;
|
|
101
|
-
} else {
|
|
102
|
-
if (token.status === undefined || token.status === ValidationState.Validating) {
|
|
103
|
-
token.status = tokenStatusCache.get(token.value);
|
|
104
|
-
}
|
|
105
|
-
return token;
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
const convertedTokens = alreadyConvertedTokens.current;
|
|
110
|
-
if (newlyConvertedTokens.length !== convertedTokens.length) {
|
|
111
|
-
priorComponentValue.current = componentValue;
|
|
112
|
-
alreadyConvertedTokens.current = newlyConvertedTokens;
|
|
113
|
-
return newlyConvertedTokens;
|
|
114
|
-
}
|
|
115
|
-
for (let i = 0; i < newlyConvertedTokens.length; i++) {
|
|
116
|
-
if (newlyConvertedTokens[i]?.value !== convertedTokens[i]?.value || newlyConvertedTokens[i]?.status !== convertedTokens[i]?.status) {
|
|
117
|
-
priorComponentValue.current = componentValue;
|
|
118
|
-
alreadyConvertedTokens.current = newlyConvertedTokens;
|
|
119
|
-
return newlyConvertedTokens;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
return convertedTokens;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function useTokenValidator(
|
|
126
|
-
convertedTokens: TokenItem[], validationQuery: TokenManagementCallbacks['validationQuery'], tokenStatusCache: Map<TokenItem['value'], TokenItem['status']>
|
|
127
|
-
) {
|
|
128
|
-
const [statusUpdatedConvertedTokens, statusUpdatedConvertedTokenValues] = useMemo(() => {
|
|
129
|
-
const res = convertedTokens
|
|
130
|
-
.map(token => {
|
|
131
|
-
return (token.status === undefined || token.status === ValidationState.Validating) && tokenStatusCache.has(token.value)
|
|
132
|
-
? {...token, status: tokenStatusCache.get(token.value)} : token;
|
|
133
|
-
});
|
|
134
|
-
return [res, res.map(token => token.value)];
|
|
135
|
-
}, [convertedTokens, tokenStatusCache]);
|
|
136
|
-
|
|
137
|
-
return useSelect((select, registry) => {
|
|
138
|
-
if (statusUpdatedConvertedTokens.some(token => token.status === undefined || token.status === ValidationState.Validating)) {
|
|
139
|
-
let res;
|
|
140
|
-
try {
|
|
141
|
-
res = validationQuery(statusUpdatedConvertedTokenValues, select, registry);
|
|
142
|
-
} catch (e) {
|
|
143
|
-
return {validatedTokens: statusUpdatedConvertedTokens, isValidating: false, thrownValidationError: potentialWpErrorToString(e)};
|
|
144
|
-
}
|
|
145
|
-
if (res !== undefined) {
|
|
146
|
-
const validTokens = new Map(res.map(token => [token.value, token]));
|
|
147
|
-
const validatedTokens = convertedTokens.map(token => validTokens.get(token.value) ?? {...token, status: ValidationState.Invalid});
|
|
148
|
-
for (const token of validatedTokens) {
|
|
149
|
-
tokenStatusCache.set(token.value, token.status);
|
|
150
|
-
}
|
|
151
|
-
return {validatedTokens, isValidating: false};
|
|
152
|
-
} else {
|
|
153
|
-
return {validatedTokens: statusUpdatedConvertedTokens, isValidating: true};
|
|
154
|
-
}
|
|
155
|
-
} else {
|
|
156
|
-
return {validatedTokens: statusUpdatedConvertedTokens, isValidating: false};
|
|
157
|
-
}
|
|
158
|
-
}, [convertedTokens, statusUpdatedConvertedTokens, statusUpdatedConvertedTokenValues, validationQuery, tokenStatusCache]);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function useTokenTitleApplier(convertedTokens: TokenItem[], tokenTitleCache: Map<TokenItem['value'], TokenItem['title']>) {
|
|
162
|
-
return useMemo(() => convertedTokens.map(token => syncTokenTitles(token, tokenTitleCache)), [convertedTokens, tokenTitleCache]);
|
|
163
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import {useCallback, useMemo} from "@wordpress/element";
|
|
2
|
-
|
|
3
|
-
import type {TokenItem} from "../../lib/useful-types";
|
|
4
|
-
|
|
5
|
-
export type ValueConverter = {value: string[], setValue(value: TokenItem[]): void};
|
|
6
|
-
export function useMultiSingleConversionLayer(
|
|
7
|
-
value: string|string[]|undefined, onChange: ((tokens: string[]) => void)|((token: string) => void),
|
|
8
|
-
makeTokenFromSuggestion: (suggestion: string) => TokenItem, multiple = true
|
|
9
|
-
): ValueConverter {
|
|
10
|
-
return {
|
|
11
|
-
value: useMemo(() => value === undefined ? [] : (Array.isArray(value) ? value : [value]), [value]),
|
|
12
|
-
setValue: useCallback(tokens => {
|
|
13
|
-
const bareTokens = tokens.filter(token => token.status === 'success').map(token => token.value);
|
|
14
|
-
onChange((multiple ? bareTokens : bareTokens[0]) as any);
|
|
15
|
-
}, [onChange, makeTokenFromSuggestion, multiple])
|
|
16
|
-
};
|
|
17
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import {useCallback, useRef} from "@wordpress/element";
|
|
2
|
-
|
|
3
|
-
export function useOutputMemoizingFilter<A extends any[], R>(filter: (...args: A) => R, externals?: any[]): typeof filter {
|
|
4
|
-
const existingState = useRef<undefined|[typeof filter, A, R]>(undefined);
|
|
5
|
-
const memoizedFilter = useCallback((...args: A): R => {
|
|
6
|
-
if (existingState.current !== undefined && existingState.current[0] === memoizedFilter
|
|
7
|
-
&& (existingState.current[1] === args || args.every((a, i) => existingState.current![1][i] === a))
|
|
8
|
-
) {
|
|
9
|
-
return existingState.current[2];
|
|
10
|
-
}
|
|
11
|
-
const res = filter(...args);
|
|
12
|
-
existingState.current = [memoizedFilter, args, res];
|
|
13
|
-
return res;
|
|
14
|
-
}, externals ? [filter, ...externals] : [filter]);
|
|
15
|
-
return memoizedFilter;
|
|
16
|
-
}
|
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
import {useEffect, useMemo, useState} from "@wordpress/element";
|
|
2
|
-
|
|
3
|
-
import {Immer} from "immer";
|
|
4
|
-
import {clone} from "../../lib/helpers";
|
|
5
|
-
|
|
6
|
-
type Listeners<D> = {
|
|
7
|
-
onAdd?: (index: number, value: D) => void,
|
|
8
|
-
onChange?: (value: D[]) => void,
|
|
9
|
-
onRemove?: (index: number) => void,
|
|
10
|
-
onReorder?: (oldIndex: number, newIndex: number) => void
|
|
11
|
-
};
|
|
12
|
-
type SortableItemsModelArgs<D> = Listeners<D>&{
|
|
13
|
-
emptyValue?: D,
|
|
14
|
-
value?: D[]
|
|
15
|
-
}
|
|
16
|
-
export type SortableItemsModelKey = ReturnType<typeof crypto.randomUUID>;
|
|
17
|
-
export type KeyedValue<D> = {value: D, key: SortableItemsModelKey};
|
|
18
|
-
export type KeyedValues<D> = KeyedValue<D>[];
|
|
19
|
-
type ActionDefs<D> = {
|
|
20
|
-
add(args: {index?: number, value: D}): void,
|
|
21
|
-
remove(args: {key: SortableItemsModelKey}): void,
|
|
22
|
-
move(args: {oldIndex: number, newIndex: number}): void,
|
|
23
|
-
change(args: {value: D, index: number}): void,
|
|
24
|
-
|
|
25
|
-
commit(args: {keyedValues: KeyedValues<D>}): void,
|
|
26
|
-
refresh(args: {listeners: Listeners<D>, value?: D[]}): void
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
type State<D> = {
|
|
30
|
-
listeners: Listeners<D>,
|
|
31
|
-
keyedValues: KeyedValues<D>,
|
|
32
|
-
value?: D[] // This is only used to avoid updating keyedValues on refresh wherever possible
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export type IndexedItemActions<D> = {
|
|
36
|
-
add(value: D): void,
|
|
37
|
-
remove(): void,
|
|
38
|
-
moveUp(): void,
|
|
39
|
-
moveDown(): void,
|
|
40
|
-
change(value: D): void
|
|
41
|
-
};
|
|
42
|
-
export type IndexedItemActionsMaker<D> = (index: number, key: SortableItemsModelKey) => IndexedItemActions<D>;
|
|
43
|
-
|
|
44
|
-
type Actions<D> = {add(value: D, index?: number): void, commit(keyedValues: KeyedValues<D>): void, makeIndexedItemActions: IndexedItemActionsMaker<D>};
|
|
45
|
-
export function useSortableItemsModel<D>(args: SortableItemsModelArgs<D>) {
|
|
46
|
-
const {mutators, state} = usePseudoReducerBasedStateManagement<D>(args);
|
|
47
|
-
|
|
48
|
-
const {emptyValue} = args;
|
|
49
|
-
// We use a separate memo for the actions because dispatch should change significantly less-often than state
|
|
50
|
-
const actions = useMemo<Actions<D>>(() => {
|
|
51
|
-
return ({
|
|
52
|
-
add: (value: D, index?: number) => {
|
|
53
|
-
mutators.add({value, index});
|
|
54
|
-
},
|
|
55
|
-
commit: (keyedValues: KeyedValues<D>) => {
|
|
56
|
-
mutators.commit({keyedValues});
|
|
57
|
-
},
|
|
58
|
-
addEmptyValue: emptyValue !== undefined && emptyValue !== null ? (index?: number) => {
|
|
59
|
-
mutators.add({value: typeof emptyValue === 'object' ? {...emptyValue} : emptyValue, index});
|
|
60
|
-
} : undefined,
|
|
61
|
-
makeIndexedItemActions(index: number, key: SortableItemsModelKey) {
|
|
62
|
-
return useMemo(() => ({
|
|
63
|
-
add: (value: D) => {
|
|
64
|
-
mutators.add({value, index});
|
|
65
|
-
},
|
|
66
|
-
remove: () => {
|
|
67
|
-
mutators.remove({key});
|
|
68
|
-
},
|
|
69
|
-
moveUp: () => {
|
|
70
|
-
mutators.move({oldIndex: index, newIndex: index - 1});
|
|
71
|
-
},
|
|
72
|
-
moveDown: () => {
|
|
73
|
-
mutators.move({oldIndex: index, newIndex: index + 1});
|
|
74
|
-
},
|
|
75
|
-
change: (value: D) => {
|
|
76
|
-
mutators.change({value, index});
|
|
77
|
-
}
|
|
78
|
-
}), [index, key, mutators]);
|
|
79
|
-
}
|
|
80
|
-
});
|
|
81
|
-
}, [emptyValue, mutators]);
|
|
82
|
-
|
|
83
|
-
return {...actions, keyedValues: state.keyedValues};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function swap<T>(value: T[], newIndex: number, oldIndex: number): T[] {
|
|
87
|
-
const splicedValue = [...value];
|
|
88
|
-
if (newIndex > oldIndex) {
|
|
89
|
-
splicedValue.splice(newIndex + 1, 0, splicedValue[oldIndex]!);
|
|
90
|
-
splicedValue.splice(oldIndex, 1);
|
|
91
|
-
} else {
|
|
92
|
-
splicedValue.splice(newIndex, 0, ...splicedValue.splice(oldIndex, 1));
|
|
93
|
-
}
|
|
94
|
-
return splicedValue;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function usePseudoReducerBasedStateManagement<D>({value, onAdd, onChange, onRemove, onReorder}: SortableItemsModelArgs<D>) {
|
|
98
|
-
const [state, setState] = useState<State<D>>({keyedValues: [], listeners: {}});
|
|
99
|
-
|
|
100
|
-
const mutators = useMemo<ActionDefs<D>>(() => {
|
|
101
|
-
return {
|
|
102
|
-
add({value, index}) {
|
|
103
|
-
const newItemIndex = index ?? state.value?.length ?? 0;
|
|
104
|
-
const newValue = state.value?.toSpliced(newItemIndex, 0, value) ?? [value];
|
|
105
|
-
onAdd?.(newItemIndex, value);
|
|
106
|
-
onChange?.(newValue);
|
|
107
|
-
setState({
|
|
108
|
-
...state,
|
|
109
|
-
value: newValue,
|
|
110
|
-
keyedValues: state.keyedValues.toSpliced(newItemIndex, 0, {value: value, key: crypto.randomUUID()})
|
|
111
|
-
});
|
|
112
|
-
},
|
|
113
|
-
remove({key}) {
|
|
114
|
-
const actionIndex = state.keyedValues?.findIndex(({key: k}) => k === key) ?? -1;
|
|
115
|
-
if (actionIndex === -1) {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
const newValue = state.value?.toSpliced(actionIndex, 1) ?? [];
|
|
119
|
-
onRemove?.(actionIndex);
|
|
120
|
-
onChange?.(newValue);
|
|
121
|
-
setState({
|
|
122
|
-
...state,
|
|
123
|
-
value: newValue,
|
|
124
|
-
keyedValues: state.keyedValues.toSpliced(actionIndex, 1)
|
|
125
|
-
});
|
|
126
|
-
},
|
|
127
|
-
move({oldIndex, newIndex}) {
|
|
128
|
-
if (newIndex === oldIndex) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const newValue = swap(state.value ?? [], newIndex, oldIndex);
|
|
132
|
-
const changesStart = Math.min(newIndex, oldIndex);
|
|
133
|
-
newValue.splice(changesStart, newValue.length, ...newValue.slice(changesStart).map(v => clone(v)));
|
|
134
|
-
onReorder?.(oldIndex, newIndex);
|
|
135
|
-
onChange?.(newValue);
|
|
136
|
-
setState({
|
|
137
|
-
...state,
|
|
138
|
-
value: newValue,
|
|
139
|
-
keyedValues: swap(state.keyedValues, newIndex, oldIndex).map(({key}, i) => ({
|
|
140
|
-
value: newValue[i]!,
|
|
141
|
-
key
|
|
142
|
-
}))
|
|
143
|
-
});
|
|
144
|
-
},
|
|
145
|
-
change({value, index}) {
|
|
146
|
-
const newValue = (state.value ?? []).toSpliced(index, 1, value);
|
|
147
|
-
onChange?.(newValue);
|
|
148
|
-
setState({
|
|
149
|
-
...state,
|
|
150
|
-
value: newValue,
|
|
151
|
-
keyedValues: state.keyedValues.toSpliced(index, 1, {...state.keyedValues[index]!, value})
|
|
152
|
-
});
|
|
153
|
-
},
|
|
154
|
-
commit({keyedValues}) {
|
|
155
|
-
const newValue = keyedValues.map(({value}) => value);
|
|
156
|
-
onChange?.(newValue);
|
|
157
|
-
setState({
|
|
158
|
-
...state,
|
|
159
|
-
value: newValue,
|
|
160
|
-
keyedValues
|
|
161
|
-
});
|
|
162
|
-
},
|
|
163
|
-
refresh(args) {
|
|
164
|
-
const prevState = state;
|
|
165
|
-
setState((new Immer({autoFreeze: false})).produce(prevState, (draft: State<D>) => {
|
|
166
|
-
for (const [key, value] of Object.entries(args.listeners) as [keyof Listeners<D>, any][]) {
|
|
167
|
-
draft.listeners[key] = value;
|
|
168
|
-
}
|
|
169
|
-
if (prevState.value !== args.value) {
|
|
170
|
-
draft.value = args.value;
|
|
171
|
-
if (args.value !== undefined) {
|
|
172
|
-
if (args.value.length !== prevState.keyedValues.length) {
|
|
173
|
-
draft.keyedValues = args.value.map((v, i) => {
|
|
174
|
-
return {
|
|
175
|
-
value: v,
|
|
176
|
-
key: prevState.keyedValues[i] !== undefined && prevState.keyedValues[i].value === v ? prevState.keyedValues[i].key : crypto.randomUUID()
|
|
177
|
-
};
|
|
178
|
-
});
|
|
179
|
-
} else {
|
|
180
|
-
for (let i = 0; i < args.value.length; i++) {
|
|
181
|
-
draft.keyedValues[i]!.value = args.value[i]!;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}));
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
}, [state, onAdd, onChange, onRemove, onReorder]);
|
|
190
|
-
|
|
191
|
-
useEffect(() => {
|
|
192
|
-
mutators.refresh({listeners: {onAdd, onChange, onRemove, onReorder}, value});
|
|
193
|
-
}, [value, onAdd, onChange, onRemove, onReorder, mutators]);
|
|
194
|
-
|
|
195
|
-
return {mutators, state};
|
|
196
|
-
}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import {useCallback, useEffect, useRef, useState} from "@wordpress/element";
|
|
2
|
-
|
|
3
|
-
import {useNonRenderingCounter} from "./useNonRenderingCounter";
|
|
4
|
-
|
|
5
|
-
import {useReducer} from "react";
|
|
6
|
-
|
|
7
|
-
type UseSuggestionsArgs<T> = {
|
|
8
|
-
expandOnFocus?: boolean,
|
|
9
|
-
getOption?: (value?: string) => Promise<T|undefined>,
|
|
10
|
-
getSuggestions(filterValue: string): Promise<T[]>
|
|
11
|
-
};
|
|
12
|
-
type SuggestionState<T> = {
|
|
13
|
-
hasLoadingError: boolean,
|
|
14
|
-
suggestions: T[],
|
|
15
|
-
requestId: number
|
|
16
|
-
};
|
|
17
|
-
function useSimpleDebouncer(delay: number) {
|
|
18
|
-
const timerId = useRef<ReturnType<typeof setTimeout>>(undefined);
|
|
19
|
-
return useCallback((action: () => void) => {
|
|
20
|
-
if (timerId.current !== undefined) {
|
|
21
|
-
clearTimeout(timerId.current);
|
|
22
|
-
}
|
|
23
|
-
timerId.current = setTimeout(() => {
|
|
24
|
-
timerId.current = undefined;
|
|
25
|
-
action();
|
|
26
|
-
}, delay);
|
|
27
|
-
}, [timerId]);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function useSuggestions<T>(initialValue: string|null|undefined, {expandOnFocus, getOption, getSuggestions}: UseSuggestionsArgs<T>) {
|
|
31
|
-
const suggestionRequestDebouncer = useSimpleDebouncer(200);
|
|
32
|
-
const [requestId, nextRequestId] = useNonRenderingCounter();
|
|
33
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
34
|
-
const [latestSuggestions, dispatchSuggestionsUpdate] = useReducer(useSuggestionSuggestionUpdateReducer<T>,
|
|
35
|
-
{hasLoadingError: false, requestId: 0, suggestions: []});
|
|
36
|
-
const [input, setInput] = useState(initialValue ?? "");
|
|
37
|
-
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const myRequestId = nextRequestId();
|
|
40
|
-
if (getOption === undefined) {
|
|
41
|
-
dispatchSuggestionsUpdate({hasLoadingError: false, suggestions: [], requestId: myRequestId});
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (!expandOnFocus && input.length < 2) {
|
|
45
|
-
dispatchSuggestionsUpdate({hasLoadingError: false, suggestions: [], requestId: myRequestId});
|
|
46
|
-
} else {
|
|
47
|
-
setIsLoading(true);
|
|
48
|
-
getOption(input)
|
|
49
|
-
.then(option => dispatchSuggestionsUpdate({hasLoadingError: false, suggestions: option ? [option] : [], requestId: myRequestId}))
|
|
50
|
-
.catch(err => {
|
|
51
|
-
dispatchSuggestionsUpdate({hasLoadingError: true, suggestions: [], requestId: myRequestId});
|
|
52
|
-
console.error("An error occurred while loading options:", err);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}, [expandOnFocus, getOption]);
|
|
56
|
-
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
const myRequestId = nextRequestId();
|
|
59
|
-
if (!expandOnFocus && input.length < 2) {
|
|
60
|
-
dispatchSuggestionsUpdate({hasLoadingError: false, suggestions: [], requestId: myRequestId});
|
|
61
|
-
} else {
|
|
62
|
-
const myRequestId = nextRequestId();
|
|
63
|
-
setIsLoading(true);
|
|
64
|
-
suggestionRequestDebouncer(() => {
|
|
65
|
-
getSuggestions(input)
|
|
66
|
-
.then(suggestions => dispatchSuggestionsUpdate({hasLoadingError: false, suggestions, requestId: myRequestId}))
|
|
67
|
-
.catch(err => {
|
|
68
|
-
dispatchSuggestionsUpdate({hasLoadingError: true, suggestions: [], requestId: myRequestId});
|
|
69
|
-
console.error("An error occurred while loading options:", err);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}, [input, suggestionRequestDebouncer]);
|
|
74
|
-
|
|
75
|
-
// This is a separate useEffect hook because we don't necessarily want to update the rendered state when a new request is sent out.
|
|
76
|
-
useEffect(() => setIsLoading(latestSuggestions.requestId < requestId.current), [latestSuggestions.requestId]);
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
hasLoadingError: latestSuggestions.hasLoadingError, isInitializing: latestSuggestions.requestId === 0, isLoading,
|
|
80
|
-
input, setInput, suggestions: latestSuggestions.suggestions
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
function useSuggestionSuggestionUpdateReducer<T>(state: SuggestionState<T>, action: SuggestionState<T>): SuggestionState<T> {
|
|
84
|
-
if (action.requestId <= state.requestId) {
|
|
85
|
-
return state;
|
|
86
|
-
}
|
|
87
|
-
if (action.hasLoadingError) {
|
|
88
|
-
return {hasLoadingError: true, requestId: action.requestId, suggestions: [...state.suggestions]};
|
|
89
|
-
}
|
|
90
|
-
return action;
|
|
91
|
-
}
|