@plaudit/gutenberg-api-extensions 2.94.2 → 2.96.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 (103) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/lib/useful-types.d.ts +1 -4
  3. package/package.json +4 -4
  4. package/src/blocks/MoveError.ts +0 -7
  5. package/src/blocks/PathError.ts +0 -18
  6. package/src/blocks/SNPFlexibleItemsListComponent.tsx +0 -30
  7. package/src/blocks/SNPGroupComponent.tsx +0 -38
  8. package/src/blocks/SNPListComponent.tsx +0 -25
  9. package/src/blocks/SNPTreeContext.tsx +0 -13
  10. package/src/blocks/basic-custom-block-bindings-support.tsx +0 -248
  11. package/src/blocks/common-native-property-constructors.tsx +0 -927
  12. package/src/blocks/conditions.ts +0 -261
  13. package/src/blocks/csnp-api.ts +0 -221
  14. package/src/blocks/data-controller/actions.ts +0 -20
  15. package/src/blocks/data-controller/reducer.ts +0 -146
  16. package/src/blocks/data-controller/trigger-handlers.ts +0 -150
  17. package/src/blocks/data-controller/utils.ts +0 -415
  18. package/src/blocks/data-controller-manager.ts +0 -50
  19. package/src/blocks/data-controller.ts +0 -165
  20. package/src/blocks/hooks/built-in-suspendable-option-protocols/select.ts +0 -51
  21. package/src/blocks/hooks/built-in-suspendable-option-protocols/settings.ts +0 -70
  22. package/src/blocks/hooks/useSuspendableOptions.ts +0 -122
  23. package/src/blocks/index.ts +0 -23
  24. package/src/blocks/layered-styles-api.ts +0 -142
  25. package/src/blocks/layered-styles-impl.ts +0 -95
  26. package/src/blocks/layout/LaidOutProperty.tsx +0 -72
  27. package/src/blocks/layout/LaidOutPropertyRow.tsx +0 -28
  28. package/src/blocks/layout/NodeContext.tsx +0 -54
  29. package/src/blocks/layout/PanelRoot.tsx +0 -30
  30. package/src/blocks/layout/TabsRoot.tsx +0 -56
  31. package/src/blocks/layout/ToolsPanelContext.tsx +0 -22
  32. package/src/blocks/problematic-blocks-blocker.ts +0 -24
  33. package/src/blocks/problematic-variations-blocker.ts +0 -32
  34. package/src/blocks/shared-exportable-types.ts +0 -6
  35. package/src/blocks/shared-internal-types.ts +0 -18
  36. package/src/blocks/simple-block.tsx +0 -74
  37. package/src/blocks/simple-native-property-api.ts +0 -173
  38. package/src/blocks/simple-native-property-impl.tsx +0 -335
  39. package/src/blocks/simple-native-property-internal-shared.ts +0 -19
  40. package/src/blocks/snp-api.ts +0 -5
  41. package/src/blocks/snp-data-store.ts +0 -72
  42. package/src/blocks/utilities.ts +0 -66
  43. package/src/controls/AsynchronousFormTokenField.tsx +0 -86
  44. package/src/controls/BaseSortableItemsControl.tsx +0 -84
  45. package/src/controls/ExtendedFormTokenField.tsx +0 -144
  46. package/src/controls/ExtendedPostPicker.ts +0 -57
  47. package/src/controls/ExtendedRadioControl.tsx +0 -107
  48. package/src/controls/ExtendedTaxonomyPicker.tsx +0 -100
  49. package/src/controls/ExtendedTermPicker.tsx +0 -61
  50. package/src/controls/ExtendedTextareaControl.tsx +0 -65
  51. package/src/controls/ExtendedUserPicker.ts +0 -56
  52. package/src/controls/FileControl.tsx +0 -48
  53. package/src/controls/FullSizeToggleControl.tsx +0 -95
  54. package/src/controls/ImageControl.tsx +0 -143
  55. package/src/controls/InspectorPanel.tsx +0 -37
  56. package/src/controls/LazySuggestionsComboboxControl.tsx +0 -64
  57. package/src/controls/MultiSelectControl.tsx +0 -59
  58. package/src/controls/PickOne.tsx +0 -88
  59. package/src/controls/PromisableComponent.tsx +0 -56
  60. package/src/controls/ProperLinkControl.tsx +0 -98
  61. package/src/controls/SimpleToggle.tsx +0 -9
  62. package/src/controls/SortableFlexibleItemsControl.tsx +0 -37
  63. package/src/controls/SortableItemsControl.tsx +0 -22
  64. package/src/controls/basicNumericallyIdedItemPicker.tsx +0 -75
  65. package/src/controls/hooks/useImprovedTokenManager.ts +0 -163
  66. package/src/controls/hooks/useMultiSingleConversionLayer.ts +0 -17
  67. package/src/controls/hooks/useNonRenderingCounter.ts +0 -6
  68. package/src/controls/hooks/useOutputMemoizingFilter.ts +0 -16
  69. package/src/controls/hooks/useSortableItemsModel.ts +0 -196
  70. package/src/controls/hooks/useSuggestions.ts +0 -91
  71. package/src/controls/hooks/useTokenManager.ts +0 -177
  72. package/src/controls/index.ts +0 -24
  73. package/src/controls/shared.ts +0 -50
  74. package/src/controls/types.ts +0 -18
  75. package/src/editor/insert-sibling-or-child-block-shortcut.tsx +0 -60
  76. package/src/editor/install-insert-sole-allowed-block-shortcut-support.tsx +0 -51
  77. package/src/editor/simple-gutenberg-endpoints-api.ts +0 -31
  78. package/src/editor/simple-gutenberg-endpoints-impl.ts +0 -126
  79. package/src/index.ts +0 -30
  80. package/src/lib/compat-types.ts +0 -21
  81. package/src/lib/gutenberg-api-extensions-state/custom-block-bindings-support-logic.ts +0 -35
  82. package/src/lib/gutenberg-api-extensions-state/general-logic.ts +0 -41
  83. package/src/lib/gutenberg-api-extensions-state/layered-block-styles-logic.ts +0 -43
  84. package/src/lib/gutenberg-api-extensions-state/snp-logic.ts +0 -240
  85. package/src/lib/gutenberg-api-extensions-state.ts +0 -69
  86. package/src/lib/helpers.ts +0 -115
  87. package/src/lib/modified-fast-deep-equals.ts +0 -91
  88. package/src/lib/plaudit-icons/column-1.tsx +0 -6
  89. package/src/lib/plaudit-icons/column-2.tsx +0 -6
  90. package/src/lib/plaudit-icons/column-3.tsx +0 -6
  91. package/src/lib/plaudit-icons/placement-center.tsx +0 -3
  92. package/src/lib/plaudit-icons/placement-end.tsx +0 -3
  93. package/src/lib/plaudit-icons/placement-start.tsx +0 -3
  94. package/src/lib/plaudit-icons/placement-stretch.tsx +0 -3
  95. package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -4
  96. package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -3
  97. package/src/lib/plaudit-icons.ts +0 -13
  98. package/src/lib/sectioned-cache-store.ts +0 -120
  99. package/src/lib/suspense/promise-handlers.ts +0 -72
  100. package/src/lib/suspense.tsx +0 -18
  101. package/src/lib/useful-types.ts +0 -82
  102. package/src/schemas/README.md +0 -1
  103. package/src/schemas/plaudit-block-schema.json +0 -818
