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