@plaudit/gutenberg-api-extensions 2.9.0 → 2.10.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 (56) hide show
  1. package/build/blocks/common-native-property-implementations.d.ts +118 -0
  2. package/build/blocks/common-native-property-implementations.js +59 -16
  3. package/build/blocks/common-native-property-implementations.js.map +1 -1
  4. package/build/blocks/index.d.ts +5 -0
  5. package/build/blocks/layered-styles.d.ts +31 -0
  6. package/build/blocks/simple-block.d.ts +22 -0
  7. package/build/blocks/simple-native-property.d.ts +30 -0
  8. package/build/blocks/simple-native-property.js +99 -94
  9. package/build/blocks/simple-native-property.js.map +1 -1
  10. package/build/controls/AsynchronousFormTokenField.d.ts +20 -0
  11. package/build/controls/ExtendedPostPicker.d.ts +13 -0
  12. package/build/controls/InspectorPanel.d.ts +6 -0
  13. package/build/controls/LazySuggestionsComboboxControl.d.ts +7 -0
  14. package/build/controls/PickOne.d.ts +19 -0
  15. package/build/controls/SimpleToggle.d.ts +3 -0
  16. package/build/controls/SortableItemsControl.d.ts +11 -0
  17. package/build/controls/SortableItemsControl.js +142 -31
  18. package/build/controls/SortableItemsControl.js.map +1 -1
  19. package/build/controls/shared.d.ts +8 -0
  20. package/build/controls/types.d.ts +12 -0
  21. package/build/lib/plaudit-icons/column-1.d.ts +2 -0
  22. package/build/lib/plaudit-icons/column-2.d.ts +2 -0
  23. package/build/lib/plaudit-icons/column-3.d.ts +2 -0
  24. package/build/lib/plaudit-icons/placement-center.d.ts +2 -0
  25. package/build/lib/plaudit-icons/placement-end.d.ts +2 -0
  26. package/build/lib/plaudit-icons/placement-start.d.ts +2 -0
  27. package/build/lib/plaudit-icons/placement-stretch.d.ts +2 -0
  28. package/build/lib/plaudit-icons/plaudit-icon.d.ts +2 -0
  29. package/build/lib/plaudit-icons/reusable-block-marker.d.ts +2 -0
  30. package/{src/lib/plaudit-icons.ts → build/lib/plaudit-icons.d.ts} +0 -4
  31. package/package.json +5 -5
  32. package/src/blocks/common-native-property-implementations.tsx +0 -361
  33. package/src/blocks/index.ts +0 -12
  34. package/src/blocks/layered-styles.tsx +0 -108
  35. package/src/blocks/simple-block.tsx +0 -72
  36. package/src/blocks/simple-native-property.tsx +0 -204
  37. package/src/controls/AsynchronousFormTokenField.tsx +0 -158
  38. package/src/controls/ExtendedPostPicker.tsx +0 -50
  39. package/src/controls/InspectorPanel.tsx +0 -16
  40. package/src/controls/LazySuggestionsComboboxControl.tsx +0 -84
  41. package/src/controls/PickOne.tsx +0 -80
  42. package/src/controls/SimpleToggle.tsx +0 -9
  43. package/src/controls/SortableItemsControl.tsx +0 -70
  44. package/src/controls/shared.ts +0 -7
  45. package/src/controls/types.ts +0 -11
  46. package/src/lib/plaudit-icons/column-1.tsx +0 -8
  47. package/src/lib/plaudit-icons/column-2.tsx +0 -8
  48. package/src/lib/plaudit-icons/column-3.tsx +0 -8
  49. package/src/lib/plaudit-icons/placement-center.tsx +0 -5
  50. package/src/lib/plaudit-icons/placement-end.tsx +0 -5
  51. package/src/lib/plaudit-icons/placement-start.tsx +0 -5
  52. package/src/lib/plaudit-icons/placement-stretch.tsx +0 -5
  53. package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -6
  54. package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -5
  55. /package/{src/controls/index.ts → build/controls/index.d.ts} +0 -0
  56. /package/{src/index.ts → build/index.d.ts} +0 -0
