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