@valu/block-components 0.2.1 → 0.3.1

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