@vryzel/file-next-ui 0.4.1 → 0.4.3

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.js CHANGED
@@ -147,6 +147,62 @@ function useExplorerLabels() {
147
147
  function formatLabel(template, vars) {
148
148
  return template.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? ""));
149
149
  }
150
+ function RenameInput({
151
+ name,
152
+ kind,
153
+ ariaLabel,
154
+ onCommit,
155
+ onCancel,
156
+ className
157
+ }) {
158
+ const ref = useRef(null);
159
+ const done = useRef(false);
160
+ useEffect(() => {
161
+ const el = ref.current;
162
+ if (!el) return;
163
+ el.focus();
164
+ const dot = name.lastIndexOf(".");
165
+ if (kind === "file" && dot > 0) el.setSelectionRange(0, dot);
166
+ else el.select();
167
+ }, [kind, name]);
168
+ const finish = (next) => {
169
+ if (done.current) return;
170
+ done.current = true;
171
+ const trimmed = next?.trim() ?? "";
172
+ if (!trimmed) {
173
+ onCancel();
174
+ return;
175
+ }
176
+ onCommit(trimmed);
177
+ };
178
+ return /* @__PURE__ */ jsx(
179
+ "input",
180
+ {
181
+ ref,
182
+ defaultValue: name,
183
+ "aria-label": ariaLabel ?? "Rename",
184
+ className: cn(
185
+ "h-7 min-w-0 flex-1 rounded-[6px] border border-ring bg-background px-1.5 text-sm outline-none",
186
+ className
187
+ ),
188
+ onClick: (event) => event.stopPropagation(),
189
+ onDoubleClick: (event) => event.stopPropagation(),
190
+ onPointerDown: (event) => event.stopPropagation(),
191
+ onKeyDown: (event) => {
192
+ event.stopPropagation();
193
+ if (event.key === "Enter") {
194
+ event.preventDefault();
195
+ finish(event.currentTarget.value);
196
+ }
197
+ if (event.key === "Escape") {
198
+ event.preventDefault();
199
+ finish(null);
200
+ }
201
+ },
202
+ onBlur: (event) => finish(event.currentTarget.value)
203
+ }
204
+ );
205
+ }
150
206
  var LONG_PRESS_MS = 450;
151
207
  var MOVE_CANCEL_PX = 10;
152
208
  function useCoarsePointer() {
@@ -244,6 +300,9 @@ function ExplorerListView(props) {
244
300
  sortKey,
245
301
  sortDirection,
246
302
  onSortChange,
303
+ renamingId,
304
+ onRenameCommit,
305
+ onRenameCancel,
247
306
  className
248
307
  } = props;
249
308
  const labels = useExplorerLabels();
@@ -284,7 +343,10 @@ function ExplorerListView(props) {
284
343
  onDragStart,
285
344
  onDragEnd,
286
345
  onDrop,
287
- onDragOverRow
346
+ onDragOverRow,
347
+ renaming: file.id === renamingId,
348
+ onRenameCommit,
349
+ onRenameCancel
288
350
  },
289
351
  file.id
290
352
  )) })
