base-js-sw 1.0.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.
- package/README.md +125 -0
- package/components/ImagePicker.js +270 -0
- package/components/LinkPicker.js +64 -0
- package/index.js +2 -0
- package/package.json +15 -0
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
<h1>Base JS Components for Gutenberg Blocks</h1>
|
|
2
|
+
|
|
3
|
+
<p>This package contains two components that can be used for WordPress Gutenberg block development:</p>
|
|
4
|
+
<ul>
|
|
5
|
+
<li><strong>Link Picker</strong></li>
|
|
6
|
+
<li><strong>Image Picker</strong></li>
|
|
7
|
+
</ul>
|
|
8
|
+
|
|
9
|
+
<h2>Installation</h2>
|
|
10
|
+
<p>To install this package, use:</p>
|
|
11
|
+
<pre><code>npm install base-js</code></pre>
|
|
12
|
+
|
|
13
|
+
<h2>Usage</h2>
|
|
14
|
+
|
|
15
|
+
<h3>1. <code>LinkPicker</code> Component</h3>
|
|
16
|
+
<p>The <code>LinkPicker</code> component allows you to select a URL, internal post, or anchor link within your block editor. It stores the selected link as a link object and supports setting a post ID or external URL.</p>
|
|
17
|
+
|
|
18
|
+
<h4>Usage in a Gutenberg Block:</h4>
|
|
19
|
+
<pre><code>import { LinkPicker } from 'base-js';
|
|
20
|
+
|
|
21
|
+
registerBlockType('sw/link-block', {
|
|
22
|
+
edit: ({ attributes, setAttributes }) => {return ..
|
|
23
|
+
```<LinkPicker
|
|
24
|
+
setAttributes={setAttributes}
|
|
25
|
+
at={attributes}
|
|
26
|
+
linkRef="link"
|
|
27
|
+
/>```},
|
|
28
|
+
save: () => null, // handled by render.php
|
|
29
|
+
});</code></pre>
|
|
30
|
+
|
|
31
|
+
<p><strong>Attributes:</strong></p>
|
|
32
|
+
<ul>
|
|
33
|
+
<li><strong>setAttributes:</strong> Function to set the block's attributes</li>
|
|
34
|
+
<li><strong>at:</strong> The current block attributes object</li>
|
|
35
|
+
<li><strong>linkRef:</strong> (Optional) The reference name for the link object (default is <code>'link'</code>)</li>
|
|
36
|
+
</ul>
|
|
37
|
+
|
|
38
|
+
<h3>2. <code>ImagePicker</code> Component</h3>
|
|
39
|
+
<p>The <code>ImagePicker</code> component allows you to select and manage images in your Gutenberg blocks, with options to crop, replace, and remove images.</p>
|
|
40
|
+
|
|
41
|
+
<h4>Usage in a Gutenberg Block:</h4>
|
|
42
|
+
<pre><code>import { ImagePicker } from 'base-js';
|
|
43
|
+
|
|
44
|
+
registerBlockType('sw/image-block', {
|
|
45
|
+
edit: ({ attributes, setAttributes }) => {
|
|
46
|
+
return ...
|
|
47
|
+
```
|
|
48
|
+
<ImagePicker
|
|
49
|
+
setAttributes={setAttributes}
|
|
50
|
+
imageObject={attributes.imageObject}
|
|
51
|
+
imageRef="imageObject"
|
|
52
|
+
buttonText="Select an image"
|
|
53
|
+
/>
|
|
54
|
+
```
|
|
55
|
+
},
|
|
56
|
+
save: () => null, // handled by render.php
|
|
57
|
+
});</code></pre>
|
|
58
|
+
|
|
59
|
+
<p><strong>Attributes:</strong></p>
|
|
60
|
+
<ul>
|
|
61
|
+
<li><strong>setAttributes:</strong> Function to set the block's attributes</li>
|
|
62
|
+
<li><strong>imageObject:</strong> The current image object with properties like <code>url</code>, <code>alt</code>, etc.</li>
|
|
63
|
+
<li><strong>imageRef:</strong> (Optional) The reference name for the image object (default is <code>'imageObject'</code>)</li>
|
|
64
|
+
<li><strong>buttonText:</strong> (Optional) The button label text (default is <code>'Select an image'</code>)</li>
|
|
65
|
+
</ul>
|
|
66
|
+
|
|
67
|
+
<h4>Block requirements</h4>
|
|
68
|
+
<pre><code>
|
|
69
|
+
{
|
|
70
|
+
"$schema": "https://schemas.wp.org/trunk/block.json",
|
|
71
|
+
"apiVersion": 2,
|
|
72
|
+
"name": "theme/block",
|
|
73
|
+
"title": "Block"
|
|
74
|
+
"attributes": {
|
|
75
|
+
"text": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"default": ""
|
|
78
|
+
},
|
|
79
|
+
"link": {
|
|
80
|
+
"type": "object",
|
|
81
|
+
"properties": {
|
|
82
|
+
"url": {
|
|
83
|
+
"type": "string",
|
|
84
|
+
"default": ""
|
|
85
|
+
},
|
|
86
|
+
"linkTarget": {
|
|
87
|
+
"type": "string",
|
|
88
|
+
"default": ""
|
|
89
|
+
},
|
|
90
|
+
"id": {
|
|
91
|
+
"type": "integer",
|
|
92
|
+
"default": false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"imageObject":{
|
|
97
|
+
"type": "object",
|
|
98
|
+
"id": {
|
|
99
|
+
"type": "integer",
|
|
100
|
+
"default": ""
|
|
101
|
+
},
|
|
102
|
+
"url": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"default": ""
|
|
105
|
+
},
|
|
106
|
+
"alt": {
|
|
107
|
+
"type": "string",
|
|
108
|
+
"default": ""
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
"editorScript": "theme-block-js",
|
|
113
|
+
"style": "theme-editor-styles",
|
|
114
|
+
"render": "file:./render.php",
|
|
115
|
+
"postTypes": ["all"]
|
|
116
|
+
}
|
|
117
|
+
</code></pre>
|
|
118
|
+
|
|
119
|
+
<h2>Contributing</h2>
|
|
120
|
+
<p>Contributions are welcome. Please submit issues or pull requests to help improve the package.</p>
|
|
121
|
+
|
|
122
|
+
<h2>License</h2>
|
|
123
|
+
<p>This project is licensed under the MIT License.</p>
|
|
124
|
+
|
|
125
|
+
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* Image Picker Component -- See params
|
|
4
|
+
* - Usage 1: <ImagePicker />
|
|
5
|
+
* - Usage 2: <ImagePickerPanelBody />
|
|
6
|
+
* - Usage 3: <ImagePickerPreview />
|
|
7
|
+
*/
|
|
8
|
+
import { __ } from '@wordpress/i18n';
|
|
9
|
+
import { MediaUpload, MediaUploadCheck } from '@wordpress/blockEditor';
|
|
10
|
+
import { PanelBody, Button } from '@wordpress/components';
|
|
11
|
+
import { __experimentalAlignmentMatrixControl as AlignmentMatrixControl } from '@wordpress/components';
|
|
12
|
+
const { siteDomain } = themeVars; // vars passed from enqueue_backend.php
|
|
13
|
+
|
|
14
|
+
export const ImagePicker = ({ setAttributes, imageObject = {}, extraClass, imageRef = 'imageObject', buttonText = 'Select image', showCrop = true }) => {
|
|
15
|
+
let componentClass = 'component-image-picker';
|
|
16
|
+
|
|
17
|
+
let imageOptionsString = 'images/width=250,height=150,crop=0';
|
|
18
|
+
let resizedImageUrl = '';
|
|
19
|
+
|
|
20
|
+
if(imageObject.hasOwnProperty('url') && imageObject.url !== ''){
|
|
21
|
+
if(imageObject.url.endsWith('.svg')) {
|
|
22
|
+
resizedImageUrl = imageObject.url;
|
|
23
|
+
} else {
|
|
24
|
+
let imageUrlMinusBase = imageObject.url.replace( siteDomain+'/app/uploads','' ); // remove base and directory
|
|
25
|
+
resizedImageUrl = imageOptionsString + imageUrlMinusBase;
|
|
26
|
+
resizedImageUrl = siteDomain + '/' + resizedImageUrl;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div className={extraClass ? componentClass + ' ' + extraClass : componentClass}>
|
|
32
|
+
{imageObject.hasOwnProperty('url') && imageObject.url !== '' ? //Has image
|
|
33
|
+
<MediaUploadCheck>
|
|
34
|
+
<div className={componentClass + "__media-wrapper"}>
|
|
35
|
+
<img
|
|
36
|
+
className={componentClass + "__media"}
|
|
37
|
+
src={resizedImageUrl}
|
|
38
|
+
alt={imageObject.alt}
|
|
39
|
+
/>
|
|
40
|
+
{showCrop && imageObject && typeof imageObject.crop !== 'undefined' &&
|
|
41
|
+
<>
|
|
42
|
+
<AlignmentMatrixControl
|
|
43
|
+
value={ imageObject.crop }
|
|
44
|
+
onChange={(value) => updateImageCrop(setAttributes, value, imageObject, imageRef)}
|
|
45
|
+
/>
|
|
46
|
+
</>
|
|
47
|
+
}
|
|
48
|
+
</div>
|
|
49
|
+
<EditImageButtons
|
|
50
|
+
componentClass={componentClass}
|
|
51
|
+
setAttributes={setAttributes}
|
|
52
|
+
imageRef={imageRef}
|
|
53
|
+
replaceText='Replace'
|
|
54
|
+
removeText='Remove'
|
|
55
|
+
imageId={imageObject.id}
|
|
56
|
+
/>
|
|
57
|
+
</MediaUploadCheck>
|
|
58
|
+
: //No image
|
|
59
|
+
<UploadImageButton
|
|
60
|
+
componentClass={componentClass}
|
|
61
|
+
setAttributes={setAttributes}
|
|
62
|
+
imageRef={imageRef}
|
|
63
|
+
buttonText={buttonText}
|
|
64
|
+
/>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/*
|
|
74
|
+
* Image Picker Panel Body
|
|
75
|
+
* Used for Inspector Controls
|
|
76
|
+
*/
|
|
77
|
+
export const ImagePickerPanelBody = ({ setAttributes, imageObject, imageRef = null, title = 'Image', showCrop = true }) => {
|
|
78
|
+
return(
|
|
79
|
+
<PanelBody title={title} initialOpen={true}>
|
|
80
|
+
<ImagePicker
|
|
81
|
+
setAttributes={setAttributes}
|
|
82
|
+
imageRef={imageRef}
|
|
83
|
+
imageObject={imageObject}
|
|
84
|
+
showCrop={showCrop}
|
|
85
|
+
/>
|
|
86
|
+
</PanelBody>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/*
|
|
91
|
+
* Image Picker Preview
|
|
92
|
+
* Used for image inside block in the edit function
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
export const ImagePickerPreview = ({
|
|
96
|
+
setAttributes,
|
|
97
|
+
imageObject = {},
|
|
98
|
+
blockClass,
|
|
99
|
+
imageClass,
|
|
100
|
+
width = 500,
|
|
101
|
+
height = 500,
|
|
102
|
+
crop = true,
|
|
103
|
+
imageRef = 'imageObject',
|
|
104
|
+
buttonText = 'Select image'
|
|
105
|
+
}) => {
|
|
106
|
+
|
|
107
|
+
let componentClass = 'component-image-picker-preview';
|
|
108
|
+
let imageElementClass = '';
|
|
109
|
+
let imageElementWrapperClass = ''
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if( imageClass ){//Image class
|
|
113
|
+
imageElementClass = blockClass + imageClass;
|
|
114
|
+
imageElementWrapperClass = blockClass + imageClass + '-wrapper';
|
|
115
|
+
|
|
116
|
+
} else if( blockClass ){//Block class
|
|
117
|
+
imageElementClass = blockClass + '__image';
|
|
118
|
+
imageElementWrapperClass = blockClass + '__image-wrapper';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
let imagecrop = crop ? '1':'0';
|
|
123
|
+
|
|
124
|
+
if(imageObject.crop){
|
|
125
|
+
imagecrop = imageObject.crop.replace(/\s+/g, '-');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
let imageOptionsString = 'images/width='+width+',height='+height+',crop='+imagecrop;
|
|
129
|
+
let imageOptionsStringDouble = 'images/width='+width*2+',height='+height*2+',crop='+imagecrop;
|
|
130
|
+
let resizedImageUrl = '';
|
|
131
|
+
let resizedImageUrlDouble = '';
|
|
132
|
+
|
|
133
|
+
if(imageObject.hasOwnProperty('url') && imageObject.url !== ''){
|
|
134
|
+
|
|
135
|
+
let imageUrlMinusBase = imageObject.url.replace( siteDomain+'/app/uploads','' ); // remove base and directory
|
|
136
|
+
|
|
137
|
+
if(imageObject.url.endsWith('.svg')) {
|
|
138
|
+
resizedImageUrl = imageObject.url;
|
|
139
|
+
resizedImageUrlDouble = imageObject.url;
|
|
140
|
+
} else {
|
|
141
|
+
resizedImageUrl = imageOptionsString + imageUrlMinusBase;
|
|
142
|
+
resizedImageUrl = siteDomain + '/' + resizedImageUrl;
|
|
143
|
+
resizedImageUrlDouble = imageOptionsStringDouble + imageUrlMinusBase
|
|
144
|
+
resizedImageUrlDouble = siteDomain + '/' + resizedImageUrlDouble;
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
componentClass += ' no-image';
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return (
|
|
151
|
+
<div className={imageElementWrapperClass ? componentClass + ' ' + imageElementWrapperClass : componentClass}>
|
|
152
|
+
{imageObject.hasOwnProperty('url') && imageObject.url !== '' ? //Has image
|
|
153
|
+
<MediaUploadCheck>
|
|
154
|
+
<img
|
|
155
|
+
className={imageElementClass ? imageElementClass : ''}
|
|
156
|
+
src={resizedImageUrl}
|
|
157
|
+
srcSet={resizedImageUrl+' 1x, '+resizedImageUrlDouble+' 2x'}
|
|
158
|
+
alt={imageObject.alt}
|
|
159
|
+
/>
|
|
160
|
+
<EditImageButtons
|
|
161
|
+
componentClass={componentClass}
|
|
162
|
+
setAttributes={setAttributes}
|
|
163
|
+
imageRef={imageRef}
|
|
164
|
+
replaceText=''
|
|
165
|
+
removeText=''
|
|
166
|
+
imageId={imageObject.id}
|
|
167
|
+
/>
|
|
168
|
+
</MediaUploadCheck>
|
|
169
|
+
: //No image
|
|
170
|
+
<UploadImageButton
|
|
171
|
+
componentClass={componentClass}
|
|
172
|
+
setAttributes={setAttributes}
|
|
173
|
+
imageRef={imageRef}
|
|
174
|
+
buttonText={buttonText}
|
|
175
|
+
/>
|
|
176
|
+
}
|
|
177
|
+
</div>
|
|
178
|
+
)
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// ============= Image Controls (appear over image within editor)
|
|
182
|
+
export const EditImageButtons = ({ componentClass, setAttributes, replaceText, removeText, imageRef, imageId }) => {
|
|
183
|
+
return (
|
|
184
|
+
<MediaUpload
|
|
185
|
+
title={'Icon'}
|
|
186
|
+
onSelect={(media) => updateImageAttr(setAttributes, media, imageRef)}
|
|
187
|
+
allowedTypes={['image']}
|
|
188
|
+
value={imageId}
|
|
189
|
+
render={({ open }) => (
|
|
190
|
+
<div className={componentClass + "__edit-buttons"}>
|
|
191
|
+
|
|
192
|
+
<Button
|
|
193
|
+
className={componentClass + "__edit-button"}
|
|
194
|
+
isSecondary
|
|
195
|
+
onClick={open}
|
|
196
|
+
icon="images-alt2"
|
|
197
|
+
>
|
|
198
|
+
{replaceText}
|
|
199
|
+
</Button>
|
|
200
|
+
|
|
201
|
+
<Button
|
|
202
|
+
className={componentClass + "__edit-button"}
|
|
203
|
+
isDestructive
|
|
204
|
+
onClick={ (media) => {
|
|
205
|
+
updateImageAttr(setAttributes, false, imageRef)
|
|
206
|
+
}}
|
|
207
|
+
icon="remove"
|
|
208
|
+
>
|
|
209
|
+
{removeText}
|
|
210
|
+
</Button>
|
|
211
|
+
</div>
|
|
212
|
+
)}
|
|
213
|
+
/>
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const UploadImageButton = ({ componentClass, setAttributes, imageRef, buttonText }) => {
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
return (
|
|
222
|
+
<MediaUploadCheck>
|
|
223
|
+
<MediaUpload
|
|
224
|
+
onSelect={(media) => {
|
|
225
|
+
updateImageAttr(setAttributes, media, imageRef)
|
|
226
|
+
}}
|
|
227
|
+
render={({ open }) => {
|
|
228
|
+
return (
|
|
229
|
+
<Button onClick={open} className={componentClass + "__upload-button"} isSecondary icon="images-alt2">
|
|
230
|
+
{buttonText}
|
|
231
|
+
</Button>
|
|
232
|
+
);
|
|
233
|
+
}}
|
|
234
|
+
/>
|
|
235
|
+
</MediaUploadCheck>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const updateImageCrop = (setAttributes, crop, imageObject, imageRef) => {
|
|
240
|
+
imageObject.crop = crop;
|
|
241
|
+
|
|
242
|
+
setAttributes({ [imageRef]: {
|
|
243
|
+
id: imageObject.id,
|
|
244
|
+
url: imageObject.url,
|
|
245
|
+
alt: imageObject.alt,
|
|
246
|
+
crop: crop
|
|
247
|
+
} }); //used to update imagePickerPreview
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const updateImageAttr = (
|
|
251
|
+
setAttributes,
|
|
252
|
+
media = false,
|
|
253
|
+
imageRef,
|
|
254
|
+
) => {
|
|
255
|
+
|
|
256
|
+
imageRef ??= 'imageObject';
|
|
257
|
+
|
|
258
|
+
let newImageUrl = media ? media.sizes.full.url : ''
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
setAttributes({
|
|
262
|
+
[imageRef]: {
|
|
263
|
+
id: media.id,
|
|
264
|
+
url: newImageUrl,
|
|
265
|
+
alt: media.alt,
|
|
266
|
+
crop: 'center center'
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
};
|
|
270
|
+
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { __experimentalLinkControl as LinkControl } from '@wordpress/blockEditor';
|
|
2
|
+
import { safeDecodeURI } from '@wordpress/url';
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
|
|
5
|
+
export const LinkPicker = ({ setAttributes, at, linkRef = 'link' }) => {
|
|
6
|
+
// Get the current link object based on linkRef or fallback to an empty object
|
|
7
|
+
const linkObj = at[linkRef] || {};
|
|
8
|
+
const { url, linkTarget, id } = linkObj;
|
|
9
|
+
const opensInNewTab = linkTarget === '_blank';
|
|
10
|
+
|
|
11
|
+
// Local state to track temporary URL, ID, and link target
|
|
12
|
+
const [localUrl, setLocalUrl] = useState(url || '');
|
|
13
|
+
const [localId, setLocalId] = useState(id || '');
|
|
14
|
+
const [localLinkTarget, setLocalLinkTarget] = useState(opensInNewTab);
|
|
15
|
+
|
|
16
|
+
// Effect to update attributes when the local state changes
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
setAttributes({
|
|
19
|
+
[linkRef]: {
|
|
20
|
+
url: localUrl ? encodeURI(safeDecodeURI(localUrl)) : '', // Ensure proper encoding for the URL if no ID
|
|
21
|
+
linkTarget: localLinkTarget ? '_blank' : undefined,
|
|
22
|
+
id: localId ? localId : undefined, // Set ID if available, else undefined
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}, [localUrl, localId, localLinkTarget]); // Trigger whenever the URL, ID, or target changes
|
|
26
|
+
|
|
27
|
+
const handleLinkChange = (updatedValue) => {
|
|
28
|
+
console.log(updatedValue)
|
|
29
|
+
const { url, opensInNewTab, id, kind } = updatedValue;
|
|
30
|
+
|
|
31
|
+
// Update ID or URL depending on whether it's an internal post or an external link/anchor
|
|
32
|
+
if (kind === 'post-type' && id) {
|
|
33
|
+
// Internal post, use ID
|
|
34
|
+
setLocalId(id);
|
|
35
|
+
setLocalUrl(url);
|
|
36
|
+
} else {
|
|
37
|
+
// External or anchor link, use URL
|
|
38
|
+
setLocalUrl(url);
|
|
39
|
+
setLocalId(''); // Clear the ID since we are using the URL
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Update the new tab option
|
|
43
|
+
setLocalLinkTarget(opensInNewTab);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<LinkControl
|
|
48
|
+
value={{ url: localUrl, opensInNewTab: localLinkTarget, id: localId }}
|
|
49
|
+
onChange={(updatedValue) => {
|
|
50
|
+
handleLinkChange(updatedValue);
|
|
51
|
+
}}
|
|
52
|
+
onBlur={() => {
|
|
53
|
+
// Save on blur to ensure the value is saved when clicking away
|
|
54
|
+
setAttributes({
|
|
55
|
+
[linkRef]: {
|
|
56
|
+
url: localId ? '' : localUrl ? encodeURI(safeDecodeURI(localUrl)) : '',
|
|
57
|
+
linkTarget: localLinkTarget ? '_blank' : undefined,
|
|
58
|
+
id: localId ? localId : undefined,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "base-js-sw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Reusable Gutenberg block components for WordPress projects",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"author": "Shape Works",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"peerDependencies": {
|
|
9
|
+
"@wordpress/blockEditor": "^12.0.0",
|
|
10
|
+
"@wordpress/i18n": "^3.0.0",
|
|
11
|
+
"@wordpress/url": "^4.0.0",
|
|
12
|
+
"@wordpress/components": "^14.0.0"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|