@valu/block-components 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,2034 @@
1
+ 'use strict';
2
+
3
+ var react$1 = require('react');
4
+ var reactQuery = require('@tanstack/react-query');
5
+ var components = require('@wordpress/components');
6
+ var i18n = require('@wordpress/i18n');
7
+ var icons = require('@wordpress/icons');
8
+ var styled = require('@emotion/styled');
9
+ var react = require('@emotion/react');
10
+ var uuid = require('uuid');
11
+ var htmlEntities = require('@wordpress/html-entities');
12
+ var url = require('@wordpress/url');
13
+ var jsxRuntime = require('react/jsx-runtime');
14
+ var data = require('@wordpress/data');
15
+ var apiFetch = require('@wordpress/api-fetch');
16
+ var a11y = require('@wordpress/a11y');
17
+ var createCache = require('@emotion/cache');
18
+ var core = require('@dnd-kit/core');
19
+ var sortable = require('@dnd-kit/sortable');
20
+ var utilities = require('@dnd-kit/utilities');
21
+ var coreData = require('@wordpress/core-data');
22
+
23
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
24
+
25
+ var styled__default = /*#__PURE__*/_interopDefault(styled);
26
+ var apiFetch__default = /*#__PURE__*/_interopDefault(apiFetch);
27
+ var createCache__default = /*#__PURE__*/_interopDefault(createCache);
28
+
29
+ // components/content-picker/ContentPicker.tsx
30
+
31
+ // components/content-picker/styles/tokens.ts
32
+ var tokens = {
33
+ accent: "var(--wp-admin-theme-color, #3858e9)",
34
+ accentHover: "color-mix(in srgb, var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)) 4%, transparent)",
35
+ accentActive: "color-mix(in srgb, var(--wp-components-color-accent, var(--wp-admin-theme-color, #3858e9)) 8%, transparent)",
36
+ bg: "var(--wp-components-color-background, #ffffff)",
37
+ fg: "var(--wp-components-color-foreground, #1e1e1e)",
38
+ gray100: "var(--wp-components-color-gray-100, #f0f0f0)",
39
+ gray200: "var(--wp-components-color-gray-200, #ebebeb)",
40
+ gray300: "var(--wp-components-color-gray-300, #dcdcde)",
41
+ gray600: "var(--wp-components-color-gray-600, #757575)",
42
+ gray900: "var(--wp-components-color-gray-900, #1e1e1e)",
43
+ danger: "#cc1818",
44
+ dangerSoft: "#fef7f7",
45
+ dangerLine: "#f0b7b7",
46
+ success: "#007017",
47
+ warning: "#bd8600",
48
+ radiusSm: "2px",
49
+ radiusMd: "4px",
50
+ rowMinHeight: "40px",
51
+ suggestionMinHeight: "44px",
52
+ thumbSm: "24px",
53
+ thumbMd: "32px",
54
+ thumbLg: "40px",
55
+ shadowPopover: "0 6px 24px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.06)",
56
+ spaceXs: "4px",
57
+ spaceSm: "8px",
58
+ spaceMd: "12px",
59
+ spaceLg: "16px",
60
+ transitionFast: "120ms ease-out",
61
+ fontSizeBody: "13px",
62
+ fontSizeSmall: "11px",
63
+ fontWeightMedium: 500,
64
+ fontWeightBold: 600
65
+ };
66
+
67
+ // components/content-picker/styles/index.ts
68
+ var focusRing = react.css`
69
+ outline: 2px solid ${tokens.accent};
70
+ outline-offset: 1px;
71
+ `;
72
+ react.css`
73
+ position: absolute !important;
74
+ clip: rect(1px, 1px, 1px, 1px);
75
+ width: 1px;
76
+ height: 1px;
77
+ overflow: hidden;
78
+ white-space: nowrap;
79
+ `;
80
+ var Root = styled__default.default.div`
81
+ color: ${tokens.fg};
82
+ font-size: ${tokens.fontSizeBody};
83
+
84
+ * {
85
+ box-sizing: border-box;
86
+ }
87
+ `;
88
+ styled__default.default.label`
89
+ display: block;
90
+ margin-bottom: ${tokens.spaceXs};
91
+ font-weight: ${tokens.fontWeightMedium};
92
+ font-size: ${tokens.fontSizeSmall};
93
+ text-transform: uppercase;
94
+ letter-spacing: 0.02em;
95
+ color: ${tokens.gray900};
96
+ `;
97
+ styled__default.default.p`
98
+ margin: ${tokens.spaceXs} 0 0;
99
+ color: ${tokens.gray600};
100
+ font-size: ${tokens.fontSizeSmall};
101
+ `;
102
+ var InputShell = styled__default.default.div`
103
+ position: relative;
104
+ display: flex;
105
+ align-items: center;
106
+ gap: ${tokens.spaceSm};
107
+ background: ${tokens.bg};
108
+ border: 1px solid ${tokens.gray300};
109
+ border-radius: ${tokens.radiusMd};
110
+ padding: 6px ${tokens.spaceMd};
111
+ min-height: 40px;
112
+ transition: border-color ${tokens.transitionFast}, box-shadow ${tokens.transitionFast};
113
+
114
+ &:focus-within {
115
+ border-color: ${tokens.accent};
116
+ box-shadow: 0 0 0 1px ${tokens.accent};
117
+ }
118
+ `;
119
+ var Input = styled__default.default.input`
120
+ flex: 1;
121
+ background: transparent;
122
+ font: inherit;
123
+ color: ${tokens.fg};
124
+ min-width: 0;
125
+
126
+ /*
127
+ * WP admin's global input styles (\`input[type="text"]\` in various
128
+ * editor/sidebar stylesheets) set their own border + box-shadow at a
129
+ * higher specificity than our Emotion class. \`!important\` is the
130
+ * pragmatic reset here — we provide the focus chrome on the enclosing
131
+ * \`InputShell\`, so every input-level border/outline/box-shadow
132
+ * contribution is noise.
133
+ */
134
+ &,
135
+ &:focus,
136
+ &:focus-visible,
137
+ &:hover {
138
+ border: 0 !important;
139
+ outline: 0 !important;
140
+ box-shadow: none !important;
141
+ }
142
+
143
+ &::placeholder {
144
+ color: ${tokens.gray600};
145
+ }
146
+ `;
147
+ var SuggestionsShell = styled__default.default.div`
148
+ background: ${tokens.bg};
149
+ border: 1px solid ${tokens.gray300};
150
+ border-radius: ${tokens.radiusMd};
151
+ overflow: hidden;
152
+ max-height: 380px;
153
+ display: flex;
154
+ flex-direction: column;
155
+
156
+ ${({ inline }) => inline ? react.css`
157
+ margin-top: ${tokens.spaceSm};
158
+ ` : react.css`
159
+ box-shadow: ${tokens.shadowPopover};
160
+ `}
161
+ `;
162
+ var ListboxScroll = styled__default.default.div`
163
+ overflow-y: auto;
164
+ flex: 1;
165
+ min-height: 0;
166
+ `;
167
+ var StickyHeader = styled__default.default.div`
168
+ position: sticky;
169
+ top: 0;
170
+ z-index: 2;
171
+ background: ${tokens.bg};
172
+ `;
173
+ var Listbox = styled__default.default.ul`
174
+ list-style: none;
175
+ margin: 0;
176
+ padding: ${tokens.spaceXs} 0;
177
+ `;
178
+ var GroupHeading = styled__default.default.div`
179
+ padding: 4px ${tokens.spaceMd};
180
+ font-size: 10px !important;
181
+ font-weight: ${tokens.fontWeightBold};
182
+ letter-spacing: 0.08em;
183
+ text-transform: uppercase !important;
184
+ color: ${tokens.gray600};
185
+ background: ${tokens.gray100};
186
+ line-height: 1.8;
187
+
188
+ [role='group']:not(:first-child) > & {
189
+ border-top: 1px solid ${tokens.gray200};
190
+ }
191
+ `;
192
+ var PrimaryText = styled__default.default.span`
193
+ font-size: ${tokens.fontSizeBody};
194
+ font-weight: ${tokens.fontWeightMedium};
195
+ color: ${({ muted }) => muted ? tokens.gray600 : tokens.gray900};
196
+ white-space: nowrap;
197
+ overflow: hidden;
198
+ text-overflow: ellipsis;
199
+ display: block;
200
+ line-height: 1.3;
201
+ transition: color ${tokens.transitionFast};
202
+ `;
203
+ var Option = styled__default.default.li`
204
+ display: flex;
205
+ align-items: center;
206
+ gap: ${tokens.spaceSm};
207
+ padding: 4px ${tokens.spaceMd};
208
+ cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
209
+ opacity: ${({ disabled }) => disabled ? 0.5 : 1};
210
+ min-height: ${tokens.suggestionMinHeight};
211
+ background: ${({ highlighted }) => highlighted ? tokens.accentActive : "transparent"};
212
+ position: relative;
213
+ transition: background ${tokens.transitionFast};
214
+
215
+ &:hover {
216
+ background: ${({ highlighted, disabled }) => disabled ? "transparent" : highlighted ? tokens.accentActive : tokens.accentHover};
217
+ }
218
+
219
+ /*
220
+ * Target the title via a stable data attribute rather than Emotion's
221
+ * generated class — the popover body has its own Emotion cache, so
222
+ * class-name interpolation isn't guaranteed to match across caches.
223
+ */
224
+ &:hover [data-valu-title] {
225
+ color: ${tokens.accent};
226
+ }
227
+
228
+ ${({ highlighted }) => highlighted && react.css`
229
+ [data-valu-title] {
230
+ color: ${tokens.accent};
231
+ }
232
+ &::before {
233
+ content: '';
234
+ position: absolute;
235
+ left: 0;
236
+ top: 4px;
237
+ bottom: 4px;
238
+ width: 2px;
239
+ border-radius: 0 ${tokens.radiusSm} ${tokens.radiusSm} 0;
240
+ background: ${tokens.accent};
241
+ }
242
+ `}
243
+ `;
244
+ var Thumb = styled__default.default.div`
245
+ flex: 0 0 auto;
246
+ width: ${({ size, hasImage, avatar }) => avatar ? "22px" : hasImage ? tokens.thumbMd : size === "md" ? tokens.thumbMd : size === "lg" ? tokens.thumbLg : size === "sm" ? tokens.thumbSm : "22px"};
247
+ height: ${({ size, hasImage, avatar }) => avatar ? "22px" : hasImage ? tokens.thumbMd : size === "md" ? tokens.thumbMd : size === "lg" ? tokens.thumbLg : size === "sm" ? tokens.thumbSm : "22px"};
248
+ display: flex;
249
+ align-items: center;
250
+ justify-content: center;
251
+ color: ${({ missing }) => missing ? tokens.danger : tokens.gray600};
252
+
253
+ ${({ hasImage, avatar }) => avatar ? react.css`
254
+ border-radius: 999px;
255
+ overflow: hidden;
256
+ ` : hasImage ? react.css`
257
+ border: 1px solid ${tokens.gray200};
258
+ border-radius: ${tokens.radiusSm};
259
+ overflow: hidden;
260
+ background: ${tokens.gray100};
261
+ ` : void 0}
262
+
263
+ img {
264
+ width: 100%;
265
+ height: 100%;
266
+ object-fit: cover;
267
+ display: block;
268
+ }
269
+
270
+ svg {
271
+ width: 22px;
272
+ height: 22px;
273
+ }
274
+ `;
275
+ var ItemBody = styled__default.default.div`
276
+ flex: 1;
277
+ min-width: 0;
278
+ display: flex;
279
+ flex-direction: column;
280
+ gap: 0;
281
+ `;
282
+ var SecondaryText = styled__default.default.span`
283
+ font-size: ${tokens.fontSizeSmall};
284
+ color: ${tokens.gray600};
285
+ white-space: nowrap;
286
+ overflow: hidden;
287
+ text-overflow: ellipsis;
288
+ display: block;
289
+ line-height: 1.3;
290
+ `;
291
+ styled__default.default.div`
292
+ display: flex;
293
+ align-items: center;
294
+ gap: ${tokens.spaceXs};
295
+ flex-wrap: wrap;
296
+ margin-top: 0;
297
+ `;
298
+ var TypeHint = styled__default.default.span`
299
+ flex: 0 0 auto;
300
+ font-size: ${tokens.fontSizeSmall};
301
+ color: ${tokens.gray600};
302
+ margin-left: ${tokens.spaceSm};
303
+ white-space: nowrap;
304
+ `;
305
+ var ActionColumn = styled__default.default.div`
306
+ flex: 0 0 auto;
307
+ display: flex;
308
+ align-items: center;
309
+ gap: ${tokens.spaceXs};
310
+ margin-left: auto;
311
+ `;
312
+ var SelectedListEl = styled__default.default.ul`
313
+ list-style: none;
314
+ margin: ${tokens.spaceSm} 0 0;
315
+ padding: 0;
316
+ display: flex;
317
+ flex-direction: column;
318
+ gap: 2px;
319
+ `;
320
+ var SelectedRow = styled__default.default.li`
321
+ display: flex;
322
+ align-items: center;
323
+ gap: ${tokens.spaceSm};
324
+ padding: 2px ${tokens.spaceSm};
325
+ background: ${({ missing }) => missing ? tokens.dangerSoft : tokens.bg};
326
+ border: 1px solid ${({ missing }) => missing ? tokens.dangerLine : tokens.gray300};
327
+ border-radius: ${tokens.radiusMd};
328
+ min-height: ${tokens.rowMinHeight};
329
+ box-shadow: ${({ dragging }) => dragging ? tokens.shadowPopover : "none"};
330
+ transition: box-shadow ${tokens.transitionFast}, border-color ${tokens.transitionFast};
331
+
332
+ &:hover {
333
+ border-color: ${({ missing }) => missing ? tokens.dangerLine : tokens.gray600};
334
+ }
335
+ `;
336
+ var DragHandleButton = styled__default.default.button`
337
+ flex: 0 0 auto;
338
+ display: flex;
339
+ align-items: center;
340
+ justify-content: center;
341
+ width: 20px;
342
+ height: 20px;
343
+ border: 0;
344
+ background: transparent;
345
+ color: ${tokens.gray600};
346
+ cursor: grab;
347
+ border-radius: ${tokens.radiusSm};
348
+ padding: 0;
349
+
350
+ &:hover {
351
+ background: ${tokens.gray100};
352
+ color: ${tokens.gray900};
353
+ }
354
+
355
+ &:focus-visible {
356
+ ${focusRing}
357
+ }
358
+
359
+ &:active {
360
+ cursor: grabbing;
361
+ }
362
+
363
+ &[disabled] {
364
+ cursor: not-allowed;
365
+ opacity: 0.4;
366
+ }
367
+
368
+ svg {
369
+ width: 14px;
370
+ height: 14px;
371
+ }
372
+ `;
373
+ var EmptyState = styled__default.default.div`
374
+ padding: ${tokens.spaceLg} ${tokens.spaceMd};
375
+ text-align: center;
376
+ color: ${tokens.gray600};
377
+ font-size: ${tokens.fontSizeBody};
378
+ `;
379
+ var LoadingState = styled__default.default.div`
380
+ padding: ${tokens.spaceLg} ${tokens.spaceMd};
381
+ display: flex;
382
+ align-items: center;
383
+ justify-content: center;
384
+ gap: ${tokens.spaceSm};
385
+ color: ${tokens.gray600};
386
+ font-size: ${tokens.fontSizeBody};
387
+ `;
388
+ var ErrorState = styled__default.default.div`
389
+ padding: ${tokens.spaceMd};
390
+ background: ${tokens.dangerSoft};
391
+ color: ${tokens.danger};
392
+ border: 1px solid ${tokens.dangerLine};
393
+ border-radius: ${tokens.radiusMd};
394
+ font-size: ${tokens.fontSizeBody};
395
+ `;
396
+ var MaxedOutBanner = styled__default.default.div`
397
+ display: flex;
398
+ align-items: center;
399
+ gap: ${tokens.spaceSm};
400
+ padding: ${tokens.spaceSm} ${tokens.spaceMd};
401
+ background: ${tokens.gray100};
402
+ color: ${tokens.gray900};
403
+ border: 1px dashed ${tokens.gray300};
404
+ border-radius: ${tokens.radiusMd};
405
+ font-size: ${tokens.fontSizeSmall};
406
+ `;
407
+ var CountPill = styled__default.default.span`
408
+ display: inline-flex;
409
+ align-items: center;
410
+ padding: 2px 8px;
411
+ background: ${tokens.gray100};
412
+ color: ${tokens.gray900};
413
+ border-radius: 999px;
414
+ font-size: 10px;
415
+ font-weight: ${tokens.fontWeightBold};
416
+ letter-spacing: 0.04em;
417
+ `;
418
+ var ChipWrap = styled__default.default.div`
419
+ display: inline-flex;
420
+ align-items: center;
421
+ gap: ${tokens.spaceSm};
422
+ background: ${tokens.gray900};
423
+ color: #fff;
424
+ border-radius: ${tokens.radiusMd};
425
+ padding: 6px ${tokens.spaceMd};
426
+ max-width: 280px;
427
+ box-shadow: ${tokens.shadowPopover};
428
+ `;
429
+ var ChipTitle = styled__default.default.span`
430
+ font-size: ${tokens.fontSizeSmall};
431
+ font-weight: ${tokens.fontWeightMedium};
432
+ white-space: nowrap;
433
+ overflow: hidden;
434
+ text-overflow: ellipsis;
435
+ `;
436
+ var LoadMoreRow = styled__default.default.li`
437
+ list-style: none;
438
+ display: flex;
439
+ justify-content: center;
440
+ padding: ${tokens.spaceSm};
441
+ `;
442
+ var SearchIconWrap = styled__default.default.span`
443
+ flex: 0 0 auto;
444
+ display: flex;
445
+ align-items: center;
446
+ color: ${tokens.gray600};
447
+ `;
448
+ var HeaderRow = styled__default.default.div`
449
+ display: flex;
450
+ align-items: center;
451
+ justify-content: space-between;
452
+ gap: ${tokens.spaceSm};
453
+ margin-top: ${tokens.spaceSm};
454
+ margin-bottom: ${tokens.spaceXs};
455
+ `;
456
+ var HeaderTitle = styled__default.default.h3`
457
+ margin: 0;
458
+ font-size: 10px !important;
459
+ text-transform: uppercase !important;
460
+ letter-spacing: 0.04em;
461
+ font-weight: ${tokens.fontWeightBold};
462
+ color: ${tokens.gray600};
463
+ line-height: 1.8;
464
+ `;
465
+ var generateKey = () => uuid.v4();
466
+ var decodeTitle = (raw) => {
467
+ if (typeof raw === "string") return htmlEntities.decodeEntities(raw);
468
+ if (raw && typeof raw === "object" && "rendered" in raw) {
469
+ const rendered = raw.rendered;
470
+ if (typeof rendered === "string") return htmlEntities.decodeEntities(rendered);
471
+ }
472
+ return "";
473
+ };
474
+ var toDisplayUrl = (url$1) => {
475
+ if (!url$1) return "";
476
+ try {
477
+ return url.filterURLForDisplay(url.safeDecodeURI(url$1)) ?? "";
478
+ } catch {
479
+ return url$1;
480
+ }
481
+ };
482
+ var groupByType = (items) => {
483
+ const map = /* @__PURE__ */ new Map();
484
+ for (const item of items) {
485
+ const bucket = map.get(item.type);
486
+ if (bucket) bucket.push(item);
487
+ else map.set(item.type, [item]);
488
+ }
489
+ return Array.from(map.entries());
490
+ };
491
+ var postTypeLabels = {
492
+ post: "Post",
493
+ page: "Page",
494
+ attachment: "Media"
495
+ };
496
+ var humanizeType = (type, kind) => {
497
+ if (kind === "user") return i18n.__("User", "valu-block-components");
498
+ if (postTypeLabels[type]) return postTypeLabels[type];
499
+ return type.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
500
+ };
501
+ var statusLabels = {
502
+ draft: "Draft",
503
+ pending: "Pending",
504
+ private: "Private",
505
+ future: "Scheduled",
506
+ trash: "Trash"
507
+ };
508
+ var humanizeStatus = (status) => {
509
+ if (!status || status === "publish") return null;
510
+ return statusLabels[status] ?? status;
511
+ };
512
+ var aria = (rootId, suffix) => `${rootId}-${suffix}`;
513
+ var isMaxedOut = (value, maxItems) => Number.isFinite(maxItems) && value.length >= maxItems;
514
+ var dedupeAgainst = (items, value, enabled) => {
515
+ if (!enabled || value.length === 0) return items;
516
+ const ids = new Set(value.map((v) => v.id));
517
+ return items.filter((i) => !ids.has(i.id));
518
+ };
519
+ var DEFAULT_ITEM_ICONS = {
520
+ user: icons.people
521
+ };
522
+ var resolveItemIcon = (item, userIcons) => {
523
+ if (userIcons && item.type in userIcons) return userIcons[item.type];
524
+ if (item.type in DEFAULT_ITEM_ICONS) return DEFAULT_ITEM_ICONS[item.type];
525
+ return void 0;
526
+ };
527
+ var SearchCombobox = react$1.forwardRef(
528
+ ({
529
+ rootId,
530
+ value,
531
+ onChange,
532
+ onFocus,
533
+ onBlur,
534
+ onKeyDown,
535
+ placeholder,
536
+ label,
537
+ hideLabelFromVision,
538
+ help,
539
+ disabled,
540
+ open,
541
+ listboxId,
542
+ activeOptionId,
543
+ labels
544
+ }, ref) => {
545
+ const inputId = aria(rootId, "input");
546
+ const helpId = help ? aria(rootId, "help") : void 0;
547
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
548
+ hideLabelFromVision ? /* @__PURE__ */ jsxRuntime.jsx(components.VisuallyHidden, { as: "label", htmlFor: inputId, children: label }) : /* @__PURE__ */ jsxRuntime.jsx("label", { htmlFor: inputId, style: { display: "block", marginBottom: 4 }, children: label }),
549
+ /* @__PURE__ */ jsxRuntime.jsxs(InputShell, { "aria-disabled": disabled || void 0, children: [
550
+ /* @__PURE__ */ jsxRuntime.jsx(SearchIconWrap, { "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.search, size: 20 }) }),
551
+ /* @__PURE__ */ jsxRuntime.jsx(
552
+ Input,
553
+ {
554
+ ref,
555
+ id: inputId,
556
+ type: "text",
557
+ role: "combobox",
558
+ "aria-expanded": open,
559
+ "aria-controls": listboxId,
560
+ "aria-autocomplete": "list",
561
+ "aria-activedescendant": activeOptionId,
562
+ "aria-describedby": helpId,
563
+ "aria-label": hideLabelFromVision ? label : void 0,
564
+ value,
565
+ placeholder: placeholder ?? labels.input ?? i18n.__("Search", "valu-block-components"),
566
+ disabled,
567
+ autoComplete: "off",
568
+ spellCheck: false,
569
+ onChange: (event) => onChange(event.target.value),
570
+ onFocus,
571
+ onBlur,
572
+ onKeyDown
573
+ }
574
+ ),
575
+ value.length > 0 && !disabled ? /* @__PURE__ */ jsxRuntime.jsx(
576
+ "button",
577
+ {
578
+ type: "button",
579
+ onClick: () => onChange(""),
580
+ "aria-label": i18n.__("Clear search", "valu-block-components"),
581
+ style: {
582
+ display: "flex",
583
+ alignItems: "center",
584
+ justifyContent: "center",
585
+ background: "transparent",
586
+ border: 0,
587
+ padding: 2,
588
+ cursor: "pointer",
589
+ borderRadius: 2,
590
+ color: "currentColor",
591
+ opacity: 0.6
592
+ },
593
+ children: /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.closeSmall, size: 18 })
594
+ }
595
+ ) : null
596
+ ] }),
597
+ help ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: helpId, style: { margin: "4px 0 0", fontSize: 11, opacity: 0.7 }, children: help }) : null
598
+ ] });
599
+ }
600
+ );
601
+ SearchCombobox.displayName = "SearchCombobox";
602
+ var tones = {
603
+ neutral: { bg: tokens.gray100, fg: tokens.gray900, border: tokens.gray300 },
604
+ warn: { bg: "#fef3d0", fg: tokens.warning, border: "#f3d782" },
605
+ danger: { bg: tokens.dangerSoft, fg: tokens.danger, border: tokens.dangerLine },
606
+ success: { bg: "#e6f4ea", fg: tokens.success, border: "#a7d5b3" }
607
+ };
608
+ var toneFor = (status) => {
609
+ if (status === "trash") return "danger";
610
+ if (status === "draft" || status === "pending" || status === "future") return "warn";
611
+ if (status === "private") return "neutral";
612
+ return "neutral";
613
+ };
614
+ var Pill = styled__default.default.span`
615
+ display: inline-flex;
616
+ align-items: center;
617
+ padding: 1px 6px;
618
+ background: ${({ tone }) => tones[tone].bg};
619
+ color: ${({ tone }) => tones[tone].fg};
620
+ border: 1px solid ${({ tone }) => tones[tone].border};
621
+ border-radius: 999px;
622
+ font-size: 10px;
623
+ font-weight: ${tokens.fontWeightBold};
624
+ letter-spacing: 0.04em;
625
+ text-transform: uppercase;
626
+ white-space: nowrap;
627
+ line-height: 1.6;
628
+ `;
629
+ var StatusBadge = ({ status }) => {
630
+ const label = humanizeStatus(status);
631
+ if (!label) return null;
632
+ const translated = status === "draft" ? i18n.__("Draft", "valu-block-components") : status === "pending" ? i18n.__("Pending", "valu-block-components") : status === "private" ? i18n.__("Private", "valu-block-components") : status === "future" ? i18n.__("Scheduled", "valu-block-components") : status === "trash" ? i18n.__("Trash", "valu-block-components") : label;
633
+ return /* @__PURE__ */ jsxRuntime.jsx(Pill, { tone: toneFor(status), children: translated });
634
+ };
635
+ var SuggestionItem = ({
636
+ item,
637
+ optionId,
638
+ highlighted,
639
+ selected,
640
+ showTypeHint,
641
+ showUrl,
642
+ icons,
643
+ onHover,
644
+ onSelect,
645
+ render
646
+ }) => {
647
+ const ref = react$1.useRef(null);
648
+ react$1.useEffect(() => {
649
+ if (!highlighted || !ref.current) return;
650
+ ref.current.scrollIntoView({ block: "nearest" });
651
+ }, [highlighted]);
652
+ const handleClick = react$1.useCallback(
653
+ (event) => {
654
+ event.preventDefault();
655
+ onSelect();
656
+ },
657
+ [onSelect]
658
+ );
659
+ const handleMouseDown = react$1.useCallback((event) => {
660
+ event.preventDefault();
661
+ }, []);
662
+ if (render) {
663
+ return /* @__PURE__ */ jsxRuntime.jsx(
664
+ Option,
665
+ {
666
+ ref,
667
+ role: "option",
668
+ id: optionId,
669
+ "aria-selected": selected,
670
+ highlighted,
671
+ onMouseEnter: onHover,
672
+ onMouseDown: handleMouseDown,
673
+ onClick: handleClick,
674
+ children: render(item, { highlighted, selected })
675
+ }
676
+ );
677
+ }
678
+ const displayUrl = toDisplayUrl(item.url);
679
+ const hasAbnormalStatus = item.status && item.status !== "publish";
680
+ const icon = resolveItemIcon(item, icons);
681
+ const isAvatar = item.kind === "user" && Boolean(item.thumbnailUrl);
682
+ const hasVisual = Boolean(item.thumbnailUrl) || Boolean(icon);
683
+ return /* @__PURE__ */ jsxRuntime.jsxs(
684
+ Option,
685
+ {
686
+ ref,
687
+ role: "option",
688
+ id: optionId,
689
+ "aria-selected": selected,
690
+ highlighted,
691
+ onMouseEnter: onHover,
692
+ onMouseDown: handleMouseDown,
693
+ onClick: handleClick,
694
+ children: [
695
+ hasVisual ? /* @__PURE__ */ jsxRuntime.jsx(
696
+ Thumb,
697
+ {
698
+ hasImage: Boolean(item.thumbnailUrl) && !isAvatar,
699
+ avatar: isAvatar,
700
+ children: item.thumbnailUrl ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.thumbnailUrl, alt: "", loading: "lazy" }) : icon ? /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon, size: 22 }) : null
701
+ }
702
+ ) : null,
703
+ /* @__PURE__ */ jsxRuntime.jsxs(ItemBody, { children: [
704
+ /* @__PURE__ */ jsxRuntime.jsx(PrimaryText, { "data-valu-title": true, children: item.title }),
705
+ showUrl && displayUrl ? /* @__PURE__ */ jsxRuntime.jsx(SecondaryText, { children: displayUrl }) : null
706
+ ] }),
707
+ hasAbnormalStatus ? /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { status: item.status }) : null,
708
+ showTypeHint ? /* @__PURE__ */ jsxRuntime.jsx(TypeHint, { children: humanizeType(item.type, item.kind) }) : null
709
+ ]
710
+ }
711
+ );
712
+ };
713
+ var SuggestionList = ({
714
+ listboxId,
715
+ rootId,
716
+ groups,
717
+ suggestions,
718
+ kind,
719
+ loading,
720
+ fetchingMore,
721
+ hasMore,
722
+ error,
723
+ query,
724
+ highlightedIndex,
725
+ selectedKeys,
726
+ icons,
727
+ labels,
728
+ showUrl,
729
+ headerSlot,
730
+ renderSuggestion,
731
+ renderGroupHeading,
732
+ onHover,
733
+ onSelect,
734
+ onLoadMore
735
+ }) => {
736
+ const scrollRef = react$1.useRef(null);
737
+ const sentinelRef = react$1.useRef(null);
738
+ react$1.useEffect(() => {
739
+ if (!hasMore || !sentinelRef.current || !scrollRef.current) return;
740
+ const io = new IntersectionObserver(
741
+ (entries) => {
742
+ for (const e of entries) {
743
+ if (e.isIntersecting) onLoadMore();
744
+ }
745
+ },
746
+ { root: scrollRef.current, rootMargin: "120px" }
747
+ );
748
+ io.observe(sentinelRef.current);
749
+ return () => io.disconnect();
750
+ }, [hasMore, onLoadMore]);
751
+ const header = headerSlot ? /* @__PURE__ */ jsxRuntime.jsx(StickyHeader, { children: headerSlot }) : null;
752
+ if (error) {
753
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
754
+ header,
755
+ /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { role: "alert", children: labels.empty ?? i18n.__("Search failed. Please try again.", "valu-block-components") })
756
+ ] });
757
+ }
758
+ if (loading && suggestions.length === 0) {
759
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
760
+ header,
761
+ /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { children: [
762
+ /* @__PURE__ */ jsxRuntime.jsx(components.Spinner, {}),
763
+ labels.loading ?? i18n.__("Searching\u2026", "valu-block-components")
764
+ ] })
765
+ ] });
766
+ }
767
+ if (suggestions.length === 0) {
768
+ const emptyMessage = query.trim().length > 0 ? labels.empty ?? i18n.__("No matches. Try a different search.", "valu-block-components") : i18n.__("Type to search.", "valu-block-components");
769
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
770
+ header,
771
+ /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: emptyMessage })
772
+ ] });
773
+ }
774
+ let runningIndex = 0;
775
+ const showGroupHeadings = groups.length > 1 || groups[0]?.[0] !== "__all__";
776
+ const showTypeHint = !showGroupHeadings;
777
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
778
+ header,
779
+ /* @__PURE__ */ jsxRuntime.jsxs(
780
+ Listbox,
781
+ {
782
+ role: "listbox",
783
+ id: listboxId,
784
+ "aria-label": labels.input ?? i18n.__("Suggestions", "valu-block-components"),
785
+ children: [
786
+ groups.map(([type, items]) => {
787
+ const headingId = aria(rootId, `group-${type}`);
788
+ const optionNodes = items.map((item) => {
789
+ const index = runningIndex++;
790
+ const optionId = aria(rootId, `option-${index}`);
791
+ return /* @__PURE__ */ jsxRuntime.jsx(
792
+ SuggestionItem,
793
+ {
794
+ item,
795
+ optionId,
796
+ highlighted: index === highlightedIndex,
797
+ selected: selectedKeys.has(item.key),
798
+ showTypeHint,
799
+ showUrl,
800
+ icons,
801
+ onHover: () => onHover(index),
802
+ onSelect: () => onSelect(item),
803
+ render: renderSuggestion
804
+ },
805
+ item.key
806
+ );
807
+ });
808
+ if (!showGroupHeadings) return optionNodes;
809
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { role: "group", "aria-labelledby": headingId, children: [
810
+ /* @__PURE__ */ jsxRuntime.jsx(GroupHeading, { id: headingId, children: renderGroupHeading ? renderGroupHeading(type, items) : humanizeType(type, kind) }),
811
+ optionNodes
812
+ ] }, type);
813
+ }),
814
+ hasMore ? /* @__PURE__ */ jsxRuntime.jsx(LoadMoreRow, { children: /* @__PURE__ */ jsxRuntime.jsx(
815
+ components.Button,
816
+ {
817
+ variant: "tertiary",
818
+ onClick: onLoadMore,
819
+ disabled: fetchingMore,
820
+ isBusy: fetchingMore,
821
+ children: fetchingMore ? i18n.__("Loading\u2026", "valu-block-components") : i18n.__("Load more", "valu-block-components")
822
+ }
823
+ ) }) : null,
824
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: sentinelRef, "aria-hidden": "true" })
825
+ ]
826
+ }
827
+ )
828
+ ] });
829
+ };
830
+ var Row = styled__default.default.div`
831
+ display: flex;
832
+ align-items: center;
833
+ gap: ${tokens.spaceXs};
834
+ padding: 6px ${tokens.spaceMd};
835
+ overflow-x: auto;
836
+ border-bottom: 1px solid ${tokens.gray200};
837
+ background: ${tokens.bg};
838
+
839
+ &::-webkit-scrollbar {
840
+ height: 4px;
841
+ }
842
+ &::-webkit-scrollbar-thumb {
843
+ background: ${tokens.gray300};
844
+ border-radius: 2px;
845
+ }
846
+ `;
847
+ var Chip = styled__default.default.button`
848
+ flex: 0 0 auto;
849
+ padding: 2px 10px;
850
+ font-size: ${tokens.fontSizeSmall};
851
+ font-weight: ${tokens.fontWeightMedium};
852
+ line-height: 1.6;
853
+ border-radius: 999px;
854
+ border: 1px solid ${({ active }) => active ? tokens.accent : tokens.gray300};
855
+ background: ${({ active }) => active ? tokens.accent : "transparent"};
856
+ color: ${({ active }) => active ? "#fff" : tokens.gray900};
857
+ cursor: pointer;
858
+ white-space: nowrap;
859
+ transition: background ${tokens.transitionFast}, border-color ${tokens.transitionFast};
860
+
861
+ &:hover {
862
+ border-color: ${tokens.accent};
863
+ }
864
+
865
+ &:focus-visible {
866
+ outline: 2px solid ${tokens.accent};
867
+ outline-offset: 1px;
868
+ }
869
+ `;
870
+ var TypeFilter = ({ types, kind, active, onChange, allLabel }) => {
871
+ if (types.length < 2) return null;
872
+ return /* @__PURE__ */ jsxRuntime.jsxs(Row, { role: "toolbar", "aria-label": i18n.__("Filter by type", "valu-block-components"), children: [
873
+ /* @__PURE__ */ jsxRuntime.jsx(
874
+ Chip,
875
+ {
876
+ type: "button",
877
+ active: active === null,
878
+ "aria-pressed": active === null,
879
+ onClick: () => onChange(null),
880
+ onMouseDown: (e) => e.preventDefault(),
881
+ children: allLabel ?? i18n.__("All", "valu-block-components")
882
+ }
883
+ ),
884
+ types.map((type) => /* @__PURE__ */ jsxRuntime.jsx(
885
+ Chip,
886
+ {
887
+ type: "button",
888
+ active: active === type,
889
+ "aria-pressed": active === type,
890
+ onClick: () => onChange(active === type ? null : type),
891
+ onMouseDown: (e) => e.preventDefault(),
892
+ children: humanizeType(type, kind)
893
+ },
894
+ type
895
+ ))
896
+ ] });
897
+ };
898
+ var pickThumbnail = (result) => {
899
+ const media = result._embedded?.["wp:featuredmedia"]?.[0];
900
+ if (!media) return void 0;
901
+ const sizes = media.media_details?.sizes;
902
+ return sizes?.thumbnail?.source_url ?? sizes?.medium?.source_url ?? media.source_url ?? void 0;
903
+ };
904
+ var pickStatus = (result) => result._embedded?.self?.[0]?.status;
905
+ var buildPath = (args) => {
906
+ const { kind, types, query, page, perPage } = args;
907
+ if (kind === "user") {
908
+ return url.addQueryArgs("/wp/v2/users", {
909
+ search: query,
910
+ per_page: perPage,
911
+ page,
912
+ context: "view"
913
+ });
914
+ }
915
+ const params = {
916
+ search: query,
917
+ type: kind,
918
+ per_page: perPage,
919
+ page,
920
+ _embed: "wp:featuredmedia,self"
921
+ };
922
+ if (types && types.length > 0) {
923
+ params.subtype = types.join(",");
924
+ }
925
+ return url.addQueryArgs("/wp/v2/search", params);
926
+ };
927
+ var parseTotalPages = (response) => {
928
+ const raw = response.headers.get("x-wp-totalpages");
929
+ if (!raw) return 1;
930
+ const parsed = parseInt(raw, 10);
931
+ return Number.isNaN(parsed) ? 1 : parsed;
932
+ };
933
+ var parseTotal = (response) => {
934
+ const raw = response.headers.get("x-wp-total");
935
+ if (!raw) return 0;
936
+ const parsed = parseInt(raw, 10);
937
+ return Number.isNaN(parsed) ? 0 : parsed;
938
+ };
939
+ var fetchSearch = async (args) => {
940
+ const rawPath = buildPath(args);
941
+ const path = args.request ? args.request(rawPath, { query: args.query, page: args.page, kind: args.kind }) : rawPath;
942
+ const response = await apiFetch__default.default({
943
+ path,
944
+ parse: false,
945
+ signal: args.signal
946
+ });
947
+ const data = await response.json();
948
+ const totalPages = parseTotalPages(response);
949
+ const total = parseTotal(response);
950
+ let items;
951
+ if (args.kind === "user") {
952
+ items = data.map((u) => ({
953
+ key: generateKey(),
954
+ id: u.id,
955
+ type: "user",
956
+ kind: "user",
957
+ title: decodeTitle(u.name),
958
+ url: u.link,
959
+ thumbnailUrl: u.avatar_urls?.["48"] ?? u.avatar_urls?.["96"],
960
+ raw: u
961
+ }));
962
+ } else {
963
+ items = data.map((r) => ({
964
+ key: generateKey(),
965
+ id: r.id,
966
+ type: r.subtype || r.type,
967
+ kind: args.kind,
968
+ title: decodeTitle(r.title),
969
+ url: r.url,
970
+ status: pickStatus(r),
971
+ thumbnailUrl: pickThumbnail(r),
972
+ raw: r
973
+ }));
974
+ }
975
+ return { items, page: args.page, hasMore: args.page < totalPages, total };
976
+ };
977
+
978
+ // components/content-picker/hooks/useContentSearch.ts
979
+ var contentSearchKey = (args) => [
980
+ "valu/content-search",
981
+ args.kind,
982
+ [...args.types ?? []].sort().join(","),
983
+ args.query,
984
+ args.perPage
985
+ ];
986
+ var useContentSearch = (args) => reactQuery.useInfiniteQuery({
987
+ queryKey: contentSearchKey(args),
988
+ queryFn: ({ pageParam, signal }) => fetchSearch({
989
+ query: args.query,
990
+ kind: args.kind,
991
+ types: args.types,
992
+ page: pageParam,
993
+ perPage: args.perPage,
994
+ request: args.request,
995
+ signal
996
+ }),
997
+ initialPageParam: 1,
998
+ getNextPageParam: (last) => last.hasMore ? last.page + 1 : void 0,
999
+ enabled: args.enabled ?? true,
1000
+ staleTime: 3e4,
1001
+ placeholderData: reactQuery.keepPreviousData
1002
+ });
1003
+ var useDebouncedValue = (value, delay) => {
1004
+ const [debounced, setDebounced] = react$1.useState(value);
1005
+ react$1.useEffect(() => {
1006
+ const id = setTimeout(() => setDebounced(value), delay);
1007
+ return () => clearTimeout(id);
1008
+ }, [value, delay]);
1009
+ return debounced;
1010
+ };
1011
+ var useLiveRegion = (debounceMs = 500) => {
1012
+ const timer = react$1.useRef(null);
1013
+ react$1.useEffect(
1014
+ () => () => {
1015
+ if (timer.current) clearTimeout(timer.current);
1016
+ },
1017
+ []
1018
+ );
1019
+ const announce = react$1.useCallback(
1020
+ (message, assertiveness = "polite") => {
1021
+ if (!message) return;
1022
+ if (timer.current) clearTimeout(timer.current);
1023
+ timer.current = setTimeout(() => a11y.speak(message, assertiveness), debounceMs);
1024
+ },
1025
+ [debounceMs]
1026
+ );
1027
+ const announceImmediate = react$1.useCallback(
1028
+ (message, assertiveness = "polite") => {
1029
+ if (!message) return;
1030
+ if (timer.current) clearTimeout(timer.current);
1031
+ a11y.speak(message, assertiveness);
1032
+ },
1033
+ []
1034
+ );
1035
+ return { announce, announceImmediate };
1036
+ };
1037
+ var useKeyboardNav = ({
1038
+ itemCount,
1039
+ onSelect,
1040
+ onEscape,
1041
+ wrap = true
1042
+ }) => {
1043
+ const [highlightedIndex, setHighlightedIndex] = react$1.useState(-1);
1044
+ const reset = react$1.useCallback(() => setHighlightedIndex(-1), []);
1045
+ const handleKeyDown = react$1.useCallback(
1046
+ (event) => {
1047
+ if (itemCount === 0) {
1048
+ if (event.key === "Escape") {
1049
+ event.preventDefault();
1050
+ onEscape?.();
1051
+ }
1052
+ return;
1053
+ }
1054
+ switch (event.key) {
1055
+ case "ArrowDown": {
1056
+ event.preventDefault();
1057
+ setHighlightedIndex((i) => {
1058
+ const next = i + 1;
1059
+ if (next >= itemCount) return wrap ? 0 : itemCount - 1;
1060
+ return next;
1061
+ });
1062
+ break;
1063
+ }
1064
+ case "ArrowUp": {
1065
+ event.preventDefault();
1066
+ setHighlightedIndex((i) => {
1067
+ if (i <= 0) return wrap ? itemCount - 1 : 0;
1068
+ return i - 1;
1069
+ });
1070
+ break;
1071
+ }
1072
+ case "Home": {
1073
+ event.preventDefault();
1074
+ setHighlightedIndex(0);
1075
+ break;
1076
+ }
1077
+ case "End": {
1078
+ event.preventDefault();
1079
+ setHighlightedIndex(itemCount - 1);
1080
+ break;
1081
+ }
1082
+ case "Enter": {
1083
+ if (highlightedIndex >= 0 && highlightedIndex < itemCount) {
1084
+ event.preventDefault();
1085
+ onSelect?.(highlightedIndex);
1086
+ }
1087
+ break;
1088
+ }
1089
+ case "Escape": {
1090
+ event.preventDefault();
1091
+ onEscape?.();
1092
+ break;
1093
+ }
1094
+ }
1095
+ },
1096
+ [itemCount, highlightedIndex, onSelect, onEscape, wrap]
1097
+ );
1098
+ return { highlightedIndex, setHighlightedIndex, reset, handleKeyDown };
1099
+ };
1100
+
1101
+ // components/content-picker/hooks/useContentPickerController.ts
1102
+ var useContentPickerController = (args) => {
1103
+ const {
1104
+ kind,
1105
+ types,
1106
+ perPage,
1107
+ inputDelayMs,
1108
+ excludeCurrentPost,
1109
+ uniqueItems,
1110
+ maxItems,
1111
+ groupByType: groupByTypeFlag,
1112
+ filter,
1113
+ request,
1114
+ value,
1115
+ onChange,
1116
+ active,
1117
+ fetchEmpty,
1118
+ onSelectSideEffect,
1119
+ onEscape
1120
+ } = args;
1121
+ const [query, setQuery] = react$1.useState("");
1122
+ const [activeTypeFilter, setActiveTypeFilter] = react$1.useState(null);
1123
+ const debouncedQuery = useDebouncedValue(query, inputDelayMs);
1124
+ const effectiveSearchTypes = react$1.useMemo(() => {
1125
+ if (activeTypeFilter && types && types.includes(activeTypeFilter)) {
1126
+ return [activeTypeFilter];
1127
+ }
1128
+ return types;
1129
+ }, [activeTypeFilter, types]);
1130
+ const currentPostId = data.useSelect((select2) => {
1131
+ if (!excludeCurrentPost || kind !== "post") return 0;
1132
+ const editor = select2("core/editor");
1133
+ return editor?.getCurrentPostId?.() ?? 0;
1134
+ }, [excludeCurrentPost, kind]);
1135
+ const enabled = active && (fetchEmpty || debouncedQuery.trim().length > 0);
1136
+ const query_ = useContentSearch({
1137
+ query: debouncedQuery,
1138
+ kind,
1139
+ types: effectiveSearchTypes,
1140
+ perPage,
1141
+ enabled,
1142
+ request
1143
+ });
1144
+ const suggestions = react$1.useMemo(() => {
1145
+ if (!query_.data) return [];
1146
+ const flat = query_.data.pages.flatMap((p) => p.items);
1147
+ let filtered = dedupeAgainst(flat, value, uniqueItems);
1148
+ if (currentPostId) {
1149
+ filtered = filtered.filter((item) => item.id !== currentPostId);
1150
+ }
1151
+ if (filter) {
1152
+ filtered = filter(filtered, { query: debouncedQuery, kind });
1153
+ }
1154
+ return filtered;
1155
+ }, [query_.data, value, uniqueItems, currentPostId, filter, debouncedQuery, kind]);
1156
+ const shouldGroup = groupByTypeFlag && (effectiveSearchTypes?.length ?? 0) > 1;
1157
+ const groups = react$1.useMemo(
1158
+ () => shouldGroup ? groupByType(suggestions) : [["__all__", suggestions]],
1159
+ [shouldGroup, suggestions]
1160
+ );
1161
+ const { announce, announceImmediate } = useLiveRegion();
1162
+ react$1.useEffect(() => {
1163
+ if (!active) return;
1164
+ if (debouncedQuery.trim().length === 0 && !fetchEmpty) return;
1165
+ if (query_.isFetching) return;
1166
+ if (query_.isError) {
1167
+ announce(i18n.__("Search failed. Try again.", "valu-block-components"));
1168
+ return;
1169
+ }
1170
+ const count = suggestions.length;
1171
+ announce(
1172
+ i18n.sprintf(
1173
+ /* translators: %d: number of search results */
1174
+ i18n._n("%d result", "%d results", count, "valu-block-components"),
1175
+ count
1176
+ )
1177
+ );
1178
+ }, [suggestions.length, query_.isFetching, query_.isError, active, debouncedQuery, fetchEmpty, announce]);
1179
+ const maxedOut = isMaxedOut(value, maxItems);
1180
+ const replace = react$1.useCallback((items) => onChange(items), [onChange]);
1181
+ const select = react$1.useCallback(
1182
+ (item) => {
1183
+ if (maxedOut) return;
1184
+ const limited = maxItems === 1;
1185
+ const normalized = {
1186
+ ...item,
1187
+ key: item.key && item.key.length > 0 ? item.key : generateKey()
1188
+ };
1189
+ const next = limited ? [normalized] : [...value, normalized];
1190
+ onChange(next);
1191
+ announceImmediate(
1192
+ i18n.sprintf(
1193
+ /* translators: 1: title, 2: selected count */
1194
+ i18n.__("Added %1$s. %2$d selected.", "valu-block-components"),
1195
+ normalized.title,
1196
+ next.length
1197
+ )
1198
+ );
1199
+ onSelectSideEffect?.();
1200
+ },
1201
+ [maxedOut, maxItems, value, onChange, announceImmediate, onSelectSideEffect]
1202
+ );
1203
+ const remove = react$1.useCallback(
1204
+ (key) => {
1205
+ const target = value.find((i) => i.key === key);
1206
+ const next = value.filter((i) => i.key !== key);
1207
+ onChange(next);
1208
+ if (target) {
1209
+ announceImmediate(
1210
+ i18n.sprintf(
1211
+ /* translators: %s: title of the removed item */
1212
+ i18n.__("Removed %s.", "valu-block-components"),
1213
+ target.title
1214
+ )
1215
+ );
1216
+ }
1217
+ },
1218
+ [value, onChange, announceImmediate]
1219
+ );
1220
+ const reorder = react$1.useCallback(
1221
+ (from, to) => {
1222
+ if (from === to || from < 0 || to < 0 || from >= value.length || to >= value.length) {
1223
+ return;
1224
+ }
1225
+ const next = value.slice();
1226
+ const [moved] = next.splice(from, 1);
1227
+ next.splice(to, 0, moved);
1228
+ onChange(next);
1229
+ announceImmediate(
1230
+ i18n.sprintf(
1231
+ /* translators: 1: title, 2: position, 3: total */
1232
+ i18n.__("Moved %1$s to position %2$d of %3$d.", "valu-block-components"),
1233
+ moved.title,
1234
+ to + 1,
1235
+ next.length
1236
+ )
1237
+ );
1238
+ },
1239
+ [value, onChange, announceImmediate]
1240
+ );
1241
+ const { highlightedIndex, setHighlightedIndex, reset: resetHighlight, handleKeyDown } = useKeyboardNav({
1242
+ itemCount: suggestions.length,
1243
+ onSelect: (i) => {
1244
+ const item = suggestions[i];
1245
+ if (item) select(item);
1246
+ },
1247
+ onEscape
1248
+ });
1249
+ react$1.useEffect(() => {
1250
+ resetHighlight();
1251
+ }, [debouncedQuery, activeTypeFilter, resetHighlight]);
1252
+ return {
1253
+ query,
1254
+ setQuery,
1255
+ debouncedQuery,
1256
+ suggestions,
1257
+ groups,
1258
+ highlightedIndex,
1259
+ setHighlightedIndex,
1260
+ handleKeyDown,
1261
+ loading: query_.isFetching && !query_.data,
1262
+ error: query_.error,
1263
+ hasMore: Boolean(query_.hasNextPage),
1264
+ loadMore: () => {
1265
+ if (query_.hasNextPage && !query_.isFetchingNextPage) void query_.fetchNextPage();
1266
+ },
1267
+ fetchingMore: query_.isFetchingNextPage,
1268
+ select,
1269
+ remove,
1270
+ reorder,
1271
+ replace,
1272
+ maxedOut,
1273
+ total: query_.data?.pages[0]?.total ?? 0,
1274
+ activeTypeFilter,
1275
+ setActiveTypeFilter,
1276
+ effectiveSearchTypes
1277
+ };
1278
+ };
1279
+ var EmotionCacheProvider = ({ children }) => {
1280
+ const [head, setHead] = react$1.useState(null);
1281
+ const captureDocument = react$1.useCallback((node) => {
1282
+ if (!node) return;
1283
+ const owner = node.ownerDocument ?? document;
1284
+ setHead((prev) => prev === owner.head ? prev : owner.head);
1285
+ }, []);
1286
+ const cache = react$1.useMemo(() => {
1287
+ if (!head) return null;
1288
+ return createCache__default.default({ key: "valu-cp", container: head, prepend: true });
1289
+ }, [head]);
1290
+ if (!cache) {
1291
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: captureDocument, style: { display: "contents" } });
1292
+ }
1293
+ return /* @__PURE__ */ jsxRuntime.jsx(react.CacheProvider, { value: cache, children: /* @__PURE__ */ jsxRuntime.jsx("div", { ref: captureDocument, style: { display: "contents" }, children }) });
1294
+ };
1295
+ var ContentPickerPopover = (props) => {
1296
+ const {
1297
+ rootId,
1298
+ label,
1299
+ hideLabelFromVision,
1300
+ help,
1301
+ placeholder,
1302
+ kind,
1303
+ types,
1304
+ perPage,
1305
+ inputDelayMs,
1306
+ excludeCurrentPost,
1307
+ uniqueItems,
1308
+ maxItems,
1309
+ groupByType: groupByType2,
1310
+ fetchOnOpen,
1311
+ filter,
1312
+ request,
1313
+ renderSuggestion,
1314
+ renderGroupHeading,
1315
+ icons,
1316
+ disableTypeFilter,
1317
+ showUrl,
1318
+ value,
1319
+ onChange,
1320
+ labels
1321
+ } = props;
1322
+ const [open, setOpen] = react$1.useState(false);
1323
+ const inputRef = react$1.useRef(null);
1324
+ const popoverBodyRef = react$1.useRef(null);
1325
+ const clearQueryRef = react$1.useRef(null);
1326
+ const controller = useContentPickerController({
1327
+ kind,
1328
+ types,
1329
+ perPage,
1330
+ inputDelayMs,
1331
+ excludeCurrentPost,
1332
+ uniqueItems,
1333
+ maxItems,
1334
+ groupByType: groupByType2,
1335
+ filter,
1336
+ request,
1337
+ value,
1338
+ onChange,
1339
+ active: open,
1340
+ fetchEmpty: fetchOnOpen,
1341
+ onSelectSideEffect: () => {
1342
+ if (maxItems === 1) {
1343
+ setOpen(false);
1344
+ return;
1345
+ }
1346
+ clearQueryRef.current?.();
1347
+ requestAnimationFrame(() => inputRef.current?.focus());
1348
+ },
1349
+ onEscape: () => {
1350
+ setOpen(false);
1351
+ requestAnimationFrame(() => inputRef.current?.focus());
1352
+ }
1353
+ });
1354
+ clearQueryRef.current = () => controller.setQuery("");
1355
+ const listboxId = aria(rootId, "listbox");
1356
+ const activeOptionId = controller.highlightedIndex >= 0 ? aria(rootId, `option-${controller.highlightedIndex}`) : void 0;
1357
+ const selectedKeys = react$1.useMemo(() => new Set(value.map((v) => v.key)), [value]);
1358
+ const handleFocus = react$1.useCallback(() => {
1359
+ if (!controller.maxedOut) setOpen(true);
1360
+ }, [controller.maxedOut]);
1361
+ react$1.useEffect(() => {
1362
+ if (controller.maxedOut) setOpen(false);
1363
+ }, [controller.maxedOut]);
1364
+ react$1.useEffect(() => {
1365
+ if (!open) return;
1366
+ const handlePointerDown = (event) => {
1367
+ const target = event.target;
1368
+ if (!target) return;
1369
+ const input = inputRef.current;
1370
+ const body = popoverBodyRef.current;
1371
+ const anchorContainer = input?.closest("div") ?? input;
1372
+ if (anchorContainer && anchorContainer.contains(target)) return;
1373
+ if (body && body.contains(target)) return;
1374
+ setOpen(false);
1375
+ };
1376
+ const docs = /* @__PURE__ */ new Set();
1377
+ if (typeof document !== "undefined") docs.add(document);
1378
+ const inputDoc = inputRef.current?.ownerDocument;
1379
+ if (inputDoc) docs.add(inputDoc);
1380
+ docs.forEach((d) => d.addEventListener("mousedown", handlePointerDown, true));
1381
+ return () => {
1382
+ docs.forEach((d) => d.removeEventListener("mousedown", handlePointerDown, true));
1383
+ };
1384
+ }, [open]);
1385
+ const disabled = controller.maxedOut;
1386
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
1387
+ /* @__PURE__ */ jsxRuntime.jsx(
1388
+ SearchCombobox,
1389
+ {
1390
+ ref: inputRef,
1391
+ rootId,
1392
+ value: controller.query,
1393
+ onChange: (next) => {
1394
+ controller.setQuery(next);
1395
+ if (!open && !disabled) setOpen(true);
1396
+ },
1397
+ onFocus: handleFocus,
1398
+ onKeyDown: (event) => {
1399
+ if (event.key === "ArrowDown" && !open && !disabled) {
1400
+ event.preventDefault();
1401
+ setOpen(true);
1402
+ return;
1403
+ }
1404
+ controller.handleKeyDown(event);
1405
+ },
1406
+ placeholder,
1407
+ label,
1408
+ hideLabelFromVision,
1409
+ help,
1410
+ disabled,
1411
+ open: open && !disabled,
1412
+ listboxId,
1413
+ activeOptionId,
1414
+ labels
1415
+ }
1416
+ ),
1417
+ disabled ? /* @__PURE__ */ jsxRuntime.jsx(MaxedOutBanner, { role: "status", style: { marginTop: 8 }, children: labels.limitReached ?? i18n.__("Selection limit reached.", "valu-block-components") }) : null,
1418
+ open && !disabled && inputRef.current ? /* @__PURE__ */ jsxRuntime.jsx(
1419
+ components.Popover,
1420
+ {
1421
+ anchor: inputRef.current.closest("div") ?? inputRef.current,
1422
+ placement: "bottom-start",
1423
+ focusOnMount: false,
1424
+ onClose: () => setOpen(false),
1425
+ onFocusOutside: () => setOpen(false),
1426
+ shift: true,
1427
+ flip: true,
1428
+ resize: true,
1429
+ offset: 4,
1430
+ className: "valu-content-picker__popover",
1431
+ children: /* @__PURE__ */ jsxRuntime.jsx(EmotionCacheProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
1432
+ "div",
1433
+ {
1434
+ ref: popoverBodyRef,
1435
+ style: {
1436
+ minWidth: inputRef.current.offsetWidth,
1437
+ width: "100%"
1438
+ },
1439
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1440
+ SuggestionList,
1441
+ {
1442
+ listboxId,
1443
+ rootId,
1444
+ groups: controller.groups,
1445
+ suggestions: controller.suggestions,
1446
+ kind,
1447
+ loading: controller.loading,
1448
+ fetchingMore: controller.fetchingMore,
1449
+ hasMore: controller.hasMore,
1450
+ error: controller.error,
1451
+ query: controller.debouncedQuery,
1452
+ highlightedIndex: controller.highlightedIndex,
1453
+ selectedKeys,
1454
+ icons,
1455
+ labels,
1456
+ showUrl: showUrl ?? true,
1457
+ headerSlot: !disableTypeFilter && types && types.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
1458
+ TypeFilter,
1459
+ {
1460
+ types,
1461
+ kind,
1462
+ active: controller.activeTypeFilter,
1463
+ onChange: controller.setActiveTypeFilter
1464
+ }
1465
+ ) : null,
1466
+ renderSuggestion,
1467
+ renderGroupHeading,
1468
+ onHover: (i) => controller.setHighlightedIndex(i),
1469
+ onSelect: (item) => controller.select(item),
1470
+ onLoadMore: () => controller.loadMore()
1471
+ }
1472
+ )
1473
+ }
1474
+ ) })
1475
+ }
1476
+ ) : null
1477
+ ] });
1478
+ };
1479
+ var ContentPickerInline = (props) => {
1480
+ const {
1481
+ rootId,
1482
+ label,
1483
+ hideLabelFromVision,
1484
+ help,
1485
+ placeholder,
1486
+ kind,
1487
+ types,
1488
+ perPage,
1489
+ inputDelayMs,
1490
+ excludeCurrentPost,
1491
+ uniqueItems,
1492
+ maxItems,
1493
+ groupByType: groupByType2,
1494
+ fetchOnOpen,
1495
+ filter,
1496
+ request,
1497
+ renderSuggestion,
1498
+ renderGroupHeading,
1499
+ icons,
1500
+ disableTypeFilter,
1501
+ showUrl,
1502
+ value,
1503
+ onChange,
1504
+ labels
1505
+ } = props;
1506
+ const inputRef = react$1.useRef(null);
1507
+ const clearQueryRef = react$1.useRef(null);
1508
+ const controller = useContentPickerController({
1509
+ kind,
1510
+ types,
1511
+ perPage,
1512
+ inputDelayMs,
1513
+ excludeCurrentPost,
1514
+ uniqueItems,
1515
+ maxItems,
1516
+ groupByType: groupByType2,
1517
+ filter,
1518
+ request,
1519
+ value,
1520
+ onChange,
1521
+ active: true,
1522
+ fetchEmpty: fetchOnOpen || value.length === 0,
1523
+ onSelectSideEffect: () => {
1524
+ clearQueryRef.current?.();
1525
+ requestAnimationFrame(() => inputRef.current?.focus());
1526
+ }
1527
+ });
1528
+ clearQueryRef.current = () => controller.setQuery("");
1529
+ const listboxId = aria(rootId, "listbox");
1530
+ const activeOptionId = controller.highlightedIndex >= 0 ? aria(rootId, `option-${controller.highlightedIndex}`) : void 0;
1531
+ const selectedKeys = react$1.useMemo(() => new Set(value.map((v) => v.key)), [value]);
1532
+ const disabled = controller.maxedOut;
1533
+ const shouldShowList = !disabled && (controller.debouncedQuery.trim().length > 0 || fetchOnOpen || value.length === 0);
1534
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1535
+ /* @__PURE__ */ jsxRuntime.jsx(
1536
+ SearchCombobox,
1537
+ {
1538
+ ref: inputRef,
1539
+ rootId,
1540
+ value: controller.query,
1541
+ onChange: (next) => controller.setQuery(next),
1542
+ onKeyDown: controller.handleKeyDown,
1543
+ placeholder,
1544
+ label,
1545
+ hideLabelFromVision,
1546
+ help,
1547
+ disabled,
1548
+ open: shouldShowList,
1549
+ listboxId,
1550
+ activeOptionId,
1551
+ labels
1552
+ }
1553
+ ),
1554
+ disabled ? /* @__PURE__ */ jsxRuntime.jsx(MaxedOutBanner, { role: "status", style: { marginTop: 8 }, children: labels.limitReached ?? i18n.__("Selection limit reached.", "valu-block-components") }) : null,
1555
+ shouldShowList ? /* @__PURE__ */ jsxRuntime.jsx(SuggestionsShell, { inline: true, style: { marginTop: 8 }, children: /* @__PURE__ */ jsxRuntime.jsx(
1556
+ SuggestionList,
1557
+ {
1558
+ listboxId,
1559
+ rootId,
1560
+ groups: controller.groups,
1561
+ suggestions: controller.suggestions,
1562
+ kind,
1563
+ loading: controller.loading,
1564
+ fetchingMore: controller.fetchingMore,
1565
+ hasMore: controller.hasMore,
1566
+ error: controller.error,
1567
+ query: controller.debouncedQuery,
1568
+ highlightedIndex: controller.highlightedIndex,
1569
+ selectedKeys,
1570
+ icons,
1571
+ labels,
1572
+ showUrl: showUrl ?? true,
1573
+ headerSlot: !disableTypeFilter && types && types.length > 1 ? /* @__PURE__ */ jsxRuntime.jsx(
1574
+ TypeFilter,
1575
+ {
1576
+ types,
1577
+ kind,
1578
+ active: controller.activeTypeFilter,
1579
+ onChange: controller.setActiveTypeFilter
1580
+ }
1581
+ ) : null,
1582
+ renderSuggestion,
1583
+ renderGroupHeading,
1584
+ onHover: (i) => controller.setHighlightedIndex(i),
1585
+ onSelect: (item) => controller.select(item),
1586
+ onLoadMore: () => controller.loadMore()
1587
+ }
1588
+ ) }) : null
1589
+ ] });
1590
+ };
1591
+ var Pill2 = styled__default.default.span`
1592
+ display: inline-flex;
1593
+ align-items: center;
1594
+ padding: 1px 6px;
1595
+ background: transparent;
1596
+ color: ${tokens.gray600};
1597
+ border: 1px solid ${tokens.gray300};
1598
+ border-radius: 999px;
1599
+ font-size: 10px;
1600
+ font-weight: ${tokens.fontWeightBold};
1601
+ letter-spacing: 0.04em;
1602
+ text-transform: uppercase;
1603
+ white-space: nowrap;
1604
+ line-height: 1.6;
1605
+ `;
1606
+ var TypeBadge = ({ type, kind, label }) => /* @__PURE__ */ jsxRuntime.jsx(Pill2, { children: label ?? humanizeType(type, kind) });
1607
+ var SelectedItem = ({
1608
+ item,
1609
+ index,
1610
+ total,
1611
+ orderable,
1612
+ showTypeBadge,
1613
+ icons: icons$1,
1614
+ labels,
1615
+ onRemove,
1616
+ onMoveUp,
1617
+ onMoveDown,
1618
+ render
1619
+ }) => {
1620
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = sortable.useSortable({
1621
+ id: item.key,
1622
+ disabled: !orderable
1623
+ });
1624
+ const style = {
1625
+ transform: utilities.CSS.Translate.toString(transform),
1626
+ transition,
1627
+ opacity: isDragging ? 0 : 1
1628
+ };
1629
+ const isMissing = item.missing === "deleted";
1630
+ const isTrash = item.missing === "trash";
1631
+ const missingLabel = labels.missing ?? i18n.__("This item is no longer available.", "valu-block-components");
1632
+ const trashLabel = labels.statusTrash ?? i18n.__("This item is in the trash.", "valu-block-components");
1633
+ const removeLabel = labels.remove ? labels.remove : i18n.sprintf(
1634
+ /* translators: %s: item title */
1635
+ i18n.__("Remove %s", "valu-block-components"),
1636
+ item.title
1637
+ );
1638
+ const moveUpLabel = labels.moveUp ? labels.moveUp : i18n.sprintf(
1639
+ /* translators: %s: item title */
1640
+ i18n.__("Move %s up", "valu-block-components"),
1641
+ item.title
1642
+ );
1643
+ const moveDownLabel = labels.moveDown ? labels.moveDown : i18n.sprintf(
1644
+ /* translators: %s: item title */
1645
+ i18n.__("Move %s down", "valu-block-components"),
1646
+ item.title
1647
+ );
1648
+ const hasAbnormalStatus = item.status && item.status !== "publish";
1649
+ const resolvedIcon = resolveItemIcon(item, icons$1);
1650
+ const hasThumbImage = Boolean(item.thumbnailUrl) && !isMissing && !isTrash;
1651
+ const isAvatar = item.kind === "user" && hasThumbImage;
1652
+ const hasVisual = isMissing || isTrash || hasThumbImage || Boolean(resolvedIcon);
1653
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1654
+ SelectedRow,
1655
+ {
1656
+ ref: setNodeRef,
1657
+ style,
1658
+ dragging: isDragging,
1659
+ missing: isMissing || isTrash,
1660
+ "aria-setsize": total,
1661
+ "aria-posinset": index + 1,
1662
+ children: [
1663
+ orderable ? /* @__PURE__ */ jsxRuntime.jsx(
1664
+ DragHandleButton,
1665
+ {
1666
+ type: "button",
1667
+ "aria-label": labels.dragHandle ?? i18n.__("Reorder item", "valu-block-components"),
1668
+ ...attributes,
1669
+ ...listeners,
1670
+ children: /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.dragHandle, size: 14 })
1671
+ }
1672
+ ) : null,
1673
+ hasVisual ? /* @__PURE__ */ jsxRuntime.jsx(
1674
+ Thumb,
1675
+ {
1676
+ hasImage: hasThumbImage && !isAvatar,
1677
+ avatar: isAvatar,
1678
+ missing: isMissing || isTrash,
1679
+ children: isMissing || isTrash ? /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.caution, size: 22 }) : hasThumbImage ? /* @__PURE__ */ jsxRuntime.jsx("img", { src: item.thumbnailUrl, alt: "", loading: "lazy" }) : resolvedIcon ? /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: resolvedIcon, size: 22 }) : null
1680
+ }
1681
+ ) : null,
1682
+ render ? render(item) : /* @__PURE__ */ jsxRuntime.jsx(ItemBody, { children: /* @__PURE__ */ jsxRuntime.jsx(PrimaryText, { muted: isMissing || isTrash, title: item.title, children: isMissing ? missingLabel : isTrash ? trashLabel : item.title }) }),
1683
+ !isMissing && showTypeBadge ? /* @__PURE__ */ jsxRuntime.jsx(TypeBadge, { type: item.type, kind: item.kind }) : null,
1684
+ !isMissing && hasAbnormalStatus ? /* @__PURE__ */ jsxRuntime.jsx(StatusBadge, { status: item.status }) : null,
1685
+ /* @__PURE__ */ jsxRuntime.jsxs(ActionColumn, { children: [
1686
+ orderable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1687
+ /* @__PURE__ */ jsxRuntime.jsx(
1688
+ components.Button,
1689
+ {
1690
+ size: "small",
1691
+ variant: "tertiary",
1692
+ icon: icons.chevronUp,
1693
+ label: moveUpLabel,
1694
+ onClick: onMoveUp,
1695
+ disabled: index === 0 || !onMoveUp
1696
+ }
1697
+ ),
1698
+ /* @__PURE__ */ jsxRuntime.jsx(
1699
+ components.Button,
1700
+ {
1701
+ size: "small",
1702
+ variant: "tertiary",
1703
+ icon: icons.chevronDown,
1704
+ label: moveDownLabel,
1705
+ onClick: onMoveDown,
1706
+ disabled: index === total - 1 || !onMoveDown
1707
+ }
1708
+ )
1709
+ ] }) : null,
1710
+ /* @__PURE__ */ jsxRuntime.jsx(
1711
+ components.Button,
1712
+ {
1713
+ size: "small",
1714
+ variant: "tertiary",
1715
+ icon: icons.closeSmall,
1716
+ label: removeLabel,
1717
+ onClick: onRemove,
1718
+ isDestructive: true
1719
+ }
1720
+ )
1721
+ ] })
1722
+ ]
1723
+ }
1724
+ );
1725
+ };
1726
+ var DragChip = ({ title }) => /* @__PURE__ */ jsxRuntime.jsxs(ChipWrap, { children: [
1727
+ /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.dragHandle, size: 16 }),
1728
+ /* @__PURE__ */ jsxRuntime.jsx(ChipTitle, { children: title })
1729
+ ] });
1730
+ var entityKindFor = (kind) => {
1731
+ if (kind === "post") return "postType";
1732
+ if (kind === "term") return "taxonomy";
1733
+ return "root";
1734
+ };
1735
+ var entityNameFor = (kind, type) => {
1736
+ if (kind === "user") return "user";
1737
+ return type;
1738
+ };
1739
+ var useResolvedItems = (value) => {
1740
+ const distinctTypes = react$1.useMemo(() => {
1741
+ const map = /* @__PURE__ */ new Map();
1742
+ for (const item of value) {
1743
+ const key = `${item.kind}:${item.type}`;
1744
+ const bucket = map.get(key);
1745
+ if (bucket) bucket.ids.push(item.id);
1746
+ else map.set(key, { kind: item.kind, ids: [item.id] });
1747
+ }
1748
+ return Array.from(map.entries()).map(([key, v]) => ({ key, ...v }));
1749
+ }, [value]);
1750
+ const records = data.useSelect(
1751
+ (select) => {
1752
+ const core = select(coreData.store);
1753
+ const out = {};
1754
+ for (const item of value) {
1755
+ const kindKey = entityKindFor(item.kind);
1756
+ const nameKey = entityNameFor(item.kind, item.type);
1757
+ const record = core.getEntityRecord(kindKey, nameKey, item.id);
1758
+ const resolved = core.hasFinishedResolution("getEntityRecord", [
1759
+ kindKey,
1760
+ nameKey,
1761
+ item.id
1762
+ ]);
1763
+ out[`${item.kind}:${item.type}:${item.id}`] = {
1764
+ record: record ?? null,
1765
+ resolved
1766
+ };
1767
+ }
1768
+ return out;
1769
+ },
1770
+ [value, distinctTypes]
1771
+ );
1772
+ return react$1.useMemo(
1773
+ () => value.map((item) => {
1774
+ const resolvedEntry = records[`${item.kind}:${item.type}:${item.id}`];
1775
+ if (!resolvedEntry) return item;
1776
+ const { record, resolved } = resolvedEntry;
1777
+ if (!resolved) {
1778
+ return item;
1779
+ }
1780
+ if (!record) {
1781
+ return { ...item, missing: "deleted" };
1782
+ }
1783
+ if (record.status === "trash") {
1784
+ return { ...item, missing: "trash", status: "trash" };
1785
+ }
1786
+ const freshTitle = decodeTitle(record.title) || (record.name ? decodeTitle(record.name) : "");
1787
+ return {
1788
+ ...item,
1789
+ title: freshTitle || item.title,
1790
+ status: record.status ?? item.status,
1791
+ url: record.link ?? item.url
1792
+ };
1793
+ }),
1794
+ [value, records]
1795
+ );
1796
+ };
1797
+ var SelectedList = ({
1798
+ value,
1799
+ orderable,
1800
+ maxItems,
1801
+ labels,
1802
+ onChange,
1803
+ renderSelected,
1804
+ showHeader = true,
1805
+ showTypeBadge,
1806
+ icons
1807
+ }) => {
1808
+ const resolved = useResolvedItems(value);
1809
+ const [activeKey, setActiveKey] = react$1.useState(null);
1810
+ const sensors = core.useSensors(
1811
+ core.useSensor(core.MouseSensor, { activationConstraint: { distance: 5 } }),
1812
+ core.useSensor(core.TouchSensor, { activationConstraint: { delay: 200, tolerance: 6 } }),
1813
+ core.useSensor(core.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
1814
+ );
1815
+ const ids = react$1.useMemo(() => resolved.map((i) => i.key), [resolved]);
1816
+ const activeItem = activeKey ? resolved.find((i) => i.key === activeKey) : null;
1817
+ const remove = react$1.useCallback(
1818
+ (key) => onChange(value.filter((i) => i.key !== key)),
1819
+ [value, onChange]
1820
+ );
1821
+ const reorder = react$1.useCallback(
1822
+ (from, to) => {
1823
+ if (from === to) return;
1824
+ onChange(sortable.arrayMove(value, from, to));
1825
+ },
1826
+ [value, onChange]
1827
+ );
1828
+ const handleDragStart = react$1.useCallback(
1829
+ (event) => setActiveKey(String(event.active.id)),
1830
+ []
1831
+ );
1832
+ const handleDragEnd = react$1.useCallback(
1833
+ (event) => {
1834
+ setActiveKey(null);
1835
+ const { active, over } = event;
1836
+ if (!over || active.id === over.id) return;
1837
+ const from = ids.indexOf(String(active.id));
1838
+ const to = ids.indexOf(String(over.id));
1839
+ if (from >= 0 && to >= 0) reorder(from, to);
1840
+ },
1841
+ [ids, reorder]
1842
+ );
1843
+ if (resolved.length === 0) return null;
1844
+ const isSingleSelect = maxItems === 1;
1845
+ const shouldShowHeader = showHeader && !isSingleSelect;
1846
+ const header = shouldShowHeader ? /* @__PURE__ */ jsxRuntime.jsxs(HeaderRow, { children: [
1847
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderTitle, { children: resolved.length === 1 ? labels.selectedSingular ?? i18n.__("Selected", "valu-block-components") : labels.selectedPlural ?? i18n.__("Selected", "valu-block-components") }),
1848
+ /* @__PURE__ */ jsxRuntime.jsx(CountPill, { "aria-hidden": "true", children: Number.isFinite(maxItems) ? i18n.sprintf(
1849
+ /* translators: 1: current count, 2: max count */
1850
+ "%1$d / %2$d",
1851
+ resolved.length,
1852
+ maxItems
1853
+ ) : String(resolved.length) })
1854
+ ] }) : null;
1855
+ const rows = resolved.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
1856
+ SelectedItem,
1857
+ {
1858
+ item,
1859
+ index,
1860
+ total: resolved.length,
1861
+ orderable,
1862
+ showTypeBadge,
1863
+ icons,
1864
+ labels,
1865
+ onRemove: () => remove(item.key),
1866
+ onMoveUp: index > 0 ? () => reorder(index, index - 1) : void 0,
1867
+ onMoveDown: index < resolved.length - 1 ? () => reorder(index, index + 1) : void 0,
1868
+ render: renderSelected
1869
+ },
1870
+ item.key
1871
+ ));
1872
+ if (!orderable) {
1873
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1874
+ header,
1875
+ /* @__PURE__ */ jsxRuntime.jsx(SelectedListEl, { role: "list", children: rows })
1876
+ ] });
1877
+ }
1878
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1879
+ header,
1880
+ /* @__PURE__ */ jsxRuntime.jsxs(
1881
+ core.DndContext,
1882
+ {
1883
+ sensors,
1884
+ collisionDetection: core.closestCenter,
1885
+ onDragStart: handleDragStart,
1886
+ onDragEnd: handleDragEnd,
1887
+ onDragCancel: () => setActiveKey(null),
1888
+ children: [
1889
+ /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: ids, strategy: sortable.verticalListSortingStrategy, children: /* @__PURE__ */ jsxRuntime.jsx(SelectedListEl, { role: "list", children: rows }) }),
1890
+ /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { dropAnimation: core.defaultDropAnimation, children: activeItem ? /* @__PURE__ */ jsxRuntime.jsx(DragChip, { title: activeItem.title }) : null })
1891
+ ]
1892
+ }
1893
+ )
1894
+ ] });
1895
+ };
1896
+ var DEFAULT_LABELS = {};
1897
+ var defaultTypesFor = (kind) => {
1898
+ if (kind === "post") return ["post", "page"];
1899
+ if (kind === "term") return ["category", "post_tag"];
1900
+ return [];
1901
+ };
1902
+ var makeQueryClient = () => new reactQuery.QueryClient({
1903
+ defaultOptions: {
1904
+ queries: { retry: 1, refetchOnWindowFocus: false }
1905
+ }
1906
+ });
1907
+ var ContentPicker = ({
1908
+ label,
1909
+ hideLabelFromVision = false,
1910
+ help,
1911
+ kind = "post",
1912
+ types,
1913
+ variant = "popover",
1914
+ value: controlledValue,
1915
+ defaultValue,
1916
+ onChange,
1917
+ maxItems = Infinity,
1918
+ orderable,
1919
+ placeholder,
1920
+ perPage,
1921
+ inputDelayMs = 250,
1922
+ fetchOnOpen = false,
1923
+ excludeCurrentPost = true,
1924
+ uniqueItems = true,
1925
+ groupByType: groupByType2,
1926
+ showTypeBadge,
1927
+ icons,
1928
+ disableTypeFilter,
1929
+ showUrl,
1930
+ filter,
1931
+ request,
1932
+ renderSuggestion,
1933
+ renderSelected,
1934
+ renderGroupHeading,
1935
+ labels = DEFAULT_LABELS,
1936
+ className,
1937
+ id
1938
+ }) => {
1939
+ const reactId = react$1.useId();
1940
+ const rootId = id ?? `valu-cp-${reactId.replace(/:/g, "")}`;
1941
+ const [uncontrolled, setUncontrolled] = react$1.useState(defaultValue ?? []);
1942
+ const isControlled = controlledValue !== void 0;
1943
+ const value = isControlled ? controlledValue : uncontrolled;
1944
+ const handleChange = react$1.useCallback(
1945
+ (next) => {
1946
+ if (!isControlled) setUncontrolled(next);
1947
+ onChange?.(next);
1948
+ },
1949
+ [isControlled, onChange]
1950
+ );
1951
+ const effectiveTypes = types ?? defaultTypesFor(kind);
1952
+ const effectivePerPage = perPage ?? (variant === "popover" ? 10 : 20);
1953
+ const effectiveOrderable = orderable ?? (Number.isFinite(maxItems) ? maxItems > 1 : true);
1954
+ const effectiveGroupByType = groupByType2 ?? effectiveTypes.length > 1;
1955
+ const effectiveShowTypeBadge = showTypeBadge ?? effectiveTypes.length > 1;
1956
+ const queryClientRef = react$1.useRef(null);
1957
+ if (!queryClientRef.current) queryClientRef.current = makeQueryClient();
1958
+ const showInput = !(Number.isFinite(maxItems) && value.length >= maxItems);
1959
+ const variantProps = react$1.useMemo(
1960
+ () => ({
1961
+ rootId,
1962
+ label,
1963
+ hideLabelFromVision,
1964
+ help,
1965
+ placeholder,
1966
+ kind,
1967
+ types: effectiveTypes,
1968
+ perPage: effectivePerPage,
1969
+ inputDelayMs,
1970
+ excludeCurrentPost,
1971
+ uniqueItems,
1972
+ maxItems,
1973
+ groupByType: effectiveGroupByType,
1974
+ fetchOnOpen,
1975
+ filter,
1976
+ request,
1977
+ renderSuggestion,
1978
+ renderGroupHeading,
1979
+ icons,
1980
+ disableTypeFilter,
1981
+ showUrl,
1982
+ value,
1983
+ onChange: handleChange,
1984
+ labels
1985
+ }),
1986
+ [
1987
+ rootId,
1988
+ label,
1989
+ hideLabelFromVision,
1990
+ help,
1991
+ placeholder,
1992
+ kind,
1993
+ effectiveTypes,
1994
+ effectivePerPage,
1995
+ inputDelayMs,
1996
+ excludeCurrentPost,
1997
+ uniqueItems,
1998
+ maxItems,
1999
+ effectiveGroupByType,
2000
+ fetchOnOpen,
2001
+ filter,
2002
+ request,
2003
+ renderSuggestion,
2004
+ renderGroupHeading,
2005
+ icons,
2006
+ disableTypeFilter,
2007
+ showUrl,
2008
+ value,
2009
+ handleChange,
2010
+ labels
2011
+ ]
2012
+ );
2013
+ return /* @__PURE__ */ jsxRuntime.jsx(EmotionCacheProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClientRef.current, children: /* @__PURE__ */ jsxRuntime.jsxs(Root, { id: rootId, className, children: [
2014
+ showInput ? variant === "popover" ? /* @__PURE__ */ jsxRuntime.jsx(ContentPickerPopover, { ...variantProps }) : /* @__PURE__ */ jsxRuntime.jsx(ContentPickerInline, { ...variantProps }) : null,
2015
+ /* @__PURE__ */ jsxRuntime.jsx(
2016
+ SelectedList,
2017
+ {
2018
+ value,
2019
+ orderable: effectiveOrderable && value.length > 1,
2020
+ maxItems,
2021
+ labels,
2022
+ onChange: handleChange,
2023
+ renderSelected,
2024
+ showHeader: value.length > 0,
2025
+ showTypeBadge: effectiveShowTypeBadge,
2026
+ icons
2027
+ }
2028
+ )
2029
+ ] }) }) });
2030
+ };
2031
+
2032
+ exports.ContentPicker = ContentPicker;
2033
+ //# sourceMappingURL=index.cjs.map
2034
+ //# sourceMappingURL=index.cjs.map