@@ -306,7 +368,10 @@ function ExplorerListRow({
306
368
  onDragStart,
307
369
  onDragEnd,
308
370
  onDrop,
309
- onDragOverRow
371
+ onDragOverRow,
372
+ renaming,
373
+ onRenameCommit,
374
+ onRenameCancel
310
375
  }) {
311
376
  const labels = useExplorerLabels();
312
377
  const isFolder = file.kind === "folder";
@@ -317,17 +382,18 @@ function ExplorerListRow({
317
382
  role: "row",
318
383
  "data-file-id": file.id,
319
384
  "aria-selected": selected,
320
- draggable: !coarse,
385
+ draggable: !coarse && !renaming,
321
386
  onDragStart: (e) => {
322
387
  e.dataTransfer.setData(DRAG_MIME, file.id);
323
388
  e.dataTransfer.effectAllowed = "move";
324
389
  onDragStart(file.id);
325
390
  },
326
391
  onDragEnd,
327
- onPointerDown: pointer.onPointerDown,
328
- onClick: pointer.onClick,
329
- onDoubleClick: pointer.onDoubleClick,
392
+ onPointerDown: renaming ? void 0 : pointer.onPointerDown,
393
+ onClick: renaming ? void 0 : pointer.onClick,
394
+ onDoubleClick: renaming ? void 0 : pointer.onDoubleClick,
330
395
  onKeyDown: (e) => {
396
+ if (renaming) return;
331
397
  if (e.key === "Enter") {
332
398
  e.preventDefault();
333
399
  onActivate(file);
@@ -369,7 +435,16 @@ function ExplorerListRow({
369
435
  /* @__PURE__ */ jsx("span", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground tabular-nums", children: String(idx + 1).padStart(2, "0") }),
370
436
  /* @__PURE__ */ jsx("span", { className: isFolder ? "text-primary" : "text-muted-foreground", children: /* @__PURE__ */ jsx(FileIcon, { kind: file.kind, mimeType: file.mimeType, className: "size-5 text-current sm:size-4" }) }),
371
437
  /* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [
372
- /* @__PURE__ */ jsx("span", { className: cn("truncate", isFolder && "text-primary"), children: file.name }),
438
+ renaming && onRenameCommit && onRenameCancel ? /* @__PURE__ */ jsx(
439
+ RenameInput,
440
+ {
441
+ name: file.name,
442
+ kind: file.kind,
443
+ ariaLabel: labels.rename,
444
+ onCommit: onRenameCommit,
445
+ onCancel: onRenameCancel
446
+ }
447
+ ) : /* @__PURE__ */ jsx("span", { className: cn("truncate", isFolder && "text-primary"), children: file.name }),
373
448
  isProtected ? /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-0.5 rounded-[10px] bg-primary/10 px-1.5 text-[10px] text-primary", children: [
374
449
  /* @__PURE__ */ jsx(Link2, { "aria-hidden": "true", className: "size-2.5" }),
375
450
  labels.inUse
@@ -446,6 +521,9 @@ function ExplorerGridView(props) {
446
521
  draggingIds,
447
522
  dropTargetId,
448
523
  onDragOverRow,
524
+ renamingId,
525
+ onRenameCommit,
526
+ onRenameCancel,
449
527
  className
450
528
  } = props;
451
529
  const labels = useExplorerLabels();
@@ -471,7 +549,10 @@ function ExplorerGridView(props) {
471
549
  onDragStart,
472
550
  onDragEnd,
473
551
  onDrop,
474
- onDragOverRow
552
+ onDragOverRow,
553
+ renaming: file.id === renamingId,
554
+ onRenameCommit,
555
+ onRenameCancel
475
556
  },
476
557
  file.id
477
558
  )) });
@@ -495,9 +576,42 @@ function ExplorerGridItem({
495
576
  onDragStart,
496
577
  onDragEnd,
497
578
  onDrop,
498
- onDragOverRow
579
+ onDragOverRow,
580
+ renaming,
581
+ onRenameCommit,
582
+ onRenameCancel
499
583
  }) {
584
+ const labels = useExplorerLabels();
500
585
  const pointer = useExplorerItemPointer({ file, coarse, onSelect, onActivate });
586
+ const bodyClass = cn(
587
+ "group relative flex w-full flex-col items-stretch gap-1 overflow-hidden rounded-[10px] border border-border bg-background p-2 text-left outline-none",
588
+ "touch-manipulation select-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring",
589
+ selected && "border-primary bg-muted",
590
+ dragging && "opacity-50",
591
+ dropTarget && "ring-2 ring-primary ring-inset"
592
+ );
593
+ const inner = /* @__PURE__ */ jsxs(Fragment, { children: [
594
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 pr-8", children: [
595
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground tabular-nums", children: String(idx + 1).padStart(2, "0") }),
596
+ isProtected ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-0.5 rounded-[10px] bg-primary/10 px-1.5 py-0.5 text-[10px] text-primary", children: [
597
+ /* @__PURE__ */ jsx(Link2, { "aria-hidden": "true", className: "size-2.5" }),
598
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: inUseLabel })
599
+ ] }) : null
600
+ ] }),
601
+ /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center py-1", file.kind === "folder" ? "text-primary" : "text-muted-foreground"), children: /* @__PURE__ */ jsx(FileIcon, { kind: file.kind, mimeType: file.mimeType, className: "size-12 text-current" }) }),
602
+ renaming && onRenameCommit && onRenameCancel ? /* @__PURE__ */ jsx(
603
+ RenameInput,
604
+ {
605
+ name: file.name,
606
+ kind: file.kind,
607
+ ariaLabel: labels.rename,
608
+ className: "text-[11px]",
609
+ onCommit: onRenameCommit,
610
+ onCancel: onRenameCancel
611
+ }
612
+ ) : /* @__PURE__ */ jsx("span", { className: cn("truncate text-[11px]", file.kind === "folder" && "text-primary"), children: file.name }),
613
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground", children: file.kind === "folder" ? folderLabel : formatSizeShort(file.size) })
614
+ ] });
501
615
  return /* @__PURE__ */ jsxs("li", { className: "relative", children: [
502
616
  coarse && onContextMenu ? /* @__PURE__ */ jsx(
503
617
  "button",
@@ -513,7 +627,7 @@ function ExplorerGridItem({
513
627
  children: /* @__PURE__ */ jsx(MoreHorizontal, { "aria-hidden": "true", className: "size-5" })
514
628
  }
515
629
  ) : null,
516
- /* @__PURE__ */ jsxs(
630
+ renaming ? /* @__PURE__ */ jsx("div", { "data-file-id": file.id, className: bodyClass, children: inner }) : /* @__PURE__ */ jsx(
517
631
  "button",
518
632
  {
519
633
  type: "button",
@@ -555,25 +669,8 @@ function ExplorerGridItem({
555
669
  e.preventDefault();
556
670
  onDrop(file.id);
557
671
  },
558
- className: cn(
559
- "group relative flex w-full flex-col items-stretch gap-1 overflow-hidden rounded-[10px] border border-border bg-background p-2 text-left outline-none",
560
- "touch-manipulation select-none hover:bg-muted focus-visible:ring-2 focus-visible:ring-ring",
561
- selected && "border-primary bg-muted",
562
- dragging && "opacity-50",
563
- dropTarget && "ring-2 ring-primary ring-inset"
564
- ),
565
- children: [
566
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1 pr-8", children: [
567
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground tabular-nums", children: String(idx + 1).padStart(2, "0") }),
568
- isProtected ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-0.5 rounded-[10px] bg-primary/10 px-1.5 py-0.5 text-[10px] text-primary", children: [
569
- /* @__PURE__ */ jsx(Link2, { "aria-hidden": "true", className: "size-2.5" }),
570
- /* @__PURE__ */ jsx("span", { className: "sr-only", children: inUseLabel })
571
- ] }) : null
572
- ] }),
573
- /* @__PURE__ */ jsx("span", { className: cn("flex items-center justify-center py-1", file.kind === "folder" ? "text-primary" : "text-muted-foreground"), children: /* @__PURE__ */ jsx(FileIcon, { kind: file.kind, mimeType: file.mimeType, className: "size-12 text-current" }) }),
574
- /* @__PURE__ */ jsx("span", { className: cn("truncate text-[11px]", file.kind === "folder" && "text-primary"), children: file.name }),
575
- /* @__PURE__ */ jsx("span", { className: "font-mono text-[9px] uppercase tracking-[0.12em] text-muted-foreground", children: file.kind === "folder" ? folderLabel : formatSizeShort(file.size) })
576
- ]
672
+ className: bodyClass,
673
+ children: inner
577
674
  }
578
675
  )