@@ -1,204 +0,0 @@
1
- import {createHigherOrderComponent} from "@wordpress/compose";
2
- import {addFilter} from "@wordpress/hooks";
3
-
4
- import type {ActualBlockEditProps} from "./common-native-property-implementations";
5
- import {InspectorPanel} from "../controls";
6
-
7
- import React from "react";
8
-
9
- export type InspectorPanelGroup = 'default'|'advanced'|'background'|'border'|'color'|'dimensions'|'effects'|'filter'|'list'|'position'|'settings'|'styles'|'typography';
10
-
11
- type GenericSimpleNativeProperty<T, V extends 'string'|'number'|'boolean'|'array'|'object'> = {
12
- name: string,
13
- type: V,
14
- enum?: T[],
15
- default?: T,
16
- alwaysStore?: boolean,
17
- renderer(value: T|undefined, onChange: (v: T|undefined) => void): React.JSX.Element,
18
- condition?(blockEditProps: ActualBlockEditProps): boolean
19
- };
20
- export type SimpleNativeProperty = GenericSimpleNativeProperty<string, 'string'>&{enum?: string[]}
21
- |GenericSimpleNativeProperty<number, 'number'>&{enum?: number[]}
22
- |GenericSimpleNativeProperty<boolean, 'boolean'>
23
- |GenericSimpleNativeProperty<any[], 'array'>
24
- |GenericSimpleNativeProperty<Record<string|number, unknown>, 'object'>;
25
-
26
- export type SimpleNativePanel = {
27
- title: string,
28
- group?: InspectorPanelGroup,
29
- initialOpen?: boolean,
30
- properties: SimpleNativeProperty[],
31
- condition?(blockEditProps: ActualBlockEditProps): boolean
32
- };
33
-
34
- export function registerSimpleNativeProperties(config: {
35
- block: `${string}/${string}`|Array<`${string}/${string}`>,
36
- panel: SimpleNativePanel|Array<SimpleNativePanel>
37
- }) {
38
- const simpleNativePanels: Record<string, Array<SimpleNativePanel>|undefined>
39
- = (window as any).plauditSimpleNativePanels ?? ((window as any).plauditSimpleNativePanels = {});
40
-
41
- if (Array.isArray(config.block)) {
42
- for (const b of config.block) {
43
- addPanels(b, config.panel, simpleNativePanels);
44
- }
45
- } else {
46
- addPanels(config.block, config.panel, simpleNativePanels);
47
- }
48
- }
49
- function addPanels(block: string, panel: SimpleNativePanel|Array<SimpleNativePanel>, simpleNativePanels: Record<string, Array<SimpleNativePanel>|undefined>) {
50
- const panels = (simpleNativePanels[block] ?? (simpleNativePanels[block] = []));
51
- if (Array.isArray(panel)) {
52
- panels.push(...panel);
53
- } else {
54
- panels.push(panel);
55
- }
56
- }
57
-
58
- export function installSimpleNativePropertiesSupport() {
59
- if ((window as any).plauditSimpleNativePropertiesSupportInstalled) {
60
- return;
61
- }
62
- (window as any).plauditSimpleNativePropertiesSupportInstalled = true;
63
-
64
- const simpleNativePanels: Record<string, Array<SimpleNativePanel>|undefined>
65
- = (window as any).plauditSimpleNativePanels ?? ((window as any).plauditSimpleNativePanels = {});
66
-
67
- type RegisterBlockAtts = { name: string, attributes: Record<string, any>, usesContext?: string[] };
68
- addFilter('blocks.registerBlockType', 'plaudit/gutenberg-api-extensions/attach-simple-native-properties', (atts: RegisterBlockAtts) => {
69
- const blockSimpleNativePanels = simpleNativePanels[atts.name];
70
- if (blockSimpleNativePanels) {
71
- const injectableProperties: Record<string, any> = {};
72
- for (const blockSimpleNativePanel of blockSimpleNativePanels) {
73
- for (const property of blockSimpleNativePanel.properties) {
74
- const attrPath = property.name.split('.');
75
- if (attrPath.length === 1) {
76
- injectableProperties[property.name] = {type: property.type, default: property.alwaysStore ? undefined : property.default};
77
- if ('enum' in property) {
78
- injectableProperties[property.name].enum = property.enum;
79
- }
80
- } else {
81
- let actualProp = injectableProperties[attrPath[0]];
82
- if (actualProp === undefined) {
83
- injectableProperties[attrPath[0]] = actualProp = {type: "object"};
84
- } else if (actualProp.type !== "object") {
85
- throw new Error(`Property type collision on ${property.name}. Attempted treat the property as an object when it was already a ${actualProp.type}`);
86
- }
87
- if (property.default !== undefined) {
88
- if (actualProp.default === undefined) {
89
- actualProp.default = {};
90
- }
91
- let target = actualProp.default;
92
- for (let i = 1; i < attrPath.length - 1; i++) {
93
- target = target[attrPath[i]] = {};
94
- }
95
- target[attrPath[attrPath.length - 1]] = property.default;
96
- }
97
- }
98
- }
99
- }
100
- for (const [name, config] of Object.entries(injectableProperties)) {
101
- atts.attributes[name] = config;
102
- }
103
- }
104
- return atts;
105
- });
106
-
107
- function setDottedAttribute(blockEditProps: ActualBlockEditProps, attr: string, value: any) {
108
- const attrPath = attr.split('.');
109
- if (attrPath.length === 1) {
110
- return blockEditProps.setAttributes({[attr]: value});
111
- }
112
- const payload = {...blockEditProps.attributes[attrPath[0]]};
113
- let currentLayer: Record<string, any> = payload;
114
- for (let i = 1; i < attrPath.length - 1; i++){
115
- const attrPathNode = attrPath[i];
116
- currentLayer = currentLayer[attrPathNode] = {};
117
- }
118
- currentLayer[attrPath[attrPath.length - 1]] = value;
119
- blockEditProps.setAttributes({[attrPath[0]]: payload});
120
- }
121
-
122
- addFilter('editor.BlockEdit', 'plaudit/gutenberg-api-extensions/simple-native-properties',
123
- createHigherOrderComponent(BlockEdit => (blockEditProps: ActualBlockEditProps) => {
124
- const blockSimpleNativePanels = simpleNativePanels[blockEditProps.name];
125
- if (!blockSimpleNativePanels) {
126
- return <BlockEdit {...blockEditProps} />;
127
- }
128
-
129
- let keyIndex = 0;
130
- return <>
131
- <BlockEdit {...blockEditProps} />
132
- {...blockSimpleNativePanels.map(p => {
133
- const items = p.properties.map(prop => {
134
- const propPath = prop.name.split('.');
135
-
136
- let existingValue;
137
- if (prop.default !== undefined) {
138
- if (propPath.length === 1) {
139
- existingValue = blockEditProps.attributes[prop.name];
140
- if (existingValue === undefined) {
141
- existingValue = prop.default;
142
- blockEditProps.setAttributes({[prop.name]: prop.default});
143
- }
144
- } else {
145
- let graphExistingValue = blockEditProps.attributes[propPath[0]];
146
- if (graphExistingValue === undefined) {
147
- blockEditProps.attributes[propPath[0]] = graphExistingValue = {};
148
- }
149
- for (let i = 1; i < propPath.length; i++) {
150
- if (graphExistingValue[propPath[i]] === undefined) {
151
- for (; i < propPath.length - 1; i++) {
152
- graphExistingValue = graphExistingValue[propPath[i]] = {};
153
- }
154
- graphExistingValue[propPath[propPath.length - 1]] = existingValue = prop.default;
155
- blockEditProps.setAttributes({[propPath[0]]: blockEditProps.attributes[propPath[0]]});
156
- break;
157
- } else {
158
- graphExistingValue = graphExistingValue[propPath[i]];
159
- }
160
- }
161
- existingValue = graphExistingValue ?? prop.default;
162
- }
163
- } else {
164
- existingValue = undefined;
165
- }
166
- let ele: React.JSX.Element;
167
- if (prop.type === "array") {// If the value is not an array or is a sparse array, then it will cause unrecoverable errors upon conversion to PHP
168
- if (existingValue !== undefined && (!Array.isArray(existingValue) || existingValue.length > existingValue.filter(() => true).length)) {
169
- throw new Error(`Invalid value passed to an array-type property: ${existingValue}`);
170
- }
171
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
172
- } else if (prop.type === "object") {
173
- if (existingValue !== undefined && (Array.isArray(existingValue) || typeof existingValue !== 'object')) {
174
- throw new Error(`Invalid value passed to an object-type property: ${existingValue}`);
175
- }
176
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
177
- } else if (prop.type === "boolean") {
178
- if (typeof existingValue !== 'boolean' && existingValue !== undefined) {
179
- existingValue = !!existingValue;
180
- }
181
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
182
- } else if (prop.type === "string") {
183
- if (typeof existingValue !== 'string' && existingValue !== undefined) {
184
- existingValue = existingValue.toString();
185
- }
186
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
187
- } else {
188
- if (typeof existingValue !== 'number' && existingValue !== undefined) {
189
- existingValue = parseFloat(existingValue);
190
- }
191
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
192
- }
193
- return prop.condition === undefined || prop.condition(blockEditProps) ? ele : undefined;
194
- }).filter((ele): ele is React.JSX.Element => ele !== undefined);
195
- return <InspectorPanel
196
- {...p} condition={() => items.length > 0 && (p.condition === undefined || p.condition(blockEditProps))}
197
- key={`plaudit-simple-native-property-${keyIndex++}`}
198
- >
199
- {...items}
200
- </InspectorPanel>;
201
- })}
202
- </>;
203
- }, 'plauditGutenbergApiExtensionsSimpleNativeProperties'));
204
- }
@@ -1,158 +0,0 @@
1
- import {Spinner, FormTokenField} from '@wordpress/components';
2
- import type {TokenItem} from "@wordpress/components/build-types/form-token-field/types";
3
- import {__} from "@wordpress/i18n";
4
- import {useEffect, useState} from '@wordpress/element';
5
- import debounce from "debounce";
6
- import React from "react";
7
-
8
- // The strange values correspond to the literals that are expected by TokenItem.status, which allows the assignment code to be cleaner
9
- export const enum ValidationState {
10
- Valid = 'success',
11
- Invalid = 'error',
12
- Validating = 'validating'
13
- }
14
-
15
- export interface AsynchronousFormTokenFieldProps {
16
- label: string;
17
- help?: string;
18
- value?: string[];
19
- onChange: (value: string[]) => void;
20
-
21
- validationQuery(tokens: string[]): Promise<Array<TokenItem>>;
22
- suggestionQuery(input: string): Promise<Array<string>>;
23
- makeTokenFromSuggestion(suggestion: string): TokenItem;
24
- validValues: Map<string, ValidationState>;
25
- validator?: (value: string) => boolean;
26
-
27
- multiple?: boolean;
28
- }
29
-
30
- export function AsynchronousFormTokenField(props: AsynchronousFormTokenFieldProps) {
31
- const [isInitializing, setIsInitializing] = useState(true);
32
- const [isLoading, setIsLoading] = useState(false);
33
- const [isValidating, setIsValidating] = useState(false);
34
-
35
- const [currentTokens, setCurrentTokens] = useState<Array<TokenItem>>([]);
36
- const [suggestions, setSuggestions] = useState<Array<string>>([]);
37
-
38
- const [myValidationRequestQueue] = useState<{queue: Promise<boolean>, currentRequest: number, trigger: (currentTokens: TokenItem[]) => void}>(() => ({
39
- queue: Promise.resolve(true), currentRequest: 0,
40
- trigger(currentTokens) {
41
- const tokensInNeedOfValidation = currentTokens.filter(token => token.status === ValidationState.Validating);
42
- if (tokensInNeedOfValidation.length > 0) {
43
- setIsValidating(true);
44
- const myNumber = ++myValidationRequestQueue.currentRequest;
45
- myValidationRequestQueue.queue = myValidationRequestQueue.queue.then(async () => {
46
- const tokensBeingValidatedList = tokensInNeedOfValidation.map(token => token.value).filter(value => !props.validValues.has(value));
47
- if (tokensBeingValidatedList.length > 0) {
48
- const tokensBeingValidated = new Set(tokensBeingValidatedList);
49
- for (const value of tokensBeingValidatedList) {
50
- props.validValues.set(value, ValidationState.Validating);
51
- }
52
- const validatedTokens = await props.validationQuery(tokensBeingValidatedList);
53
-
54
- for (const validToken of validatedTokens) {
55
- tokensBeingValidated.delete(validToken.value);
56
- props.validValues.set(validToken.value, ValidationState.Valid);
57
- }
58
- for (const value of tokensBeingValidated) {
59
- if (props.validValues.get(value) === ValidationState.Validating) {
60
- props.validValues.set(value, ValidationState.Invalid);
61
- }
62
- }
63
- }
64
-
65
- if (myNumber === myValidationRequestQueue.currentRequest) {
66
- let changed = false;
67
- for (const token of currentTokens) {
68
- if (token.status === ValidationState.Validating) {
69
- const status = props.validValues.get(token.value);
70
- if (status !== undefined && token.status !== status) {
71
- changed = true;
72
- token.status = status;
73
- }
74
- }
75
- }
76
- if (changed) {
77
- setCurrentTokens(currentTokens);
78
- }
79
- setIsValidating(false);
80
- }
81
- return true;
82
- });
83
- }
84
- }
85
- }));
86
-
87
- const [mySuggestionRequestQueue] = useState<{
88
- queue: Promise<boolean>, currentRequest: number, debouncer: (input: string) => void
89
- }>(() => ({
90
- queue: Promise.resolve(true),
91
- currentRequest: 0,
92
- debouncer: debounce(input => {
93
- if (input.length < 2) {
94
- setSuggestions([]);
95
- } else {
96
- setIsLoading(true);
97
- const myNumber = ++mySuggestionRequestQueue.currentRequest;
98
- mySuggestionRequestQueue.queue = mySuggestionRequestQueue.queue.then(async () => {
99
- if (myNumber === mySuggestionRequestQueue.currentRequest) {
100
- setSuggestions(await props.suggestionQuery(input));
101
- setIsLoading(false);
102
- }
103
- return true;
104
- });
105
- }
106
- }, 500)
107
- }));
108
-
109
- useEffect(() => {
110
- props.validationQuery(props.value ?? []).then(data => {
111
- const tokenLabels = new Map<string, string|undefined>();
112
- for (const rep of data) {
113
- tokenLabels.set(rep.value, rep.title);
114
- props.validValues.set(rep.value, ValidationState.Valid);
115
- }
116
-
117
- if (props.value) {
118
- for (const value of props.value) {
119
- if (!props.validValues.has(value)) {
120
- props.validValues.set(value, ValidationState.Invalid);
121
- }
122
- }
123
- }
124
- setCurrentTokens(props.value?.map(value => ({value, title: tokenLabels.get(value) ?? value, status: props.validValues.get(value)})) ?? []);
125
- setIsInitializing(false);
126
- });
127
- }, []);
128
-
129
- useEffect(() => myValidationRequestQueue.trigger(currentTokens), [currentTokens]);
130
-
131
- if (isInitializing) {
132
- return <Spinner />;
133
- }
134
-
135
- const tokenTitleMappings = new Map(currentTokens.map(currentToken => [currentToken.value, currentToken.title]));
136
-
137
- return <>
138
- <FormTokenField
139
- value={currentTokens}
140
- label={props.label}
141
- placeholder="Start typing to see suggestions"
142
- suggestions={suggestions}
143
- onChange={tokens => {
144
- const tokenItems = tokens.map(token => typeof token === 'string' ? props.makeTokenFromSuggestion(token) : token);
145
-
146
- props.onChange(tokenItems.map(token => token.value));
147
- setCurrentTokens(tokenItems);
148
- }}
149
- __experimentalValidateInput={props.validator}
150
- __experimentalAutoSelectFirstMatch={true}
151
- displayTransform={token => tokenTitleMappings.get(token) ?? token}
152
- onInputChange={mySuggestionRequestQueue.debouncer}
153
- />
154
- {props.help && <div><span className="components-form-token-field__help">{props.help}</span></div>}
155
- {isLoading && <div><Spinner /><span className="components-form-token-field__help">{__("Updating Suggestions")}</span></div>}
156
- {isValidating && <div><Spinner /><span className="components-form-token-field__help">{__("Validating")}</span></div>}
157
- </>;
158
- }
@@ -1,50 +0,0 @@
1
- import {AsynchronousFormTokenField, ValidationState} from "./AsynchronousFormTokenField";
2
- import {useState} from "@wordpress/element";
3
-
4
- import {requestPostsFromAPI} from "./shared";
5
-
6
- import React from "react";
7
-
8
- type ExtendedPostPickerProps = {
9
- onChange: (value: string[]) => void;
10
- label: string;
11
- help?: string;
12
- postTypes: string[];
13
- placeholder: string;
14
- value?: string[];
15
- multiple?: boolean;
16
- };
17
-
18
- export type ExtendedPostPickerConstructorProps = Partial<Omit<ExtendedPostPickerProps, 'onChange'|'value'|'label'>> & Pick<ExtendedPostPickerProps, 'onChange'|'value'|'label'>;
19
-
20
- export function ExtendedPostPicker(props: ExtendedPostPickerConstructorProps) {
21
- const [validPostIds, _] = useState(new Map<string, ValidationState>());
22
- const value = props.value ?? [];
23
- return <AsynchronousFormTokenField
24
- {...props}
25
- value={value}
26
- makeTokenFromSuggestion={token => {
27
- if (isNumeric(token)) {
28
- return {value: token, status: validPostIds.get(token) ?? ValidationState.Validating, title: token};
29
- } else {
30
- const tokenId = /\(#([0-9]+)\)$/.exec(token)?.[1];
31
- if (tokenId) {
32
- return {value: tokenId, status: validPostIds.get(tokenId) ?? ValidationState.Validating, title: token};
33
- }
34
- return {value: token, status: ValidationState.Invalid, title: token};
35
- }
36
- }}
37
- suggestionQuery={input => requestPostsFromAPI({search: input, postTypes: props.postTypes?.join(',')})
38
- .then(posts => posts.map(post => `${post.title} (#${post.id})`))}
39
- validationQuery={idsBeingValidated => requestPostsFromAPI({ids: idsBeingValidated.join(','), postTypes: props.postTypes?.join(',')})
40
- .then(posts => posts.map(post => ({value: post.id, title: post.title, status: ValidationState.Valid})))}
41
- validValues={validPostIds}
42
- validator={token => /\(#([0-9]+)\)$/.exec(token)?.[1] !== undefined}
43
- />;
44
- }
45
-
46
- function isNumeric(str: string|number) {
47
- if (typeof str !== "string") return true; // we only process strings!
48
- return !isNaN(str as any) && // use type coercion to parse the _entirety_ of the string (`parseFloat` alone does not do this)...
49
- !isNaN(parseFloat(str)) // ...and ensure strings of whitespace fail
50
- }
@@ -1,16 +0,0 @@
1
- import type {PanelBodyProps} from "@wordpress/components/build-types/panel/types";
2
- import {InspectorControls} from "@wordpress/block-editor";
3
- import {PanelBody} from "@wordpress/components";
4
-
5
- import React from "react";
6
-
7
- type InspectorControlsType = {
8
- // WordPress' types don't declare the group parameter, which is what we use to put the InspectorPanel in the correct spot
9
- (props: InspectorControls.Props & { group?: string }): ReturnType<typeof InspectorControls>;
10
- } & (typeof InspectorControls);
11
- export function InspectorPanel(props: PanelBodyProps&{group?: string, condition?(): boolean}) {
12
- const FullyTypedInspectorControls = InspectorControls as InspectorControlsType;
13
- return <FullyTypedInspectorControls group={props.group}>
14
- <PanelBody {...props} children={props.condition === undefined || props.condition() ? props.children : []} />
15
- </FullyTypedInspectorControls>;
16
- }
@@ -1,84 +0,0 @@
1
- import {BaseControl, ComboboxControl, Spinner} from "@wordpress/components";
2
- import type {ComboboxControlOption, ComboboxControlProps} from "@wordpress/components/build-types/combobox-control/types";
3
- import {useEffect, useState} from "@wordpress/element";
4
- import {__} from "@wordpress/i18n";
5
-
6
- import debounce from "debounce";
7
-
8
- import React from "react";
9
-
10
- export type LazySuggestionsComboboxControlProps = Omit<ComboboxControlProps, 'options'> & {
11
- getOption(value?: string): Promise<ComboboxControlOption|undefined>;
12
- getSuggestions(filterValue: string): Promise<ComboboxControlOption[]>;
13
- };
14
- export function LazySuggestionsComboboxControl(props: LazySuggestionsComboboxControlProps) {
15
- const [isInitializing, setIsInitializing] = useState(true);
16
- const [isLoading, setIsLoading] = useState(false);
17
-
18
- const [suggestions, setSuggestions] = useState<ComboboxControlOption[]>([]);
19
-
20
- const [mySuggestionRequestQueue] = useState<{
21
- queue: Promise<boolean>, currentRequest: number, debouncer: (input: string) => void
22
- }>(() => ({
23
- queue: Promise.resolve(true),
24
- currentRequest: 0,
25
- debouncer: debounce((input: string) => {
26
- setIsLoading(true);
27
- const myNumber = ++mySuggestionRequestQueue.currentRequest;
28
- mySuggestionRequestQueue.queue = mySuggestionRequestQueue.queue.then(async () => {
29
- let suggestions: ComboboxControlOption[];
30
- if (input.length < 2) {
31
- if (props.value) {
32
- const propValue = await props.getOption(props.value);
33
- if (propValue) {
34
- suggestions = [propValue];
35
- } else {
36
- suggestions = [];
37
- }
38
- } else {
39
- suggestions = [];
40
- }
41
- } else {
42
- suggestions = await props.getSuggestions(input);
43
- }
44
- if (myNumber === mySuggestionRequestQueue.currentRequest) {
45
- if (props.onFilterValueChange) {
46
- props.onFilterValueChange(input);
47
- }
48
- setSuggestions(suggestions);
49
- setIsLoading(false);
50
- }
51
- return true;
52
- });
53
- }, 500)
54
- }));
55
-
56
- useEffect(() => {
57
- if (props.value) {
58
- props.getOption(props.value).then(option => {
59
- setSuggestions(option ? [option] : []);
60
- setIsInitializing(false);
61
- }, () => {
62
- //TODO: Add a notice for the error
63
- });
64
- } else {
65
- setIsInitializing(false);
66
- }
67
- }, []);
68
-
69
- if (isInitializing) {
70
- return <BaseControl {...props}>
71
- <Spinner /><span>{__(`Initializing ${props.label}`)}</span>
72
- </BaseControl>
73
- }
74
-
75
- return <>
76
- <ComboboxControl
77
- {...props}
78
- options={suggestions}
79
- onFilterValueChange={mySuggestionRequestQueue.debouncer}
80
- allowReset={props.allowReset !== false}
81
- />
82
- {isLoading && <div><Spinner /><span className="components-base-control__help">{__("Updating Suggestions")}</span></div>}
83
- </>;
84
- }
@@ -1,80 +0,0 @@
1
- import {
2
- __experimentalToggleGroupControl as ToggleGroupControl,
3
- __experimentalToggleGroupControlOption as ToggleGroupControlOption,
4
- __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon, BaseControl,
5
- ColorPalette, RadioControl, SelectControl, useBaseControlProps
6
- } from "@wordpress/components";
7
- import type {ColorObject} from "@wordpress/components/build-types/color-palette/types";
8
-
9
- import type {PickableOptions, SimpleBlockControlProps} from "./types";
10
-
11
- import React, {type ReactElement} from "react";
12
-
13
- function makeSharedRadioAndSelectProps<T extends string>(props: SimpleBlockControlProps<T, string>&{options: PickableOptions<string>}) {
14
- return {
15
- label: props.label,
16
- onChange(value: string) {
17
- props.setAttributes({[props.attribute]: value});
18
- },
19
- options: props.options.map(([value, label]) => ({
20
- value, label: typeof label === 'string' ? label : label.text
21
- }))
22
- } as Pick<Parameters<(typeof SelectControl)|(typeof RadioControl)>[0], 'label'|'options'>&{onChange(value: string): void};
23
- }
24
-
25
- export type PickOneFromToggleGroupProps<T extends string> = SimpleBlockControlProps<T, string|number>&{options: PickableOptions<string, {icon: ReactElement}>};
26
- export function PickOneFromToggleGroup<T extends string>(props: PickOneFromToggleGroupProps<T>) {
27
- return <ToggleGroupControl
28
- label={props.label}
29
- value={props.attributes[props.attribute]}
30
- onChange={value => props.setAttributes({[props.attribute]: value})}
31
- children={
32
- props.options.map(([value, label]) => typeof label === 'string'
33
- ? <ToggleGroupControlOption key={value} value={value} label={label} />
34
- : <ToggleGroupControlOptionIcon key={value} value={value} label={label.text} icon={label.icon} />)
35
- }
36
- />;
37
- }
38
-
39
- export function PickOneFromSelect<T extends string>(props: SimpleBlockControlProps<T, string>& { options: PickableOptions<string> }) {
40
- return <SelectControl
41
- {...makeSharedRadioAndSelectProps(props)}
42
- value={props.attributes[props.attribute]}
43
- />;
44
- }
45
- export function PickOneFromRadios<T extends string>(props: SimpleBlockControlProps<T, string>& { options: PickableOptions<string> }) {
46
- return <RadioControl
47
- {...makeSharedRadioAndSelectProps(props)}
48
- selected={props.attributes[props.attribute]}
49
- />;
50
- }
51
-
52
- export function PickOneFromColors<T extends string>(props: SimpleBlockControlProps<T, string>&{options: PickableOptions<string, {color?: string|undefined}>}) {
53
- const valueToColorMap = new Map<string, string>();
54
- const colorToValueMap = new Map<string, string>();
55
- const colors: ColorObject[] = [];
56
- for (const [value, display] of props.options) {
57
- if (typeof display === 'string') {
58
- colors.push({color: value, name: display});
59
- } else if (display.color) {
60
- valueToColorMap.set(value, display.color);
61
- colorToValueMap.set(display.color, value);
62
- colors.push({color: display.color, name: display.text});
63
- } else {
64
- colors.push({color: value, name: display.text});
65
- }
66
- }
67
-
68
- const currentColor = valueToColorMap.get(props.attributes[props.attribute]) ?? props.attributes[props.attribute];
69
- const {baseControlProps, controlProps} = useBaseControlProps({label: props.label});
70
- return <BaseControl {...baseControlProps}>
71
- <ColorPalette
72
- {...controlProps}
73
- disableCustomColors={true}
74
- onChange={color => props.setAttributes({[props.attribute]: colorToValueMap.get(color ?? currentColor) ?? color ?? currentColor})}
75
- colors={colors}
76
- value={currentColor}
77
- clearable={false}
78
- />
79
- </BaseControl>;
80
- }
@@ -1,9 +0,0 @@
1
- import {ToggleControl} from "@wordpress/components";
2
-
3
- import type {SimpleBlockControlProps} from "./types";
4
-
5
- import React from "react";
6
-
7
- export function SimpleToggle<T extends string>(props: SimpleBlockControlProps<T, boolean>) {
8
- return <ToggleControl checked={props.attributes[props.attribute]} label={props.label} onChange={checked => props.setAttributes({[props.attribute]: checked})} />;
9
- }
@@ -1,70 +0,0 @@
1
- import {useEffect, useRef, useState} from "@wordpress/element";
2
-
3
- import React, {type PropsWithChildren, type ReactNode} from "react";
4
- import {Button} from "@wordpress/components";
5
-
6
- export type SortableItemsControlProps<D> = {
7
- value: D[],
8
- onChange: (value: D[]) => void,
9
- childProducer: (datum: D, props: { onChange: (datum: D) => void, index: number }) => ReactNode,
10
- emptyValue: D
11
- };
12
-
13
- export function SortableItemsControl<D>(props: SortableItemsControlProps<D>) {
14
- const value = [...props.value];
15
- const containerRef = useRef<HTMLDivElement|null>(null);
16
- useEffect(() => {
17
-
18
- }, [containerRef]);
19
- const [dirtiness, setDirtiness] = useState(0);
20
- const makeDirty = () => setDirtiness(dirtiness + 1);
21
-
22
- const onChangeByIndex = (datum: D, index: number) => {
23
- value[index] = datum;
24
- props.onChange(value);
25
- makeDirty();
26
- };
27
- const moveByIndex = (index: number, direction: -1|1) => {
28
- const moved = value.splice(index, 1);
29
- value.splice(index + direction, 0, ...moved);
30
- makeDirty();
31
- };
32
- const remove = (index: number) => {
33
- value.splice(index, 1);
34
- props.onChange(value);
35
- makeDirty();
36
- }
37
- return <div>
38
- <div ref={containerRef}>
39
- {...props.value.map((datum, index) => <SortableItem index={index} children={makeSortableItemChildren(props, datum, index, onChangeByIndex, remove)}/>)}
40
- </div>
41
- <div>
42
- <Button onClick={() => {
43
- value.push(props.emptyValue);
44
- makeDirty();
45
- }}>
46
- Add Row
47
- </Button>
48
- </div>
49
- </div>;
50
- }
51
-
52
- function makeSortableItemChildren<D>(
53
- props: SortableItemsControlProps<D>, datum: D, index: number, onChangeByIndex: (datum: D, index: number) => void, remove: (index: number) => void
54
- ) {
55
- return props.childProducer(datum, {index, onChange: datum => onChangeByIndex(datum, index)});
56
- }
57
-
58
- function SortableItem(props: PropsWithChildren<{ index: number }>) {
59
- return <div data-index={props.index}>
60
- <div className="plaudit-sortable-items-drag-handler">
61
- //TODO: Make this look pretty
62
- </div>
63
- <div>
64
- {props.children}
65
- </div>
66
- <div>
67
- //TODO: Add a removal button
68
- </div>
69
- </div>
70
- }
@@ -1,7 +0,0 @@
1
- import apiFetch from "@wordpress/api-fetch";
2
- import {addQueryArgs} from "@wordpress/url";
3
-
4
- export async function requestPostsFromAPI(data: {search?: string, ids?: string, postTypes?: string|undefined}) {
5
- return (await apiFetch<Array<{ id: number, title: string }>>({path: addQueryArgs("/plaudit/common/v1/post-table-search", data)}))
6
- .map(item => ({id: item.id.toString(), title: item.title}));
7
- }