@siratik/wp-component-library 0.1.1 → 0.1.2

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 (96) hide show
  1. package/LICENSE +5 -0
  2. package/README.md +43 -5
  3. package/components/attachment-image/README.md +118 -0
  4. package/components/attachment-image/index.js +105 -0
  5. package/components/attributes-panel/README.md +92 -0
  6. package/components/attributes-panel/index.js +324 -0
  7. package/components/badge/README.md +61 -0
  8. package/components/badge/index.js +105 -0
  9. package/components/block-appender/README.md +158 -0
  10. package/components/block-appender/index.js +135 -0
  11. package/components/block-extension/index.js +214 -0
  12. package/components/business-hours/SpecialHoursControl.js +211 -0
  13. package/components/business-hours/WeeklyHoursControl.js +419 -0
  14. package/components/business-hours/index.js +8 -0
  15. package/components/button/README.md +172 -0
  16. package/components/button/index.js +84 -0
  17. package/components/captcha-placeholder/CloudflareTurnstile.js +111 -0
  18. package/components/captcha-placeholder/GoogleRecaptcha.js +132 -0
  19. package/components/captcha-placeholder/HCaptcha.js +99 -0
  20. package/components/captcha-placeholder/README.md +228 -0
  21. package/components/captcha-placeholder/index.js +39 -0
  22. package/components/captcha-placeholder/styles.js +33 -0
  23. package/components/geocoder/AddressEntryControl.js +390 -0
  24. package/components/geocoder/GeocoderSearchControl.js +353 -0
  25. package/components/geocoder/countryOptions.js +66 -0
  26. package/components/geocoder/geocoder.js +400 -0
  27. package/components/geocoder/index.js +16 -0
  28. package/components/icon-picker/Icon.js +53 -0
  29. package/components/icon-picker/IconPicker.js +70 -0
  30. package/components/icon-picker/IconPickerModal.js +474 -0
  31. package/components/icon-picker/IconPickerToolbarButton.js +60 -0
  32. package/components/icon-picker/InlineIconPicker.js +92 -0
  33. package/components/icon-picker/api.js +181 -0
  34. package/components/icon-picker/config-bootstrap.js +108 -0
  35. package/components/icon-picker/hooks.js +157 -0
  36. package/components/icon-picker/index.js +39 -0
  37. package/components/icon-picker/load-script.js +33 -0
  38. package/components/icon-picker/loaders.js +79 -0
  39. package/components/icon-picker/store.js +223 -0
  40. package/components/icon-picker/styles.js +232 -0
  41. package/components/icon-picker/utils.js +138 -0
  42. package/components/layout/README.md +268 -0
  43. package/components/link-tools-panel/README.md +49 -0
  44. package/components/link-tools-panel/index.js +198 -0
  45. package/components/link-tools-panel/styles.js +90 -0
  46. package/components/media/README.md +288 -0
  47. package/components/media/SettingsMediaUpload.js +187 -0
  48. package/components/media/editor-media-upload.js +65 -0
  49. package/components/media/image-controls.js +196 -0
  50. package/components/media/index.js +13 -0
  51. package/components/media/inspector-media-upload.js +290 -0
  52. package/components/media/toolbar-media-upload.js +53 -0
  53. package/components/media/utils/aspect-ratios.js +118 -0
  54. package/components/meta/MetaPanel.js +75 -0
  55. package/components/meta/README.md +46 -0
  56. package/components/meta/index.js +6 -0
  57. package/components/meta/meta-gated-editor.js +282 -0
  58. package/components/query/README.md +340 -0
  59. package/components/query/cardTemplatePartPanel.js +69 -0
  60. package/components/query/cardTemplatePartSelect.js +165 -0
  61. package/components/query/cardTemplatePartUtils.js +119 -0
  62. package/components/query/columnCountControl.js +63 -0
  63. package/components/query/displayTypeSelect.js +52 -0
  64. package/components/query/examples.js +166 -0
  65. package/components/query/hideCurrentPostControl.js +47 -0
  66. package/components/query/index.js +20 -0
  67. package/components/query/manualPostSelector.js +125 -0
  68. package/components/query/manualTermSelector.js +141 -0
  69. package/components/query/orderBySelect.js +95 -0
  70. package/components/query/postTypeSelect.js +67 -0
  71. package/components/query/postsPerPageControl.js +51 -0
  72. package/components/query/selectionModeControl.js +61 -0
  73. package/components/query/taxonomySelect.js +139 -0
  74. package/components/query/utils.js +75 -0
  75. package/components/repeater/README.md +159 -0
  76. package/components/repeater/index.js +829 -0
  77. package/components/segmented-bar/README.md +30 -0
  78. package/components/segmented-bar/index.js +68 -0
  79. package/components/sortable-select/README.md +153 -0
  80. package/components/sortable-select/custom-sortable-token.js +127 -0
  81. package/components/sortable-select/index.js +693 -0
  82. package/components/style-picker/README.md +36 -0
  83. package/components/style-picker/index.js +92 -0
  84. package/components/variable-field/README.md +299 -0
  85. package/components/variable-field/index.js +592 -0
  86. package/components/variable-field/value-to-chip-html.js +38 -0
  87. package/components/variable-inserter/README.md +68 -0
  88. package/components/variable-inserter/index.js +168 -0
  89. package/constants/statusColors.js +1 -1
  90. package/editor.js +16 -0
  91. package/index.js +127 -0
  92. package/package.json +21 -13
  93. package/utils/kitaris-localize.js +128 -0
  94. package/utils/slug-aware-color-setter.js +27 -0
  95. package/build/index.asset.php +0 -20
  96. package/build/index.js +0 -1242
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Meta Panel Component
3
+ *
4
+ * A reusable component for adding meta fields to the Gutenberg Document sidebar.
5
+ * This component provides the core structure (modal, button) for meta panels.
6
+ *
7
+ * Usage:
8
+ * ```jsx
9
+ * <MetaPanel title="My Panel">
10
+ * <TextControl ... />
11
+ * <TextareaControl ... />
12
+ * </MetaPanel>
13
+ * ```
14
+ */
15
+
16
+ import { Button, Modal } from "@wordpress/components";
17
+ import { compose } from "@wordpress/compose";
18
+ import { withSelect } from "@wordpress/data";
19
+ import { PluginPostStatusInfo } from "@wordpress/editor";
20
+ import { useState, memo } from "@wordpress/element";
21
+
22
+ const MetaPanelBase = ({
23
+ title,
24
+ postType,
25
+ children,
26
+ buttonText = "Edit Meta Details",
27
+ currentPostType,
28
+ }) => {
29
+ const [isOpen, setOpen] = useState(false);
30
+
31
+ // Only show for specified post type
32
+ if (postType && postType !== currentPostType) { return null; }
33
+
34
+ return (
35
+ <>
36
+ <PluginPostStatusInfo>
37
+ <Button
38
+ __next40pxDefaultSize
39
+ variant="secondary"
40
+ onClick={() => setOpen(true)}
41
+ style={{ flexGrow: 1, justifyContent: "center" }}
42
+ >
43
+ {buttonText}
44
+ </Button>
45
+ </PluginPostStatusInfo>
46
+ {isOpen && (
47
+ <Modal
48
+ title={title || "Meta Details"}
49
+ size="medium"
50
+ onRequestClose={() => setOpen(false)}
51
+ >
52
+ <div style={{ padding: "16px" }}>{children}</div>
53
+ <Button variant="secondary" onClick={() => setOpen(false)}>
54
+ Close
55
+ </Button>
56
+ </Modal>
57
+ )}
58
+ </>
59
+ );
60
+ };
61
+
62
+ // Memoize to prevent unnecessary re-renders
63
+ const MetaPanelMemo = memo(MetaPanelBase);
64
+
65
+ const MetaPanel = compose([
66
+ withSelect((select) => {
67
+ const { getCurrentPostType } = select("core/editor");
68
+
69
+ return {
70
+ currentPostType: getCurrentPostType(),
71
+ };
72
+ }),
73
+ ])(MetaPanelMemo);
74
+
75
+ export { MetaPanel };
@@ -0,0 +1,46 @@
1
+ # Meta Components
2
+
3
+ Components for post meta in the Gutenberg editor.
4
+
5
+ ## MetaPanel
6
+
7
+ Adds a button to the document sidebar that opens a modal for editing post meta fields. Used by plugins such as Kitaris Directory and Kitaris Reviews.
8
+
9
+ ```jsx
10
+ import { MetaPanel } from "@siratik/wp-component-library";
11
+ import { TextControl } from "@wordpress/components";
12
+
13
+ function ListingMetaPanel({ postMeta, onChange }) {
14
+ return (
15
+ <MetaPanel title="Listing Details" postType="kitaris_listing">
16
+ <TextControl
17
+ label="Website"
18
+ value={postMeta.kitaris_directory_website_url || ""}
19
+ onChange={(value) => onChange({ kitaris_directory_website_url: value })}
20
+ />
21
+ </MetaPanel>
22
+ );
23
+ }
24
+ ```
25
+
26
+ Restrict to a post type with the `postType` prop. Meta fields must be registered with `show_in_rest => true`.
27
+
28
+ ## Meta-gated editor visibility
29
+
30
+ For blocks that support meta-gated visibility (via wp-utility on the frontend), use:
31
+
32
+ - `useMetaGatedEditorVisibility` — hook for custom editor UI
33
+ - `isMetaValueEmpty` — shared empty check
34
+ - `isTemplateEditorSurface` — detect template/template-part editor context
35
+
36
+ See `meta-gated-editor.js` and the `meta-gated-editor-visibility` filter in wp-blocks.
37
+
38
+ ## Template bindings
39
+
40
+ For template parts and patterns, bind blocks to post meta with the Block Bindings API in block markup:
41
+
42
+ ```html
43
+ <!-- wp:image {"metadata":{"bindings":{"id":{"source":"core/post-meta","args":{"key":"kitaris_directory_logo_id"}}}}} -->
44
+ ```
45
+
46
+ Register meta on the post type in your plugin's `post_types_config.php`.
@@ -0,0 +1,6 @@
1
+ export {
2
+ isMetaValueEmpty,
3
+ isTemplateEditorSurface,
4
+ useMetaGatedEditorVisibility,
5
+ } from './meta-gated-editor';
6
+ export { MetaPanel } from './MetaPanel';
@@ -0,0 +1,282 @@
1
+ /**
2
+ * Editor visibility rules for hide-when-empty blocks (hideWhenMetaEmpty).
3
+ *
4
+ * Frontend output is suppressed by siratik-utility MetaGatedBlockSupport /
5
+ * render.php. In the editor, blocks stay visible on template surfaces so card
6
+ * layouts remain editable; they hide in query/post-feed loop previews when
7
+ * their Block Bindings are empty — the block's own, or for a wrapper with no
8
+ * bindings of its own, every bound block inside it.
9
+ */
10
+
11
+ import { getBlockBindingsSource } from "@wordpress/blocks";
12
+ import { store as blockEditorStore } from "@wordpress/block-editor";
13
+ import { store as coreStore } from "@wordpress/core-data";
14
+ import { useSelect } from "@wordpress/data";
15
+
16
+ /**
17
+ * @param {string|null|undefined} editorPostType Current editor post type.
18
+ * @returns {boolean}
19
+ */
20
+ export function isTemplateEditorSurface(editorPostType) {
21
+ return (
22
+ !editorPostType ||
23
+ editorPostType === "wp_template" ||
24
+ editorPostType === "wp_template_part" ||
25
+ editorPostType === "wp_block"
26
+ );
27
+ }
28
+
29
+ /** Characters PHP's trim() strips (not JS's wider whitespace set). */
30
+ const PHP_TRIM = /^[ \t\n\r\0\x0B]+|[ \t\n\r\0\x0B]+$/g;
31
+
32
+ /**
33
+ * PHP's empty() for a scalar.
34
+ *
35
+ * @param {unknown} value Value.
36
+ * @returns {boolean}
37
+ */
38
+ function isPhpEmpty(value) {
39
+ return !value || value === "0" || (Array.isArray(value) && value.length === 0);
40
+ }
41
+
42
+ /**
43
+ * Whether a bound value counts as empty, as siratik-utility
44
+ * MetaGatedRender::is_value_empty() decides on the front end: arrays and
45
+ * objects are empty unless they are a usable icon (name + source).
46
+ *
47
+ * @param {unknown} value Post meta value.
48
+ * @returns {boolean}
49
+ */
50
+ export function isMetaValueEmpty(value) {
51
+ if (value === undefined || value === null || value === false || value === "") {
52
+ return true;
53
+ }
54
+
55
+ if (Array.isArray(value)) {
56
+ return true;
57
+ }
58
+
59
+ if (typeof value === "object") {
60
+ return isPhpEmpty(value.name) || isPhpEmpty(value.source);
61
+ }
62
+
63
+ if (typeof value === "string") {
64
+ return value.replace(PHP_TRIM, "") === "";
65
+ }
66
+
67
+ return false;
68
+ }
69
+
70
+ const POST_META_SOURCE = "core/post-meta";
71
+
72
+ /**
73
+ * A post meta value as the front end reads it. Core's PHP post-meta source
74
+ * gives null for a protected key and for a key not shown in REST (absent from
75
+ * the record). REST types an unsaved integer or number field as 0 where the
76
+ * front end reads '', so 0 counts as unsaved.
77
+ *
78
+ * @returns {unknown} Meta value, or undefined when it isn't readable.
79
+ */
80
+ function readablePostMeta(select, key, context) {
81
+ if (typeof key !== "string" || key === "" || key.startsWith("_")) {
82
+ return undefined;
83
+ }
84
+
85
+ const store = select(coreStore);
86
+ const record = (store.getEditedEntityRecord ?? store.getEntityRecord)(
87
+ "postType",
88
+ context.postType,
89
+ context.postId,
90
+ );
91
+ const value = record?.meta?.[key];
92
+
93
+ return value === 0 ? "" : value;
94
+ }
95
+
96
+ /**
97
+ * Values of a block's own bindings resolved through their registered
98
+ * sources, as the front end does; null when none resolves (no binding, only
99
+ * unregistered sources, or only attributes without binding support).
100
+ *
101
+ * @returns {unknown[]|null} Bound values.
102
+ */
103
+ function ownBoundValues(select, blockName, bindings, context, clientId) {
104
+ const supported =
105
+ select(blockEditorStore).getSettings?.()?.__experimentalBlockBindingsSupportedAttributes?.[blockName];
106
+ const values = [];
107
+
108
+ for (const [attribute, binding] of Object.entries(bindings || {})) {
109
+ if (Array.isArray(supported) && !supported.includes(attribute)) {
110
+ continue;
111
+ }
112
+
113
+ if (typeof binding?.source !== "string") {
114
+ continue;
115
+ }
116
+
117
+ const args = binding.args && typeof binding.args === "object" ? binding.args : {};
118
+
119
+ if (binding.source === POST_META_SOURCE) {
120
+ values.push(readablePostMeta(select, args.key, context));
121
+ continue;
122
+ }
123
+
124
+ const source = getBlockBindingsSource(binding.source);
125
+
126
+ if (!source?.getValues) {
127
+ continue;
128
+ }
129
+
130
+ const result = source.getValues({
131
+ select,
132
+ context,
133
+ bindings: { [attribute]: { ...binding, args } },
134
+ clientId,
135
+ });
136
+ values.push(result?.[attribute]);
137
+ }
138
+
139
+ return values.length > 0 ? values : null;
140
+ }
141
+
142
+ /**
143
+ * Values of a block's own bindings, else of its descendants' bindings (for
144
+ * wrappers such as a group around a bound paragraph), as siratik-utility
145
+ * MetaGatedRender::bound_values() reads them on the front end.
146
+ *
147
+ * @returns {unknown[]|null} Bound values, or null when nothing is bound.
148
+ */
149
+ function boundValues(select, blockName, bindings, innerBlocks, context, clientId) {
150
+ const own = ownBoundValues(select, blockName, bindings, context, clientId);
151
+
152
+ if (own !== null) {
153
+ return own;
154
+ }
155
+
156
+ let values = null;
157
+
158
+ for (const inner of innerBlocks || []) {
159
+ const innerValues = boundValues(
160
+ select,
161
+ inner.name,
162
+ inner.attributes?.metadata?.bindings,
163
+ inner.innerBlocks,
164
+ context,
165
+ clientId,
166
+ );
167
+
168
+ if (innerValues !== null) {
169
+ values = [...(values ?? []), ...innerValues];
170
+ }
171
+ }
172
+
173
+ return values;
174
+ }
175
+
176
+ /**
177
+ * @param {object} props
178
+ * @param {object} props.attributes Block attributes.
179
+ * @param {string} [props.clientId] Block client ID.
180
+ * @param {object} [props.context] Block context (postId, postType).
181
+ * @returns {{ shouldHide: boolean, isTemplateSurface: boolean, isLoopPreview: boolean }}
182
+ */
183
+ export function useMetaGatedEditorVisibility({ attributes, clientId, context }) {
184
+ const { hideWhenMetaEmpty, metaField, metadata } = attributes || {};
185
+ const ownBindings = metadata?.bindings;
186
+ const contextPostId = context?.postId;
187
+ const contextPostType = context?.postType;
188
+
189
+ return useSelect(
190
+ (select) => {
191
+ const defaultResult = {
192
+ shouldHide: false,
193
+ isTemplateSurface: false,
194
+ isLoopPreview: false,
195
+ };
196
+
197
+ if (!hideWhenMetaEmpty) {
198
+ return defaultResult;
199
+ }
200
+
201
+ const editor = select("core/editor");
202
+ const editorPostType = editor?.getCurrentPostType?.() ?? null;
203
+ const editorPostId = editor?.getCurrentPostId?.() ?? null;
204
+ const isTemplateSurface = isTemplateEditorSurface(editorPostType);
205
+
206
+ if (isTemplateSurface) {
207
+ return {
208
+ shouldHide: false,
209
+ isTemplateSurface: true,
210
+ isLoopPreview: false,
211
+ };
212
+ }
213
+
214
+ const inPostContext = Boolean(contextPostId && contextPostType);
215
+ const isLoopPreview =
216
+ inPostContext &&
217
+ editorPostId !== null &&
218
+ Number(contextPostId) !== Number(editorPostId);
219
+
220
+ // Editing the source post — keep visible so fields can be filled in.
221
+ if (!isLoopPreview) {
222
+ return defaultResult;
223
+ }
224
+
225
+ const block = clientId ? select(blockEditorStore).getBlock(clientId) : null;
226
+ const values = boundValues(
227
+ select,
228
+ block?.name,
229
+ ownBindings,
230
+ block?.innerBlocks ?? [],
231
+ { postId: contextPostId, postType: contextPostType },
232
+ clientId,
233
+ );
234
+
235
+ if (values !== null) {
236
+ return {
237
+ shouldHide: values.every(isMetaValueEmpty),
238
+ isTemplateSurface: false,
239
+ isLoopPreview: true,
240
+ };
241
+ }
242
+
243
+ // Legacy: content saved before bindings keyed the gate by metaField.
244
+ if (!metaField) {
245
+ return defaultResult;
246
+ }
247
+
248
+ const record = select(coreStore).getEntityRecord(
249
+ "postType",
250
+ contextPostType,
251
+ contextPostId,
252
+ );
253
+
254
+ // The front end reads any unprotected field; the editor only sees
255
+ // REST meta, so a field not in REST stays visible.
256
+ const unreadable =
257
+ !metaField.startsWith("_") &&
258
+ !(record?.meta && Object.prototype.hasOwnProperty.call(record.meta, metaField));
259
+
260
+ return {
261
+ shouldHide: unreadable
262
+ ? false
263
+ : isMetaValueEmpty(
264
+ readablePostMeta(select, metaField, {
265
+ postType: contextPostType,
266
+ postId: contextPostId,
267
+ }),
268
+ ),
269
+ isTemplateSurface: false,
270
+ isLoopPreview: true,
271
+ };
272
+ },
273
+ [
274
+ hideWhenMetaEmpty,
275
+ metaField,
276
+ ownBindings,
277
+ clientId,
278
+ contextPostId,
279
+ contextPostType,
280
+ ],
281
+ );
282
+ }