579
676
  ] });
@@ -742,6 +839,13 @@ function swallowClickThrough() {
742
839
  window.removeEventListener("pointerup", swallow, true);
743
840
  }, 400);
744
841
  }
842
+
843
+ // src/share-url.ts
844
+ function absoluteShareUrl(link) {
845
+ if (link.startsWith("http://") || link.startsWith("https://")) return link;
846
+ const origin = typeof window === "undefined" ? "" : window.location.origin;
847
+ return `${origin}${link.startsWith("/") ? link : `/${link}`}`;
848
+ }
745
849
  function ExplorerContextMenu(props) {
746
850
  const {
747
851
  target,
@@ -834,24 +938,26 @@ function ExplorerContextMenu(props) {
834
938
  /* @__PURE__ */ jsx(RotateCcw, { "aria-hidden": "true", className: "size-4" }),
835
939
  labels.restore
836
940
  ] }),
837
- /* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
838
- /* @__PURE__ */ jsxs(
839
- "button",
840
- {
841
- type: "button",
842
- role: "menuitem",
843
- className: cn(itemClass, "text-destructive"),
844
- onClick: () => {
845
- close();
846
- if (onRequestPurge) onRequestPurge(target.file);
847
- else void actions.purgeNode?.({ id: target.file.id });
848
- },
849
- children: [
850
- /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-4" }),
851
- labels.deleteForever
852
- ]
853
- }
854
- )
941
+ onRequestPurge || actions.purgeNode ? /* @__PURE__ */ jsxs(Fragment, { children: [
942
+ /* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
943
+ /* @__PURE__ */ jsxs(
944
+ "button",
945
+ {
946
+ type: "button",
947
+ role: "menuitem",
948
+ className: cn(itemClass, "text-destructive"),
949
+ onClick: () => {
950
+ close();
951
+ if (onRequestPurge) onRequestPurge(target.file);
952
+ else void actions.purgeNode?.({ id: target.file.id });
953
+ },
954
+ children: [
955
+ /* @__PURE__ */ jsx(X, { "aria-hidden": "true", className: "size-4" }),
956
+ labels.deleteForever
957
+ ]
958
+ }
959
+ )
960
+ ] }) : null
855
961
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
856
962
  target.file.kind === "file" && onPreview ? /* @__PURE__ */ jsxs("button", { type: "button", role: "menuitem", className: itemClass, onClick: () => {
857
963
  close();
@@ -892,7 +998,7 @@ function ExplorerContextMenu(props) {
892
998
  /* @__PURE__ */ jsx(Copy, { "aria-hidden": "true", className: "size-4" }),
893
999
  labels.paste
894
1000
  ] }) : null,
895
- actions.createShare ? /* @__PURE__ */ jsxs(
1001
+ target.file.kind === "file" && actions.createShare ? /* @__PURE__ */ jsxs(
896
1002
  "button",
897
1003
  {
898
1004
  type: "button",
@@ -901,7 +1007,7 @@ function ExplorerContextMenu(props) {
901
1007
  onClick: () => {
902
1008
  close();
903
1009
  void actions.createShare({ id: target.file.id }).then(
904
- (token) => navigator.clipboard.writeText(token)
1010
+ (link) => navigator.clipboard.writeText(absoluteShareUrl(link))
905
1011
  );
906
1012
  },
907
1013
  children: [
@@ -961,92 +1067,6 @@ function Overlay({
961
1067
  }
962
1068
  );
963
1069
  }
964
- function CreateFolderDialog({
965
- open,
966
- onOpenChange,
967
- onCreate,
968
- title,
969
- description,
970
- confirmLabel,
971
- initialName = ""
972
- }) {
973
- const labels = useExplorerLabels();
974
- const [name, setName] = useState(initialName);
975
- const [saving, setSaving] = useState(false);
976
- const [error, setError] = useState(null);
977
- const inputRef = useRef(null);
978
- useEffect(() => {
979
- if (!open) {
980
- setSaving(false);
981
- setError(null);
982
- return;
983
- }
984
- setName(initialName);
985
- const id = window.setTimeout(() => {
986
- inputRef.current?.focus();
987
- inputRef.current?.select();
988
- }, 0);
989
- return () => window.clearTimeout(id);
990
- }, [initialName, open]);
991
- async function submit() {
992
- const trimmed = name.trim();
993
- if (!trimmed || saving) return;
994
- setSaving(true);
995
- setError(null);
996
- try {
997
- await onCreate(trimmed);
998
- onOpenChange(false);
999
- } catch (cause) {
1000
- const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
1001
- setError(code === "Conflict" ? labels.folderExists : labels.folderCreateFailed);
1002
- } finally {
1003
- setSaving(false);
1004
- }
1005
- }
1006
- return /* @__PURE__ */ jsxs(Overlay, { open, onClose: () => onOpenChange(false), children: [
1007
- /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title ?? labels.newFolder }),
1008
- /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description ?? labels.folderNameHint }),
1009
- /* @__PURE__ */ jsx(
1010
- "input",
1011
- {
1012
- ref: inputRef,
1013
- value: name,
1014
- onChange: (event) => setName(event.target.value),
1015
- onKeyDown: (event) => {
1016
- if (event.key === "Enter") {
1017
- event.preventDefault();
1018
- void submit();
1019
- }
1020
- },
1021
- placeholder: labels.folderName,
1022
- "aria-invalid": Boolean(error),
1023
- className: "mt-3 h-9 w-full rounded-[10px] border border-border bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
1024
- }
1025
- ),
1026
- error ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-destructive", children: error }) : null,
1027
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
1028
- /* @__PURE__ */ jsx(
1029
- "button",
1030
- {
1031
- type: "button",
1032
- className: "inline-flex h-8 items-center rounded-[10px] border border-border px-3 text-sm",
1033
- onClick: () => onOpenChange(false),
1034
- children: labels.cancel
1035
- }
1036
- ),
1037
- /* @__PURE__ */ jsx(
1038
- "button",
1039
- {
1040
- type: "button",
1041
- disabled: !name.trim() || saving,
1042
- className: "inline-flex h-8 items-center rounded-[10px] bg-primary px-3 text-sm text-primary-foreground disabled:opacity-50",
1043
- onClick: () => void submit(),
1044
- children: confirmLabel ?? labels.createFolder
1045
- }
1046
- )
1047
- ] })
1048
- ] });
1049
- }
1050
1070
  function ConfirmDeleteDialog({
1051
1071
  open,
1052
1072
  names,
@@ -1512,6 +1532,32 @@ function UploadQueueProvider({
1512
1532
  }
1513
1533
  var PAGE_SIZE = 50;
1514
1534
  var DEFAULT_PREFS = { view: "list", sortKey: "name", sortDir: "asc" };
1535
+ var DRAFT_FOLDER_ID = "__draft-folder__";
1536
+ function unusedFolderName(files, base) {
1537
+ const taken = new Set(files.map((file) => file.name.toLowerCase()));
1538
+ if (!taken.has(base.toLowerCase())) return base;
1539
+ let n = 2;
1540
+ while (taken.has(`${base} ${n}`.toLowerCase())) n += 1;
1541
+ return `${base} ${n}`;
1542
+ }
1543
+ function draftFolder(name, tenantId, parentId) {
1544
+ return {
1545
+ id: DRAFT_FOLDER_ID,
1546
+ tenantId,
1547
+ parentId,
1548
+ name,
1549
+ path: name,
1550
+ kind: "folder",
1551
+ size: 0,
1552
+ mimeType: "",
1553
+ s3Key: "",
1554
+ ownerId: "",
1555
+ metadata: {},
1556
+ createdAt: /* @__PURE__ */ new Date(0),
1557
+ updatedAt: /* @__PURE__ */ new Date(0),
1558
+ deletedAt: null
1559
+ };
1560
+ }
1515
1561
  function reviveFile(node) {
1516
1562
  const asDate = (value) => value instanceof Date ? value : new Date(value);
1517
1563
  return {
@@ -1635,7 +1681,7 @@ function FileExplorerInner(props) {
1635
1681
  const [prefsReady, setPrefsReady] = useState(!persistViewKey);
1636
1682
  const skipPrefsWrite = useRef(!persistViewKey);
1637
1683
  const [menu, setMenu] = useState(null);
1638
- const [folderOpen, setFolderOpen] = useState(false);
1684
+ const [draftFolderName, setDraftFolderName] = useState(null);
1639
1685
  const [renameTarget, setRenameTarget] = useState(null);
1640
1686
  const [deleteTargets, setDeleteTargets] = useState([]);
1641
1687
  const [purgeTargets, setPurgeTargets] = useState([]);
@@ -1817,6 +1863,7 @@ function FileExplorerInner(props) {
1817
1863
  }, [refreshKey]);
1818
1864
  const handleActivate = useCallback(
1819
1865
  (file) => {
1866
+ if (file.id === DRAFT_FOLDER_ID) return;
1820
1867
  if (file.kind === "folder") {
1821
1868
  onOpenFolder?.(file);
1822
1869
  return;
@@ -1828,6 +1875,7 @@ function FileExplorerInner(props) {
1828
1875
  [onActivate, onDownload, onOpenFolder, onPreview]
1829
1876
  );
1830
1877
  const handleContextMenu = useCallback((file, event) => {
1878
+ if (file.id === DRAFT_FOLDER_ID) return;
1831
1879
  setMenu({ kind: "item", file, x: event.clientX, y: event.clientY });
1832
1880
  }, []);
1833
1881
  const clearSelection = useCallback(() => {
@@ -1839,6 +1887,7 @@ function FileExplorerInner(props) {
1839
1887
  }, [clearSelection, parentId, trashOpen]);
1840
1888
  const handleSelect = useCallback(
1841
1889
  (file, event) => {
1890
+ if (file.id === DRAFT_FOLDER_ID) return;
1842
1891
  const id = file.id;
1843
1892
  if (event.shiftKey && anchorId.current) {
1844
1893
  const from = sortedFiles.findIndex((item) => item.id === anchorId.current);
@@ -1952,7 +2001,7 @@ function FileExplorerInner(props) {
1952
2001
  if ((event.key === "Delete" || event.key === "Backspace") && selectedFiles.length > 0) {
1953
2002
  event.preventDefault();
1954
2003
  if (trashOpen) {
1955
- setPurgeTargets(selectedFiles);
2004
+ if (actions.purgeNode) setPurgeTargets(selectedFiles);
1956
2005
  return;
1957
2006
  }
1958
2007
  if (selectedFiles.some((file) => protectedSet.has(file.id))) return;
@@ -1977,6 +2026,37 @@ function FileExplorerInner(props) {
1977
2026
  trashOpen
1978
2027
  ]
1979
2028
  );
2029
+ const commitRename = (name) => {
2030
+ if (draftFolderName != null) {
2031
+ setDraftFolderName(null);
2032
+ void actions.createFolder?.({ name, parentId }).then(() => reload());
2033
+ return;
2034
+ }
2035
+ if (!renameTarget) return;
2036
+ const target = renameTarget;
2037
+ setRenameTarget(null);
2038
+ if (name === target.name) return;
2039
+ void actions.renameFile(target.id, name).then(() => reload());
2040
+ };
2041
+ const cancelRename = () => {
2042
+ setDraftFolderName(null);
2043
+ setRenameTarget(null);
2044
+ };
2045
+ const startCreateFolder = () => {
2046
+ if (!actions.createFolder || trashOpen) return;
2047
+ setMenu(null);
2048
+ setRenameTarget(null);
2049
+ setQuery("");
2050
+ setDraftFolderName(unusedFolderName(pageItems, labels.newFolder));
2051
+ };
2052
+ const displayFiles = useMemo(() => {
2053
+ if (draftFolderName == null) return sortedFiles;
2054
+ return [
2055
+ draftFolder(draftFolderName, tenantId, parentId),
2056
+ ...sortedFiles
2057
+ ];
2058
+ }, [draftFolderName, parentId, sortedFiles, tenantId]);
2059
+ const renamingId = draftFolderName != null ? DRAFT_FOLDER_ID : renameTarget?.id ?? null;
1980
2060
  const openUpload = () => uploadInputRef.current?.click();
1981
2061
  return /* @__PURE__ */ jsxs(
1982
2062
  "div",
@@ -2025,7 +2105,7 @@ function FileExplorerInner(props) {
2025
2105
  view: explorer.view,
2026
2106
  onViewChange: explorer.setView,
2027
2107
  onUpload: requestUpload && !trashOpen ? openUpload : void 0,
2028
- onNewFolder: actions.createFolder && !trashOpen ? () => setFolderOpen(true) : void 0,
2108
+ onNewFolder: actions.createFolder && !trashOpen ? startCreateFolder : void 0,
2029
2109
  query,
2030
2110
  onQueryChange: searchFiles ? setQuery : void 0,
2031
2111
  trashOpen,
@@ -2040,7 +2120,7 @@ function FileExplorerInner(props) {
2040
2120
  }
2041
2121
  )
2042
2122
  ] }),
2043
- /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: (overlayLoading || pageStatus === "loading") && sortedFiles.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-12 text-center font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: labels.loading }) : overlayError || pageStatus === "error" && pageError ? /* @__PURE__ */ jsx("div", { className: "border-b border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive", children: (overlayError ?? pageError)?.message ?? labels.folderCreateFailed }) : sortedFiles.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col justify-center px-4 py-16 text-center", children: [
2123
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: (overlayLoading || pageStatus === "loading") && displayFiles.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-4 py-12 text-center font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: labels.loading }) : overlayError || pageStatus === "error" && pageError ? /* @__PURE__ */ jsx("div", { className: "border-b border-destructive/30 bg-destructive/5 px-4 py-3 text-sm text-destructive", children: (overlayError ?? pageError)?.message ?? labels.folderCreateFailed }) : displayFiles.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col justify-center px-4 py-16 text-center", children: [
2044
2124
  /* @__PURE__ */ jsx("p", { className: "font-mono text-[10px] uppercase tracking-[0.12em] text-muted-foreground", children: trashOpen ? labels.emptyTrash : query.trim() ? labels.noMatches : labels.emptyFolder }),
2045
2125
  trashOpen ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.trashRetention }) : !query.trim() ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: labels.dropHint }) : null
2046
2126
  ] }) : explorer.view === "list" ? /* @__PURE__ */ jsx(
@@ -2053,7 +2133,7 @@ function FileExplorerInner(props) {
2053
2133
  children: /* @__PURE__ */ jsx(
2054
2134
  ExplorerListView,
2055
2135
  {
2056
- files: sortedFiles,
2136
+ files: displayFiles,
2057
2137
  protectedIds: protectedSet,
2058
2138
  selectedIds,
2059
2139
  onSelect: handleSelect,
@@ -2068,7 +2148,10 @@ function FileExplorerInner(props) {
2068
2148
  onDragOverRow: explorer.setDropTarget,
2069
2149
  sortKey,
2070
2150
  sortDirection: sortDir,
2071
- onSortChange: handleSortChange
2151
+ onSortChange: handleSortChange,
2152
+ renamingId,
2153
+ onRenameCommit: commitRename,
2154
+ onRenameCancel: cancelRename
2072
2155
  }
2073
2156
  )
2074
2157
  }
@@ -2082,7 +2165,7 @@ function FileExplorerInner(props) {
2082
2165
  children: /* @__PURE__ */ jsx(
2083
2166
  ExplorerGridView,
2084
2167
  {
2085
- files: sortedFiles,
2168
+ files: displayFiles,
2086
2169
  protectedIds: protectedSet,
2087
2170
  selectedIds,
2088
2171
  onSelect: handleSelect,
@@ -2094,7 +2177,10 @@ function FileExplorerInner(props) {
2094
2177
  draggingId: explorer.draggingId,
2095
2178
  draggingIds,
2096
2179
  dropTargetId: explorer.dropTargetId,
2097
- onDragOverRow: explorer.setDropTarget
2180
+ onDragOverRow: explorer.setDropTarget,
2181
+ renamingId,
2182
+ onRenameCommit: commitRename,
2183
+ onRenameCancel: cancelRename
2098
2184
  }
2099
2185
  )
2100
2186
  }
@@ -2155,10 +2241,10 @@ function FileExplorerInner(props) {
2155
2241
  onDownload,
2156
2242
  onRename: setRenameTarget,
2157
2243
  onRequestDelete: (file) => setDeleteTargets([file]),
2158
- onRequestPurge: (file) => setPurgeTargets([file]),
2244
+ onRequestPurge: actions.purgeNode ? (file) => setPurgeTargets([file]) : void 0,
2159
2245
  onCopy: (file) => copyToClipboard([file]),
2160
2246
  onPaste: clipboard.length > 0 && !trashOpen ? (folder) => void pasteClipboard(folder ? folder.id : parentId) : void 0,
2161
- onNewFolder: actions.createFolder ? () => setFolderOpen(true) : void 0,
2247
+ onNewFolder: actions.createFolder ? startCreateFolder : void 0,
2162
2248
  onUpload: requestUpload ? openUpload : void 0
2163
2249
  }
2164
2250
  ),
@@ -2176,7 +2262,11 @@ function FileExplorerInner(props) {
2176
2262
  if (file.kind !== "file") continue;
2177
2263
  tokens.push(await actions.createShare({ id: file.id }));
2178
2264
  }
2179
- if (tokens.length > 0) await navigator.clipboard.writeText(tokens.join("\n"));
2265
+ if (tokens.length > 0) {
2266
+ await navigator.clipboard.writeText(
2267
+ tokens.map(absoluteShareUrl).join("\n")
2268
+ );
2269
+ }
2180
2270
  } : void 0,
2181
2271
  onDelete: () => setDeleteTargets(selectedFiles),
2182
2272
  onRestore: actions.restoreNode ? async () => {
@@ -2184,7 +2274,7 @@ function FileExplorerInner(props) {
2184
2274
  clearSelection();
2185
2275
  await reload();
2186
2276
  } : void 0,
2187
- onPurge: () => setPurgeTargets(selectedFiles),
2277
+ onPurge: actions.purgeNode ? () => setPurgeTargets(selectedFiles) : void 0,
2188
2278
  onClear: clearSelection
2189
2279
  }
2190
2280
  ),
