@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,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
- }