@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.
package/dist/index.cjs CHANGED
@@ -19,6 +19,7 @@ var core = require('@dnd-kit/core');
19
19
  var sortable = require('@dnd-kit/sortable');
20
20
  var utilities = require('@dnd-kit/utilities');
21
21
  var coreData = require('@wordpress/core-data');
22
+ var mediaUtils = require('@wordpress/media-utils');
22
23
 
23
24
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
24
25
 
@@ -2241,6 +2242,22 @@ var SummaryCell = styled__default.default.td`
2241
2242
  color: ${tokens2.gray600};
2242
2243
  font-variant-numeric: tabular-nums;
2243
2244
  `;
2245
+ var FillerCell = styled__default.default.td`
2246
+ width: 100%;
2247
+ padding: 0;
2248
+ `;
2249
+ var FillerHeaderCell = styled__default.default.th`
2250
+ width: 100%;
2251
+ padding: 0;
2252
+
2253
+ ${(p) => p.$sticky && react.css`
2254
+ position: sticky;
2255
+ top: 0;
2256
+ z-index: ${tokens2.zHeader};
2257
+ background: ${tokens2.bg};
2258
+ box-shadow: ${tokens2.shadowStickyTop};
2259
+ `};
2260
+ `;
2244
2261
  var CellControlWrap = styled__default.default.div`
2245
2262
  display: inline-flex;
2246
2263
  align-items: center;
@@ -2309,7 +2326,8 @@ var MatrixHeader = ({
2309
2326
  children: renderHeader ? renderHeader(col) : col.label
2310
2327
  },
2311
2328
  col.id
2312
- ))
2329
+ )),
2330
+ /* @__PURE__ */ jsxRuntime.jsx(FillerHeaderCell, { "aria-hidden": true, $sticky: stickyHeader })
2313
2331
  ] }) });
2314
2332
  }
2315
2333
  const TOP_ROW_HEIGHT = "36px";
@@ -2366,7 +2384,8 @@ var MatrixHeader = ({
2366
2384
  },
2367
2385
  group.id
2368
2386
  );
2369
- })
2387
+ }),
2388
+ /* @__PURE__ */ jsxRuntime.jsx(FillerHeaderCell, { "aria-hidden": true, rowSpan: 2, $sticky: stickyHeader })
2370
2389
  ] }),
2371
2390
  /* @__PURE__ */ jsxRuntime.jsx("tr", { children: groups.map((group) => {
2372
2391
  const open = isExpanded(group, expandedGroups);
@@ -2578,7 +2597,8 @@ var MatrixRowView = ({
2578
2597
  },
2579
2598
  `${group.id}::${col.id}`
2580
2599
  ));
2581
- })
2600
+ }),
2601
+ /* @__PURE__ */ jsxRuntime.jsx(FillerCell, { "aria-hidden": true })
2582
2602
  ] });
2583
2603
  };
2584
2604
  var EmotionCacheProvider2 = ({ children }) => {
@@ -2661,6 +2681,7 @@ var Matrix = (props) => {
2661
2681
  renderHeader,
2662
2682
  stickyHeader = true,
2663
2683
  stickyFirstColumn = true,
2684
+ rowHeaderWidth = 220,
2664
2685
  maxHeight = DEFAULT_MAX_HEIGHT,
2665
2686
  caption,
2666
2687
  labels,
@@ -2680,9 +2701,16 @@ var Matrix = (props) => {
2680
2701
  onChange: onExpandedChange
2681
2702
  });
2682
2703
  const maxHeightCss = typeof maxHeight === "number" ? `${maxHeight}px` : maxHeight;
2683
- const masterLeftOffset = "220px";
2704
+ const cssLength = (v) => typeof v === "number" ? `${v}px` : v;
2705
+ const rowHeaderWidthCss = cssLength(rowHeaderWidth);
2706
+ const masterWidthCss = master ? cssLength(master.width ?? 96) : "0";
2707
+ const masterLeftOffset = rowHeaderWidthCss;
2684
2708
  return /* @__PURE__ */ jsxRuntime.jsx(EmotionCacheProvider2, { children: /* @__PURE__ */ jsxRuntime.jsx(Root2, { id: rootId, className, children: /* @__PURE__ */ jsxRuntime.jsx(Viewport, { $maxHeight: maxHeightCss, children: /* @__PURE__ */ jsxRuntime.jsxs(Table, { children: [
2685
2709
  caption && /* @__PURE__ */ jsxRuntime.jsx(Caption, { children: caption }),
2710
+ /* @__PURE__ */ jsxRuntime.jsxs("colgroup", { children: [
2711
+ /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: rowHeaderWidthCss } }),
2712
+ master && /* @__PURE__ */ jsxRuntime.jsx("col", { style: { width: masterWidthCss } })
2713
+ ] }),
2686
2714
  /* @__PURE__ */ jsxRuntime.jsx(
2687
2715
  MatrixHeader,
2688
2716
  {
@@ -2720,10 +2748,1555 @@ var Matrix = (props) => {
2720
2748
  )) })
2721
2749
  ] }) }) }) });
2722
2750
  };
2751
+ var SelectedRow2 = styled__default.default.li`
2752
+ container-type: inline-size;
2753
+ container-name: valu-dp-row;
2754
+ display: flex;
2755
+ align-items: center;
2756
+ gap: ${tokens.spaceSm};
2757
+ padding: 2px ${tokens.spaceSm};
2758
+ background: ${({ missing }) => missing ? tokens.dangerSoft : tokens.bg};
2759
+ border: 1px solid ${({ missing }) => missing ? tokens.dangerLine : tokens.gray300};
2760
+ border-radius: ${tokens.radiusMd};
2761
+ min-height: ${tokens.rowMinHeight};
2762
+ box-shadow: ${({ dragging }) => dragging ? tokens.shadowPopover : "none"};
2763
+ transition: box-shadow ${tokens.transitionFast}, border-color ${tokens.transitionFast};
2764
+
2765
+ &:hover {
2766
+ border-color: ${({ missing }) => missing ? tokens.dangerLine : tokens.gray600};
2767
+ }
2768
+ `;
2769
+ var WIDE_ROW = "380px";
2770
+ var Option2 = styled__default.default.li`
2771
+ container-type: inline-size;
2772
+ container-name: valu-dp-row;
2773
+ display: flex;
2774
+ align-items: center;
2775
+ gap: ${tokens.spaceSm};
2776
+ padding: 4px ${tokens.spaceMd};
2777
+ cursor: ${({ disabled }) => disabled ? "not-allowed" : "pointer"};
2778
+ opacity: ${({ disabled }) => disabled ? 0.5 : 1};
2779
+ min-height: ${tokens.suggestionMinHeight};
2780
+ background: ${({ highlighted }) => highlighted ? tokens.accentActive : "transparent"};
2781
+ position: relative;
2782
+ transition: background ${tokens.transitionFast};
2783
+
2784
+ &:hover {
2785
+ background: ${({ highlighted, disabled }) => disabled ? "transparent" : highlighted ? tokens.accentActive : tokens.accentHover};
2786
+ }
2787
+
2788
+ &:hover [data-valu-title] {
2789
+ color: ${tokens.accent};
2790
+ }
2791
+
2792
+ ${({ highlighted }) => highlighted && react.css`
2793
+ [data-valu-title] {
2794
+ color: ${tokens.accent};
2795
+ }
2796
+ &::before {
2797
+ content: '';
2798
+ position: absolute;
2799
+ left: 0;
2800
+ top: 4px;
2801
+ bottom: 4px;
2802
+ width: 2px;
2803
+ border-radius: 0 ${tokens.radiusSm} ${tokens.radiusSm} 0;
2804
+ background: ${tokens.accent};
2805
+ }
2806
+ `}
2807
+ `;
2808
+ var DocRowBody = styled__default.default.div`
2809
+ flex: 1;
2810
+ min-width: 0;
2811
+ display: flex;
2812
+ flex-direction: column;
2813
+ gap: 0;
2814
+
2815
+ @container valu-dp-row (min-width: ${WIDE_ROW}) {
2816
+ flex-direction: row;
2817
+ align-items: baseline;
2818
+ gap: ${tokens.spaceMd};
2819
+ }
2820
+ `;
2821
+ var FilenameText = styled__default.default.span`
2822
+ font-size: ${tokens.fontSizeSmall};
2823
+ color: ${tokens.gray600};
2824
+ white-space: nowrap;
2825
+ overflow: hidden;
2826
+ text-overflow: ellipsis;
2827
+ display: block;
2828
+ line-height: 1.3;
2829
+ min-width: 0;
2830
+
2831
+ @container valu-dp-row (min-width: ${WIDE_ROW}) {
2832
+ flex: 1;
2833
+ }
2834
+ `;
2835
+ var DocMetaInline = styled__default.default.div`
2836
+ display: none;
2837
+
2838
+ @container valu-dp-row (min-width: ${WIDE_ROW}) {
2839
+ display: inline-flex;
2840
+ align-items: center;
2841
+ gap: ${tokens.spaceSm};
2842
+ flex: 0 0 auto;
2843
+ color: ${tokens.gray600};
2844
+ font-size: ${tokens.fontSizeSmall};
2845
+ white-space: nowrap;
2846
+ }
2847
+ `;
2848
+ var Root3 = styled__default.default.div`
2849
+ color: ${tokens.fg};
2850
+ font-size: ${tokens.fontSizeBody};
2851
+
2852
+ * {
2853
+ box-sizing: border-box;
2854
+ }
2855
+ `;
2856
+ var ActionsRow = styled__default.default.div`
2857
+ display: flex;
2858
+ align-items: center;
2859
+ gap: ${tokens.spaceSm};
2860
+ margin-top: ${tokens.spaceSm};
2861
+ `;
2862
+ var FileMeta = styled__default.default.span`
2863
+ font-size: ${tokens.fontSizeSmall};
2864
+ color: ${tokens.gray600};
2865
+ white-space: nowrap;
2866
+ display: inline-flex;
2867
+ align-items: center;
2868
+ gap: ${tokens.spaceXs};
2869
+
2870
+ &::before {
2871
+ content: '·';
2872
+ opacity: 0.6;
2873
+ }
2874
+
2875
+ &:first-of-type::before {
2876
+ content: '';
2877
+ }
2878
+ `;
2879
+ styled__default.default.div`
2880
+ display: flex;
2881
+ align-items: center;
2882
+ gap: ${tokens.spaceSm};
2883
+ flex-wrap: wrap;
2884
+ min-width: 0;
2885
+ color: ${tokens.gray600};
2886
+ `;
2887
+ var ExtensionPill = styled__default.default.span`
2888
+ flex: 0 0 auto;
2889
+ display: inline-flex;
2890
+ align-items: center;
2891
+ justify-content: center;
2892
+ min-width: 38px;
2893
+ padding: 2px 6px;
2894
+ font-size: 10px;
2895
+ font-weight: ${tokens.fontWeightBold};
2896
+ letter-spacing: 0.04em;
2897
+ text-transform: uppercase;
2898
+ border-radius: ${tokens.radiusSm};
2899
+ border: 1px solid currentColor;
2900
+ line-height: 1.4;
2901
+ white-space: nowrap;
2902
+
2903
+ ${({ tone = "gray" }) => {
2904
+ switch (tone) {
2905
+ case "red":
2906
+ return react.css`
2907
+ color: #c0392b;
2908
+ background: #fdecea;
2909
+ `;
2910
+ case "blue":
2911
+ return react.css`
2912
+ color: #1a4fb8;
2913
+ background: #e8efff;
2914
+ `;
2915
+ case "green":
2916
+ return react.css`
2917
+ color: #1f6b3a;
2918
+ background: #e6f4ea;
2919
+ `;
2920
+ case "orange":
2921
+ return react.css`
2922
+ color: #b15a06;
2923
+ background: #fff1de;
2924
+ `;
2925
+ default:
2926
+ return react.css`
2927
+ color: ${tokens.gray900};
2928
+ background: ${tokens.gray100};
2929
+ `;
2930
+ }
2931
+ }}
2932
+ `;
2933
+ var generateKey2 = () => uuid.v4();
2934
+ var aria2 = (rootId, suffix) => `${rootId}-${suffix}`;
2935
+ var isMaxedOut2 = (value, maxItems) => Number.isFinite(maxItems) && value.length >= maxItems;
2936
+ var DEFAULT_DOCUMENT_MIME_TYPES = [
2937
+ "application/pdf",
2938
+ "application/msword",
2939
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
2940
+ "application/vnd.ms-excel",
2941
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
2942
+ "application/vnd.ms-powerpoint",
2943
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation"
2944
+ ];
2945
+ var decodeRendered = (raw) => {
2946
+ if (typeof raw === "string") return htmlEntities.decodeEntities(raw);
2947
+ if (raw && typeof raw === "object" && "rendered" in raw) {
2948
+ const rendered = raw.rendered;
2949
+ if (typeof rendered === "string") return htmlEntities.decodeEntities(rendered);
2950
+ }
2951
+ return "";
2952
+ };
2953
+ var basenameFromUrl = (url) => {
2954
+ if (!url) return "";
2955
+ const cleaned = url.split("#")[0].split("?")[0];
2956
+ const segments = cleaned.split("/");
2957
+ return segments[segments.length - 1] ?? "";
2958
+ };
2959
+ var extensionFromUrl = (url) => {
2960
+ const base = basenameFromUrl(url);
2961
+ const dot = base.lastIndexOf(".");
2962
+ if (dot <= 0 || dot === base.length - 1) return "";
2963
+ return base.slice(dot + 1).toUpperCase();
2964
+ };
2965
+ var formatFileSize = (bytes) => {
2966
+ if (!bytes || bytes <= 0 || !Number.isFinite(bytes)) return "";
2967
+ const units = ["B", "KB", "MB", "GB", "TB"];
2968
+ let value = bytes;
2969
+ let unit = 0;
2970
+ while (value >= 1024 && unit < units.length - 1) {
2971
+ value /= 1024;
2972
+ unit += 1;
2973
+ }
2974
+ const fixed = value >= 100 || unit === 0 ? value.toFixed(0) : value.toFixed(1);
2975
+ return `${fixed} ${units[unit]}`;
2976
+ };
2977
+ var formatModified = (iso) => {
2978
+ if (!iso) return "";
2979
+ const date = new Date(iso);
2980
+ if (Number.isNaN(date.getTime())) return "";
2981
+ try {
2982
+ return new Intl.DateTimeFormat(void 0, { dateStyle: "medium" }).format(date);
2983
+ } catch {
2984
+ return date.toISOString().slice(0, 10);
2985
+ }
2986
+ };
2987
+ var mapMediaToDocumentItem = (raw) => {
2988
+ const url = raw.source_url ?? raw.url ?? raw.link ?? "";
2989
+ const filename = raw.filename || basenameFromUrl(url);
2990
+ const title = decodeRendered(raw.title) || filename;
2991
+ const mimeType = raw.mime_type ?? raw.mime ?? "";
2992
+ const extension = extensionFromUrl(url) || extensionFromUrl(filename);
2993
+ const fileSize = raw.media_details?.filesize;
2994
+ const modified = raw.modified ?? raw.modified_gmt ?? raw.date;
2995
+ return {
2996
+ key: generateKey2(),
2997
+ id: raw.id,
2998
+ title,
2999
+ filename,
3000
+ url,
3001
+ mimeType,
3002
+ extension,
3003
+ fileSize,
3004
+ modified,
3005
+ raw
3006
+ };
3007
+ };
3008
+ var dedupeAgainst2 = (items, value, enabled) => {
3009
+ if (!enabled || value.length === 0) return items;
3010
+ const ids = new Set(value.map((v) => v.id));
3011
+ return items.filter((i) => !ids.has(i.id));
3012
+ };
3013
+ var matchesAllowed = (item, allowedMimeTypes, allowedExtensions) => {
3014
+ const hasMime = allowedMimeTypes && allowedMimeTypes.length > 0;
3015
+ const hasExt = allowedExtensions && allowedExtensions.length > 0;
3016
+ if (!hasMime && !hasExt) return true;
3017
+ if (hasMime && allowedMimeTypes.includes(item.mimeType)) return true;
3018
+ if (hasExt) {
3019
+ const upper = item.extension.toUpperCase();
3020
+ if (allowedExtensions.some((e) => e.replace(/^\./, "").toUpperCase() === upper)) {
3021
+ return true;
3022
+ }
3023
+ }
3024
+ return false;
3025
+ };
3026
+ var extensionA11yLabel = (extension) => extension ? i18n.__("File type", "valu-block-components") + ": " + extension : i18n.__("Unknown file type", "valu-block-components");
3027
+ var SearchCombobox2 = react$1.forwardRef(
3028
+ ({
3029
+ rootId,
3030
+ value,
3031
+ onChange,
3032
+ onFocus,
3033
+ onBlur,
3034
+ onKeyDown,
3035
+ placeholder,
3036
+ label,
3037
+ hideLabelFromVision,
3038
+ help,
3039
+ disabled,
3040
+ open,
3041
+ listboxId,
3042
+ activeOptionId,
3043
+ labels
3044
+ }, ref) => {
3045
+ const inputId = aria2(rootId, "input");
3046
+ const helpId = help ? aria2(rootId, "help") : void 0;
3047
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3048
+ 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 }),
3049
+ /* @__PURE__ */ jsxRuntime.jsxs(InputShell, { "aria-disabled": disabled || void 0, children: [
3050
+ /* @__PURE__ */ jsxRuntime.jsx(SearchIconWrap, { "aria-hidden": "true", children: /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.search, size: 20 }) }),
3051
+ /* @__PURE__ */ jsxRuntime.jsx(
3052
+ Input,
3053
+ {
3054
+ ref,
3055
+ id: inputId,
3056
+ type: "text",
3057
+ role: "combobox",
3058
+ "aria-expanded": open,
3059
+ "aria-controls": listboxId,
3060
+ "aria-autocomplete": "list",
3061
+ "aria-activedescendant": activeOptionId,
3062
+ "aria-describedby": helpId,
3063
+ "aria-label": hideLabelFromVision ? label : void 0,
3064
+ value,
3065
+ placeholder: placeholder ?? labels.input ?? i18n.__("Search documents", "valu-block-components"),
3066
+ disabled,
3067
+ autoComplete: "off",
3068
+ spellCheck: false,
3069
+ onChange: (event) => onChange(event.target.value),
3070
+ onFocus,
3071
+ onBlur,
3072
+ onKeyDown
3073
+ }
3074
+ ),
3075
+ value.length > 0 && !disabled ? /* @__PURE__ */ jsxRuntime.jsx(
3076
+ "button",
3077
+ {
3078
+ type: "button",
3079
+ onClick: () => onChange(""),
3080
+ "aria-label": i18n.__("Clear search", "valu-block-components"),
3081
+ style: {
3082
+ display: "flex",
3083
+ alignItems: "center",
3084
+ justifyContent: "center",
3085
+ background: "transparent",
3086
+ border: 0,
3087
+ padding: 2,
3088
+ cursor: "pointer",
3089
+ borderRadius: 2,
3090
+ color: "currentColor",
3091
+ opacity: 0.6
3092
+ },
3093
+ children: /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.closeSmall, size: 18 })
3094
+ }
3095
+ ) : null
3096
+ ] }),
3097
+ help ? /* @__PURE__ */ jsxRuntime.jsx("p", { id: helpId, style: { margin: "4px 0 0", fontSize: 11, opacity: 0.7 }, children: help }) : null
3098
+ ] });
3099
+ }
3100
+ );
3101
+ SearchCombobox2.displayName = "SearchCombobox";
3102
+ var toneFor2 = (extension) => {
3103
+ const ext = extension.toUpperCase();
3104
+ if (ext === "PDF") return "red";
3105
+ if (ext === "DOC" || ext === "DOCX" || ext === "RTF" || ext === "ODT") return "blue";
3106
+ if (ext === "XLS" || ext === "XLSX" || ext === "CSV" || ext === "ODS") return "green";
3107
+ if (ext === "PPT" || ext === "PPTX" || ext === "ODP") return "orange";
3108
+ return "gray";
3109
+ };
3110
+ var ExtensionBadge = ({ extension, label }) => {
3111
+ const visible = label ?? (extension || "?");
3112
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3113
+ /* @__PURE__ */ jsxRuntime.jsx(ExtensionPill, { tone: toneFor2(extension), "aria-hidden": "true", children: visible }),
3114
+ /* @__PURE__ */ jsxRuntime.jsx(components.VisuallyHidden, { children: extensionA11yLabel(extension) })
3115
+ ] });
3116
+ };
3117
+ var SuggestionItem2 = ({
3118
+ item,
3119
+ optionId,
3120
+ highlighted,
3121
+ selected,
3122
+ showExtensionBadge,
3123
+ showFileSize,
3124
+ showModifiedDate,
3125
+ onHover,
3126
+ onSelect,
3127
+ render
3128
+ }) => {
3129
+ const ref = react$1.useRef(null);
3130
+ react$1.useEffect(() => {
3131
+ if (!highlighted || !ref.current) return;
3132
+ ref.current.scrollIntoView({ block: "nearest" });
3133
+ }, [highlighted]);
3134
+ const handleClick = react$1.useCallback(
3135
+ (event) => {
3136
+ event.preventDefault();
3137
+ onSelect();
3138
+ },
3139
+ [onSelect]
3140
+ );
3141
+ const handleMouseDown = react$1.useCallback((event) => {
3142
+ event.preventDefault();
3143
+ }, []);
3144
+ if (render) {
3145
+ return /* @__PURE__ */ jsxRuntime.jsx(
3146
+ Option2,
3147
+ {
3148
+ ref,
3149
+ role: "option",
3150
+ id: optionId,
3151
+ "aria-selected": selected,
3152
+ highlighted,
3153
+ onMouseEnter: onHover,
3154
+ onMouseDown: handleMouseDown,
3155
+ onClick: handleClick,
3156
+ children: render(item, { highlighted, selected })
3157
+ }
3158
+ );
3159
+ }
3160
+ const modified = showModifiedDate ? formatModified(item.modified) : "";
3161
+ const hasMeta = Boolean(showFileSize && item.fileSize || modified);
3162
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3163
+ Option2,
3164
+ {
3165
+ ref,
3166
+ role: "option",
3167
+ id: optionId,
3168
+ "aria-selected": selected,
3169
+ highlighted,
3170
+ onMouseEnter: onHover,
3171
+ onMouseDown: handleMouseDown,
3172
+ onClick: handleClick,
3173
+ children: [
3174
+ showExtensionBadge ? /* @__PURE__ */ jsxRuntime.jsx(ExtensionBadge, { extension: item.extension }) : null,
3175
+ /* @__PURE__ */ jsxRuntime.jsxs(DocRowBody, { children: [
3176
+ /* @__PURE__ */ jsxRuntime.jsx(PrimaryText, { "data-valu-title": true, title: item.title, children: item.title }),
3177
+ /* @__PURE__ */ jsxRuntime.jsx(FilenameText, { title: item.filename, children: item.filename })
3178
+ ] }),
3179
+ hasMeta ? /* @__PURE__ */ jsxRuntime.jsxs(DocMetaInline, { children: [
3180
+ showFileSize && item.fileSize ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatFileSize(item.fileSize) }) : null,
3181
+ modified ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: modified }) : null
3182
+ ] }) : null
3183
+ ]
3184
+ }
3185
+ );
3186
+ };
3187
+ var SuggestionList2 = ({
3188
+ listboxId,
3189
+ rootId,
3190
+ suggestions,
3191
+ loading,
3192
+ fetchingMore,
3193
+ hasMore,
3194
+ error,
3195
+ query,
3196
+ highlightedIndex,
3197
+ selectedKeys,
3198
+ labels,
3199
+ showExtensionBadge,
3200
+ showFileSize,
3201
+ showModifiedDate,
3202
+ headerSlot,
3203
+ renderSuggestion,
3204
+ onHover,
3205
+ onSelect,
3206
+ onLoadMore
3207
+ }) => {
3208
+ const scrollRef = react$1.useRef(null);
3209
+ const sentinelRef = react$1.useRef(null);
3210
+ react$1.useEffect(() => {
3211
+ if (!hasMore || !sentinelRef.current || !scrollRef.current) return;
3212
+ const io = new IntersectionObserver(
3213
+ (entries) => {
3214
+ for (const e of entries) {
3215
+ if (e.isIntersecting) onLoadMore();
3216
+ }
3217
+ },
3218
+ { root: scrollRef.current, rootMargin: "120px" }
3219
+ );
3220
+ io.observe(sentinelRef.current);
3221
+ return () => io.disconnect();
3222
+ }, [hasMore, onLoadMore]);
3223
+ const header = headerSlot ? /* @__PURE__ */ jsxRuntime.jsx(StickyHeader, { children: headerSlot }) : null;
3224
+ if (error) {
3225
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
3226
+ header,
3227
+ /* @__PURE__ */ jsxRuntime.jsx(ErrorState, { role: "alert", children: labels.empty ?? i18n.__("Search failed. Please try again.", "valu-block-components") })
3228
+ ] });
3229
+ }
3230
+ if (loading && suggestions.length === 0) {
3231
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
3232
+ header,
3233
+ /* @__PURE__ */ jsxRuntime.jsxs(LoadingState, { children: [
3234
+ /* @__PURE__ */ jsxRuntime.jsx(components.Spinner, {}),
3235
+ labels.loading ?? i18n.__("Searching\u2026", "valu-block-components")
3236
+ ] })
3237
+ ] });
3238
+ }
3239
+ if (suggestions.length === 0) {
3240
+ 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");
3241
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
3242
+ header,
3243
+ /* @__PURE__ */ jsxRuntime.jsx(EmptyState, { children: emptyMessage })
3244
+ ] });
3245
+ }
3246
+ return /* @__PURE__ */ jsxRuntime.jsxs(ListboxScroll, { ref: scrollRef, children: [
3247
+ header,
3248
+ /* @__PURE__ */ jsxRuntime.jsxs(
3249
+ Listbox,
3250
+ {
3251
+ role: "listbox",
3252
+ id: listboxId,
3253
+ "aria-label": labels.input ?? i18n.__("Document suggestions", "valu-block-components"),
3254
+ children: [
3255
+ suggestions.map((item, index) => {
3256
+ const optionId = aria2(rootId, `option-${index}`);
3257
+ return /* @__PURE__ */ jsxRuntime.jsx(
3258
+ SuggestionItem2,
3259
+ {
3260
+ item,
3261
+ optionId,
3262
+ highlighted: index === highlightedIndex,
3263
+ selected: selectedKeys.has(item.key),
3264
+ showExtensionBadge,
3265
+ showFileSize,
3266
+ showModifiedDate,
3267
+ onHover: () => onHover(index),
3268
+ onSelect: () => onSelect(item),
3269
+ ...renderSuggestion ? { render: renderSuggestion } : {}
3270
+ },
3271
+ item.key
3272
+ );
3273
+ }),
3274
+ hasMore ? /* @__PURE__ */ jsxRuntime.jsx(LoadMoreRow, { children: /* @__PURE__ */ jsxRuntime.jsx(
3275
+ components.Button,
3276
+ {
3277
+ variant: "tertiary",
3278
+ onClick: onLoadMore,
3279
+ disabled: fetchingMore,
3280
+ isBusy: fetchingMore,
3281
+ children: fetchingMore ? i18n.__("Loading\u2026", "valu-block-components") : i18n.__("Load more", "valu-block-components")
3282
+ }
3283
+ ) }) : null,
3284
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: sentinelRef, "aria-hidden": "true" })
3285
+ ]
3286
+ }
3287
+ )
3288
+ ] });
3289
+ };
3290
+ var MediaUpload = mediaUtils.MediaUpload;
3291
+ var normalize = (raw, allowedMimeTypes, allowedExtensions) => {
3292
+ const list = Array.isArray(raw) ? raw : [raw];
3293
+ return list.map((r) => mapMediaToDocumentItem(r)).filter((item) => matchesAllowed(item, allowedMimeTypes, allowedExtensions));
3294
+ };
3295
+ var MediaLibraryButton = ({
3296
+ allowedMimeTypes,
3297
+ allowedExtensions,
3298
+ multiple,
3299
+ value,
3300
+ onSelect,
3301
+ labels,
3302
+ disabled,
3303
+ variant = "secondary",
3304
+ label
3305
+ }) => {
3306
+ const selectedIds = multiple ? value.map((v) => v.id) : value[0]?.id;
3307
+ const buttonLabel = label ?? labels.browseMediaLibrary ?? i18n.__("Browse Media Library", "valu-block-components");
3308
+ return /* @__PURE__ */ jsxRuntime.jsx(
3309
+ MediaUpload,
3310
+ {
3311
+ allowedTypes: allowedMimeTypes,
3312
+ multiple,
3313
+ gallery: false,
3314
+ ...selectedIds !== void 0 ? { value: selectedIds } : {},
3315
+ onSelect: (selection) => {
3316
+ const items = normalize(selection, allowedMimeTypes, allowedExtensions);
3317
+ if (items.length > 0) onSelect(items);
3318
+ },
3319
+ render: ({ open }) => /* @__PURE__ */ jsxRuntime.jsx(components.Button, { variant, icon: icons.upload, onClick: open, disabled, children: buttonLabel })
3320
+ }
3321
+ );
3322
+ };
3323
+ var buildPath2 = (args) => {
3324
+ const params = {
3325
+ per_page: args.perPage,
3326
+ page: args.page,
3327
+ _fields: [
3328
+ "id",
3329
+ "title",
3330
+ "source_url",
3331
+ "mime_type",
3332
+ "media_details",
3333
+ "modified",
3334
+ "date"
3335
+ ].join(","),
3336
+ order: "desc"
3337
+ };
3338
+ const trimmed = args.query.trim();
3339
+ if (trimmed.length > 0) {
3340
+ params.search = trimmed;
3341
+ params.orderby = "relevance";
3342
+ } else {
3343
+ params.orderby = "date";
3344
+ }
3345
+ if (args.mimeTypes.length > 0) {
3346
+ params.mime_type = args.mimeTypes.join(",");
3347
+ }
3348
+ return url.addQueryArgs("/wp/v2/media", params);
3349
+ };
3350
+ var parseHeader = (response, key) => {
3351
+ const raw = response.headers.get(key);
3352
+ if (!raw) return 0;
3353
+ const parsed = parseInt(raw, 10);
3354
+ return Number.isNaN(parsed) ? 0 : parsed;
3355
+ };
3356
+ var fetchDocuments = async (args) => {
3357
+ const rawPath = buildPath2(args);
3358
+ const path = args.request ? args.request(rawPath, { query: args.query, page: args.page }) : rawPath;
3359
+ const response = await apiFetch__default.default({
3360
+ path,
3361
+ parse: false,
3362
+ signal: args.signal
3363
+ });
3364
+ const data = await response.json();
3365
+ const totalPages = parseHeader(response, "x-wp-totalpages") || 1;
3366
+ const total = parseHeader(response, "x-wp-total");
3367
+ const items = data.map(mapMediaToDocumentItem).filter((item) => matchesAllowed(item, args.mimeTypes, args.allowedExtensions));
3368
+ return {
3369
+ items,
3370
+ page: args.page,
3371
+ hasMore: args.page < totalPages,
3372
+ total
3373
+ };
3374
+ };
3375
+
3376
+ // components/document-picker/hooks/useDocumentSearch.ts
3377
+ var documentSearchKey = (args) => [
3378
+ "valu/document-search",
3379
+ [...args.mimeTypes].sort().join(","),
3380
+ [...args.allowedExtensions ?? []].sort().join(","),
3381
+ args.query,
3382
+ args.perPage
3383
+ ];
3384
+ var useDocumentSearch = (args) => reactQuery.useInfiniteQuery({
3385
+ queryKey: documentSearchKey(args),
3386
+ queryFn: ({ pageParam, signal }) => fetchDocuments({
3387
+ query: args.query,
3388
+ page: pageParam,
3389
+ perPage: args.perPage,
3390
+ mimeTypes: args.mimeTypes,
3391
+ ...args.allowedExtensions !== void 0 ? { allowedExtensions: args.allowedExtensions } : {},
3392
+ ...args.request !== void 0 ? { request: args.request } : {},
3393
+ signal
3394
+ }),
3395
+ initialPageParam: 1,
3396
+ getNextPageParam: (last) => last.hasMore ? last.page + 1 : void 0,
3397
+ enabled: args.enabled ?? true,
3398
+ staleTime: 3e4,
3399
+ placeholderData: reactQuery.keepPreviousData
3400
+ });
3401
+
3402
+ // components/document-picker/hooks/useDocumentPickerController.ts
3403
+ var useDocumentPickerController = (args) => {
3404
+ const {
3405
+ mimeTypes,
3406
+ allowedExtensions,
3407
+ perPage,
3408
+ inputDelayMs,
3409
+ uniqueItems,
3410
+ maxItems,
3411
+ filter,
3412
+ request,
3413
+ value,
3414
+ onChange,
3415
+ active,
3416
+ fetchEmpty,
3417
+ onSelectSideEffect,
3418
+ onEscape
3419
+ } = args;
3420
+ const [query, setQuery] = react$1.useState("");
3421
+ const debouncedQuery = useDebouncedValue(query, inputDelayMs);
3422
+ const enabled = active && (fetchEmpty || debouncedQuery.trim().length > 0);
3423
+ const query_ = useDocumentSearch({
3424
+ query: debouncedQuery,
3425
+ mimeTypes,
3426
+ ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
3427
+ perPage,
3428
+ enabled,
3429
+ ...request !== void 0 ? { request } : {}
3430
+ });
3431
+ const suggestions = react$1.useMemo(() => {
3432
+ if (!query_.data) return [];
3433
+ const flat = query_.data.pages.flatMap((p) => p.items);
3434
+ let filtered = dedupeAgainst2(flat, value, uniqueItems);
3435
+ if (filter) {
3436
+ filtered = filter(filtered, { query: debouncedQuery });
3437
+ }
3438
+ return filtered;
3439
+ }, [query_.data, value, uniqueItems, filter, debouncedQuery]);
3440
+ const { announce, announceImmediate } = useLiveRegion();
3441
+ react$1.useEffect(() => {
3442
+ if (!active) return;
3443
+ if (debouncedQuery.trim().length === 0 && !fetchEmpty) return;
3444
+ if (query_.isFetching) return;
3445
+ if (query_.isError) {
3446
+ announce(i18n.__("Search failed. Try again.", "valu-block-components"));
3447
+ return;
3448
+ }
3449
+ const count = suggestions.length;
3450
+ announce(
3451
+ i18n.sprintf(
3452
+ /* translators: %d: number of search results */
3453
+ i18n._n("%d document found", "%d documents found", count, "valu-block-components"),
3454
+ count
3455
+ )
3456
+ );
3457
+ }, [
3458
+ suggestions.length,
3459
+ query_.isFetching,
3460
+ query_.isError,
3461
+ active,
3462
+ debouncedQuery,
3463
+ fetchEmpty,
3464
+ announce
3465
+ ]);
3466
+ const maxedOut = isMaxedOut2(value, maxItems);
3467
+ const replace = react$1.useCallback((items) => onChange(items), [onChange]);
3468
+ const selectMany = react$1.useCallback(
3469
+ (items) => {
3470
+ if (items.length === 0) return;
3471
+ const limited = maxItems === 1;
3472
+ const normalized = items.map((item) => ({
3473
+ ...item,
3474
+ key: item.key && item.key.length > 0 ? item.key : generateKey2()
3475
+ }));
3476
+ if (limited) {
3477
+ const next2 = [normalized[normalized.length - 1]];
3478
+ onChange(next2);
3479
+ announceImmediate(
3480
+ i18n.sprintf(
3481
+ /* translators: %s: document title */
3482
+ i18n.__("Selected %s.", "valu-block-components"),
3483
+ next2[0].title
3484
+ )
3485
+ );
3486
+ onSelectSideEffect?.();
3487
+ return;
3488
+ }
3489
+ const existingIds = new Set(value.map((v) => v.id));
3490
+ const remaining = Number.isFinite(maxItems) ? Math.max(0, maxItems - value.length) : normalized.length;
3491
+ const toAdd = (uniqueItems ? normalized.filter((n) => !existingIds.has(n.id)) : normalized).slice(0, remaining);
3492
+ if (toAdd.length === 0) return;
3493
+ const next = [...value, ...toAdd];
3494
+ onChange(next);
3495
+ announceImmediate(
3496
+ i18n.sprintf(
3497
+ /* translators: 1: number added, 2: total selected */
3498
+ i18n._n(
3499
+ "Added %1$d document. %2$d selected.",
3500
+ "Added %1$d documents. %2$d selected.",
3501
+ toAdd.length,
3502
+ "valu-block-components"
3503
+ ),
3504
+ toAdd.length,
3505
+ next.length
3506
+ )
3507
+ );
3508
+ onSelectSideEffect?.();
3509
+ },
3510
+ [maxItems, value, uniqueItems, onChange, announceImmediate, onSelectSideEffect]
3511
+ );
3512
+ const select = react$1.useCallback(
3513
+ (item) => {
3514
+ if (maxedOut && maxItems !== 1) return;
3515
+ selectMany([item]);
3516
+ },
3517
+ [maxedOut, maxItems, selectMany]
3518
+ );
3519
+ const remove = react$1.useCallback(
3520
+ (key) => {
3521
+ const target = value.find((i) => i.key === key);
3522
+ const next = value.filter((i) => i.key !== key);
3523
+ onChange(next);
3524
+ if (target) {
3525
+ announceImmediate(
3526
+ i18n.sprintf(
3527
+ /* translators: %s: title of the removed document */
3528
+ i18n.__("Removed %s.", "valu-block-components"),
3529
+ target.title
3530
+ )
3531
+ );
3532
+ }
3533
+ },
3534
+ [value, onChange, announceImmediate]
3535
+ );
3536
+ const reorder = react$1.useCallback(
3537
+ (from, to) => {
3538
+ if (from === to || from < 0 || to < 0 || from >= value.length || to >= value.length) {
3539
+ return;
3540
+ }
3541
+ const next = value.slice();
3542
+ const [moved] = next.splice(from, 1);
3543
+ next.splice(to, 0, moved);
3544
+ onChange(next);
3545
+ announceImmediate(
3546
+ i18n.sprintf(
3547
+ /* translators: 1: title, 2: position, 3: total */
3548
+ i18n.__("Moved %1$s to position %2$d of %3$d.", "valu-block-components"),
3549
+ moved.title,
3550
+ to + 1,
3551
+ next.length
3552
+ )
3553
+ );
3554
+ },
3555
+ [value, onChange, announceImmediate]
3556
+ );
3557
+ const {
3558
+ highlightedIndex,
3559
+ setHighlightedIndex,
3560
+ reset: resetHighlight,
3561
+ handleKeyDown
3562
+ } = useKeyboardNav({
3563
+ itemCount: suggestions.length,
3564
+ onSelect: (i) => {
3565
+ const item = suggestions[i];
3566
+ if (item) select(item);
3567
+ },
3568
+ ...onEscape !== void 0 ? { onEscape } : {}
3569
+ });
3570
+ react$1.useEffect(() => {
3571
+ resetHighlight();
3572
+ }, [debouncedQuery, resetHighlight]);
3573
+ return {
3574
+ query,
3575
+ setQuery,
3576
+ debouncedQuery,
3577
+ suggestions,
3578
+ highlightedIndex,
3579
+ setHighlightedIndex,
3580
+ handleKeyDown,
3581
+ loading: query_.isFetching && !query_.data,
3582
+ error: query_.error,
3583
+ hasMore: Boolean(query_.hasNextPage),
3584
+ loadMore: () => {
3585
+ if (query_.hasNextPage && !query_.isFetchingNextPage) void query_.fetchNextPage();
3586
+ },
3587
+ fetchingMore: query_.isFetchingNextPage,
3588
+ select,
3589
+ selectMany,
3590
+ remove,
3591
+ reorder,
3592
+ replace,
3593
+ maxedOut,
3594
+ total: query_.data?.pages[0]?.total ?? 0
3595
+ };
3596
+ };
3597
+ var DocumentPickerPopover = (props) => {
3598
+ const {
3599
+ rootId,
3600
+ label,
3601
+ hideLabelFromVision,
3602
+ help,
3603
+ placeholder,
3604
+ allowedMimeTypes,
3605
+ allowedExtensions,
3606
+ perPage,
3607
+ inputDelayMs,
3608
+ uniqueItems,
3609
+ maxItems,
3610
+ fetchOnOpen,
3611
+ enableMediaLibrary,
3612
+ showExtensionBadge,
3613
+ showFileSize,
3614
+ showModifiedDate,
3615
+ filter,
3616
+ request,
3617
+ renderSuggestion,
3618
+ value,
3619
+ onChange,
3620
+ labels
3621
+ } = props;
3622
+ const [open, setOpen] = react$1.useState(false);
3623
+ const inputRef = react$1.useRef(null);
3624
+ const popoverBodyRef = react$1.useRef(null);
3625
+ const clearQueryRef = react$1.useRef(null);
3626
+ const controller = useDocumentPickerController({
3627
+ mimeTypes: allowedMimeTypes,
3628
+ ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
3629
+ perPage,
3630
+ inputDelayMs,
3631
+ uniqueItems,
3632
+ maxItems,
3633
+ ...filter !== void 0 ? { filter } : {},
3634
+ ...request !== void 0 ? { request } : {},
3635
+ value,
3636
+ onChange,
3637
+ active: open,
3638
+ fetchEmpty: fetchOnOpen,
3639
+ onSelectSideEffect: () => {
3640
+ if (maxItems === 1) {
3641
+ setOpen(false);
3642
+ return;
3643
+ }
3644
+ clearQueryRef.current?.();
3645
+ requestAnimationFrame(() => inputRef.current?.focus());
3646
+ },
3647
+ onEscape: () => {
3648
+ setOpen(false);
3649
+ requestAnimationFrame(() => inputRef.current?.focus());
3650
+ }
3651
+ });
3652
+ clearQueryRef.current = () => controller.setQuery("");
3653
+ const listboxId = aria2(rootId, "listbox");
3654
+ const activeOptionId = controller.highlightedIndex >= 0 ? aria2(rootId, `option-${controller.highlightedIndex}`) : void 0;
3655
+ const selectedKeys = react$1.useMemo(() => new Set(value.map((v) => v.key)), [value]);
3656
+ const handleFocus = react$1.useCallback(() => {
3657
+ if (!controller.maxedOut) setOpen(true);
3658
+ }, [controller.maxedOut]);
3659
+ react$1.useEffect(() => {
3660
+ if (controller.maxedOut) setOpen(false);
3661
+ }, [controller.maxedOut]);
3662
+ react$1.useEffect(() => {
3663
+ if (!open) return;
3664
+ const handlePointerDown = (event) => {
3665
+ const target = event.target;
3666
+ if (!target) return;
3667
+ const input = inputRef.current;
3668
+ const body = popoverBodyRef.current;
3669
+ const anchorContainer = input?.closest("div") ?? input;
3670
+ if (anchorContainer && anchorContainer.contains(target)) return;
3671
+ if (body && body.contains(target)) return;
3672
+ setOpen(false);
3673
+ };
3674
+ const docs = /* @__PURE__ */ new Set();
3675
+ if (typeof document !== "undefined") docs.add(document);
3676
+ const inputDoc = inputRef.current?.ownerDocument;
3677
+ if (inputDoc) docs.add(inputDoc);
3678
+ docs.forEach((d) => d.addEventListener("mousedown", handlePointerDown, true));
3679
+ return () => {
3680
+ docs.forEach((d) => d.removeEventListener("mousedown", handlePointerDown, true));
3681
+ };
3682
+ }, [open]);
3683
+ const disabled = controller.maxedOut;
3684
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { position: "relative" }, children: [
3685
+ /* @__PURE__ */ jsxRuntime.jsx(
3686
+ SearchCombobox2,
3687
+ {
3688
+ ref: inputRef,
3689
+ rootId,
3690
+ value: controller.query,
3691
+ onChange: (next) => {
3692
+ controller.setQuery(next);
3693
+ if (!open && !disabled) setOpen(true);
3694
+ },
3695
+ onFocus: handleFocus,
3696
+ onKeyDown: (event) => {
3697
+ if (event.key === "ArrowDown" && !open && !disabled) {
3698
+ event.preventDefault();
3699
+ setOpen(true);
3700
+ return;
3701
+ }
3702
+ controller.handleKeyDown(event);
3703
+ },
3704
+ ...placeholder !== void 0 ? { placeholder } : {},
3705
+ label,
3706
+ hideLabelFromVision,
3707
+ ...help !== void 0 ? { help } : {},
3708
+ disabled,
3709
+ open: open && !disabled,
3710
+ listboxId,
3711
+ ...activeOptionId !== void 0 ? { activeOptionId } : {},
3712
+ labels
3713
+ }
3714
+ ),
3715
+ enableMediaLibrary ? /* @__PURE__ */ jsxRuntime.jsx(ActionsRow, { children: /* @__PURE__ */ jsxRuntime.jsx(
3716
+ MediaLibraryButton,
3717
+ {
3718
+ allowedMimeTypes,
3719
+ ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
3720
+ multiple: maxItems !== 1,
3721
+ value,
3722
+ onSelect: (items) => controller.selectMany(items),
3723
+ labels,
3724
+ disabled
3725
+ }
3726
+ ) }) : null,
3727
+ disabled ? /* @__PURE__ */ jsxRuntime.jsx(MaxedOutBanner, { role: "status", style: { marginTop: 8 }, children: labels.limitReached ?? i18n.__("Selection limit reached.", "valu-block-components") }) : null,
3728
+ open && !disabled && inputRef.current ? /* @__PURE__ */ jsxRuntime.jsx(
3729
+ components.Popover,
3730
+ {
3731
+ anchor: inputRef.current.closest("div") ?? inputRef.current,
3732
+ placement: "bottom-start",
3733
+ focusOnMount: false,
3734
+ onClose: () => setOpen(false),
3735
+ onFocusOutside: () => setOpen(false),
3736
+ shift: true,
3737
+ flip: true,
3738
+ resize: true,
3739
+ offset: 4,
3740
+ className: "valu-document-picker__popover",
3741
+ children: /* @__PURE__ */ jsxRuntime.jsx(EmotionCacheProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(
3742
+ "div",
3743
+ {
3744
+ ref: popoverBodyRef,
3745
+ style: {
3746
+ minWidth: inputRef.current.offsetWidth,
3747
+ width: "100%"
3748
+ },
3749
+ children: /* @__PURE__ */ jsxRuntime.jsx(
3750
+ SuggestionList2,
3751
+ {
3752
+ listboxId,
3753
+ rootId,
3754
+ suggestions: controller.suggestions,
3755
+ loading: controller.loading,
3756
+ fetchingMore: controller.fetchingMore,
3757
+ hasMore: controller.hasMore,
3758
+ error: controller.error,
3759
+ query: controller.debouncedQuery,
3760
+ highlightedIndex: controller.highlightedIndex,
3761
+ selectedKeys,
3762
+ labels,
3763
+ showExtensionBadge,
3764
+ showFileSize,
3765
+ showModifiedDate,
3766
+ ...renderSuggestion ? { renderSuggestion } : {},
3767
+ onHover: (i) => controller.setHighlightedIndex(i),
3768
+ onSelect: (item) => controller.select(item),
3769
+ onLoadMore: () => controller.loadMore()
3770
+ }
3771
+ )
3772
+ }
3773
+ ) })
3774
+ }
3775
+ ) : null
3776
+ ] });
3777
+ };
3778
+ var DocumentPickerInline = (props) => {
3779
+ const {
3780
+ rootId,
3781
+ label,
3782
+ hideLabelFromVision,
3783
+ help,
3784
+ placeholder,
3785
+ allowedMimeTypes,
3786
+ allowedExtensions,
3787
+ perPage,
3788
+ inputDelayMs,
3789
+ uniqueItems,
3790
+ maxItems,
3791
+ fetchOnOpen,
3792
+ enableMediaLibrary,
3793
+ showExtensionBadge,
3794
+ showFileSize,
3795
+ showModifiedDate,
3796
+ filter,
3797
+ request,
3798
+ renderSuggestion,
3799
+ value,
3800
+ onChange,
3801
+ labels
3802
+ } = props;
3803
+ const inputRef = react$1.useRef(null);
3804
+ const clearQueryRef = react$1.useRef(null);
3805
+ const controller = useDocumentPickerController({
3806
+ mimeTypes: allowedMimeTypes,
3807
+ ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
3808
+ perPage,
3809
+ inputDelayMs,
3810
+ uniqueItems,
3811
+ maxItems,
3812
+ ...filter !== void 0 ? { filter } : {},
3813
+ ...request !== void 0 ? { request } : {},
3814
+ value,
3815
+ onChange,
3816
+ active: true,
3817
+ fetchEmpty: fetchOnOpen || value.length === 0,
3818
+ onSelectSideEffect: () => {
3819
+ clearQueryRef.current?.();
3820
+ requestAnimationFrame(() => inputRef.current?.focus());
3821
+ }
3822
+ });
3823
+ clearQueryRef.current = () => controller.setQuery("");
3824
+ const listboxId = aria2(rootId, "listbox");
3825
+ const activeOptionId = controller.highlightedIndex >= 0 ? aria2(rootId, `option-${controller.highlightedIndex}`) : void 0;
3826
+ const selectedKeys = react$1.useMemo(() => new Set(value.map((v) => v.key)), [value]);
3827
+ const disabled = controller.maxedOut;
3828
+ const shouldShowList = !disabled && (controller.debouncedQuery.trim().length > 0 || fetchOnOpen || value.length === 0);
3829
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3830
+ /* @__PURE__ */ jsxRuntime.jsx(
3831
+ SearchCombobox2,
3832
+ {
3833
+ ref: inputRef,
3834
+ rootId,
3835
+ value: controller.query,
3836
+ onChange: (next) => controller.setQuery(next),
3837
+ onKeyDown: controller.handleKeyDown,
3838
+ ...placeholder !== void 0 ? { placeholder } : {},
3839
+ label,
3840
+ hideLabelFromVision,
3841
+ ...help !== void 0 ? { help } : {},
3842
+ disabled,
3843
+ open: shouldShowList,
3844
+ listboxId,
3845
+ ...activeOptionId !== void 0 ? { activeOptionId } : {},
3846
+ labels
3847
+ }
3848
+ ),
3849
+ enableMediaLibrary ? /* @__PURE__ */ jsxRuntime.jsx(ActionsRow, { children: /* @__PURE__ */ jsxRuntime.jsx(
3850
+ MediaLibraryButton,
3851
+ {
3852
+ allowedMimeTypes,
3853
+ ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
3854
+ multiple: maxItems !== 1,
3855
+ value,
3856
+ onSelect: (items) => controller.selectMany(items),
3857
+ labels,
3858
+ disabled
3859
+ }
3860
+ ) }) : null,
3861
+ disabled ? /* @__PURE__ */ jsxRuntime.jsx(MaxedOutBanner, { role: "status", style: { marginTop: 8 }, children: labels.limitReached ?? i18n.__("Selection limit reached.", "valu-block-components") }) : null,
3862
+ shouldShowList ? /* @__PURE__ */ jsxRuntime.jsx(SuggestionsShell, { inline: true, style: { marginTop: 8 }, children: /* @__PURE__ */ jsxRuntime.jsx(
3863
+ SuggestionList2,
3864
+ {
3865
+ listboxId,
3866
+ rootId,
3867
+ suggestions: controller.suggestions,
3868
+ loading: controller.loading,
3869
+ fetchingMore: controller.fetchingMore,
3870
+ hasMore: controller.hasMore,
3871
+ error: controller.error,
3872
+ query: controller.debouncedQuery,
3873
+ highlightedIndex: controller.highlightedIndex,
3874
+ selectedKeys,
3875
+ labels,
3876
+ showExtensionBadge,
3877
+ showFileSize,
3878
+ showModifiedDate,
3879
+ ...renderSuggestion ? { renderSuggestion } : {},
3880
+ onHover: (i) => controller.setHighlightedIndex(i),
3881
+ onSelect: (item) => controller.select(item),
3882
+ onLoadMore: () => controller.loadMore()
3883
+ }
3884
+ ) }) : null
3885
+ ] });
3886
+ };
3887
+ var SelectedItem2 = ({
3888
+ item,
3889
+ index,
3890
+ total,
3891
+ orderable,
3892
+ showExtensionBadge,
3893
+ showFileSize,
3894
+ showModifiedDate,
3895
+ labels,
3896
+ onRemove,
3897
+ onMoveUp,
3898
+ onMoveDown,
3899
+ render
3900
+ }) => {
3901
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = sortable.useSortable({
3902
+ id: item.key,
3903
+ disabled: !orderable
3904
+ });
3905
+ const style = {
3906
+ transform: utilities.CSS.Translate.toString(transform),
3907
+ transition,
3908
+ opacity: isDragging ? 0 : 1
3909
+ };
3910
+ const isMissing = item.missing === "deleted";
3911
+ const missingLabel = labels.missing ?? i18n.__("This document is no longer available.", "valu-block-components");
3912
+ const removeLabel = labels.remove ? labels.remove : i18n.sprintf(
3913
+ /* translators: %s: document title */
3914
+ i18n.__("Remove %s", "valu-block-components"),
3915
+ item.title
3916
+ );
3917
+ const moveUpLabel = labels.moveUp ? labels.moveUp : i18n.sprintf(
3918
+ /* translators: %s: document title */
3919
+ i18n.__("Move %s up", "valu-block-components"),
3920
+ item.title
3921
+ );
3922
+ const moveDownLabel = labels.moveDown ? labels.moveDown : i18n.sprintf(
3923
+ /* translators: %s: document title */
3924
+ i18n.__("Move %s down", "valu-block-components"),
3925
+ item.title
3926
+ );
3927
+ const modified = showModifiedDate ? formatModified(item.modified) : "";
3928
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3929
+ SelectedRow2,
3930
+ {
3931
+ ref: setNodeRef,
3932
+ style,
3933
+ dragging: isDragging,
3934
+ missing: isMissing,
3935
+ "aria-setsize": total,
3936
+ "aria-posinset": index + 1,
3937
+ children: [
3938
+ orderable ? /* @__PURE__ */ jsxRuntime.jsx(
3939
+ DragHandleButton,
3940
+ {
3941
+ type: "button",
3942
+ "aria-label": labels.dragHandle ?? i18n.__("Reorder item", "valu-block-components"),
3943
+ ...attributes,
3944
+ ...listeners,
3945
+ children: /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.dragHandle, size: 14 })
3946
+ }
3947
+ ) : null,
3948
+ isMissing ? /* @__PURE__ */ jsxRuntime.jsx(components.Icon, { icon: icons.caution, size: 20 }) : showExtensionBadge ? /* @__PURE__ */ jsxRuntime.jsx(ExtensionBadge, { extension: item.extension }) : null,
3949
+ render ? render(item) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3950
+ /* @__PURE__ */ jsxRuntime.jsxs(DocRowBody, { children: [
3951
+ /* @__PURE__ */ jsxRuntime.jsx(PrimaryText, { muted: isMissing, title: item.title, children: isMissing ? missingLabel : item.title }),
3952
+ !isMissing ? /* @__PURE__ */ jsxRuntime.jsx(FilenameText, { title: item.filename, children: item.filename }) : null
3953
+ ] }),
3954
+ !isMissing && (showFileSize || modified) ? /* @__PURE__ */ jsxRuntime.jsxs(DocMetaInline, { children: [
3955
+ showFileSize && item.fileSize ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: formatFileSize(item.fileSize) }) : null,
3956
+ modified ? /* @__PURE__ */ jsxRuntime.jsx("span", { children: modified }) : null
3957
+ ] }) : null
3958
+ ] }),
3959
+ /* @__PURE__ */ jsxRuntime.jsxs(ActionColumn, { children: [
3960
+ orderable ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3961
+ /* @__PURE__ */ jsxRuntime.jsx(
3962
+ components.Button,
3963
+ {
3964
+ size: "small",
3965
+ variant: "tertiary",
3966
+ icon: icons.chevronUp,
3967
+ label: moveUpLabel,
3968
+ onClick: onMoveUp,
3969
+ disabled: index === 0 || !onMoveUp
3970
+ }
3971
+ ),
3972
+ /* @__PURE__ */ jsxRuntime.jsx(
3973
+ components.Button,
3974
+ {
3975
+ size: "small",
3976
+ variant: "tertiary",
3977
+ icon: icons.chevronDown,
3978
+ label: moveDownLabel,
3979
+ onClick: onMoveDown,
3980
+ disabled: index === total - 1 || !onMoveDown
3981
+ }
3982
+ )
3983
+ ] }) : null,
3984
+ /* @__PURE__ */ jsxRuntime.jsx(
3985
+ components.Button,
3986
+ {
3987
+ size: "small",
3988
+ variant: "tertiary",
3989
+ icon: icons.closeSmall,
3990
+ label: removeLabel,
3991
+ onClick: onRemove,
3992
+ isDestructive: true
3993
+ }
3994
+ )
3995
+ ] })
3996
+ ]
3997
+ }
3998
+ );
3999
+ };
4000
+ var useResolvedDocuments = (value) => {
4001
+ const records = data.useSelect(
4002
+ (select) => {
4003
+ const core = select(coreData.store);
4004
+ const out = {};
4005
+ for (const item of value) {
4006
+ const record = core.getEntityRecord("postType", "attachment", item.id);
4007
+ const resolved = core.hasFinishedResolution("getEntityRecord", [
4008
+ "postType",
4009
+ "attachment",
4010
+ item.id
4011
+ ]);
4012
+ out[item.id] = { record: record ?? null, resolved };
4013
+ }
4014
+ return out;
4015
+ },
4016
+ [value]
4017
+ );
4018
+ return react$1.useMemo(
4019
+ () => value.map((item) => {
4020
+ const entry = records[item.id];
4021
+ if (!entry) return item;
4022
+ const { record, resolved } = entry;
4023
+ if (!resolved) return item;
4024
+ if (!record) return { ...item, missing: "deleted" };
4025
+ const fresh = mapMediaToDocumentItem(
4026
+ record
4027
+ );
4028
+ return {
4029
+ ...item,
4030
+ title: fresh.title || item.title,
4031
+ filename: fresh.filename || item.filename,
4032
+ url: fresh.url || item.url,
4033
+ mimeType: fresh.mimeType || item.mimeType,
4034
+ extension: fresh.extension || item.extension,
4035
+ fileSize: fresh.fileSize ?? item.fileSize,
4036
+ modified: fresh.modified ?? item.modified
4037
+ };
4038
+ }),
4039
+ [value, records]
4040
+ );
4041
+ };
4042
+ var SelectedList2 = ({
4043
+ value,
4044
+ orderable,
4045
+ maxItems,
4046
+ labels,
4047
+ onChange,
4048
+ renderSelected,
4049
+ showHeader = true,
4050
+ showExtensionBadge,
4051
+ showFileSize,
4052
+ showModifiedDate
4053
+ }) => {
4054
+ const resolved = useResolvedDocuments(value);
4055
+ const [activeKey, setActiveKey] = react$1.useState(null);
4056
+ const sensors = core.useSensors(
4057
+ core.useSensor(core.MouseSensor, { activationConstraint: { distance: 5 } }),
4058
+ core.useSensor(core.TouchSensor, { activationConstraint: { delay: 200, tolerance: 6 } }),
4059
+ core.useSensor(core.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
4060
+ );
4061
+ const ids = react$1.useMemo(() => resolved.map((i) => i.key), [resolved]);
4062
+ const activeItem = activeKey ? resolved.find((i) => i.key === activeKey) : null;
4063
+ const remove = react$1.useCallback(
4064
+ (key) => onChange(value.filter((i) => i.key !== key)),
4065
+ [value, onChange]
4066
+ );
4067
+ const reorder = react$1.useCallback(
4068
+ (from, to) => {
4069
+ if (from === to) return;
4070
+ onChange(sortable.arrayMove(value, from, to));
4071
+ },
4072
+ [value, onChange]
4073
+ );
4074
+ const handleDragStart = react$1.useCallback(
4075
+ (event) => setActiveKey(String(event.active.id)),
4076
+ []
4077
+ );
4078
+ const handleDragEnd = react$1.useCallback(
4079
+ (event) => {
4080
+ setActiveKey(null);
4081
+ const { active, over } = event;
4082
+ if (!over || active.id === over.id) return;
4083
+ const from = ids.indexOf(String(active.id));
4084
+ const to = ids.indexOf(String(over.id));
4085
+ if (from >= 0 && to >= 0) reorder(from, to);
4086
+ },
4087
+ [ids, reorder]
4088
+ );
4089
+ if (resolved.length === 0) return null;
4090
+ const isSingleSelect = maxItems === 1;
4091
+ const shouldShowHeader = showHeader && !isSingleSelect;
4092
+ const header = shouldShowHeader ? /* @__PURE__ */ jsxRuntime.jsxs(HeaderRow, { children: [
4093
+ /* @__PURE__ */ jsxRuntime.jsx(HeaderTitle, { children: resolved.length === 1 ? labels.selectedSingular ?? i18n.__("Selected", "valu-block-components") : labels.selectedPlural ?? i18n.__("Selected", "valu-block-components") }),
4094
+ /* @__PURE__ */ jsxRuntime.jsx(CountPill, { "aria-hidden": "true", children: Number.isFinite(maxItems) ? i18n.sprintf(
4095
+ /* translators: 1: current count, 2: max count */
4096
+ "%1$d / %2$d",
4097
+ resolved.length,
4098
+ maxItems
4099
+ ) : String(resolved.length) })
4100
+ ] }) : null;
4101
+ const rows = resolved.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
4102
+ SelectedItem2,
4103
+ {
4104
+ item,
4105
+ index,
4106
+ total: resolved.length,
4107
+ orderable,
4108
+ showExtensionBadge,
4109
+ showFileSize,
4110
+ showModifiedDate,
4111
+ labels,
4112
+ onRemove: () => remove(item.key),
4113
+ ...index > 0 ? { onMoveUp: () => reorder(index, index - 1) } : {},
4114
+ ...index < resolved.length - 1 ? { onMoveDown: () => reorder(index, index + 1) } : {},
4115
+ ...renderSelected ? { render: renderSelected } : {}
4116
+ },
4117
+ item.key
4118
+ ));
4119
+ if (!orderable) {
4120
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4121
+ header,
4122
+ /* @__PURE__ */ jsxRuntime.jsx(SelectedListEl, { role: "list", children: rows })
4123
+ ] });
4124
+ }
4125
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4126
+ header,
4127
+ /* @__PURE__ */ jsxRuntime.jsxs(
4128
+ core.DndContext,
4129
+ {
4130
+ sensors,
4131
+ collisionDetection: core.closestCenter,
4132
+ onDragStart: handleDragStart,
4133
+ onDragEnd: handleDragEnd,
4134
+ onDragCancel: () => setActiveKey(null),
4135
+ children: [
4136
+ /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: ids, strategy: sortable.verticalListSortingStrategy, children: /* @__PURE__ */ jsxRuntime.jsx(SelectedListEl, { role: "list", children: rows }) }),
4137
+ /* @__PURE__ */ jsxRuntime.jsx(core.DragOverlay, { dropAnimation: core.defaultDropAnimation, children: activeItem ? /* @__PURE__ */ jsxRuntime.jsx(DragChip, { title: activeItem.title }) : null })
4138
+ ]
4139
+ }
4140
+ )
4141
+ ] });
4142
+ };
4143
+ var DEFAULT_LABELS2 = {};
4144
+ var DEFAULT_MIME = [...DEFAULT_DOCUMENT_MIME_TYPES];
4145
+ var makeQueryClient2 = () => new reactQuery.QueryClient({
4146
+ defaultOptions: {
4147
+ queries: { retry: 1, refetchOnWindowFocus: false }
4148
+ }
4149
+ });
4150
+ var DocumentPicker = ({
4151
+ label,
4152
+ hideLabelFromVision = false,
4153
+ help,
4154
+ variant = "popover",
4155
+ value: controlledValue,
4156
+ defaultValue,
4157
+ onChange,
4158
+ maxItems = Infinity,
4159
+ orderable,
4160
+ placeholder,
4161
+ allowedMimeTypes,
4162
+ allowedExtensions,
4163
+ perPage,
4164
+ inputDelayMs = 250,
4165
+ fetchOnOpen = false,
4166
+ uniqueItems = true,
4167
+ enableMediaLibrary = true,
4168
+ showExtensionBadge = true,
4169
+ showFileSize = true,
4170
+ showModifiedDate = false,
4171
+ filter,
4172
+ request,
4173
+ renderSuggestion,
4174
+ renderSelected,
4175
+ labels = DEFAULT_LABELS2,
4176
+ className,
4177
+ id
4178
+ }) => {
4179
+ const reactId = react$1.useId();
4180
+ const rootId = id ?? `valu-dp-${reactId.replace(/:/g, "")}`;
4181
+ const [uncontrolled, setUncontrolled] = react$1.useState(defaultValue ?? []);
4182
+ const isControlled = controlledValue !== void 0;
4183
+ const value = isControlled ? controlledValue : uncontrolled;
4184
+ const handleChange = react$1.useCallback(
4185
+ (next) => {
4186
+ if (!isControlled) setUncontrolled(next);
4187
+ onChange?.(next);
4188
+ },
4189
+ [isControlled, onChange]
4190
+ );
4191
+ const effectiveMime = allowedMimeTypes ?? DEFAULT_MIME;
4192
+ const effectivePerPage = perPage ?? (variant === "popover" ? 10 : 20);
4193
+ const effectiveOrderable = orderable ?? (Number.isFinite(maxItems) ? maxItems > 1 : true);
4194
+ const queryClientRef = react$1.useRef(null);
4195
+ if (!queryClientRef.current) queryClientRef.current = makeQueryClient2();
4196
+ const isSingleSelect = maxItems === 1;
4197
+ const isFull = Number.isFinite(maxItems) && value.length >= maxItems;
4198
+ const hideInputForSingleSelect = isSingleSelect && value.length > 0;
4199
+ const showInput = !isFull && !hideInputForSingleSelect;
4200
+ const variantProps = react$1.useMemo(
4201
+ () => ({
4202
+ rootId,
4203
+ label,
4204
+ hideLabelFromVision,
4205
+ ...help !== void 0 ? { help } : {},
4206
+ ...placeholder !== void 0 ? { placeholder } : {},
4207
+ allowedMimeTypes: effectiveMime,
4208
+ ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
4209
+ perPage: effectivePerPage,
4210
+ inputDelayMs,
4211
+ uniqueItems,
4212
+ maxItems,
4213
+ fetchOnOpen,
4214
+ enableMediaLibrary,
4215
+ showExtensionBadge,
4216
+ showFileSize,
4217
+ showModifiedDate,
4218
+ ...filter !== void 0 ? { filter } : {},
4219
+ ...request !== void 0 ? { request } : {},
4220
+ ...renderSuggestion ? { renderSuggestion } : {},
4221
+ value,
4222
+ onChange: handleChange,
4223
+ labels
4224
+ }),
4225
+ [
4226
+ rootId,
4227
+ label,
4228
+ hideLabelFromVision,
4229
+ help,
4230
+ placeholder,
4231
+ effectiveMime,
4232
+ allowedExtensions,
4233
+ effectivePerPage,
4234
+ inputDelayMs,
4235
+ uniqueItems,
4236
+ maxItems,
4237
+ fetchOnOpen,
4238
+ enableMediaLibrary,
4239
+ showExtensionBadge,
4240
+ showFileSize,
4241
+ showModifiedDate,
4242
+ filter,
4243
+ request,
4244
+ renderSuggestion,
4245
+ value,
4246
+ handleChange,
4247
+ labels
4248
+ ]
4249
+ );
4250
+ return /* @__PURE__ */ jsxRuntime.jsx(EmotionCacheProvider, { children: /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClientRef.current, children: /* @__PURE__ */ jsxRuntime.jsxs(Root3, { id: rootId, ...className !== void 0 ? { className } : {}, children: [
4251
+ showInput ? variant === "popover" ? /* @__PURE__ */ jsxRuntime.jsx(DocumentPickerPopover, { ...variantProps }) : /* @__PURE__ */ jsxRuntime.jsx(DocumentPickerInline, { ...variantProps }) : null,
4252
+ hideInputForSingleSelect && enableMediaLibrary ? /* @__PURE__ */ jsxRuntime.jsx(ActionsRow, { children: /* @__PURE__ */ jsxRuntime.jsx(
4253
+ MediaLibraryButton,
4254
+ {
4255
+ allowedMimeTypes: effectiveMime,
4256
+ ...allowedExtensions !== void 0 ? { allowedExtensions } : {},
4257
+ multiple: false,
4258
+ value,
4259
+ onSelect: (items) => handleChange([items[items.length - 1]]),
4260
+ labels,
4261
+ variant: "tertiary",
4262
+ label: labels.replace
4263
+ }
4264
+ ) }) : null,
4265
+ /* @__PURE__ */ jsxRuntime.jsx(
4266
+ SelectedList2,
4267
+ {
4268
+ value,
4269
+ orderable: effectiveOrderable && value.length > 1,
4270
+ maxItems,
4271
+ labels,
4272
+ onChange: handleChange,
4273
+ ...renderSelected ? { renderSelected } : {},
4274
+ showHeader: value.length > 0,
4275
+ showExtensionBadge,
4276
+ showFileSize,
4277
+ showModifiedDate
4278
+ }
4279
+ )
4280
+ ] }) }) });
4281
+ };
4282
+ var FileSize = ({ bytes }) => {
4283
+ const formatted = formatFileSize(bytes);
4284
+ if (!formatted) return null;
4285
+ return /* @__PURE__ */ jsxRuntime.jsx(FileMeta, { children: formatted });
4286
+ };
2723
4287
 
2724
4288
  exports.ContentPicker = ContentPicker;
4289
+ exports.DEFAULT_DOCUMENT_MIME_TYPES = DEFAULT_DOCUMENT_MIME_TYPES;
4290
+ exports.DocumentPicker = DocumentPicker;
4291
+ exports.ExtensionBadge = ExtensionBadge;
4292
+ exports.FileSize = FileSize;
2725
4293
  exports.Matrix = Matrix;
4294
+ exports.MediaLibraryButton = MediaLibraryButton;
4295
+ exports.basenameFromUrl = basenameFromUrl;
2726
4296
  exports.cellKey = cellKey;
4297
+ exports.extensionFromUrl = extensionFromUrl;
4298
+ exports.formatFileSize = formatFileSize;
4299
+ exports.mapMediaToDocumentItem = mapMediaToDocumentItem;
2727
4300
  exports.selectionByRow = selectionByRow;
2728
4301
  exports.toggleCell = toggleCell;
2729
4302
  //# sourceMappingURL=index.cjs.map