@valu/block-components 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
 
@@ -2747,10 +2748,1555 @@ var Matrix = (props) => {
2747
2748
  )) })
2748
2749
  ] }) }) }) });
2749
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
+ };
2750
4287
 
2751
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;
2752
4293
  exports.Matrix = Matrix;
4294
+ exports.MediaLibraryButton = MediaLibraryButton;
4295
+ exports.basenameFromUrl = basenameFromUrl;
2753
4296
  exports.cellKey = cellKey;
4297
+ exports.extensionFromUrl = extensionFromUrl;
4298
+ exports.formatFileSize = formatFileSize;
4299
+ exports.mapMediaToDocumentItem = mapMediaToDocumentItem;
2754
4300
  exports.selectionByRow = selectionByRow;
2755
4301
  exports.toggleCell = toggleCell;
2756
4302
  //# sourceMappingURL=index.cjs.map