@plaudit/gutenberg-api-extensions 2.8.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 (91) hide show
  1. package/build/blocks/common-native-property-implementations.d.ts +118 -0
  2. package/build/blocks/common-native-property-implementations.js +141 -20
  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/layered-styles.js +9 -7
  7. package/build/blocks/layered-styles.js.map +1 -1
  8. package/build/blocks/simple-block.d.ts +22 -0
  9. package/build/blocks/simple-block.js +10 -6
  10. package/build/blocks/simple-block.js.map +1 -1
  11. package/build/blocks/simple-native-property.d.ts +30 -0
  12. package/build/blocks/simple-native-property.js +106 -95
  13. package/build/blocks/simple-native-property.js.map +1 -1
  14. package/build/controls/AsynchronousFormTokenField.d.ts +20 -0
  15. package/build/controls/AsynchronousFormTokenField.js +23 -12
  16. package/build/controls/AsynchronousFormTokenField.js.map +1 -1
  17. package/build/controls/ExtendedPostPicker.d.ts +13 -0
  18. package/build/controls/ExtendedPostPicker.js +5 -10
  19. package/build/controls/ExtendedPostPicker.js.map +1 -1
  20. package/build/controls/InspectorPanel.d.ts +6 -0
  21. package/build/controls/InspectorPanel.js +3 -2
  22. package/build/controls/InspectorPanel.js.map +1 -1
  23. package/build/controls/LazySuggestionsComboboxControl.d.ts +7 -0
  24. package/build/controls/LazySuggestionsComboboxControl.js +70 -0
  25. package/build/controls/LazySuggestionsComboboxControl.js.map +1 -0
  26. package/build/controls/PickOne.d.ts +19 -0
  27. package/build/controls/PickOne.js +8 -7
  28. package/build/controls/PickOne.js.map +1 -1
  29. package/build/controls/SimpleToggle.d.ts +3 -0
  30. package/build/controls/SimpleToggle.js +2 -2
  31. package/build/controls/SimpleToggle.js.map +1 -1
  32. package/build/controls/SortableItemsControl.d.ts +11 -0
  33. package/build/controls/SortableItemsControl.js +154 -0
  34. package/build/controls/SortableItemsControl.js.map +1 -0
  35. package/{src/controls/index.ts → build/controls/index.d.ts} +1 -0
  36. package/build/controls/index.js +1 -0
  37. package/build/controls/index.js.map +1 -1
  38. package/build/controls/shared.d.ts +8 -0
  39. package/build/controls/shared.js +7 -0
  40. package/build/controls/shared.js.map +1 -0
  41. package/build/controls/types.d.ts +12 -0
  42. package/build/lib/plaudit-icons/column-1.d.ts +2 -0
  43. package/build/lib/plaudit-icons/column-1.js +3 -2
  44. package/build/lib/plaudit-icons/column-1.js.map +1 -1
  45. package/build/lib/plaudit-icons/column-2.d.ts +2 -0
  46. package/build/lib/plaudit-icons/column-2.js +3 -2
  47. package/build/lib/plaudit-icons/column-2.js.map +1 -1
  48. package/build/lib/plaudit-icons/column-3.d.ts +2 -0
  49. package/build/lib/plaudit-icons/column-3.js +3 -2
  50. package/build/lib/plaudit-icons/column-3.js.map +1 -1
  51. package/build/lib/plaudit-icons/placement-center.d.ts +2 -0
  52. package/build/lib/plaudit-icons/placement-center.js +3 -2
  53. package/build/lib/plaudit-icons/placement-center.js.map +1 -1
  54. package/build/lib/plaudit-icons/placement-end.d.ts +2 -0
  55. package/build/lib/plaudit-icons/placement-end.js +3 -2
  56. package/build/lib/plaudit-icons/placement-end.js.map +1 -1
  57. package/build/lib/plaudit-icons/placement-start.d.ts +2 -0
  58. package/build/lib/plaudit-icons/placement-start.js +3 -2
  59. package/build/lib/plaudit-icons/placement-start.js.map +1 -1
  60. package/build/lib/plaudit-icons/placement-stretch.d.ts +2 -0
  61. package/build/lib/plaudit-icons/placement-stretch.js +3 -2
  62. package/build/lib/plaudit-icons/placement-stretch.js.map +1 -1
  63. package/build/lib/plaudit-icons/plaudit-icon.d.ts +2 -0
  64. package/build/lib/plaudit-icons/plaudit-icon.js +3 -2
  65. package/build/lib/plaudit-icons/plaudit-icon.js.map +1 -1
  66. package/build/lib/plaudit-icons/reusable-block-marker.d.ts +2 -0
  67. package/build/lib/plaudit-icons/reusable-block-marker.js +3 -2
  68. package/build/lib/plaudit-icons/reusable-block-marker.js.map +1 -1
  69. package/{src/lib/plaudit-icons.ts → build/lib/plaudit-icons.d.ts} +0 -4
  70. package/package.json +14 -14
  71. package/src/blocks/common-native-property-implementations.tsx +0 -271
  72. package/src/blocks/index.ts +0 -12
  73. package/src/blocks/layered-styles.tsx +0 -108
  74. package/src/blocks/simple-block.tsx +0 -72
  75. package/src/blocks/simple-native-property.tsx +0 -200
  76. package/src/controls/AsynchronousFormTokenField.tsx +0 -151
  77. package/src/controls/ExtendedPostPicker.tsx +0 -50
  78. package/src/controls/InspectorPanel.tsx +0 -16
  79. package/src/controls/PickOne.tsx +0 -80
  80. package/src/controls/SimpleToggle.tsx +0 -9
  81. package/src/controls/types.ts +0 -11
  82. package/src/lib/plaudit-icons/column-1.tsx +0 -8
  83. package/src/lib/plaudit-icons/column-2.tsx +0 -8
  84. package/src/lib/plaudit-icons/column-3.tsx +0 -8
  85. package/src/lib/plaudit-icons/placement-center.tsx +0 -5
  86. package/src/lib/plaudit-icons/placement-end.tsx +0 -5
  87. package/src/lib/plaudit-icons/placement-start.tsx +0 -5
  88. package/src/lib/plaudit-icons/placement-stretch.tsx +0 -5
  89. package/src/lib/plaudit-icons/plaudit-icon.tsx +0 -6
  90. package/src/lib/plaudit-icons/reusable-block-marker.tsx +0 -5
  91. /package/{src/index.ts → build/index.d.ts} +0 -0
