@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.
Files changed (113) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/blocks/basic-custom-block-bindings-support.js +1 -1
  3. package/dist/blocks/basic-custom-block-bindings-support.js.map +1 -1
  4. package/dist/blocks/conditions.js.map +1 -1
  5. package/dist/blocks/problematic-blocks-blocker.js.map +1 -1
  6. package/dist/blocks/simple-block.d.ts +4 -4
  7. package/dist/blocks/simple-block.js +2 -2
  8. package/dist/blocks/simple-block.js.map +1 -1
  9. package/dist/lib/useful-types.d.ts +6 -7
  10. package/package.json +13 -15
  11. package/dist/lib/compat-types.d.ts +0 -34
  12. package/dist/lib/compat-types.js +0 -3
  13. package/dist/lib/compat-types.js.map +0 -1
  14. package/src/blocks/MoveError.ts +0 -7
  15. package/src/blocks/PathError.ts +0 -18
  16. package/src/blocks/SNPFlexibleItemsListComponent.tsx +0 -30
  17. package/src/blocks/SNPGroupComponent.tsx +0 -38
  18. package/src/blocks/SNPListComponent.tsx +0 -25
  19. package/src/blocks/SNPTreeContext.tsx +0 -13
  20. package/src/blocks/basic-custom-block-bindings-support.tsx +0 -248
  21. package/src/blocks/common-native-property-constructors.tsx +0 -927
  22. package/src/blocks/conditions.ts +0 -261
  23. package/src/blocks/csnp-api.ts +0 -221
  24. package/src/blocks/data-controller/actions.ts +0 -20
  25. package/src/blocks/data-controller/reducer.ts +0 -146
  26. package/src/blocks/data-controller/trigger-handlers.ts +0 -150
  27. package/src/blocks/data-controller/utils.ts +0 -415
  28. package/src/blocks/data-controller-manager.ts +0 -50
  29. package/src/blocks/data-controller.ts +0 -165
  30. package/src/blocks/hooks/built-in-suspendable-option-protocols/select.ts +0 -51
  31. package/src/blocks/hooks/built-in-suspendable-option-protocols/settings.ts +0 -70
  32. package/src/blocks/hooks/useSuspendableOptions.ts +0 -122
  33. package/src/blocks/index.ts +0 -23
  34. package/src/blocks/layered-styles-api.ts +0 -142
  35. package/src/blocks/layered-styles-impl.ts +0 -95
  36. package/src/blocks/layout/LaidOutProperty.tsx +0 -72
  37. package/src/blocks/layout/LaidOutPropertyRow.tsx +0 -28
  38. package/src/blocks/layout/NodeContext.tsx +0 -54
  39. package/src/blocks/layout/PanelRoot.tsx +0 -30
  40. package/src/blocks/layout/TabsRoot.tsx +0 -56
  41. package/src/blocks/layout/ToolsPanelContext.tsx +0 -22
  42. package/src/blocks/problematic-blocks-blocker.ts +0 -24
  43. package/src/blocks/problematic-variations-blocker.ts +0 -32
  44. package/src/blocks/shared-exportable-types.ts +0 -6
  45. package/src/blocks/shared-internal-types.ts +0 -18
  46. package/src/blocks/simple-block.tsx +0 -74
  47. package/src/blocks/simple-native-property-api.ts +0 -173
  48. package/src/blocks/simple-native-property-impl.tsx +0 -335
  49. package/src/blocks/simple-native-property-internal-shared.ts +0 -19
  50. package/src/blocks/snp-api.ts +0 -5
  51. package/src/blocks/snp-data-store.ts +0 -72
  52. package/src/blocks/utilities.ts +0 -66
  53. package/src/controls/AsynchronousFormTokenField.tsx +0 -86
  54. package/src/controls/BaseSortableItemsControl.tsx +0 -84
  55. package/src/controls/ExtendedFormTokenField.tsx +0 -144
  56. package/src/controls/ExtendedPostPicker.ts +0 -57
  57. package/src/controls/ExtendedRadioControl.tsx +0 -107
  58. package/src/controls/ExtendedTaxonomyPicker.tsx +0 -100
  59. package/src/controls/ExtendedTermPicker.tsx +0 -61
  60. package/src/controls/ExtendedTextareaControl.tsx +0 -65
  61. package/src/controls/ExtendedUserPicker.ts +0 -56
  62. package/src/controls/FileControl.tsx +0 -48
  63. package/src/controls/FullSizeToggleControl.tsx +0 -95
  64. package/src/controls/ImageControl.tsx +0 -143
  65. package/src/controls/InspectorPanel.tsx +0 -37
  66. package/src/controls/LazySuggestionsComboboxControl.tsx +0 -64
  67. package/src/controls/MultiSelectControl.tsx +0 -59
  68. package/src/controls/PickOne.tsx +0 -88
  69. package/src/controls/PromisableComponent.tsx +0 -56
  70. package/src/controls/ProperLinkControl.tsx +0 -98
  71. package/src/controls/SimpleToggle.tsx +0 -9
  72. package/src/controls/SortableFlexibleItemsControl.tsx +0 -37
  73. package/src/controls/SortableItemsControl.tsx +0 -22
  74. package/src/controls/basicNumericallyIdedItemPicker.tsx +0 -75
  75. package/src/controls/hooks/useImprovedTokenManager.ts +0 -163
  76. package/src/controls/hooks/useMultiSingleConversionLayer.ts +0 -17
  77. package/src/controls/hooks/useNonRenderingCounter.ts +0 -6
  78. package/src/controls/hooks/useOutputMemoizingFilter.ts +0 -16
  79. package/src/controls/hooks/useSortableItemsModel.ts +0 -196
  80. package/src/controls/hooks/useSuggestions.ts +0 -91
  81. package/src/controls/hooks/useTokenManager.ts +0 -177
  82. package/src/controls/index.ts +0 -24
  83. package/src/controls/shared.ts +0 -50
  84. package/src/controls/types.ts +0 -18
  85. package/src/editor/insert-sibling-or-child-block-shortcut.tsx +0 -60
  86. package/src/editor/install-insert-sole-allowed-block-shortcut-support.tsx +0 -51
  87. package/src/editor/simple-gutenberg-endpoints-api.ts +0 -31
  88. package/src/editor/simple-gutenberg-endpoints-impl.ts +0 -126
  89. package/src/index.ts +0 -30
  90. package/src/lib/compat-types.ts +0 -21
  91. package/src/lib/gutenberg-api-extensions-state/custom-block-bindings-support-logic.ts +0 -35
  92. package/src/lib/gutenberg-api-extensions-state/general-logic.ts +0 -41
  93. package/src/lib/gutenberg-api-extensions-state/layered-block-styles-logic.ts +0 -43
  94. package/src/lib/gutenberg-api-extensions-state/snp-logic.ts +0 -240
  95. package/src/lib/gutenberg-api-extensions-state.ts +0 -69
  96. package/src/lib/helpers.ts +0 -115
  97. package/src/lib/modified-fast-deep-equals.ts +0 -91
  98. package/src/lib/plaudit-icons/column-1.tsx +0 -6
  99. package/src/lib/plaudit-icons/column-2.tsx +0 -6
  100. package/src/lib/plaudit-icons/column-3.tsx +0 -6
  101. package/src/lib/plaudit-icons/placement-center.tsx +0 -3
  102. package/src/lib/plaudit-icons/placement-end.tsx +0 -3
  103. package/src/lib/plaudit-icons/placement-start.tsx +0 -3
  104. package/src/lib/plaudit-icons/placement-stretch.tsx +0 -3
  105. package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -4
  106. package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -3
  107. package/src/lib/plaudit-icons.ts +0 -13
  108. package/src/lib/sectioned-cache-store.ts +0 -120
  109. package/src/lib/suspense/promise-handlers.ts +0 -72
  110. package/src/lib/suspense.tsx +0 -18
  111. package/src/lib/useful-types.ts +0 -82
  112. package/src/schemas/README.md +0 -1
  113. 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,6 +0,0 @@
1
- import {useRef} from "@wordpress/element";
2
-
3
- export function useNonRenderingCounter(initialValue = 0): [{ readonly current: number }, () => number] {
4
- const count = useRef(initialValue);
5
- return [count, () => ++count.current];
6
- }
@@ -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
- }