@@ -1,95 +0,0 @@
1
- import {
2
- __experimentalToggleGroupControl as ToggleGroupControl, __experimentalToggleGroupControlOption as ToggleGroupControlOption,
3
- __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon
4
- } from "@wordpress/components";
5
- import {memo, useCallback, useMemo} from "@wordpress/element";
6
- import {__} from "@wordpress/i18n";
7
- import * as icons from "@wordpress/icons";
8
-
9
- import type {IconName, OptionalLabel} from "../blocks";
10
- import type {TogglePropertyCSNPConfig} from "../blocks/csnp-api";
11
- import type {CSNPControlComponentProps, NormalSwitch, PlauditIconName, WordPressIconName} from "../blocks/shared-internal-types";
12
- import * as plauditIcons from "../lib/plaudit-icons";
13
-
14
- import parse from "html-react-parser";
15
-
16
- import type {ComponentPropsWithoutRef, ReactElement} from "react";
17
-
18
- type ToggleGroupControlProps = ComponentPropsWithoutRef<typeof ToggleGroupControl>;
19
- export type FullSizeToggleControlProps = Omit<CSNPControlComponentProps<TogglePropertyCSNPConfig, boolean, {switchCfg: NormalSwitch}>, 'config'>&{
20
- componentConfig?: Partial<ToggleGroupControlProps>,
21
- label: ToggleGroupControlProps['label'],
22
- help?: ToggleGroupControlProps['help']
23
- };
24
- export function FullSizeToggleControl({componentConfig, label, help, onChange, switchCfg, value}: FullSizeToggleControlProps) {
25
- const [switchLabels, style] = useMemo(() => {
26
- let switchLabels: {onLabel: NonNullable<OptionalLabel>, offLabel: NonNullable<OptionalLabel>};
27
- if (typeof switchCfg === 'string') {
28
- switchCfg = {type: switchCfg};
29
- }
30
- switch (switchCfg.type) {
31
- case "showHide":
32
- switchLabels = {onLabel: "Show", offLabel: "Hide"};
33
- break;
34
- case "yesNo":
35
- switchLabels = {onLabel: "Yes", offLabel: "No"};
36
- break;
37
- default:
38
- if (switchCfg.type !== undefined) {
39
- console.error("Invalid switch type:", switchCfg.type);
40
- }
41
- switchLabels = {onLabel: "On", offLabel: "Off"};
42
- break;
43
- }
44
- if (switchCfg.onLabel) {
45
- switchLabels.onLabel = switchCfg.onLabel;
46
- }
47
- if (switchCfg.offLabel) {
48
- switchLabels.offLabel = switchCfg.offLabel;
49
- }
50
- return [{onLabel: i18nSwitchLabel(switchLabels.onLabel), offLabel: i18nSwitchLabel(switchLabels.offLabel)}, {width: switchCfg.narrow ? "min-content" : undefined}];
51
- }, [switchCfg]);
52
-
53
- const safeOnChange = useCallback((v: string|number|undefined) => onChange(v === "true"), [onChange]);
54
-
55
- return <ToggleGroupControl
56
- {...componentConfig}
57
- value={value ? "true" : "false"} label={label} help={help} isBlock
58
- onChange={safeOnChange} style={style} __next40pxDefaultSize __nextHasNoMarginBottom
59
- >
60
- <ToggleGroupControlOptionWithOptionalIcon key="true" value="true" label={switchLabels.onLabel} />
61
- <ToggleGroupControlOptionWithOptionalIcon key="false" value="false" label={switchLabels.offLabel} />
62
- </ToggleGroupControl>;
63
- }
64
-
65
- export const ToggleGroupControlOptionWithOptionalIcon
66
- = memo(function<V extends string|number>({value, label}: {value: V, label: NonNullable<OptionalLabel>}) {
67
- if (typeof label === 'string') {
68
- return <ToggleGroupControlOption value={value} label={label} />;
69
- } else {
70
- const icon = resolveIcon(label.icon);
71
- if (icon) {
72
- return <ToggleGroupControlOptionIcon value={value} label={label.text} icon={icon} aria-label={label.tooltip} />;
73
- } else {
74
- return <ToggleGroupControlOption value={value} label={label.text} aria-label={label.tooltip} showTooltip={!!label.tooltip} />;
75
- }
76
- }
77
- });
78
-
79
- function i18nSwitchLabel(label: NonNullable<OptionalLabel>): typeof label {
80
- // eslint-disable-next-line @wordpress/i18n-no-variables -- There's nothing we can do about this - it's a requirement for being able to pass arbitrary labels
81
- return typeof label === 'string' ? __(label) : {...label, text: __(label.text)};
82
- }
83
-
84
- function resolveIcon(icon: ReactElement|IconName|undefined): ReactElement|undefined {
85
- if (typeof icon !== 'string') {
86
- return icon;
87
- }
88
- if (icon.startsWith("plaudit:")) {
89
- return plauditIcons[icon.substring(8) as PlauditIconName];
90
- } else if (icon.startsWith("<svg")) {
91
- return parse(icon) as ReactElement;
92
- } else {
93
- return icons[icon as WordPressIconName];
94
- }
95
- }
@@ -1,143 +0,0 @@
1
- import {MediaUpload, MediaUploadCheck} from "@wordpress/block-editor";
2
- import {BaseControl, Button, FocalPointPicker, ResponsiveWrapper, useBaseControlProps} from "@wordpress/components";
3
- import {type Attachment, type Context, store as coreStore} from "@wordpress/core-data";
4
- import {useSelect} from "@wordpress/data";
5
- import {useCallback, useMemo} from "@wordpress/element";
6
- import {__} from "@wordpress/i18n";
7
-
8
- import type {ProvidedSlot} from "../blocks";
9
-
10
- import type {ComponentPropsWithoutRef, ReactNode} from "react";
11
-
12
- export type ImageData = {media?: {id?: number, url?: string}, pos?: {x?: number, y?: number}};
13
-
14
- type FocalPoint = ComponentPropsWithoutRef<typeof FocalPointPicker>['value'];
15
-
16
- export type ImageControlProps = {
17
- includeFocalPointPicker?: boolean,
18
- label: string,
19
- help?: ReactNode,
20
- onChange: (value?: Record<string|number, unknown>) => void,
21
- value?: ImageData,
22
- Label?: ProvidedSlot, Messages?: ProvidedSlot,
23
- storage?: 'default'|'slim'
24
- };
25
- export function ImageControl(props: ImageControlProps) {
26
- const {label, help, value, Label, Messages} = props;
27
-
28
- const media = useSelect(select => value?.media?.id ? select(coreStore).getMedia(value.media.id) : undefined, [value]);
29
- const {baseControlProps, controlProps} = useBaseControlProps({label: Label ? <Label /> : label, help});
30
- return <BaseControl {...baseControlProps} __nextHasNoMarginBottom>
31
- <div {...controlProps} className="editor-post-featured-image">
32
- {media
33
- ? <ImageControlWithUploadedImage {...props} media={media} />
34
- : <ImageControlWithoutUploadedImage {...props} media={media} />}
35
- </div>
36
- {Messages && <Messages />}
37
- </BaseControl>;
38
- }
39
-
40
- export const RETAINED_FIELDS = ["id", "filename", "url", "date", "mime", "height", "width", "orientation"] as const;
41
- export const MINIMAL_STORAGE_FIELDS = ['id', 'url'];
42
- type HypotheticalImageDataType<C extends Context> = Partial<Attachment<C>>&ImageData['media']&{[k in typeof RETAINED_FIELDS[number]]?: k extends keyof Attachment<C> ? Attachment<C>[k] : unknown};
43
- export function filterImageValueForSaving<C extends Context = 'edit'>(attachment: HypotheticalImageDataType<C>, storage: 'default'|'slim'): Pick<HypotheticalImageDataType<C>, typeof RETAINED_FIELDS[number]> {
44
- if (storage === 'slim') {
45
- return Object.fromEntries(Object.entries(attachment).filter((entry): entry is [typeof MINIMAL_STORAGE_FIELDS[number], any] => MINIMAL_STORAGE_FIELDS.includes(entry[0] as any)));
46
- }
47
- return Object.fromEntries(Object.entries(attachment).filter((entry): entry is [typeof RETAINED_FIELDS[number], any] => RETAINED_FIELDS.includes(entry[0] as any)));
48
- }
49
-
50
- const ALLOWED_TYPES = ['image'];
51
- function ImageControlWithUploadedImage(props: ImageControlProps&{media: Attachment}) {
52
- const {includeFocalPointPicker, media, storage, onChange, value} = props;
53
- const onFPPChange = useCallback((pos: FocalPoint) => onChange(mergeInUpdatedValuePart(value, 'pos', {x: pos.x * 100, y: pos.y * 100}, storage)), [onChange, value]);
54
- const onSelect = useCallback((media: {id: number}&{[k: string]: any}) => onChange(mergeInUpdatedValuePart(value, 'media', media, storage)), [onChange, value]);
55
- const renderOpenButton = useCallback(({open}: {open(): void}) => <Button onClick={open} variant="secondary" __next40pxDefaultSize>{__('Replace image', 'plaudit')}</Button>, []);
56
- const clear = useCallback(() => onChange(undefined), [onChange]);
57
-
58
- const fppValue = useMemo(() => ({
59
- x: (value?.pos?.x ?? 50) / 100,
60
- y: (value?.pos?.y ?? 50) / 100
61
- }), [value?.pos?.x, value?.pos?.y]);
62
-
63
- let fppOrMedia;
64
- if (includeFocalPointPicker !== false) {
65
- fppOrMedia = <FocalPointPicker
66
- onChange={onFPPChange}
67
- url={value?.media?.url ?? ''}
68
- value={fppValue}
69
- __nextHasNoMarginBottom
70
- />;
71
- } else {
72
- fppOrMedia = <ResponsiveWrapper
73
- naturalWidth={parsePossiblyUndefinedFloat(media.media_details["width"])}
74
- naturalHeight={parsePossiblyUndefinedFloat(media.media_details["height"])}
75
- >
76
- <img src={value?.media?.url ?? ''} alt="The currently-selected image." />
77
- </ResponsiveWrapper>
78
- }
79
- return <>
80
- {value?.media?.url && fppOrMedia}
81
- <MediaUploadCheck>
82
- <MediaUpload
83
- title={__('Replace image', 'plaudit')}
84
- value={value?.media?.id ?? 0}
85
- onSelect={onSelect}
86
- allowedTypes={ALLOWED_TYPES}
87
- render={renderOpenButton}
88
- />
89
- <Button onClick={clear} isDestructive __next40pxDefaultSize>{__('Remove image', 'plaudit')}</Button>
90
- </MediaUploadCheck>
91
- </>;
92
- }
93
- function ImageControlWithoutUploadedImage(props: ImageControlProps&{media: Attachment|undefined}) {
94
- const {media, onChange, storage, value} = props;
95
- const onSelect = useCallback((media: {id: number}&{[k: string]: any}) => onChange(mergeInUpdatedValuePart(value, 'media', media, storage)), [onChange, value]);
96
- const renderOpenButton = useCallback(({open}: {open(): void}) => (
97
- <Button
98
- className={value?.media?.id ? 'editor-post-featured-image__preview' : 'editor-post-featured-image__toggle'} onClick={open} __next40pxDefaultSize
99
- >
100
- {!value?.media?.id && __('Choose an image', 'plaudit')}
101
- {media &&
102
- <ResponsiveWrapper
103
- naturalWidth={parsePossiblyUndefinedFloat(media.media_details["width"])}
104
- naturalHeight={parsePossiblyUndefinedFloat(media.media_details["height"])}
105
- >
106
- <img src={media.source_url} alt="The currently-selected image." />
107
- </ResponsiveWrapper>
108
- }
109
- </Button>
110
- ), [media, value?.media?.id]);
111
- return <MediaUploadCheck>
112
- <MediaUpload
113
- onSelect={onSelect}
114
- value={value?.media?.id ?? 0}
115
- allowedTypes={ALLOWED_TYPES}
116
- render={renderOpenButton}
117
- />
118
- </MediaUploadCheck>;
119
- }
120
-
121
- function parsePossiblyUndefinedFloat(string: string|number|undefined): number|undefined {
122
- return typeof string === 'number' ? string : (string !== undefined ? parseFloat(string) : undefined);
123
- }
124
-
125
- function mergeInUpdatedValuePart(value: ImageData|undefined, partName: 'media'|'pos', part: Required<ImageData>[typeof partName]|undefined, storage: 'default'|'slim'|undefined): ImageData|undefined {
126
- if (part === undefined) {
127
- return undefined;
128
- }
129
- if (partName === 'media') {
130
- const prt = (part as Required<ImageData>[typeof partName]);
131
- if (prt.id === 0) {
132
- return undefined;
133
- } else if (value === undefined) {
134
- return {media: filterImageValueForSaving(prt, storage ?? 'default'), pos: {x: 50, y: 50}};
135
- }
136
- return {media: filterImageValueForSaving(prt, storage ?? 'default'), pos: {x: value.pos?.x ?? 50, y: value.pos?.y ?? 50}};
137
- } else if (value === undefined || !value.media?.id) {
138
- return undefined;
139
- } else {
140
- const prt = (part as Required<ImageData>[typeof partName]);
141
- return {media: value.media, pos: {x: prt.x ?? value.pos?.x ?? 50, y: prt.y ?? value.pos?.y ?? 50}};
142
- }
143
- }
@@ -1,37 +0,0 @@
1
- import {InspectorControls} from "@wordpress/block-editor";
2
- import {Panel, PanelBody, TabPanel} from "@wordpress/components";
3
- import {useState} from "@wordpress/element";
4
-
5
- import {type ComponentPropsWithoutRef, type ReactNode, useCallback} from "react";
6
-
7
- type TabPanelProps = ComponentPropsWithoutRef<typeof TabPanel>;
8
- type PanelBodyProps = ComponentPropsWithoutRef<typeof PanelBody>;
9
- type Tab = TabPanelProps['tabs'][number]&{items: ReactNode};
10
- type GroupAndCondition = {group?: string, condition?(): boolean};
11
- type NormalPanelInspectorPanelProps = {tabbed?: false|undefined, raw?: boolean|undefined}&Omit<PanelBodyProps, 'children'>&Pick<InspectorControls.Props, 'children'>&GroupAndCondition;
12
- type TabbedPanelInspectorPanelProps = {tabbed: true, tabs: Tab[]}&Omit<TabPanelProps, 'children'|'tabs'>&GroupAndCondition;
13
- export type InspectorPanelProps = (NormalPanelInspectorPanelProps|TabbedPanelInspectorPanelProps);
14
- export function InspectorPanel(props: InspectorPanelProps) {
15
- if (props["tabbed"]) {
16
- const {tabbed, tabs, group, condition, ...tabPanelProps} = props;
17
- return <InspectorControls group={group}>
18
- <TabPanel {...tabPanelProps} tabs={condition === undefined || condition() ? tabs : []}>
19
- {tab => <Panel>{(tab as Tab).items}</Panel>}
20
- </TabPanel>
21
- </InspectorControls>;
22
- } else {
23
- return <InspectorPanelPanel {...props} />;
24
- }
25
- }
26
- function InspectorPanelPanel(props: NormalPanelInspectorPanelProps) {
27
- const {tabbed, raw, group, condition, children, onToggle, initialOpen, ...panelBodyProps} = props;
28
- const [wasOpened, setWasOpened] = useState(initialOpen);
29
- const memoOnToggle = useCallback((value: boolean) => {
30
- onToggle?.(value);
31
- setWasOpened(value);
32
- }, [onToggle, setWasOpened]);
33
- return <InspectorControls group={group} children={raw
34
- ? (condition === undefined || condition() ? children : [])
35
- : <PanelBody {...panelBodyProps} children={condition === undefined || condition() ? children : []} onToggle={memoOnToggle} initialOpen={wasOpened} />
36
- } />;
37
- }
@@ -1,64 +0,0 @@
1
- import {BaseControl, ComboboxControl, Spinner} from "@wordpress/components";
2
- import {useCallback, useMemo, useState} from "@wordpress/element";
3
- import {__, sprintf} from "@wordpress/i18n";
4
-
5
- import {useSuggestions} from "./hooks/useSuggestions";
6
-
7
- import type {ComponentProps, FocusEvent} from "react";
8
-
9
- type ComboboxControlProps = ComponentProps<typeof ComboboxControl>;
10
- type ComboboxControlOption = ComboboxControlProps['options'][number];
11
- export type LazySuggestionsComboboxControlProps = Omit<ComboboxControlProps, 'options'> & {
12
- getOption(value?: string): Promise<ComboboxControlOption|undefined>;
13
- getSuggestions(filterValue: string): Promise<ComboboxControlOption[]>;
14
- };
15
- export function LazySuggestionsComboboxControl(props: LazySuggestionsComboboxControlProps) {
16
- const {help, allowReset, className, ...passthroughProps} = props;
17
-
18
- const {
19
- hasLoadingError, isInitializing, isLoading, suggestions, input, setInput
20
- } = useSuggestions(props.value, props);
21
-
22
- const [hasFocus, setHasFocus] = useState(false);
23
- const options = useMemo(
24
- () => hasFocus && input.length < 2 || (isLoading && !suggestions.length) ? [{label: input, value: "", disabled: true}] : suggestions,
25
- [hasFocus, input, isLoading, suggestions]);
26
-
27
- const onFocus = useCallback((e: FocusEvent<HTMLElement>) => setHasFocus(e.currentTarget.contains(e.target)), [setHasFocus]);
28
- const onBlur = useCallback((e: FocusEvent<HTMLElement>) => setHasFocus(e.currentTarget.contains(e.relatedTarget)), [setHasFocus]);
29
-
30
- const onFilterValueChange = useCallback((value: string) => {
31
- if (props.onFilterValueChange) {
32
- props.onFilterValueChange(value);
33
- }
34
- setInput(value);
35
- }, [props.onFilterValueChange, setInput]);
36
-
37
- const __experimentalRenderItem = useMemo(() => {
38
- return hasFocus && input.length < 2 ? () => __("Start typing to see suggestions", 'plaudit')
39
- : (isLoading && !suggestions.length ? () => __("Loading suggestions", 'plaudit') : undefined);
40
- }, [hasFocus, input, isLoading, suggestions])
41
-
42
- if (isInitializing) {
43
- return <BaseControl {...props} __nextHasNoMarginBottom>
44
- <Spinner /><span>{/* translators: %s is replaced with the label of the property */ sprintf(__("Initializing %s", 'plaudit'), props.label)}</span>
45
- </BaseControl>
46
- }
47
-
48
- const needsGreyOut = input.length < 2 || (isLoading && !suggestions.length);
49
- return <div onFocus={onFocus} onBlur={onBlur}>
50
- {isLoading && <Spinner style={{marginTop: "30px", position: "absolute", right: "40px"}} aria-label="Updating Suggestions" />}
51
- <ComboboxControl
52
- {...passthroughProps}
53
- className={className ? `${className}${needsGreyOut ? " insufficient-input-length" : ""}` : (needsGreyOut ? "insufficient-input-length" : undefined)}
54
- help={help}
55
- options={options}
56
- onFilterValueChange={onFilterValueChange}
57
- __experimentalRenderItem={__experimentalRenderItem}
58
- allowReset={allowReset !== false}
59
- __next40pxDefaultSize
60
- __nextHasNoMarginBottom
61
- />
62
- {hasLoadingError && <div><span className="components-base-control__help">{__("An error occurred while updating suggestions", 'plaudit')}</span></div>}
63
- </div>;
64
- }
@@ -1,59 +0,0 @@
1
- import {useCallback, useMemo} from "@wordpress/element";
2
-
3
- import {ExtendedFormTokenField, ValidationState} from "./ExtendedFormTokenField";
4
- import type {SNPControlSlots} from "../blocks";
5
- import {getLabel, normalizePickableOptionsToPairs} from "./shared";
6
- import type {PickableOptions} from "./types";
7
-
8
- import type {ReactNode} from "react";
9
-
10
- type MultiSelectProps = {
11
- onChange: (value: string[]) => void;
12
- options: PickableOptions<string|number>;
13
- label: string;
14
- help?: ReactNode;
15
- placeholder: string;
16
- value?: string[];
17
- expandOnFocus?: boolean;
18
- maxLength?: number;
19
- }&Partial<Pick<SNPControlSlots, 'Messages'>>;
20
-
21
- type MandatoryKeys = 'onChange'|'value'|'label'|'options';
22
- export type MultiSelectConstructorProps = Partial<Omit<MultiSelectProps, MandatoryKeys>> & Pick<MultiSelectProps, MandatoryKeys>;
23
-
24
- export function MultiSelectControl(props: MultiSelectConstructorProps) {
25
- const {value, options, ...fieldProps} = props;
26
- const normalizedOptions = useMemo(() => normalizePickableOptionsToPairs(options), [options]);
27
- const validOptions = useMemo(
28
- () => Object.fromEntries(normalizedOptions.map(opt => [opt[0], getLabel(opt)])),
29
- [normalizedOptions]);
30
-
31
- return <ExtendedFormTokenField
32
- {...fieldProps}
33
- value={value}
34
- multiple={true}
35
- stringToTokenConverter={useCallback(token => {
36
- if (validOptions[token]) {
37
- return {value: token, title: validOptions[token], status: ValidationState.Valid};
38
- } else {
39
- const result = /(.+) \(#([^)]+)\)$/.exec(token);
40
- return result && result[1] && result[2] ? {value: result[2], title: result[1], status: ValidationState.Validating} : {value: token, status: ValidationState.Invalid};
41
- }
42
- }, [validOptions])}
43
- suggestionQuery={useCallback(input => {
44
- return normalizedOptions
45
- .filter(option => option[0]?.toString().includes(input) || getLabel(option).includes(input))
46
- .map(option => ({value: option[0]?.toString(), status: ValidationState.Valid, title: getLabel(option)}));
47
- }, [normalizedOptions])}
48
- validationQuery={useCallback(values => {
49
- return values
50
- .filter(value => validOptions[value] !== undefined)
51
- .map(value => ({value, title: validOptions[value], status: ValidationState.Valid}));
52
- }, [validOptions])}
53
- validator={validator}
54
- />;
55
- }
56
-
57
- function validator(token: string) {
58
- return /\(#([^)]+)\)$/.exec(token)?.[1] !== undefined;
59
- }
@@ -1,88 +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
-
8
- import {normalizePickableOptionsToPairs} from "./shared";
9
- import type {PickableOptions, SimpleBlockControlProps} from "./types";
10
-
11
- import type {ComponentPropsWithoutRef, ReactElement} from "react";
12
-
13
- function makeSharedRadioAndSelectProps<T extends string>(props: SimpleBlockControlProps<T, string>&{options: PickableOptions<string>}) {
14
- return {
15
- label: props.label,
16
- help: props.help,
17
- onChange(value: string) {
18
- props.setAttributes({[props.attribute]: value});
19
- },
20
- options: normalizePickableOptionsToPairs(props.options).map(([value, label]) => ({
21
- value, label: typeof label === 'string' ? label : label.text
22
- }))
23
- } as Pick<Parameters<typeof RadioControl>[0], 'label'|'options'>&{onChange(value: string): void};
24
- }
25
-
26
- export type PickOneFromToggleGroupProps<T extends string> = SimpleBlockControlProps<T, string|number>&{options: PickableOptions<string, {icon: ReactElement}>};
27
- export function PickOneFromToggleGroup<T extends string>(props: PickOneFromToggleGroupProps<T>) {
28
- return <ToggleGroupControl
29
- label={props.label}
30
- help={props.help}
31
- value={props.attributes[props.attribute]}
32
- onChange={value => props.setAttributes({[props.attribute]: value})}
33
- children={
34
- normalizePickableOptionsToPairs(props.options).map(([value, label]) => typeof label === 'string'
35
- ? <ToggleGroupControlOption key={value} value={value} label={label} />
36
- : <ToggleGroupControlOptionIcon key={value} value={value} label={label.text} icon={label.icon} />)
37
- }
38
- __next40pxDefaultSize
39
- __nextHasNoMarginBottom
40
- />;
41
- }
42
-
43
- export function PickOneFromSelect<T extends string>(props: SimpleBlockControlProps<T, string>& { options: PickableOptions<string> }) {
44
- return <SelectControl
45
- {...makeSharedRadioAndSelectProps(props)}
46
- value={props.attributes[props.attribute]}
47
- __next40pxDefaultSize
48
- __nextHasNoMarginBottom
49
- />;
50
- }
51
- export function PickOneFromRadios<T extends string>(props: SimpleBlockControlProps<T, string>& { options: PickableOptions<string> }) {
52
- return <RadioControl
53
- {...makeSharedRadioAndSelectProps(props)}
54
- selected={props.attributes[props.attribute]}
55
- />;
56
- }
57
-
58
- type ColorObject = Exclude<NonNullable<ComponentPropsWithoutRef<typeof ColorPalette>['colors']>, {colors: any}[]>[number];
59
-
60
- export function PickOneFromColors<T extends string>(props: SimpleBlockControlProps<T, string>&{options: PickableOptions<string, {color?: string|undefined}>}) {
61
- const valueToColorMap = new Map<string, string>();
62
- const colorToValueMap = new Map<string, string>();
63
- const colors: ColorObject[] = [];
64
- for (const [value, display] of normalizePickableOptionsToPairs(props.options)) {
65
- if (typeof display === 'string') {
66
- colors.push({color: value, name: display});
67
- } else if (display.color) {
68
- valueToColorMap.set(value, display.color);
69
- colorToValueMap.set(display.color, value);
70
- colors.push({color: display.color, name: display.text});
71
- } else {
72
- colors.push({color: value, name: display.text});
73
- }
74
- }
75
-
76
- const currentColor = valueToColorMap.get(props.attributes[props.attribute]) ?? props.attributes[props.attribute];
77
- const {baseControlProps, controlProps} = useBaseControlProps({label: props.label, help: props.help});
78
- return <BaseControl {...baseControlProps} __nextHasNoMarginBottom>
79
- <ColorPalette
80
- {...controlProps}
81
- disableCustomColors={true}
82
- onChange={color => props.setAttributes({[props.attribute]: colorToValueMap.get(color ?? currentColor) ?? color ?? currentColor})}
83
- colors={colors}
84
- value={currentColor}
85
- clearable={false}
86
- />
87
- </BaseControl>;
88
- }
@@ -1,56 +0,0 @@
1
- import {Spinner} from '@wordpress/components';
2
- import {useRef} from "@wordpress/element";
3
-
4
- import {use, type WrappedPromise, wrapPromise} from "../lib/suspense";
5
-
6
- import {Suspense, type ReactNode} from "react";
7
-
8
- export type AwaitedProps<T extends object> = {[K in keyof T]: Awaited<T[K]>};
9
- export type PromisableComponentProps<T extends Awaited<object>> = {
10
- promisedProps: T|Promise<T>, initializing?: (() => ReactNode)|ReactNode, renderer(props: AwaitedProps<T>): ReactNode,
11
- forceSuspend?: boolean
12
- };
13
-
14
- export function PromisableComponent<T extends Awaited<object>>(props: PromisableComponentProps<T>) {
15
- const wrappedPromise = useWrappedArgsParameter(props.promisedProps, props.forceSuspend);
16
- if (!(wrappedPromise instanceof Promise)) {
17
- return props.renderer(wrappedPromise);
18
- }
19
- return <Suspense fallback={typeof props.initializing === 'function' ? props.initializing() : <><Spinner /> {props.initializing}</>}>
20
- <PromisableComponentDelegator promise={wrappedPromise} children={props.renderer} />
21
- </Suspense>;
22
- }
23
- function PromisableComponentDelegator<T extends AwaitedProps<Awaited<object>>>(
24
- {promise, children}: {promise: WrappedPromise<T>, children(props: T): ReactNode}
25
- ) {
26
- return children(use(promise));
27
- }
28
-
29
- function useWrappedArgsParameter<T extends {}>(args: T, forcePromise?: boolean) {
30
- const ref = useRef<WrappedArgsHolder<T>|null>(null);
31
- if (ref.current === null || args !== ref.current.args || forcePromise !== ref.current.forcePromise) {
32
- return (ref.current = {args, forcePromise, value: actuallyWrapTheArgsParameter(args, forcePromise)}).value;
33
- } else {
34
- const oldArgs = Object.values(ref.current.args);
35
- const newArgs = Object.values(args);
36
- if (oldArgs.length !== newArgs.length) {
37
- return (ref.current = {args, forcePromise, value: actuallyWrapTheArgsParameter(args, forcePromise)}).value;
38
- }
39
- for (let i = 0; i < newArgs.length; i++) {
40
- if (newArgs[i] !== oldArgs[i]) {
41
- return (ref.current = {args, forcePromise, value: actuallyWrapTheArgsParameter(args, forcePromise)}).value;
42
- }
43
- }
44
- }
45
- return ref.current.value;
46
- }
47
-
48
- type WrappedArgsHolder<T extends {}> = {args: T, forcePromise?: boolean, value: ReturnType<typeof actuallyWrapTheArgsParameter<T>>};
49
- function actuallyWrapTheArgsParameter<T extends {}>(args: T, forcePromise?: boolean) {
50
- if (!Object.values(args).some(v => v instanceof Promise)) {
51
- return forcePromise ? (wrapPromise(Promise.resolve(args)) as WrappedPromise<AwaitedProps<Awaited<T>>>) : (args as AwaitedProps<Awaited<T>>);
52
- }
53
- return wrapPromise(Promise
54
- .all(Object.entries(args).map(async ([key, value]): Promise<[typeof key, Awaited<typeof value>]> => [key, await value]))
55
- .then<AwaitedProps<T>>(entries => Object.fromEntries(entries) as any));
56
- }
@@ -1,98 +0,0 @@
1
- import {LinkControl} from "@wordpress/block-editor";
2
- import {BaseControl, Button, TextControl, ToggleControl} from "@wordpress/components";
3
- import {useCallback, useEffect, useRef, useState} from "@wordpress/element";
4
- import {chevronDown, chevronUp} from "@wordpress/icons";
5
-
6
- import type {ReactNode} from "react";
7
-
8
- export type ProperLinkControlSetting = LinkControl.Setting&({type?: 'toggle'|undefined, default?: boolean|undefined}|{type: 'text', default?: string|undefined});
9
-
10
- export type ProperLinkControlProps = Omit<LinkControl.Props, 'settings'>&{label: ReactNode, help?: ReactNode, settings?: ProperLinkControlSetting[]|false};
11
- export function ProperLinkControl(props: ProperLinkControlProps) {
12
- const {value, onChange, onRemove, label, help,
13
- settings = ProperLinkControl.DEFAULT_LINK_SETTINGS, ...linkControlProps} = props;
14
- const [areAdvancedSettingsVisible, setAreAdvancedSettingsVisible] = useState(false);
15
- const advancedSettingsRef = useRef<HTMLDivElement|null>(null);
16
-
17
- useEffect(() => {
18
- const advancedSettingsElement = advancedSettingsRef.current;
19
- if (!advancedSettingsElement) {
20
- return () => {};
21
- }
22
- const transitionListener = (evt: TransitionEvent) => {
23
- if (evt.propertyName === 'max-height' && evt.target === advancedSettingsElement) {
24
- if (advancedSettingsElement.style.maxHeight === '0px') {
25
- advancedSettingsElement.style.display = 'none';
26
- } else {
27
- advancedSettingsElement.style.maxHeight = '';
28
- }
29
- }
30
- };
31
- advancedSettingsElement.addEventListener('transitionend', transitionListener);
32
- return () => advancedSettingsElement.addEventListener('transitionend', transitionListener);
33
- }, [advancedSettingsRef]);
34
-
35
- useEffect(() => {
36
- const advancedSettingsElement = advancedSettingsRef.current;
37
- if (!advancedSettingsElement) {
38
- return;
39
- }
40
- if (areAdvancedSettingsVisible) {
41
- advancedSettingsElement.style.display = '';
42
- advancedSettingsElement.style.maxHeight = advancedSettingsElement.scrollHeight + "px";
43
- } else {
44
- advancedSettingsElement.style.maxHeight = advancedSettingsElement.scrollHeight + "px";
45
- // We have to use window.requestAnimationFrame in order to force the maxHeight to be treated as "recalculated".
46
- // Unless something else on the page goes horribly wrong, there shouldn't be any actual painting done as part of updating the max height.
47
- window.requestAnimationFrame(() => advancedSettingsElement.style.maxHeight = "0px");
48
- }
49
- }, [areAdvancedSettingsVisible, advancedSettingsRef]);
50
-
51
- const safeOnRemove = useCallback(() => onRemove !== undefined ? onRemove() : onChange?.(undefined), [onRemove, onChange]);
52
-
53
- const renderableSettings = settings === false ? [] : settings;
54
- const renderControlBottom = useCallback(() => (value?.url && renderableSettings?.length && <>
55
- <Button
56
- variant="tertiary" onClick={() => setAreAdvancedSettingsVisible(!areAdvancedSettingsVisible)}
57
- icon={areAdvancedSettingsVisible ? chevronUp : chevronDown} iconPosition="right" text="Advanced"
58
- __next40pxDefaultSize accessibleWhenDisabled
59
- />
60
- <div ref={advancedSettingsRef} style={{display: "none", transition: "max-height 200ms ease-in-out", overflow: "hidden"}}>
61
- {...renderableSettings.map(setting => <ProperLinkControlSetting key={setting.id} setting={setting} onChange={onChange} value={value} />)}
62
- </div>
63
- </>) || "", [value, settings, areAdvancedSettingsVisible, onChange]);
64
-
65
- return <BaseControl __nextHasNoMarginBottom id="" label={props.label} help={props.help}>
66
- <LinkControl
67
- {...linkControlProps}
68
- value={value}
69
- onChange={onChange}
70
- onRemove={safeOnRemove}
71
- settings={ProperLinkControl.DUMMY_LINK_SETTINGS}
72
- renderControlBottom={renderControlBottom}
73
- />
74
- </BaseControl>
75
- }
76
- ProperLinkControl.DEFAULT_LINK_SETTINGS = LinkControl.DEFAULT_LINK_SETTINGS;
77
- ProperLinkControl.DUMMY_LINK_SETTINGS = [] as LinkControl.Setting[];
78
-
79
- export interface ProperLinkControl {
80
- (props: ProperLinkControlProps): ReactNode;
81
- readonly DEFAULT_LINK_SETTINGS: ReadonlyArray<ProperLinkControlSetting>;
82
- readonly DUMMY_LINK_SETTINGS: ReadonlyArray<LinkControl.Setting>;
83
- }
84
-
85
- type ProperLinkControlSettingProps = {value?: LinkControl.Value, onChange?: (nextValue?: LinkControl.Value) => void, setting: ProperLinkControlSetting};
86
- function ProperLinkControlSetting({value, onChange, setting}: ProperLinkControlSettingProps) {
87
- return <div style={{marginTop: "8px"}}>
88
- {setting.type === 'text'
89
- ? <TextControl
90
- style={{marginTop: "8px"}} value={value?.[setting.id] ?? setting.default ?? ''} label={setting.title}
91
- onChange={v => onChange?.({...value, [setting.id]: v})}
92
- __next40pxDefaultSize __nextHasNoMarginBottom
93
- />
94
- : <ToggleControl style={{marginTop: "8px"}} checked={value?.[setting.id] ?? setting.default ?? false} label={setting.title}
95
- onChange={v => onChange?.({...value, [setting.id]: v})} />
96
- }
97
- </div>;
98
- }
@@ -1,9 +0,0 @@
1
- import {ToggleControl} from "@wordpress/components";
2
- import {useCallback} from "@wordpress/element";
3
-
4
- import type {SimpleBlockControlProps} from "./types";
5
-
6
- export function SimpleToggle<T extends string>(props: SimpleBlockControlProps<T, boolean>) {
7
- const onChange = useCallback((checked: boolean) => props.setAttributes({[props.attribute]: checked}), [props.setAttributes, props.attribute]);
8
- return <ToggleControl __nextHasNoMarginBottom checked={props.attributes[props.attribute]} label={props.label} onChange={onChange} />;
9
- }