@@ -2196,36 +2286,6 @@ function FileExplorerInner(props) {
2196
2286
  onClear: () => setClipboard([])
2197
2287
  }
2198
2288
  ) }),
2199
- /* @__PURE__ */ jsx(
2200
- CreateFolderDialog,
2201
- {
2202
- open: folderOpen,
2203
- onOpenChange: setFolderOpen,
2204
- onCreate: async (name) => {
2205
- await actions.createFolder?.({ name, parentId });
2206
- await reload();
2207
- }
2208
- }
2209
- ),
2210
- /* @__PURE__ */ jsx(
2211
- CreateFolderDialog,
2212
- {
2213
- open: Boolean(renameTarget),
2214
- onOpenChange: (open) => {
2215
- if (!open) setRenameTarget(null);
2216
- },
2217
- title: labels.rename,
2218
- description: labels.renameHint,
2219
- confirmLabel: labels.rename,
2220
- initialName: renameTarget?.name ?? "",
2221
- onCreate: async (name) => {
2222
- if (!renameTarget) return;
2223
- await actions.renameFile(renameTarget.id, name);
2224
- setRenameTarget(null);
2225
- await reload();
2226
- }
2227
- }
2228
- ),
2229
2289
  /* @__PURE__ */ jsx(
2230
2290
  ConfirmDeleteDialog,
2231
2291
  {
@@ -2380,6 +2440,92 @@ function FilePreviewDialog({
2380
2440
  ) })
2381
2441
  ] });
