react-os-shell 3.10.1 → 3.11.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.
Files changed (31) hide show
  1. package/dist/{Browser-3SDMP5NU.js → Browser-OMB2YWT7.js} +4 -4
  2. package/dist/{Browser-3SDMP5NU.js.map → Browser-OMB2YWT7.js.map} +1 -1
  3. package/dist/{Documents-FOCBCZHY.js → Documents-FNUDJADL.js} +3 -3
  4. package/dist/{Documents-FOCBCZHY.js.map → Documents-FNUDJADL.js.map} +1 -1
  5. package/dist/Files-YVG267R2.js +12 -0
  6. package/dist/{Files-P37UZJVD.js.map → Files-YVG267R2.js.map} +1 -1
  7. package/dist/{Notepad-WDDJQVXF.js → Notepad-ZOF2A7Q7.js} +3 -3
  8. package/dist/{Notepad-WDDJQVXF.js.map → Notepad-ZOF2A7Q7.js.map} +1 -1
  9. package/dist/Preview-PNMJG2WL.js +8 -0
  10. package/dist/{Preview-CESYNTOE.js.map → Preview-PNMJG2WL.js.map} +1 -1
  11. package/dist/{Spreadsheet-6SICRWKK.js → Spreadsheet-BPJIYAXF.js} +4 -4
  12. package/dist/{Spreadsheet-6SICRWKK.js.map → Spreadsheet-BPJIYAXF.js.map} +1 -1
  13. package/dist/apps/index.js +12 -12
  14. package/dist/{chunk-SRFLPGGO.js → chunk-5HBGCZKG.js} +3 -3
  15. package/dist/{chunk-SRFLPGGO.js.map → chunk-5HBGCZKG.js.map} +1 -1
  16. package/dist/{chunk-3SOG3MFC.js → chunk-76SEPQRP.js} +4 -4
  17. package/dist/{chunk-3SOG3MFC.js.map → chunk-76SEPQRP.js.map} +1 -1
  18. package/dist/{chunk-YPIDICFR.js → chunk-GOT6PRT2.js} +3 -3
  19. package/dist/{chunk-YPIDICFR.js.map → chunk-GOT6PRT2.js.map} +1 -1
  20. package/dist/{chunk-OZFGITVF.js → chunk-MSP7WNTK.js} +3 -3
  21. package/dist/{chunk-OZFGITVF.js.map → chunk-MSP7WNTK.js.map} +1 -1
  22. package/dist/{chunk-WEQXKNOA.js → chunk-PQYTYQ62.js} +3 -3
  23. package/dist/{chunk-WEQXKNOA.js.map → chunk-PQYTYQ62.js.map} +1 -1
  24. package/dist/{chunk-L5SPKS76.js → chunk-XORQ5BQL.js} +4 -4
  25. package/dist/{chunk-L5SPKS76.js.map → chunk-XORQ5BQL.js.map} +1 -1
  26. package/dist/index.d.ts +138 -2
  27. package/dist/index.js +456 -12
  28. package/dist/index.js.map +1 -1
  29. package/package.json +1 -1
  30. package/dist/Files-P37UZJVD.js +0 -12
  31. package/dist/Preview-CESYNTOE.js +0 -8
package/dist/index.js CHANGED
@@ -1,26 +1,26 @@
1
1
  import { subscribePomo, getPomoSnapshot } from './chunk-VGJ3KCOP.js';
2
2
  export { setShellTodoProvider } from './chunk-VGJ3KCOP.js';
3
- import { PREVIEW_OPENED_EVENT, publishDesktopFolders, requestFilesTrashView, FolderGlyph, openPreviewFile, requestFilesDesktopFolderView, FileIconTile, hashGradient } from './chunk-L5SPKS76.js';
4
- export { Breadcrumbs } from './chunk-L5SPKS76.js';
3
+ import { PREVIEW_OPENED_EVENT, publishDesktopFolders, requestFilesTrashView, FolderGlyph, openPreviewFile, requestFilesDesktopFolderView, FileIconTile, hashGradient } from './chunk-XORQ5BQL.js';
4
+ export { Breadcrumbs } from './chunk-XORQ5BQL.js';
5
5
  import { SidebarLayout } from './chunk-YG45DAUT.js';
6
6
  export { SidebarLayout } from './chunk-YG45DAUT.js';
7
7
  import { playNotification, playStartup, soundsEnabled, getSoundConfig, SOUND_PACK_KEYS, SOUND_PACKS, SOUND_TYPES, SOUND_TYPE_LABELS, setSoundForType, previewSound, setAllSounds, playLogout } from './chunk-D7PYW2QS.js';