@@ -1,271 +0,0 @@
1
- import {MediaUpload, MediaUploadCheck} from "@wordpress/block-editor";
2
- import {
3
- Button,
4
- Card,
5
- CardBody,
6
- CardHeader,
7
- FocalPointPicker,
8
- RadioControl,
9
- RangeControl,
10
- SelectControl,
11
- ResponsiveWrapper,
12
- TextareaControl,
13
- TextControl,
14
- ToggleControl,
15
- __experimentalToggleGroupControl as ToggleGroupControl,
16
- __experimentalHeading as Heading,
17
- __experimentalToggleGroupControlOption as ToggleGroupControlOption,
18
- __experimentalToggleGroupControlOptionIcon as ToggleGroupControlOptionIcon
19
- } from "@wordpress/components";
20
- import type {RadioControlProps} from "@wordpress/components/build-types/radio-control/types";
21
- import type {RangeControlProps} from "@wordpress/components/build-types/range-control/types";
22
- import type {TextControlProps} from "@wordpress/components/build-types/text-control/types";
23
- import type {TextareaControlProps} from "@wordpress/components/build-types/textarea-control/types";
24
- import type {ToggleControlProps} from "@wordpress/components/build-types/toggle-control/types";
25
- import type {ToggleGroupControlProps} from "@wordpress/components/build-types/toggle-group-control/types";
26
- import {useSelect} from "@wordpress/data";
27
- import {__} from "@wordpress/i18n";
28
-
29
- import type {PickableOptions} from "../controls/types";
30
- import type {SimpleNativeProperty} from "./simple-native-property";
31
-
32
- import React, {type ReactElement} from "react";
33
-
34
- export type ActualBlockEditProps = { attributes: Record<string, any>, setAttributes: (attributes: Record<string, any>) => void, name: string };
35
- type CommonPropertyConfig<T, V> = {
36
- name: string,
37
- label: string,
38
- default: T,
39
- enum?: T[],
40
- component?: Partial<V>,
41
- help?: string,
42
- alwaysStore?: boolean,
43
- condition?(blockEditProps: ActualBlockEditProps): boolean
44
- }
45
-
46
- export function rangeProperty(config: CommonPropertyConfig<number, RangeControlProps> & {
47
- min: number,
48
- max: number,
49
- step?: number,
50
- enum?: number[]
51
- }): SimpleNativeProperty {
52
- return {
53
- name: config.name,
54
- type: 'number',
55
- enum: config.enum,
56
- default: config.default,
57
- condition: config.condition,
58
- alwaysStore: config.alwaysStore,
59
- renderer(value, onChange) {
60
- return <RangeControl value={value} onChange={onChange} min={config.min} max={config.max} label={config.label}
61
- step={config.step} help={config.help} {...config.component} />;
62
- }
63
- };
64
- }
65
-
66
- export function radioProperty(config: CommonPropertyConfig<string, RadioControlProps> & {
67
- options: PickableOptions<string>
68
- }): SimpleNativeProperty {
69
- return {
70
- name: config.name,
71
- type: 'string',
72
- enum: config.enum,
73
- default: config.default,
74
- condition: config.condition,
75
- alwaysStore: config.alwaysStore,
76
- renderer(value, onChange) {
77
- return <RadioControl selected={value} onChange={onChange} options={asOptions(config.options)} label={config.label}
78
- help={config.help} {...config.component} />;
79
- }
80
- };
81
- }
82
-
83
- export function selectProperty(config: CommonPropertyConfig<string, never> & {
84
- options: PickableOptions<string>
85
- }): SimpleNativeProperty {
86
- return {
87
- name: config.name,
88
- type: 'string',
89
- enum: config.enum,
90
- default: config.default,
91
- condition: config.condition,
92
- alwaysStore: config.alwaysStore,
93
- renderer(value, onChange) {
94
- return <SelectControl value={value ?? ''} onChange={onChange} options={asOptions(config.options, '')} label={config.label} help={config.help} />;
95
- }
96
- };
97
- }
98
-
99
- export function toggleProperty(config: CommonPropertyConfig<boolean, ToggleControlProps>): SimpleNativeProperty {
100
- return {
101
- name: config.name,
102
- type: 'boolean',
103
- default: config.default,
104
- condition: config.condition,
105
- alwaysStore: config.alwaysStore,
106
- renderer(value, onChange) {
107
- return <ToggleControl checked={value} onChange={onChange} label={config.label} help={config.help} {...config.component} />;
108
- }
109
- };
110
- }
111
-
112
- type ToggleGroupPropertyConfigBase<T extends string|number> = CommonPropertyConfig<T, ToggleGroupControlProps>&{options: PickableOptions<T, {icon: ReactElement}>};
113
- export type ToggleGroupPropertyConfig = ToggleGroupPropertyConfigBase<string>|ToggleGroupPropertyConfigBase<number>;
114
-
115
- export function toggleGroupProperty(config: ToggleGroupPropertyConfig): SimpleNativeProperty {
116
- const children = config.options.map(([value, label]) => typeof label === 'string'
117
- ? <ToggleGroupControlOption key={value} value={value} label={label}/>
118
- : <ToggleGroupControlOptionIcon key={value} value={value} label={label.text} icon={label.icon}/>);
119
-
120
- if (typeof config.default === 'string') {
121
- return {
122
- name: config.name,
123
- type: 'string',
124
- default: config.default,
125
- condition: config.condition,
126
- alwaysStore: config.alwaysStore,
127
- renderer(value, onChange) {
128
- return <ToggleGroupControl
129
- value={value} onChange={v => onChange(v?.toString())}
130
- label={config.label} help={config.help} isBlock {...config.component}
131
- children={children} />;
132
- }
133
- };
134
- } else {
135
- return {
136
- name: config.name,
137
- type: 'number',
138
- default: config.default,
139
- condition: config.condition,
140
- alwaysStore: config.alwaysStore,
141
- renderer(value, onChange) {
142
- return <ToggleGroupControl
143
- value={value} label={config.label} help={config.help} isBlock {...config.component}
144
- onChange={v => onChange(v === undefined || typeof v === 'number' ? v : (v.includes('.') ? parseFloat(v) : parseInt(v)))}
145
- children={children} />;
146
- }
147
- };
148
- }
149
- }
150
-
151
- export function textProperty(config: CommonPropertyConfig<string, TextControlProps>): SimpleNativeProperty {
152
- return {
153
- name: config.name,
154
- type: 'string',
155
- default: config.default,
156
- condition: config.condition,
157
- alwaysStore: config.alwaysStore,
158
- renderer(value, onChange) {
159
- return <TextControl value={value} onChange={onChange} label={config.label} help={config.help} {...config.component} />;
160
- }
161
- };
162
- }
163
-
164
- export function textareaProperty(config: CommonPropertyConfig<string, TextareaControlProps>): SimpleNativeProperty {
165
- return {
166
- name: config.name,
167
- type: 'string',
168
- default: config.default,
169
- condition: config.condition,
170
- alwaysStore: config.alwaysStore,
171
- renderer(value, onChange) {
172
- return <TextareaControl value={value} onChange={onChange} label={config.label} help={config.help} {...config.component} />;
173
- }
174
- };
175
- }
176
-
177
- export type ImagePropertyData = {media?: {id?: number, url?: string}, pos?: {x?: number, y?: number}};
178
- export function imageProperty(config: Omit<CommonPropertyConfig<ImagePropertyData, {}>, 'default'> & Partial<Pick<CommonPropertyConfig<ImagePropertyData, {}>, 'default'>> & {
179
- enableFocalPointPicker?: boolean
180
- }): SimpleNativeProperty {
181
- return {
182
- name: config.name,
183
- type: "object",
184
- condition: config.condition,
185
- alwaysStore: config.alwaysStore,
186
- default: {media: {id: config.default?.media?.id ?? 0, url: config.default?.media?.url ?? ''}, pos: {x: config.default?.pos?.x ?? 50, y: config.default?.pos?.y ?? 50}},
187
- renderer(value: ImagePropertyData, onChange) {
188
- const {media} = useSelect(select => {
189
- return { media: value.media?.id ? (select('core') as any).getMedia(value.media.id) : undefined };
190
- }, [value]);
191
-
192
- const noImageUploadedVersion = <MediaUploadCheck>
193
- <MediaUpload
194
- onSelect={media => onChange({...value, media: {id: media.id, url: media['url']}})}
195
- value={value.media?.id ?? 0}
196
- allowedTypes={['image']}
197
- render={({open}) => (
198
- <Button
199
- className={!value.media?.id ? 'editor-post-featured-image__toggle' : 'editor-post-featured-image__preview'}
200
- onClick={open}
201
- >
202
- {!value.media?.id && __('Choose an image', 'plaudit')}
203
- {media &&
204
- <ResponsiveWrapper
205
- naturalWidth={media.media_details.width}
206
- naturalHeight={media.media_details.height}
207
- >
208
- <img src={media.source_url} alt="The currently-selected image." />
209
- </ResponsiveWrapper>
210
- }
211
- </Button>
212
- )}
213
- />
214
- </MediaUploadCheck>;
215
- let fppOrMedia;
216
- if (config.enableFocalPointPicker !== false) {
217
- fppOrMedia = <FocalPointPicker
218
- onChange={pos => onChange({...value, pos: {x: pos.x * 100, y: pos.y * 100}})}
219
- url={value.media?.url ?? ''}
220
- value={{
221
- x: (value.pos?.x ?? 50) / 100,
222
- y: (value.pos?.y ?? 50) / 100
223
- }}
224
- />;
225
- } else {
226
- fppOrMedia = <ResponsiveWrapper
227
- naturalWidth={media?.media_details.width}
228
- naturalHeight={media?.media_details.height}
229
- >
230
- <img src={value.media?.url ?? ''} alt="The currently-selected image." />
231
- </ResponsiveWrapper>
232
- }
233
-
234
- const imageUploadedVersion = <>
235
- {value.media?.url && fppOrMedia}
236
- <MediaUploadCheck>
237
- <MediaUpload
238
- title={__('Replace image', 'plaudit')}
239
- value={value.media?.id ?? 0}
240
- onSelect={media => onChange({...value, media: {id: media.id, url: media['url']}})}
241
- allowedTypes={['image']}
242
- render={({open}) => (
243
- <Button onClick={open} variant="secondary">{__('Replace image', 'plaudit')}</Button>
244
- )}
245
- />
246
- </MediaUploadCheck>
247
- <MediaUploadCheck>
248
- <Button onClick={() => onChange({...value, media: {id: 0, url: ''}})} isDestructive>{__('Remove image', 'plaudit')}</Button>
249
- </MediaUploadCheck>
250
- </>;
251
- return <Card>
252
- <CardHeader>
253
- <Heading>{config.label}</Heading>
254
- </CardHeader>
255
- <CardBody>
256
- <div className="editor-post-featured-image">
257
- {value.media?.id ? imageUploadedVersion : noImageUploadedVersion}
258
- </div>
259
- </CardBody>
260
- </Card>;
261
- }
262
- };
263
- }
264
-
265
- function asOptions<T extends string|number>(options: PickableOptions<T>, noSelectionValue?: T): Array<{ value: T, label: string }> {
266
- const res = options.map(opt => ({value: opt[0], label: typeof opt[1] === 'string' ? opt[1] : opt[1].text}));
267
- if (noSelectionValue !== undefined && !res.some(opt => !opt.value)) {
268
- res.splice(0, 0, {value: noSelectionValue, label: "-- No Selection --"});
269
- }
270
- return res;
271
- }
@@ -1,12 +0,0 @@
1
- import {installLayeredBlockStylesSupport} from "./layered-styles";
2
- import {installSimpleNativePropertiesSupport} from "./simple-native-property";
3
-
4
- export * from "./simple-block";
5
- export * from "./layered-styles";
6
- export * from "./simple-native-property";
7
- export * from './common-native-property-implementations';
8
-
9
- export function installGutenbergBlockExtensions() {
10
- installLayeredBlockStylesSupport();
11
- installSimpleNativePropertiesSupport();
12
- }
@@ -1,108 +0,0 @@
1
- import {createHigherOrderComponent} from "@wordpress/compose";
2
- import {addFilter} from "@wordpress/hooks";
3
-
4
- import React, {type ReactElement} from "react";
5
-
6
- import {InspectorPanel, PickOneFromColors, PickOneFromRadios, PickOneFromSelect, PickOneFromToggleGroup} from "../controls";
7
- import type {SimpleBlockControlProps, PickableOptions, SpecificValueBlockProps} from "../controls/types";
8
-
9
- type BaseLayeredBlockStyleLayer = {
10
- name: string;
11
- title: string;
12
- default?: string|undefined;
13
- }
14
-
15
- type LayeredBlockStyleColorLayer = BaseLayeredBlockStyleLayer&{
16
- picker: 'color';
17
- options: PickableOptions<string, {color?: string}>;
18
- }
19
-
20
- type LayeredBlockStyleToggleLayer = BaseLayeredBlockStyleLayer&{
21
- picker?: 'toggle-control';
22
- options: PickableOptions<string, {icon: ReactElement}>;
23
- }
24
- type LayeredBlockStyleSelectOrRadioLayer = BaseLayeredBlockStyleLayer&{
25
- picker: 'select'|'radio';
26
- options: PickableOptions<string>;
27
- }
28
-
29
- export type LayeredBlockStyleLayer = LayeredBlockStyleColorLayer|LayeredBlockStyleToggleLayer|LayeredBlockStyleSelectOrRadioLayer;
30
-
31
- export interface LayeredBlockStylesConfig {
32
- block: `${string}/${string}`;
33
- layers: LayeredBlockStyleLayer[];
34
- }
35
-
36
- export function registerLayeredBlockStyles(config: LayeredBlockStylesConfig) {
37
- const layeredBlockStyles: Map<string, LayeredBlockStylesConfig> = (window as any).plauditLayeredBlockStyles ?? ((window as any).plauditLayeredBlockStyles = new Map());
38
- layeredBlockStyles.set(config.block, config);
39
- }
40
-
41
- export function installLayeredBlockStylesSupport() {
42
- if ((window as any).plauditLayeredBlockStylesInstalled) {
43
- return;
44
- }
45
- (window as any).plauditLayeredBlockStylesInstalled = true;
46
- const layeredBlockStyles: Map<string, LayeredBlockStylesConfig> = (window as any).plauditLayeredBlockStyles ?? ((window as any).plauditLayeredBlockStyles = new Map());
47
-
48
- function layeredStyleValueGetter(layer: LayeredBlockStyleLayer, block: SpecificValueBlockProps<'className', string|undefined>) {
49
- const currentLayerClassPrefix = `style-${layer.name}-`;
50
- return block.attributes['className']?.split(/\s+/).filter(cn => cn.startsWith(currentLayerClassPrefix))[0]?.substring(currentLayerClassPrefix.length) ?? layer.default ?? '';
51
- }
52
- function layeredStyleValueSetter(
53
- attrs: Record<string, string>, layers: LayeredBlockStylesConfig['layers'], block: SpecificValueBlockProps<'className', string|undefined>
54
- ) {
55
- const [key, value] = Object.entries(attrs)[0];
56
- if (!key.startsWith("layeredStyles__")) {
57
- return;
58
- }
59
-
60
- const layerName = key.substring(15);
61
- const currentLayerClassPrefix = `style-${layerName}-`;
62
- const classNames = block.attributes['className'] ? block.attributes['className'].split(/\s+/).filter(cn => !cn.startsWith(currentLayerClassPrefix)) : [];
63
-
64
- const layerClassPrefixes = layers.map(layer => `style-${layer.name}-`);
65
- const layerClasses = classNames.filter(cn => layerClassPrefixes.some(lcp => cn.startsWith(lcp)));
66
- const nonLayerClasses = classNames.filter(cn => !layerClasses.includes(cn));
67
-
68
- layerClasses.push(`${currentLayerClassPrefix}${value}`);
69
- layerClasses.sort();
70
-
71
- block.setAttributes({className: [...nonLayerClasses, ...layerClasses].join(' ')});
72
- }
73
-
74
- function convertLayersToRadios(
75
- layer: LayeredBlockStyleLayer, layers: LayeredBlockStylesConfig['layers'], props: SimpleBlockControlProps<'className', string|undefined>
76
- ): React.JSX.Element {
77
- const value = layeredStyleValueGetter(layer, props);
78
- const sharedProps: SimpleBlockControlProps<string, string> = {
79
- label: layer.title,
80
- attribute: `layeredStyles__${layer.name}`,
81
- attributes: {[`layeredStyles__${layer.name}`]: value},
82
- setAttributes: attrs => layeredStyleValueSetter(attrs, layers, props)
83
- }
84
- switch (layer.picker) {
85
- case undefined:
86
- case 'toggle-control':
87
- return <PickOneFromToggleGroup {...layer} {...sharedProps} />;
88
- case 'color':
89
- return <PickOneFromColors {...layer} {...sharedProps} />;
90
- case 'radio':
91
- return <PickOneFromRadios {...layer} {...sharedProps} />;
92
- case 'select':
93
- return <PickOneFromSelect {...layer} {...sharedProps} />;
94
- }
95
- }
96
-
97
- addFilter('editor.BlockEdit', 'plaudit/gutenberg-api-extensions/layered-styles', createHigherOrderComponent(BlockEdit => (props: any) => {
98
- if (!layeredBlockStyles.has(props['name'])) {
99
- return <BlockEdit {...props} />;
100
- }
101
- return <>
102
- <BlockEdit {...props} />
103
- <InspectorPanel title="Layered Styles" initialOpen={true} group="styles">
104
- {...layeredBlockStyles.get(props['name'])!.layers.map(layer => convertLayersToRadios(layer, layeredBlockStyles.get(props['name'])!.layers, props))}
105
- </InspectorPanel>
106
- </>;
107
- }, 'plauditGutenbergApiExtensionsLayeredStyles'));
108
- }
@@ -1,72 +0,0 @@
1
- import {type Block, type BlockConfiguration, type BlockSaveProps, registerBlockType} from "@wordpress/blocks";
2
- import {InnerBlocks, useBlockProps, useInnerBlocksProps} from "@wordpress/block-editor";
3
- import type {UseBlockProps} from "@wordpress/block-editor/components/use-block-props";
4
-
5
- import {plauditIcon} from "../lib/plaudit-icons";
6
-
7
- import React, {type ComponentType, type ReactNode} from "react";
8
-
9
- export type SimpleBlockTypeConfig<TAttributes extends Record<string, any>> = {
10
- icon?: BlockConfiguration<TAttributes>['icon']|undefined,
11
- defaults?: Required<TAttributes>,
12
- controls?: NonNullable<Block<TAttributes>['edit']>,
13
- renderer: (attributes: BlockSaveProps<TAttributes>, wrapperProps: UseBlockProps|UseBlockProps['save'], InnerBlocks: () => React.ReactElement, mode: "edit"|"save") => ReactNode,
14
- innerBlocksProps?: InnerBlocks.Props
15
- settings?: Partial<BlockConfiguration<TAttributes>>,
16
- };
17
- function applyDefaults<TAttributes extends Record<string, any>, K extends BlockSaveProps<TAttributes>>(config: Omit<SimpleBlockTypeConfig<TAttributes>, 'renderer'>, props: K) {
18
- type attrs = { [P in keyof K['attributes']]: K['attributes'][P] };
19
- const mutableProps = {...props, attributes: {...props.attributes}} as Omit<typeof props, 'attributes'>&{attributes: attrs};
20
- for (const [k, v] of Object.entries(config.defaults ?? {}) as Array<[keyof typeof mutableProps.attributes, any]>) {
21
- if (mutableProps.attributes[k] === undefined) {
22
- mutableProps.attributes[k] = v;
23
- }
24
- }
25
- return mutableProps;
26
- }
27
-
28
- export function registerSimpleBlockType<TAttributes extends Record<string, any> = {}>(
29
- metadata: BlockConfiguration<TAttributes>, config: SimpleBlockTypeConfig<TAttributes>
30
- ): Block<TAttributes>|undefined {
31
- return registerBlockType<TAttributes>(metadata, {
32
- ...config.settings,
33
- icon: config.icon ?? plauditIcon,
34
- edit(props) {
35
- const populatedProps = applyDefaults(config, props);
36
- return <>
37
- {config.controls && <config.controls {...populatedProps} />}
38
- {config.renderer(populatedProps, useBlockProps, () => <InnerBlocks {...config.innerBlocksProps} />, "edit")}
39
- </>;
40
- },
41
- save(props) {
42
- return config.renderer(applyDefaults(config, props), useBlockProps.save, () => <InnerBlocks.Content />, "save");
43
- }
44
- });
45
- }
46
-
47
- type SimpleContainerTypeConfig<TAttributes extends Record<string, any>> = Omit<SimpleBlockTypeConfig<TAttributes>, 'renderer'>&{
48
- tag?: ComponentType,
49
- attributesFromProps?: (arg: ReturnType<typeof applyDefaults<TAttributes, BlockSaveProps<TAttributes>>>, mode: "edit"|"save") => Parameters<UseBlockProps|UseBlockProps['save']>[0]
50
- }
51
-
52
- export function registerSimpleContainerBlock<TAttributes extends Record<string, any> = {}>(
53
- metadata: BlockConfiguration<TAttributes>, config: SimpleContainerTypeConfig<TAttributes>
54
- ): Block<TAttributes>|undefined {
55
- const Tag = config.tag ?? ((props: any) => <div {...props} />);
56
- return registerBlockType<TAttributes>(metadata, {
57
- ...config.settings,
58
- icon: config.icon ?? plauditIcon,
59
- edit(props) {
60
- const populatedProps = applyDefaults(config, props);
61
- return <>
62
- {config.controls && <config.controls {...populatedProps} />}
63
- <Tag {...useInnerBlocksProps(useBlockProps(
64
- (config.attributesFromProps ?? (() => ({} as Parameters<UseBlockProps>[0])))(populatedProps, "edit")), config.innerBlocksProps)} />
65
- </>;
66
- },
67
- save(props) {
68
- return <Tag {...useInnerBlocksProps.save(useBlockProps.save(
69
- (config.attributesFromProps ?? (() => ({} as Parameters<UseBlockProps['save']>[0])))(applyDefaults(config, props), "edit")))} />;
70
- }
71
- })
72
- }
@@ -1,200 +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, 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 (propPath.length === 1) {
138
- existingValue = blockEditProps.attributes[prop.name];
139
- if (existingValue === undefined) {
140
- existingValue = prop.default;
141
- blockEditProps.setAttributes({[prop.name]: prop.default});
142
- }
143
- } else {
144
- let graphExistingValue = blockEditProps.attributes[propPath[0]];
145
- if (graphExistingValue === undefined) {
146
- blockEditProps.attributes[propPath[0]] = graphExistingValue = {};
147
- }
148
- for (let i = 1; i < propPath.length; i++) {
149
- if (graphExistingValue[propPath[i]] === undefined) {
150
- for (; i < propPath.length - 1; i++) {
151
- graphExistingValue = graphExistingValue[propPath[i]] = {};
152
- }
153
- graphExistingValue[propPath[propPath.length - 1]] = existingValue = prop.default;
154
- blockEditProps.setAttributes({[propPath[0]]: blockEditProps.attributes[propPath[0]]});
155
- break;
156
- } else {
157
- graphExistingValue = graphExistingValue[propPath[i]];
158
- }
159
- }
160
- existingValue = graphExistingValue ?? prop.default;
161
- }
162
- let ele: React.JSX.Element;
163
- 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
164
- if (!Array.isArray(existingValue) || existingValue.length > existingValue.filter(() => true).length) {
165
- throw new Error(`Invalid value passed to an array-type property: ${existingValue}`);
166
- }
167
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
168
- } else if (prop.type === "object") {
169
- if (Array.isArray(existingValue) || typeof existingValue !== 'object') {
170
- throw new Error(`Invalid value passed to an object-type property: ${existingValue}`);
171
- }
172
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
173
- } else if (prop.type === "boolean") {
174
- if (typeof existingValue !== 'boolean' && existingValue !== undefined) {
175
- existingValue = !!existingValue;
176
- }
177
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
178
- } else if (prop.type === "string") {
179
- if (typeof existingValue !== 'string' && existingValue !== undefined) {
180
- existingValue = existingValue.toString();
181
- }
182
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
183
- } else {
184
- if (typeof existingValue !== 'number' && existingValue !== undefined) {
185
- existingValue = parseFloat(existingValue);
186
- }
187
- ele = prop.renderer(existingValue, value => setDottedAttribute(blockEditProps, prop.name, value));
188
- }
189
- return prop.condition === undefined || prop.condition(blockEditProps) ? ele : undefined;
190
- }).filter((ele): ele is React.JSX.Element => ele !== undefined);
191
- return <InspectorPanel
192
- {...p} condition={() => items.length > 0 && (p.condition === undefined || p.condition(blockEditProps))}
193
- key={`plaudit-simple-native-property-${keyIndex++}`}
194
- >
195
- {...items}
196
- </InspectorPanel>;
197
- })}
198
- </>;
199
- }, 'plauditGutenbergApiExtensionsSimpleNativeProperties'));
200
- }