2382
2442
  }
2443
+ function CreateFolderDialog({
2444
+ open,
2445
+ onOpenChange,
2446
+ onCreate,
2447
+ title,
2448
+ description,
2449
+ confirmLabel,
2450
+ initialName = ""
2451
+ }) {
2452
+ const labels = useExplorerLabels();
2453
+ const [name, setName] = useState(initialName);
2454
+ const [saving, setSaving] = useState(false);
2455
+ const [error, setError] = useState(null);
2456
+ const inputRef = useRef(null);
2457
+ useEffect(() => {
2458
+ if (!open) {
2459
+ setSaving(false);
2460
+ setError(null);
2461
+ return;
2462
+ }
2463
+ setName(initialName);
2464
+ const id = window.setTimeout(() => {
2465
+ inputRef.current?.focus();
2466
+ inputRef.current?.select();
2467
+ }, 0);
2468
+ return () => window.clearTimeout(id);
2469
+ }, [initialName, open]);
2470
+ async function submit() {
2471
+ const trimmed = name.trim();
2472
+ if (!trimmed || saving) return;
2473
+ setSaving(true);
2474
+ setError(null);
2475
+ try {
2476
+ await onCreate(trimmed);
2477
+ onOpenChange(false);
2478
+ } catch (cause) {
2479
+ const code = cause && typeof cause === "object" && "code" in cause ? String(cause.code) : "";
2480
+ setError(code === "Conflict" ? labels.folderExists : labels.folderCreateFailed);
2481
+ } finally {
2482
+ setSaving(false);
2483
+ }
2484
+ }
2485
+ return /* @__PURE__ */ jsxs(Overlay, { open, onClose: () => onOpenChange(false), children: [
2486
+ /* @__PURE__ */ jsx("h2", { className: "text-base font-semibold", children: title ?? labels.newFolder }),
2487
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: description ?? labels.folderNameHint }),
2488
+ /* @__PURE__ */ jsx(
2489
+ "input",
2490
+ {
2491
+ ref: inputRef,
2492
+ value: name,
2493
+ onChange: (event) => setName(event.target.value),
2494
+ onKeyDown: (event) => {
2495
+ if (event.key === "Enter") {
2496
+ event.preventDefault();
2497
+ void submit();
2498
+ }
2499
+ },
2500
+ placeholder: labels.folderName,
2501
+ "aria-invalid": Boolean(error),
2502
+ className: "mt-3 h-9 w-full rounded-[10px] border border-border bg-background px-3 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring"
2503
+ }
2504
+ ),
2505
+ error ? /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-destructive", children: error }) : null,
2506
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
2507
+ /* @__PURE__ */ jsx(
2508
+ "button",
2509
+ {
2510
+ type: "button",
2511
+ className: "inline-flex h-8 items-center rounded-[10px] border border-border px-3 text-sm",
2512
+ onClick: () => onOpenChange(false),
2513
+ children: labels.cancel
2514
+ }
2515
+ ),
2516
+ /* @__PURE__ */ jsx(
2517
+ "button",
2518
+ {
2519
+ type: "button",
2520
+ disabled: !name.trim() || saving,
2521
+ className: "inline-flex h-8 items-center rounded-[10px] bg-primary px-3 text-sm text-primary-foreground disabled:opacity-50",
2522
+ onClick: () => void submit(),
2523
+ children: confirmLabel ?? labels.createFolder
2524
+ }
2525
+ )
2526
+ ] })
2527
+ ] });
2528
+ }
2383
2529
 
2384
2530
  export { Breadcrumbs, ConfirmDeleteDialog, CreateFolderDialog, EmptyState, ErrorState, ExplorerClipboardToast, ExplorerContextMenu, ExplorerGridView, ExplorerLabelsProvider, ExplorerListView, ExplorerSelectionToast, ExplorerToolbar, ExplorerUploadStatus, FILE_ICON_MAP, FileExplorer, FileIcon, FilePreviewDialog, UploadQueueProvider, canPreviewFile, cn, defaultLabels, resolveFileIcon, useExplorerLabels, useUploadEnqueue, useUploadQueue };
2385
2531
  //# sourceMappingURL=index.js.map