8
- import { setPdfPreview } from './chunk-WEQXKNOA.js';
9
- export { ImageAnnotator_default as ImageAnnotator } from './chunk-WEQXKNOA.js';
8
+ import { setPdfPreview } from './chunk-PQYTYQ62.js';
9
+ export { ImageAnnotator_default as ImageAnnotator } from './chunk-PQYTYQ62.js';
10
10
  import { toast_default } from './chunk-VENYVK3L.js';
11
11
  export { toast_default as toast } from './chunk-VENYVK3L.js';
12
- import { EditableGrid } from './chunk-YPIDICFR.js';
13
- export { EditableGrid } from './chunk-YPIDICFR.js';
12
+ import { EditableGrid } from './chunk-GOT6PRT2.js';
13
+ export { EditableGrid } from './chunk-GOT6PRT2.js';
14
14
  import { useAuth, useShellAuth } from './chunk-ADJ3CERD.js';
15
15
  export { ShellAuthProvider, setShellAuthBridge, useShellAuth } from './chunk-ADJ3CERD.js';
16
- import { APP_VERSION, VERSION } from './chunk-SRFLPGGO.js';
17
- export { VERSION } from './chunk-SRFLPGGO.js';
16
+ import { APP_VERSION, VERSION } from './chunk-5HBGCZKG.js';
17
+ export { VERSION } from './chunk-5HBGCZKG.js';
18
18
  import { useWindowManager, PopupMenu, PopupMenuLabel, PopupMenuDivider, PopupMenuItem, WINDOW_REGISTRY, isPageEntry, Modal, useShellPrefs, useIsMobile, ModalActions, useModalActive, client_default, LoadingSpinner, CancelButton, setWindowPosition, ThumbCard, activateModal } from './chunk-HLWEKEKD.js';
19
19
  export { CancelButton, CopyButton, DocFavStar, Modal, ModalActions, PopupMenu, PopupMenuDivider, PopupMenuItem, PopupMenuLabel, ShellPrefsProvider, WindowCrashedFallback, WindowErrorBoundary, WindowManagerProvider, WindowTitle, commitExposeHighlight, exitExposeMode, getActiveWindowRoute, getExposeHighlight, getWindowPosition, isEntityEntry, isPageEntry, registerModalEscapeInterceptor, setExposeHighlight, setShellApiClient, setShellWindowRegistry, setWindowDefaultPosition, setWindowPosition, subscribeExposeHighlight, toggleExposeMode, useLocalStoragePrefs, useModalActive, useShellPrefs, useWidgetSettings, useWindowManager, useWindowMenuItem, useWindowTitle } from './chunk-HLWEKEKD.js';
20
20
  export { ConfirmProvider, confirm, confirmDestructive, prompt } from './chunk-UBN4IUDE.js';
21
21
  import { glassStyle, startMenuCategories, navSections, isSection, GLASS_INPUT_BG, navIcons, sectionIcons } from './chunk-ZF6AYO4G.js';
22
22
  export { GLASS_DIVIDER, GLASS_INPUT_BG, glassStyle, setShellNavIcons } from './chunk-ZF6AYO4G.js';
23
- import { createContext, lazy, forwardRef, useCallback, useState, useRef, useEffect, useMemo, useLayoutEffect, useContext, isValidElement, cloneElement, Suspense, Fragment as Fragment$1, Children, useId, useSyncExternalStore } from 'react';
23
+ import { createContext, lazy, forwardRef, useCallback, useState, useRef, useEffect, useMemo, useLayoutEffect, useContext, isValidElement, cloneElement, Suspense, Fragment as Fragment$1, useId, Children, useSyncExternalStore } from 'react';
24
24
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
25
25
  import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react';
26
26
  import { createPortal } from 'react-dom';
@@ -6436,6 +6436,7 @@ var Radio_default = Radio;
6436
6436
  function FormField({
6437
6437
  label,
6438
6438
  htmlFor,
6439
+ labelId,
6439
6440
  hint,
6440
6441
  error,
6441
6442
  required,
@@ -6443,12 +6444,12 @@ function FormField({
6443
6444
  children
6444
6445
  }) {
6445
6446
  return /* @__PURE__ */ jsxs("div", { className, children: [
6446
- label && /* @__PURE__ */ jsxs("label", { htmlFor, className: "mb-1 block text-xs font-medium text-gray-600", children: [
6447
+ label && /* @__PURE__ */ jsxs("label", { id: labelId, htmlFor, className: "mb-1 block text-xs font-medium text-gray-600", children: [
6447
6448
  label,
6448
6449
  required && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-red-500", children: "*" })
6449
6450
  ] }),
6450
6451
  children,
6451
- error ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-red-600", children: error }) : hint ? /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-gray-400", children: hint }) : null
6452
+ error ? /* @__PURE__ */ jsx("p", { id: htmlFor ? `${htmlFor}-error` : void 0, className: "mt-1 text-[11px] text-red-600", children: error }) : hint ? /* @__PURE__ */ jsx("p", { id: htmlFor ? `${htmlFor}-hint` : void 0, className: "mt-1 text-[11px] text-gray-400", children: hint }) : null
6452
6453
  ] });
6453
6454
  }
6454
6455
  function Label({ required, children, className = "", ...rest }) {
@@ -6457,6 +6458,449 @@ function Label({ required, children, className = "", ...rest }) {
6457
6458
  required && /* @__PURE__ */ jsx("span", { className: "ml-0.5 text-red-500", children: "*" })
6458
6459
  ] });
6459
6460
  }
6461
+ function mediaFileName(url) {
6462
+ const path = (url || "").split("?")[0].split("#")[0];
6463
+ const last = path.substring(path.lastIndexOf("/") + 1);
6464
+ try {
6465
+ return decodeURIComponent(last.replace(/^[0-9a-f]{32}_/i, "")) || last || "media";
6466
+ } catch {
6467
+ return last || "media";
6468
+ }
6469
+ }
6470
+ var VIDEO_EXT_RE = /\.(mp4|webm|ogg|ogv|mov|m4v)(\?|#|$)/i;
6471
+ function isVideoUrl(url, accept = "") {
6472
+ if (!url) return false;
6473
+ const acceptsVideo = accept.includes("video");
6474
+ const acceptsImage = accept.includes("image") || accept === "*" || accept === "";
6475
+ return VIDEO_EXT_RE.test(url) || acceptsVideo && !acceptsImage;
6476
+ }
6477
+ function Spinner2({ className = "h-5 w-5" }) {
6478
+ return /* @__PURE__ */ jsxs("svg", { className: `${className} animate-spin text-gray-400`, viewBox: "0 0 24 24", fill: "none", "aria-hidden": "true", children: [
6479
+ /* @__PURE__ */ jsx("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }),
6480
+ /* @__PURE__ */ jsx("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z" })
6481
+ ] });
6482
+ }
6483
+ function UploadGlyph({ className = "h-6 w-6" }) {
6484
+ return /* @__PURE__ */ jsxs(
6485
+ "svg",
6486
+ {
6487
+ viewBox: "0 0 24 24",
6488
+ fill: "none",
6489
+ stroke: "currentColor",
6490
+ strokeWidth: 1.6,
6491
+ strokeLinecap: "round",
6492
+ strokeLinejoin: "round",
6493
+ "aria-hidden": "true",
6494
+ className,
6495
+ children: [
6496
+ /* @__PURE__ */ jsx("path", { d: "M12 15V4" }),
6497
+ /* @__PURE__ */ jsx("path", { d: "M8 8l4-4 4 4" }),
6498
+ /* @__PURE__ */ jsx("path", { d: "M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" })
6499
+ ]
6500
+ }
6501
+ );
6502
+ }
6503
+ function MediaUploadField({
6504
+ value,
6505
+ onChange,
6506
+ onPick,
6507
+ label,
6508
+ hint,
6509
+ error,
6510
+ required,
6511
+ className,
6512
+ accept = "image/*",
6513
+ fit = "cover",
6514
+ height = 112,
6515
+ placeholder,
6516
+ cta = "Choose from library or upload",
6517
+ acceptHint,
6518
+ showFilename = true,
6519
+ allowReplace = true,
6520
+ allowRemove = true,
6521
+ replaceLabel = "Replace",
6522
+ removeLabel = "Remove",
6523
+ busy = false,
6524
+ busyLabel = "Uploading\u2026",
6525
+ disabled = false
6526
+ }) {
6527
+ const inputRef = useRef(null);
6528
+ const [dragOver, setDragOver] = useState(false);
6529
+ const [focused, setFocused] = useState(false);
6530
+ const fieldId = useId();
6531
+ const objectUrlRef = useRef(null);
6532
+ const [videoBlobUrl, setVideoBlobUrl] = useState(null);
6533
+ useEffect(() => () => {
6534
+ if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
6535
+ }, []);
6536
+ const locked = disabled || busy;
6537
+ const acceptsVideo = accept.includes("video");
6538
+ const acceptsImage = accept.includes("image") || accept === "*" || accept === "";
6539
+ const isVideo = !!value && (isVideoUrl(value, accept) || value === videoBlobUrl);
6540
+ const kindWord = acceptsVideo && !acceptsImage ? "video" : acceptsImage && !acceptsVideo ? "image" : "file";
6541
+ const dimLine = placeholder ?? `Upload ${kindWord === "image" ? "an image" : `a ${kindWord}`}`;
6542
+ const emitObjectUrl = (file) => {
6543
+ if (objectUrlRef.current) URL.revokeObjectURL(objectUrlRef.current);
6544
+ const url = URL.createObjectURL(file);
6545
+ objectUrlRef.current = url;
6546
+ setVideoBlobUrl(file.type.startsWith("video/") ? url : null);
6547
+ onChange(url);
6548
+ };
6549
+ const clearValue = () => {
6550
+ if (objectUrlRef.current) {
6551
+ URL.revokeObjectURL(objectUrlRef.current);
6552
+ objectUrlRef.current = null;
6553
+ }
6554
+ setVideoBlobUrl(null);
6555
+ onChange("");
6556
+ };
6557
+ const pick = (file) => {
6558
+ if (locked) return;
6559
+ if (onPick) {
6560
+ onPick(file ?? void 0);
6561
+ return;
6562
+ }
6563
+ if (file) {
6564
+ emitObjectUrl(file);
6565
+ return;
6566
+ }
6567
+ inputRef.current?.click();
6568
+ };
6569
+ const onNativeFile = (e) => {
6570
+ const file = e.target.files?.[0];
6571
+ if (file) emitObjectUrl(file);
6572
+ if (inputRef.current) inputRef.current.value = "";
6573
+ };
6574
+ const dropHandlers = locked ? {} : {
6575
+ onDragOver: (e) => {
6576
+ e.preventDefault();
6577
+ setDragOver(true);
6578
+ },
6579
+ onDragLeave: () => setDragOver(false),
6580
+ onDrop: (e) => {
6581
+ e.preventDefault();
6582
+ setDragOver(false);
6583
+ const file = e.dataTransfer.files?.[0];
6584
+ if (file) pick(file);
6585
+ }
6586
+ };
6587
+ const previewName = value ? mediaFileName(value) : "";
6588
+ const filled = /* @__PURE__ */ jsxs("div", { children: [
6589
+ /* @__PURE__ */ jsxs(
6590
+ "div",
6591
+ {
6592
+ ...dropHandlers,
6593
+ className: [
6594
+ "relative overflow-hidden rounded-md border bg-gray-100 transition-colors",
6595
+ dragOver ? "border-blue-500" : "border-gray-200"
6596
+ ].join(" "),
6597
+ style: { height },
6598
+ children: [
6599
+ isVideo ? /* @__PURE__ */ jsx(
6600
+ "video",
6601
+ {
6602
+ src: value ?? void 0,
6603
+ muted: true,
6604
+ playsInline: true,
6605
+ preload: "metadata",
6606
+ "aria-label": previewName || void 0,
6607
+ className: "h-full w-full",
6608
+ style: { objectFit: fit }
6609
+ }
6610
+ ) : /* @__PURE__ */ jsx("img", { src: value ?? void 0, alt: previewName, className: "h-full w-full", style: { objectFit: fit } }),
6611
+ showFilename && value && /* @__PURE__ */ jsx(
6612
+ "span",
6613
+ {
6614
+ className: "absolute inset-x-1 bottom-1 truncate rounded px-1.5 py-0.5 text-xs text-white",
6615
+ style: { background: "rgba(0,0,0,0.6)" },
6616
+ children: previewName
6617
+ }
6618
+ ),
6619
+ busy && /* @__PURE__ */ jsxs(
6620
+ "div",
6621
+ {
6622
+ role: "status",
6623
+ className: "absolute inset-0 flex items-center justify-center gap-2",
6624
+ style: { background: "rgba(255,255,255,0.6)" },
6625
+ children: [
6626
+ /* @__PURE__ */ jsx(Spinner2, {}),
6627
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-600", children: busyLabel })
6628
+ ]
6629
+ }
6630
+ )
6631
+ ]
6632
+ }
6633
+ ),
6634
+ !disabled && (allowReplace || allowRemove) && /* @__PURE__ */ jsxs("div", { className: "mt-2 flex gap-2", children: [
6635
+ allowReplace && /* @__PURE__ */ jsx(Button_default, { id: fieldId, variant: "secondary", size: "sm", onClick: () => pick(), disabled: busy, children: replaceLabel }),
6636
+ allowRemove && /* @__PURE__ */ jsx(Button_default, { variant: "ghost", size: "sm", onClick: clearValue, disabled: busy, children: removeLabel })
6637
+ ] })
6638
+ ] });
6639
+ const empty = /* @__PURE__ */ jsx(
6640
+ "button",
6641
+ {
6642
+ type: "button",
6643
+ id: fieldId,
6644
+ onClick: () => pick(),
6645
+ disabled: locked,
6646
+ "aria-label": typeof dimLine === "string" ? dimLine : "Upload media",
6647
+ "aria-busy": busy || void 0,
6648
+ "aria-invalid": error ? true : void 0,
6649
+ "aria-required": required || void 0,
6650
+ "aria-describedby": error ? `${fieldId}-error` : hint ? `${fieldId}-hint` : void 0,
6651
+ onFocus: () => setFocused(true),
6652
+ onBlur: () => setFocused(false),
6653
+ ...dropHandlers,
6654
+ className: [
6655
+ "flex w-full flex-col items-center justify-center gap-1.5 rounded-lg border-2 border-dashed text-center transition-colors",
6656
+ locked ? "cursor-not-allowed opacity-60" : "cursor-pointer",
6657
+ dragOver ? "border-blue-500 bg-blue-50" : "border-gray-300 bg-gray-50 hover:bg-gray-100"
6658
+ ].join(" "),
6659
+ style: {
6660
+ minHeight: height,
6661
+ outline: "none",
6662
+ boxShadow: focused && !locked ? "0 0 0 2px rgba(59,130,246,0.45)" : void 0
6663
+ },
6664
+ children: busy ? /* @__PURE__ */ jsxs("span", { role: "status", className: "flex flex-col items-center gap-1.5", children: [
6665
+ /* @__PURE__ */ jsx(Spinner2, {}),
6666
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: busyLabel })
6667
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
6668
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: /* @__PURE__ */ jsx(UploadGlyph, {}) }),
6669
+ /* @__PURE__ */ jsx("span", { className: "px-3 text-xs text-gray-500", children: dimLine }),
6670
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-blue-600", children: cta }),
6671
+ acceptHint && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: acceptHint })
6672
+ ] })
6673
+ }
6674
+ );
6675
+ return /* @__PURE__ */ jsxs(FormField, { label, htmlFor: fieldId, hint, error, required, className, children: [
6676
+ value ? filled : empty,
6677
+ !onPick && /* @__PURE__ */ jsx("input", { ref: inputRef, type: "file", accept, className: "hidden", onChange: onNativeFile })
6678
+ ] });
6679
+ }
6680
+ var REMOVE_ICON = /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", "aria-hidden": "true", className: "h-3 w-3", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6L6 18" }) });
6681
+ function MediaUploadGrid({
6682
+ items,
6683
+ onPick,
6684
+ onRemove,
6685
+ onReorder,
6686
+ label,
6687
+ hint,
6688
+ error,
6689
+ required,
6690
+ className,
6691
+ accept = "image/*",
6692
+ fit = "cover",
6693
+ thumbSize = 96,
6694
+ placeholder,
6695
+ cta = "Choose from library or upload",
6696
+ acceptHint,
6697
+ showCover = false,
6698
+ coverLabel = "Cover",
6699
+ busy = false,
6700
+ busyLabel = "Uploading\u2026",
6701
+ disabled = false,
6702
+ readOnly = false
6703
+ }) {
6704
+ const gridId = useId();
6705
+ const [zoneDragOver, setZoneDragOver] = useState(false);
6706
+ const [draggingIndex, setDraggingIndex] = useState(null);
6707
+ const [focusedKey, setFocusedKey] = useState(null);
6708
+ const ringStyle = (key) => ({
6709
+ outline: "none",
6710
+ ...focusedKey === key && !locked ? { boxShadow: "0 0 0 2px rgba(59,130,246,0.45)" } : {}
6711
+ });
6712
+ const blurKey = (key) => setFocusedKey((k) => k === key ? null : k);
6713
+ const locked = disabled || busy;
6714
+ const canAdd = !!onPick && !locked && !readOnly;
6715
+ const canRemove = !!onRemove && !locked && !readOnly;
6716
+ const reorderEnabled = !!onReorder && !locked && !readOnly;
6717
+ const acceptsVideo = accept.includes("video");
6718
+ const acceptsImage = accept.includes("image") || accept === "*" || accept === "";
6719
+ const kindWord = acceptsVideo && !acceptsImage ? "video" : acceptsImage && !acceptsVideo ? "image" : "file";
6720
+ const dimLine = placeholder ?? `Upload ${kindWord === "image" ? "an image" : `a ${kindWord}`}`;
6721
+ const pick = (file) => {
6722
+ if (!canAdd) return;
6723
+ onPick(file ?? void 0);
6724
+ };
6725
+ const isFileDrag = (e) => Array.from(e.dataTransfer.types || []).includes("Files");
6726
+ const zoneHandlers = canAdd ? {
6727
+ onDragOver: (e) => {
6728
+ if (!isFileDrag(e)) return;
6729
+ e.preventDefault();
6730
+ setZoneDragOver(true);
6731
+ },
6732
+ onDragLeave: () => setZoneDragOver(false),
6733
+ onDrop: (e) => {
6734
+ const file = e.dataTransfer.files?.[0];
6735
+ if (!file) return;
6736
+ e.preventDefault();
6737
+ setZoneDragOver(false);
6738
+ pick(file);
6739
+ }
6740
+ } : {};
6741
+ const square = { width: thumbSize, height: thumbSize };
6742
+ const thumb = (item, index) => {
6743
+ const video = item.kind ? item.kind === "video" : isVideoUrl(item.url, accept);
6744
+ const name = mediaFileName(item.url);
6745
+ const isCover = showCover && (item.cover ?? index === 0);
6746
+ return /* @__PURE__ */ jsxs(
6747
+ "div",
6748
+ {
6749
+ draggable: reorderEnabled || void 0,
6750
+ onDragStart: reorderEnabled ? (e) => {
6751
+ e.dataTransfer.effectAllowed = "move";
6752
+ e.dataTransfer.setData("text/plain", String(index));
6753
+ setDraggingIndex(index);
6754
+ } : void 0,
6755
+ onDragEnd: reorderEnabled ? () => setDraggingIndex(null) : void 0,
6756
+ onDragOver: reorderEnabled ? (e) => {
6757
+ if (draggingIndex == null) return;
6758
+ e.preventDefault();
6759
+ e.stopPropagation();
6760
+ } : void 0,
6761
+ onDrop: reorderEnabled ? (e) => {
6762
+ if (draggingIndex == null) return;
6763
+ e.preventDefault();
6764
+ e.stopPropagation();
6765
+ if (draggingIndex !== index) onReorder(draggingIndex, index);
6766
+ setDraggingIndex(null);
6767
+ } : void 0,
6768
+ tabIndex: reorderEnabled ? 0 : void 0,
6769
+ "aria-label": reorderEnabled ? `${name}, item ${index + 1} of ${items.length}. Use arrow keys to reorder.` : void 0,
6770
+ onKeyDown: reorderEnabled ? (e) => {
6771
+ if (e.key === "ArrowLeft" || e.key === "ArrowUp") {
6772
+ if (index > 0) {
6773
+ e.preventDefault();
6774
+ onReorder(index, index - 1);
6775
+ }
6776
+ } else if (e.key === "ArrowRight" || e.key === "ArrowDown") {
6777
+ if (index < items.length - 1) {
6778
+ e.preventDefault();
6779
+ onReorder(index, index + 1);
6780
+ }
6781
+ }
6782
+ } : void 0,
6783
+ onFocus: reorderEnabled ? (e) => {
6784
+ if (e.target === e.currentTarget) setFocusedKey(`thumb:${item.id}`);
6785
+ } : void 0,
6786
+ onBlur: reorderEnabled ? (e) => {
6787
+ if (e.target === e.currentTarget) blurKey(`thumb:${item.id}`);
6788
+ } : void 0,
6789
+ className: [
6790
+ "relative shrink-0 overflow-hidden rounded-md border border-gray-200 bg-gray-100",
6791
+ draggingIndex === index ? "opacity-50" : ""
6792
+ ].join(" "),
6793
+ style: { ...square, cursor: reorderEnabled ? "move" : void 0, ...ringStyle(`thumb:${item.id}`) },
6794
+ children: [
6795
+ video ? /* @__PURE__ */ jsx("video", { src: item.url, muted: true, playsInline: true, preload: "metadata", "aria-label": name, className: "h-full w-full", style: { objectFit: fit } }) : /* @__PURE__ */ jsx("img", { src: item.url, alt: name, className: "h-full w-full", style: { objectFit: fit } }),
6796
+ isCover && /* @__PURE__ */ jsx("span", { className: "absolute left-1 top-1 rounded bg-blue-600 px-1.5 py-0.5 text-xs font-medium text-white", children: coverLabel }),
6797
+ item.caption != null && /* @__PURE__ */ jsx(
6798
+ "span",
6799
+ {
6800
+ className: "absolute inset-x-1 bottom-1 truncate rounded px-1.5 py-0.5 text-xs text-white",
6801
+ style: { background: "rgba(0,0,0,0.6)" },
6802
+ children: item.caption
6803
+ }
6804
+ ),
6805
+ canRemove && /* @__PURE__ */ jsx(
6806
+ "button",
6807
+ {
6808
+ type: "button",
6809
+ onClick: () => onRemove(item.id),
6810
+ "aria-label": `Remove ${name}`,
6811
+ onFocus: () => setFocusedKey(`rm:${item.id}`),
6812
+ onBlur: () => blurKey(`rm:${item.id}`),
6813
+ className: "absolute right-1 top-1 flex h-5 w-5 items-center justify-center rounded-full text-white",
6814
+ style: { background: "rgba(0,0,0,0.55)", ...ringStyle(`rm:${item.id}`) },
6815
+ children: REMOVE_ICON
6816
+ }
6817
+ )
6818
+ ]
6819
+ },
6820
+ item.id
6821
+ );
6822
+ };
6823
+ const addTile = canAdd && /* @__PURE__ */ jsxs(
6824
+ "button",
6825
+ {
6826
+ type: "button",
6827
+ onClick: () => pick(),
6828
+ "aria-label": "Add media",
6829
+ onFocus: () => setFocusedKey("add"),
6830
+ onBlur: () => blurKey("add"),
6831
+ className: "flex shrink-0 flex-col items-center justify-center gap-1 rounded-md border-2 border-dashed border-gray-300 bg-gray-50 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-500",
6832
+ style: { ...square, ...ringStyle("add") },
6833
+ children: [
6834
+ /* @__PURE__ */ jsx(UploadGlyph, { className: "h-5 w-5" }),
6835
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-500", children: "Add" })
6836
+ ]
6837
+ }
6838
+ );
6839
+ const emptyDropzone = canAdd ? /* @__PURE__ */ jsxs(
6840
+ "button",
6841
+ {
6842
+ type: "button",
6843
+ onClick: () => pick(),
6844
+ "aria-label": typeof dimLine === "string" ? dimLine : "Upload media",
6845
+ onFocus: () => setFocusedKey("zone"),
6846
+ onBlur: () => blurKey("zone"),
6847
+ className: [
6848
+ "flex w-full flex-col items-center justify-center gap-1.5 rounded-lg border-2 border-dashed text-center transition-colors cursor-pointer",
6849
+ zoneDragOver ? "border-blue-500 bg-blue-50" : "border-gray-300 bg-gray-50 hover:bg-gray-100"
6850
+ ].join(" "),
6851
+ style: { minHeight: thumbSize + 32, ...ringStyle("zone") },
6852
+ children: [
6853
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400", children: /* @__PURE__ */ jsx(UploadGlyph, {}) }),
6854
+ /* @__PURE__ */ jsx("span", { className: "px-3 text-xs text-gray-500", children: dimLine }),
6855
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-blue-600", children: cta }),
6856
+ acceptHint && /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400", children: acceptHint })
6857
+ ]
6858
+ }
6859
+ ) : /* @__PURE__ */ jsx(
6860
+ "div",
6861
+ {
6862
+ className: "flex w-full items-center justify-center rounded-lg border border-gray-200 bg-gray-50 px-3 text-xs text-gray-400",
6863
+ style: { minHeight: thumbSize + 32 },
6864
+ children: "No media."
6865
+ }
6866
+ );
6867
+ return /* @__PURE__ */ jsx(FormField, { label, labelId: label ? `${gridId}-label` : void 0, htmlFor: gridId, hint, error, required, className, children: /* @__PURE__ */ jsxs(
6868
+ "div",
6869
+ {
6870
+ id: gridId,
6871
+ role: "group",
6872
+ "aria-labelledby": label ? `${gridId}-label` : void 0,
6873
+ "aria-describedby": error ? `${gridId}-error` : hint ? `${gridId}-hint` : void 0,
6874
+ "aria-busy": busy || void 0,
6875
+ ...zoneHandlers,
6876
+ className: "relative rounded-lg transition-colors",
6877
+ style: {
6878
+ ...locked ? { opacity: 0.6 } : {},
6879
+ // Inline box-shadow, not `ring-*` — the kit's shipped stylesheet has no
6880
+ // Tailwind ring machinery (see MediaUploadField's focus ring).
6881
+ ...zoneDragOver ? { boxShadow: "0 0 0 2px rgba(59,130,246,0.5)" } : {}
6882
+ },
6883
+ children: [
6884
+ items.length === 0 ? emptyDropzone : /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
6885
+ items.map(thumb),
6886
+ addTile
6887
+ ] }),
6888
+ busy && /* @__PURE__ */ jsxs(
6889
+ "div",
6890
+ {
6891
+ role: "status",
6892
+ className: "absolute inset-0 flex items-center justify-center gap-2 rounded-lg",
6893
+ style: { background: "rgba(255,255,255,0.6)" },
6894
+ children: [
6895
+ /* @__PURE__ */ jsx(Spinner2, {}),
6896
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-600", children: busyLabel })
6897
+ ]
6898
+ }
6899
+ )
6900
+ ]
6901
+ }
6902
+ ) });
6903
+ }
6460
6904
  function Card({ children, header, footer, padded = true, className = "" }) {
6461
6905
  return /* @__PURE__ */ jsxs("div", { className: `rounded-lg border border-gray-200 bg-white shadow-sm ${className}`.trim(), children: [
6462
6906
  header && /* @__PURE__ */ jsx("div", { className: "border-b border-gray-100 px-4 py-3 text-sm font-semibold text-gray-900", children: header }),
@@ -8968,6 +9412,6 @@ function useEditHotkey(callback) {
8968
9412
  }, [callback, isActive]);
8969
9413
  }
8970
9414
 
8971
- export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, BehaviorPanel, BulkImportGrid, Button_default as Button, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Card, ChangePasswordForm, ChatTemplate, Checkbox_default as Checkbox, CheckoutTemplate, ColoredBadge, ContainerFillChart, Customization, DEV_BANNER_TEXT, DashboardTemplate, DataTablePage, Desktop, DesktopHostProvider, DevIndicator, DonutChart, ENTER, EmailTemplate, EmptyState, EntityList, ErrorPage, FilterBar, FormField, FormLayoutPage, GalleryTemplate, GlobalSearch, HelpCenter, INPUT_BASE, Input_default as Input, Kanban, Label, Layout, ListFooter, LoadingSpinner2 as LoadingSpinner, MOD, Markdown, MilestoneTimeline, NotificationBell, PageHeader, Pagination, PdfActionButton, Radio_default as Radio, ResizableTable, SHIFT, SearchableSelect, Select_default as Select, ServerStatusIndicator, ShellEntityFetcherProvider, ShortcutHelp, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, SoundsPanel, Sparkline, StartMenu, StatCard, StatusBadge, StatusBadgeProvider, SystemPreferences, Tabs, Textarea_default as Textarea, Tooltip, TopNav, WidgetManager, applyDevTitle, createWindowRegistry, findDuplicateKeys, formatDate, inputClasses, isDevEnv, isMac, mergeBulkItems, useClickOutside, useColumnConfig, useDesktopHost, useEditHotkey, useFilters, useInfiniteScroll, useNewHotkey, useShellEntityFetcher, useSort, useTableNav };
9415
+ export { ALT, ALT_SHIFT_D, ALT_SHIFT_E, ALT_SHIFT_N, Accordion, AuthScreen, Avatar, AvatarGroup, Banner, BarChart, BehaviorPanel, BulkImportGrid, Button_default as Button, CMD_A, CMD_DOT, CMD_ENTER, CMD_K, CMD_S, Card, ChangePasswordForm, ChatTemplate, Checkbox_default as Checkbox, CheckoutTemplate, ColoredBadge, ContainerFillChart, Customization, DEV_BANNER_TEXT, DashboardTemplate, DataTablePage, Desktop, DesktopHostProvider, DevIndicator, DonutChart, ENTER, EmailTemplate, EmptyState, EntityList, ErrorPage, FilterBar, FormField, FormLayoutPage, GalleryTemplate, GlobalSearch, HelpCenter, INPUT_BASE, Input_default as Input, Kanban, Label, Layout, ListFooter, LoadingSpinner2 as LoadingSpinner, MOD, Markdown, MediaUploadField, MediaUploadGrid, MilestoneTimeline, NotificationBell, PageHeader, Pagination, PdfActionButton, Radio_default as Radio, ResizableTable, SHIFT, SearchableSelect, Select_default as Select, ServerStatusIndicator, ShellEntityFetcherProvider, ShortcutHelp, SidebarActionButton, SidebarGroupLabel, SidebarNavItem, SoundsPanel, Sparkline, StartMenu, StatCard, StatusBadge, StatusBadgeProvider, SystemPreferences, Tabs, Textarea_default as Textarea, Tooltip, TopNav, WidgetManager, applyDevTitle, createWindowRegistry, findDuplicateKeys, formatDate, inputClasses, isDevEnv, isMac, mediaFileName, mergeBulkItems, useClickOutside, useColumnConfig, useDesktopHost, useEditHotkey, useFilters, useInfiniteScroll, useNewHotkey, useShellEntityFetcher, useSort, useTableNav };
8972
9416
  //# sourceMappingURL=index.js.map
8973
9417
  //# sourceMappingURL=index.js.map