@particle-academy/react-fancy 4.12.0 → 4.13.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/README.md +5 -4
- package/dist/index.cjs +919 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +269 -4
- package/dist/index.d.ts +269 -4
- package/dist/index.js +919 -2
- package/dist/index.js.map +1 -1
- package/docs/AccordionPanel.md +6 -6
- package/docs/Action.md +3 -82
- package/docs/FileBrowser.md +231 -0
- package/docs/utilities.md +5 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { clsx } from 'clsx';
|
|
|
3
3
|
import { twMerge } from 'tailwind-merge';
|
|
4
4
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
|
-
import { X, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Search, Menu, File, Upload, PanelLeftOpen, PanelLeftClose, Check, XCircle, AlertTriangle, Info } from 'lucide-react';
|
|
6
|
+
import { X, ChevronUp, ChevronDown, ChevronLeft, ChevronRight, Search, Menu, File, Upload, PanelLeftOpen, PanelLeftClose, Loader2, ArrowUp, ArrowDown, Pencil, CircleAlert, RotateCw, Check, XCircle, AlertTriangle, Info } from 'lucide-react';
|
|
7
7
|
import { marked } from 'marked';
|
|
8
8
|
|
|
9
9
|
// src/components/Button/Button.tsx
|
|
@@ -13140,6 +13140,923 @@ TreeNavRoot.displayName = "TreeNav";
|
|
|
13140
13140
|
var TreeNav = Object.assign(TreeNavRoot, {
|
|
13141
13141
|
Node: TreeNode
|
|
13142
13142
|
});
|
|
13143
|
+
var FileBrowserContext = createContext(null);
|
|
13144
|
+
function useFileBrowser() {
|
|
13145
|
+
const ctx = useContext(FileBrowserContext);
|
|
13146
|
+
if (!ctx) {
|
|
13147
|
+
throw new Error("useFileBrowser must be used within a <FileBrowser> component");
|
|
13148
|
+
}
|
|
13149
|
+
return ctx;
|
|
13150
|
+
}
|
|
13151
|
+
|
|
13152
|
+
// src/components/FileBrowser/FileBrowser.utils.ts
|
|
13153
|
+
function normalizePath(input) {
|
|
13154
|
+
const raw = input.trim().replace(/\\/g, "/");
|
|
13155
|
+
if (raw === "") return "/";
|
|
13156
|
+
const absolute = raw.startsWith("/");
|
|
13157
|
+
const out = [];
|
|
13158
|
+
for (const segment of raw.split("/")) {
|
|
13159
|
+
if (segment === "" || segment === ".") continue;
|
|
13160
|
+
if (segment === "..") {
|
|
13161
|
+
out.pop();
|
|
13162
|
+
continue;
|
|
13163
|
+
}
|
|
13164
|
+
out.push(segment);
|
|
13165
|
+
}
|
|
13166
|
+
if (out.length === 0) return "/";
|
|
13167
|
+
return (absolute ? "/" : "") + out.join("/");
|
|
13168
|
+
}
|
|
13169
|
+
function parentPath(path) {
|
|
13170
|
+
const i = path.lastIndexOf("/");
|
|
13171
|
+
if (i === -1) return "/";
|
|
13172
|
+
if (i === 0) return "/";
|
|
13173
|
+
return path.slice(0, i);
|
|
13174
|
+
}
|
|
13175
|
+
function pathSegments(path) {
|
|
13176
|
+
const absolute = path.startsWith("/");
|
|
13177
|
+
const parts = path.split("/").filter(Boolean);
|
|
13178
|
+
const segments = [];
|
|
13179
|
+
let acc = "";
|
|
13180
|
+
parts.forEach((part, i) => {
|
|
13181
|
+
acc = i === 0 && !absolute ? part : `${acc}/${part}`;
|
|
13182
|
+
segments.push({ label: part, path: acc });
|
|
13183
|
+
});
|
|
13184
|
+
return segments;
|
|
13185
|
+
}
|
|
13186
|
+
function compareNames(a, b) {
|
|
13187
|
+
const cmp = a.localeCompare(b, "en", { numeric: true, sensitivity: "base" });
|
|
13188
|
+
if (cmp !== 0) return cmp;
|
|
13189
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
13190
|
+
}
|
|
13191
|
+
function compareFileEntries(a, b, sort) {
|
|
13192
|
+
if (a.kind !== b.kind) return a.kind === "dir" ? -1 : 1;
|
|
13193
|
+
const direction = sort.direction === "desc" ? -1 : 1;
|
|
13194
|
+
let cmp;
|
|
13195
|
+
switch (sort.by) {
|
|
13196
|
+
case "size":
|
|
13197
|
+
cmp = (a.size ?? -1) - (b.size ?? -1);
|
|
13198
|
+
break;
|
|
13199
|
+
case "mtime": {
|
|
13200
|
+
const am = a.mtime ?? "";
|
|
13201
|
+
const bm = b.mtime ?? "";
|
|
13202
|
+
cmp = am < bm ? -1 : am > bm ? 1 : 0;
|
|
13203
|
+
break;
|
|
13204
|
+
}
|
|
13205
|
+
default:
|
|
13206
|
+
cmp = compareNames(a.name, b.name);
|
|
13207
|
+
}
|
|
13208
|
+
if (cmp !== 0) return direction * cmp;
|
|
13209
|
+
return compareNames(a.name, b.name);
|
|
13210
|
+
}
|
|
13211
|
+
function isEntryExpandable(entry, children, hasProvider) {
|
|
13212
|
+
if (entry.kind !== "dir" || entry.hasChildren === false) return false;
|
|
13213
|
+
if (children !== void 0) return children.length > 0;
|
|
13214
|
+
return hasProvider;
|
|
13215
|
+
}
|
|
13216
|
+
function entryMatchesFilter(entry, query, entriesFor, visited = /* @__PURE__ */ new Set()) {
|
|
13217
|
+
if (entry.name.toLowerCase().includes(query)) return true;
|
|
13218
|
+
if (entry.kind !== "dir" || visited.has(entry.path)) return false;
|
|
13219
|
+
visited.add(entry.path);
|
|
13220
|
+
const children = entriesFor(entry.path);
|
|
13221
|
+
return children?.some((child) => entryMatchesFilter(child, query, entriesFor, visited)) ?? false;
|
|
13222
|
+
}
|
|
13223
|
+
function formatFileSize(bytes) {
|
|
13224
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "";
|
|
13225
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
13226
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
13227
|
+
let value = bytes;
|
|
13228
|
+
let unit = -1;
|
|
13229
|
+
do {
|
|
13230
|
+
value /= 1024;
|
|
13231
|
+
unit++;
|
|
13232
|
+
} while (value >= 1024 && unit < units.length - 1);
|
|
13233
|
+
const rounded = value >= 100 ? Math.round(value) : Math.round(value * 10) / 10;
|
|
13234
|
+
return `${rounded} ${units[unit]}`;
|
|
13235
|
+
}
|
|
13236
|
+
var SEGMENT_CLASS = "shrink-0 rounded text-[13px] text-zinc-500 transition-colors hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-300";
|
|
13237
|
+
var ACTIVE_SEGMENT_CLASS = "shrink-0 truncate text-[13px] font-medium text-zinc-900 dark:text-white";
|
|
13238
|
+
function FileBrowserPathBar({
|
|
13239
|
+
editable = true,
|
|
13240
|
+
placeholder = "/path/to/folder",
|
|
13241
|
+
className
|
|
13242
|
+
}) {
|
|
13243
|
+
const { path, navigate } = useFileBrowser();
|
|
13244
|
+
const [editing, setEditing] = useState(false);
|
|
13245
|
+
const [draft, setDraft] = useState("");
|
|
13246
|
+
const startEdit = () => {
|
|
13247
|
+
setDraft(path);
|
|
13248
|
+
setEditing(true);
|
|
13249
|
+
};
|
|
13250
|
+
const commit = () => {
|
|
13251
|
+
navigate(normalizePath(draft));
|
|
13252
|
+
setEditing(false);
|
|
13253
|
+
};
|
|
13254
|
+
const segments = pathSegments(path);
|
|
13255
|
+
const atRoot = segments.length === 0;
|
|
13256
|
+
return /* @__PURE__ */ jsxs(
|
|
13257
|
+
"div",
|
|
13258
|
+
{
|
|
13259
|
+
"data-react-fancy-file-browser-path": "",
|
|
13260
|
+
className: cn(
|
|
13261
|
+
"flex items-center gap-1 border-b border-zinc-200 px-2 py-1.5 dark:border-zinc-700",
|
|
13262
|
+
className
|
|
13263
|
+
),
|
|
13264
|
+
children: [
|
|
13265
|
+
editing ? /* @__PURE__ */ jsx(
|
|
13266
|
+
"input",
|
|
13267
|
+
{
|
|
13268
|
+
"data-react-fancy-file-browser-path-input": "",
|
|
13269
|
+
type: "text",
|
|
13270
|
+
value: draft,
|
|
13271
|
+
onChange: (e) => setDraft(e.target.value),
|
|
13272
|
+
onKeyDown: (e) => {
|
|
13273
|
+
if (e.key === "Enter") {
|
|
13274
|
+
e.preventDefault();
|
|
13275
|
+
commit();
|
|
13276
|
+
} else if (e.key === "Escape") {
|
|
13277
|
+
e.preventDefault();
|
|
13278
|
+
setEditing(false);
|
|
13279
|
+
}
|
|
13280
|
+
},
|
|
13281
|
+
onBlur: () => setEditing(false),
|
|
13282
|
+
autoFocus: true,
|
|
13283
|
+
spellCheck: false,
|
|
13284
|
+
placeholder,
|
|
13285
|
+
"aria-label": "Path",
|
|
13286
|
+
className: "min-w-0 flex-1 rounded-md border border-zinc-300 bg-transparent px-2 py-0.5 font-mono text-xs text-zinc-700 outline-none placeholder:text-zinc-400 focus:border-blue-400 dark:border-zinc-600 dark:text-zinc-300"
|
|
13287
|
+
}
|
|
13288
|
+
) : /* @__PURE__ */ jsxs(
|
|
13289
|
+
"nav",
|
|
13290
|
+
{
|
|
13291
|
+
"aria-label": "Path",
|
|
13292
|
+
onDoubleClick: editable ? startEdit : void 0,
|
|
13293
|
+
className: "flex min-w-0 flex-1 items-center gap-1 overflow-x-auto",
|
|
13294
|
+
children: [
|
|
13295
|
+
atRoot ? /* @__PURE__ */ jsx("span", { "data-react-fancy-file-browser-path-segment": "", "aria-current": "location", className: ACTIVE_SEGMENT_CLASS, children: "/" }) : /* @__PURE__ */ jsx(
|
|
13296
|
+
"button",
|
|
13297
|
+
{
|
|
13298
|
+
type: "button",
|
|
13299
|
+
"data-react-fancy-file-browser-path-segment": "",
|
|
13300
|
+
onClick: () => navigate("/"),
|
|
13301
|
+
className: SEGMENT_CLASS,
|
|
13302
|
+
children: "/"
|
|
13303
|
+
}
|
|
13304
|
+
),
|
|
13305
|
+
segments.map((segment, i) => {
|
|
13306
|
+
const last = i === segments.length - 1;
|
|
13307
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
13308
|
+
i > 0 && /* @__PURE__ */ jsx(ChevronRight, { size: 12, "aria-hidden": "true", className: "shrink-0 text-zinc-400" }),
|
|
13309
|
+
last ? /* @__PURE__ */ jsx(
|
|
13310
|
+
"span",
|
|
13311
|
+
{
|
|
13312
|
+
"data-react-fancy-file-browser-path-segment": "",
|
|
13313
|
+
"aria-current": "location",
|
|
13314
|
+
className: ACTIVE_SEGMENT_CLASS,
|
|
13315
|
+
children: segment.label
|
|
13316
|
+
}
|
|
13317
|
+
) : /* @__PURE__ */ jsx(
|
|
13318
|
+
"button",
|
|
13319
|
+
{
|
|
13320
|
+
type: "button",
|
|
13321
|
+
"data-react-fancy-file-browser-path-segment": "",
|
|
13322
|
+
onClick: () => navigate(segment.path),
|
|
13323
|
+
className: SEGMENT_CLASS,
|
|
13324
|
+
children: segment.label
|
|
13325
|
+
}
|
|
13326
|
+
)
|
|
13327
|
+
] }, segment.path);
|
|
13328
|
+
})
|
|
13329
|
+
]
|
|
13330
|
+
}
|
|
13331
|
+
),
|
|
13332
|
+
editable && !editing && /* @__PURE__ */ jsx(
|
|
13333
|
+
"button",
|
|
13334
|
+
{
|
|
13335
|
+
type: "button",
|
|
13336
|
+
"aria-label": "Edit path",
|
|
13337
|
+
onClick: startEdit,
|
|
13338
|
+
className: "shrink-0 rounded-md p-1 text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-300",
|
|
13339
|
+
children: /* @__PURE__ */ jsx(Pencil, { size: 13 })
|
|
13340
|
+
}
|
|
13341
|
+
)
|
|
13342
|
+
]
|
|
13343
|
+
}
|
|
13344
|
+
);
|
|
13345
|
+
}
|
|
13346
|
+
FileBrowserPathBar.displayName = "FileBrowserPathBar";
|
|
13347
|
+
var SORT_FIELDS = [
|
|
13348
|
+
{ field: "name", label: "Name" },
|
|
13349
|
+
{ field: "size", label: "Size" },
|
|
13350
|
+
{ field: "mtime", label: "Modified" }
|
|
13351
|
+
];
|
|
13352
|
+
function FileBrowserToolbar({ filterPlaceholder = "Filter", className }) {
|
|
13353
|
+
const { filter, setFilter, sort, setSort } = useFileBrowser();
|
|
13354
|
+
return /* @__PURE__ */ jsxs(
|
|
13355
|
+
"div",
|
|
13356
|
+
{
|
|
13357
|
+
"data-react-fancy-file-browser-toolbar": "",
|
|
13358
|
+
className: cn(
|
|
13359
|
+
"flex items-center gap-2 border-b border-zinc-200 px-2 py-1.5 dark:border-zinc-700",
|
|
13360
|
+
className
|
|
13361
|
+
),
|
|
13362
|
+
children: [
|
|
13363
|
+
/* @__PURE__ */ jsxs(
|
|
13364
|
+
"div",
|
|
13365
|
+
{
|
|
13366
|
+
"data-react-fancy-file-browser-filter": "",
|
|
13367
|
+
className: "flex min-w-0 flex-1 items-center gap-1.5 rounded-md border border-zinc-200 px-2 dark:border-zinc-700",
|
|
13368
|
+
children: [
|
|
13369
|
+
/* @__PURE__ */ jsx(Search, { size: 13, "aria-hidden": "true", className: "shrink-0 text-zinc-400" }),
|
|
13370
|
+
/* @__PURE__ */ jsx(
|
|
13371
|
+
"input",
|
|
13372
|
+
{
|
|
13373
|
+
type: "text",
|
|
13374
|
+
value: filter,
|
|
13375
|
+
onChange: (e) => setFilter(e.target.value),
|
|
13376
|
+
placeholder: filterPlaceholder,
|
|
13377
|
+
"aria-label": "Filter by name",
|
|
13378
|
+
spellCheck: false,
|
|
13379
|
+
className: "min-w-0 flex-1 bg-transparent py-1 text-xs text-zinc-700 outline-none placeholder:text-zinc-400 dark:text-zinc-300"
|
|
13380
|
+
}
|
|
13381
|
+
),
|
|
13382
|
+
filter !== "" && /* @__PURE__ */ jsx(
|
|
13383
|
+
"button",
|
|
13384
|
+
{
|
|
13385
|
+
type: "button",
|
|
13386
|
+
"aria-label": "Clear filter",
|
|
13387
|
+
onClick: () => setFilter(""),
|
|
13388
|
+
className: "shrink-0 text-zinc-400 transition-colors hover:text-zinc-600 dark:hover:text-zinc-300",
|
|
13389
|
+
children: /* @__PURE__ */ jsx(X, { size: 12 })
|
|
13390
|
+
}
|
|
13391
|
+
)
|
|
13392
|
+
]
|
|
13393
|
+
}
|
|
13394
|
+
),
|
|
13395
|
+
/* @__PURE__ */ jsx(
|
|
13396
|
+
"div",
|
|
13397
|
+
{
|
|
13398
|
+
"data-react-fancy-file-browser-sort": "",
|
|
13399
|
+
role: "group",
|
|
13400
|
+
"aria-label": "Sort",
|
|
13401
|
+
className: "flex shrink-0 items-center gap-0.5",
|
|
13402
|
+
children: SORT_FIELDS.map(({ field, label }) => {
|
|
13403
|
+
const active = sort.by === field;
|
|
13404
|
+
return /* @__PURE__ */ jsxs(
|
|
13405
|
+
"button",
|
|
13406
|
+
{
|
|
13407
|
+
type: "button",
|
|
13408
|
+
"data-sort-field": field,
|
|
13409
|
+
"aria-pressed": active,
|
|
13410
|
+
onClick: () => setSort(
|
|
13411
|
+
active ? { by: field, direction: sort.direction === "asc" ? "desc" : "asc" } : { by: field, direction: "asc" }
|
|
13412
|
+
),
|
|
13413
|
+
className: cn(
|
|
13414
|
+
"flex items-center gap-0.5 rounded-md px-1.5 py-0.5 text-xs transition-colors",
|
|
13415
|
+
active ? "bg-zinc-100 font-medium text-zinc-900 dark:bg-zinc-800 dark:text-white" : "text-zinc-500 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-300"
|
|
13416
|
+
),
|
|
13417
|
+
children: [
|
|
13418
|
+
label,
|
|
13419
|
+
active && (sort.direction === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { size: 11, "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ArrowDown, { size: 11, "aria-hidden": "true" }))
|
|
13420
|
+
]
|
|
13421
|
+
},
|
|
13422
|
+
field
|
|
13423
|
+
);
|
|
13424
|
+
})
|
|
13425
|
+
}
|
|
13426
|
+
)
|
|
13427
|
+
]
|
|
13428
|
+
}
|
|
13429
|
+
);
|
|
13430
|
+
}
|
|
13431
|
+
FileBrowserToolbar.displayName = "FileBrowserToolbar";
|
|
13432
|
+
var EXT_COLORS2 = {
|
|
13433
|
+
ts: "#3178c6",
|
|
13434
|
+
tsx: "#3178c6",
|
|
13435
|
+
js: "#f7df1e",
|
|
13436
|
+
jsx: "#f7df1e",
|
|
13437
|
+
php: "#777bb4",
|
|
13438
|
+
html: "#e34c26",
|
|
13439
|
+
htm: "#e34c26",
|
|
13440
|
+
css: "#264de4",
|
|
13441
|
+
json: "#a1a1aa",
|
|
13442
|
+
md: "#71717a",
|
|
13443
|
+
yaml: "#cb171e",
|
|
13444
|
+
yml: "#cb171e"
|
|
13445
|
+
};
|
|
13446
|
+
function FileIcon2({ ext }) {
|
|
13447
|
+
const color = ext && EXT_COLORS2[ext.toLowerCase()] || "#71717a";
|
|
13448
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", className: "shrink-0", children: [
|
|
13449
|
+
/* @__PURE__ */ jsx("path", { d: "M4 1h5.5L13 4.5V14a1 1 0 01-1 1H4a1 1 0 01-1-1V2a1 1 0 011-1z", stroke: color, strokeWidth: "1.2" }),
|
|
13450
|
+
/* @__PURE__ */ jsx("path", { d: "M9 1v4h4", stroke: color, strokeWidth: "1.2" })
|
|
13451
|
+
] });
|
|
13452
|
+
}
|
|
13453
|
+
function FolderIcon2({ open }) {
|
|
13454
|
+
if (open) {
|
|
13455
|
+
return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", className: "shrink-0", children: [
|
|
13456
|
+
/* @__PURE__ */ jsx("path", { d: "M1.5 3.5a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1V5H2.5V3.5z", fill: "#fbbf24" }),
|
|
13457
|
+
/* @__PURE__ */ jsx("path", { d: "M1 6h13l-1.5 7.5H2.5L1 6z", fill: "#fbbf24", opacity: "0.7" })
|
|
13458
|
+
] });
|
|
13459
|
+
}
|
|
13460
|
+
return /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", className: "shrink-0", children: /* @__PURE__ */ jsx("path", { d: "M1.5 3a1 1 0 011-1h3l1.5 1.5H13a1 1 0 011 1v8a1 1 0 01-1 1H2.5a1 1 0 01-1-1V3z", fill: "#fbbf24" }) });
|
|
13461
|
+
}
|
|
13462
|
+
function FileBrowserStatusRow({ depth, kind, message, onRetry }) {
|
|
13463
|
+
const { indentSize } = useFileBrowser();
|
|
13464
|
+
const paddingLeft = depth * indentSize + 4 + 18;
|
|
13465
|
+
if (kind === "error") {
|
|
13466
|
+
return /* @__PURE__ */ jsxs(
|
|
13467
|
+
"div",
|
|
13468
|
+
{
|
|
13469
|
+
role: "none",
|
|
13470
|
+
"data-react-fancy-file-browser-status": "error",
|
|
13471
|
+
className: "flex items-center gap-1.5 py-0.5 pr-2 text-xs text-red-600 dark:text-red-400",
|
|
13472
|
+
style: { paddingLeft },
|
|
13473
|
+
children: [
|
|
13474
|
+
/* @__PURE__ */ jsx(CircleAlert, { size: 12, className: "shrink-0" }),
|
|
13475
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: message || "Failed to load" }),
|
|
13476
|
+
onRetry && /* @__PURE__ */ jsxs(
|
|
13477
|
+
"button",
|
|
13478
|
+
{
|
|
13479
|
+
type: "button",
|
|
13480
|
+
onClick: onRetry,
|
|
13481
|
+
className: "flex shrink-0 items-center gap-1 rounded-md px-1.5 py-0.5 text-[11px] text-zinc-500 transition-colors hover:bg-zinc-100 hover:text-zinc-700 dark:text-zinc-400 dark:hover:bg-zinc-800 dark:hover:text-zinc-200",
|
|
13482
|
+
children: [
|
|
13483
|
+
/* @__PURE__ */ jsx(RotateCw, { size: 11 }),
|
|
13484
|
+
"Retry"
|
|
13485
|
+
]
|
|
13486
|
+
}
|
|
13487
|
+
)
|
|
13488
|
+
]
|
|
13489
|
+
}
|
|
13490
|
+
);
|
|
13491
|
+
}
|
|
13492
|
+
return /* @__PURE__ */ jsxs(
|
|
13493
|
+
"div",
|
|
13494
|
+
{
|
|
13495
|
+
role: "none",
|
|
13496
|
+
"data-react-fancy-file-browser-status": kind,
|
|
13497
|
+
className: "flex items-center gap-1.5 py-0.5 text-xs text-zinc-400 italic dark:text-zinc-500",
|
|
13498
|
+
style: { paddingLeft },
|
|
13499
|
+
children: [
|
|
13500
|
+
kind === "loading" && /* @__PURE__ */ jsx(Loader2, { size: 12, className: "shrink-0 animate-spin", "aria-hidden": "true" }),
|
|
13501
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", children: kind === "loading" ? "Loading\u2026" : kind === "empty" ? "Empty" : kind === "no-matches" ? "No matches" : "No entries" })
|
|
13502
|
+
]
|
|
13503
|
+
}
|
|
13504
|
+
);
|
|
13505
|
+
}
|
|
13506
|
+
FileBrowserStatusRow.displayName = "FileBrowserStatusRow";
|
|
13507
|
+
function FileBrowserNode({ entry, depth }) {
|
|
13508
|
+
const {
|
|
13509
|
+
entriesFor,
|
|
13510
|
+
visibleChildrenFor,
|
|
13511
|
+
statusFor,
|
|
13512
|
+
errorFor,
|
|
13513
|
+
loadPath,
|
|
13514
|
+
hasProvider,
|
|
13515
|
+
expandedPaths,
|
|
13516
|
+
toggleExpanded,
|
|
13517
|
+
navigate,
|
|
13518
|
+
isSelected,
|
|
13519
|
+
isSelectable,
|
|
13520
|
+
selectEntry,
|
|
13521
|
+
tabFocusPath,
|
|
13522
|
+
setFocusedPath,
|
|
13523
|
+
focusRow,
|
|
13524
|
+
registerRow,
|
|
13525
|
+
indentSize,
|
|
13526
|
+
showIcons
|
|
13527
|
+
} = useFileBrowser();
|
|
13528
|
+
const rawChildren = entriesFor(entry.path);
|
|
13529
|
+
const childrenKnown = rawChildren !== void 0;
|
|
13530
|
+
const expandable = isEntryExpandable(entry, rawChildren, hasProvider);
|
|
13531
|
+
const expanded = expandable && expandedPaths.includes(entry.path);
|
|
13532
|
+
const status = statusFor(entry.path);
|
|
13533
|
+
const selectable = isSelectable(entry);
|
|
13534
|
+
const selected = isSelected(entry.path);
|
|
13535
|
+
const loading = expanded && !childrenKnown && status === "loading";
|
|
13536
|
+
const paddingLeft = depth * indentSize + 4;
|
|
13537
|
+
const ext = entry.name.includes(".") ? entry.name.split(".").pop() : void 0;
|
|
13538
|
+
const handleClick = () => {
|
|
13539
|
+
if (entry.disabled) return;
|
|
13540
|
+
setFocusedPath(entry.path);
|
|
13541
|
+
focusRow(entry.path);
|
|
13542
|
+
if (selectable) selectEntry(entry);
|
|
13543
|
+
if (expandable) toggleExpanded(entry.path);
|
|
13544
|
+
};
|
|
13545
|
+
const handleDoubleClick = () => {
|
|
13546
|
+
if (entry.disabled || entry.kind !== "dir") return;
|
|
13547
|
+
navigate(entry.path);
|
|
13548
|
+
};
|
|
13549
|
+
const handleChevronClick = (e) => {
|
|
13550
|
+
e.stopPropagation();
|
|
13551
|
+
if (!entry.disabled && expandable) toggleExpanded(entry.path);
|
|
13552
|
+
};
|
|
13553
|
+
const visibleChildren = expanded && childrenKnown ? visibleChildrenFor(entry.path) : [];
|
|
13554
|
+
return /* @__PURE__ */ jsxs(
|
|
13555
|
+
"div",
|
|
13556
|
+
{
|
|
13557
|
+
role: "treeitem",
|
|
13558
|
+
"data-react-fancy-file-browser-node": "",
|
|
13559
|
+
"data-path": entry.path,
|
|
13560
|
+
"data-kind": entry.kind,
|
|
13561
|
+
"aria-level": depth + 1,
|
|
13562
|
+
"aria-expanded": expandable ? expanded : void 0,
|
|
13563
|
+
"aria-selected": selectable ? selected : void 0,
|
|
13564
|
+
"aria-disabled": entry.disabled || void 0,
|
|
13565
|
+
"aria-busy": loading || void 0,
|
|
13566
|
+
tabIndex: tabFocusPath === entry.path ? 0 : -1,
|
|
13567
|
+
ref: (el) => registerRow(entry.path, el),
|
|
13568
|
+
onFocus: (e) => {
|
|
13569
|
+
if (e.target === e.currentTarget) setFocusedPath(entry.path);
|
|
13570
|
+
},
|
|
13571
|
+
className: "group outline-none",
|
|
13572
|
+
children: [
|
|
13573
|
+
/* @__PURE__ */ jsxs(
|
|
13574
|
+
"div",
|
|
13575
|
+
{
|
|
13576
|
+
"data-react-fancy-file-browser-row": "",
|
|
13577
|
+
onClick: handleClick,
|
|
13578
|
+
onDoubleClick: handleDoubleClick,
|
|
13579
|
+
className: cn(
|
|
13580
|
+
"flex w-full cursor-pointer items-center gap-1 rounded-md py-0.5 pr-2 text-left text-[13px] transition-colors select-none",
|
|
13581
|
+
selected ? "bg-blue-500/15 text-blue-600 dark:text-blue-400" : "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800",
|
|
13582
|
+
entry.disabled && "pointer-events-none opacity-40",
|
|
13583
|
+
"group-focus-visible:ring-2 group-focus-visible:ring-blue-500/40 group-focus-visible:ring-inset"
|
|
13584
|
+
),
|
|
13585
|
+
style: { paddingLeft },
|
|
13586
|
+
children: [
|
|
13587
|
+
expandable ? loading ? /* @__PURE__ */ jsx(Loader2, { size: 14, className: "shrink-0 animate-spin text-zinc-400", "aria-hidden": "true" }) : /* @__PURE__ */ jsx(
|
|
13588
|
+
"button",
|
|
13589
|
+
{
|
|
13590
|
+
type: "button",
|
|
13591
|
+
tabIndex: -1,
|
|
13592
|
+
"aria-hidden": "true",
|
|
13593
|
+
onClick: handleChevronClick,
|
|
13594
|
+
className: "flex shrink-0 items-center justify-center text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300",
|
|
13595
|
+
children: /* @__PURE__ */ jsx(
|
|
13596
|
+
ChevronRight,
|
|
13597
|
+
{
|
|
13598
|
+
size: 14,
|
|
13599
|
+
className: cn("transition-transform duration-150", expanded && "rotate-90")
|
|
13600
|
+
}
|
|
13601
|
+
)
|
|
13602
|
+
}
|
|
13603
|
+
) : /* @__PURE__ */ jsx("span", { className: "w-3.5 shrink-0" }),
|
|
13604
|
+
showIcons && (entry.kind === "dir" ? /* @__PURE__ */ jsx(FolderIcon2, { open: expanded }) : /* @__PURE__ */ jsx(FileIcon2, { ext })),
|
|
13605
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: entry.name }),
|
|
13606
|
+
entry.kind === "file" && entry.size !== void 0 && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[11px] text-zinc-400 tabular-nums dark:text-zinc-500", children: formatFileSize(entry.size) })
|
|
13607
|
+
]
|
|
13608
|
+
}
|
|
13609
|
+
),
|
|
13610
|
+
expanded && /* @__PURE__ */ jsxs("div", { role: "group", "data-react-fancy-file-browser-node-children": "", children: [
|
|
13611
|
+
!childrenKnown && (status === "loading" || status === "idle") && /* @__PURE__ */ jsx(FileBrowserStatusRow, { depth: depth + 1, kind: "loading" }),
|
|
13612
|
+
!childrenKnown && status === "error" && /* @__PURE__ */ jsx(
|
|
13613
|
+
FileBrowserStatusRow,
|
|
13614
|
+
{
|
|
13615
|
+
depth: depth + 1,
|
|
13616
|
+
kind: "error",
|
|
13617
|
+
message: errorFor(entry.path),
|
|
13618
|
+
onRetry: () => loadPath(entry.path, { reload: true })
|
|
13619
|
+
}
|
|
13620
|
+
),
|
|
13621
|
+
childrenKnown && (visibleChildren.length > 0 ? visibleChildren.map((child) => /* @__PURE__ */ jsx(FileBrowserNode, { entry: child, depth: depth + 1 }, child.path)) : /* @__PURE__ */ jsx(
|
|
13622
|
+
FileBrowserStatusRow,
|
|
13623
|
+
{
|
|
13624
|
+
depth: depth + 1,
|
|
13625
|
+
kind: rawChildren.length === 0 ? "empty" : "no-matches"
|
|
13626
|
+
}
|
|
13627
|
+
))
|
|
13628
|
+
] })
|
|
13629
|
+
]
|
|
13630
|
+
}
|
|
13631
|
+
);
|
|
13632
|
+
}
|
|
13633
|
+
FileBrowserNode.displayName = "FileBrowserNode";
|
|
13634
|
+
function FileBrowserTree({ ariaLabel = "Files", className }) {
|
|
13635
|
+
const ctx = useFileBrowser();
|
|
13636
|
+
const {
|
|
13637
|
+
path,
|
|
13638
|
+
entriesFor,
|
|
13639
|
+
visibleChildrenFor,
|
|
13640
|
+
statusFor,
|
|
13641
|
+
errorFor,
|
|
13642
|
+
loadPath,
|
|
13643
|
+
hasProvider,
|
|
13644
|
+
visibleRows,
|
|
13645
|
+
tabFocusPath,
|
|
13646
|
+
setFocusedPath,
|
|
13647
|
+
focusRow,
|
|
13648
|
+
toggleExpanded,
|
|
13649
|
+
isSelectable,
|
|
13650
|
+
selectEntry,
|
|
13651
|
+
navigate,
|
|
13652
|
+
multiple
|
|
13653
|
+
} = ctx;
|
|
13654
|
+
const rootEntries = entriesFor(path);
|
|
13655
|
+
const rootStatus = statusFor(path);
|
|
13656
|
+
const rootChildren = rootEntries !== void 0 ? visibleChildrenFor(path) : [];
|
|
13657
|
+
const moveTo = (row) => {
|
|
13658
|
+
setFocusedPath(row.entry.path);
|
|
13659
|
+
focusRow(row.entry.path);
|
|
13660
|
+
};
|
|
13661
|
+
const activate = (row) => {
|
|
13662
|
+
if (row.entry.disabled) return;
|
|
13663
|
+
if (isSelectable(row.entry)) selectEntry(row.entry);
|
|
13664
|
+
if (row.expandable) toggleExpanded(row.entry.path);
|
|
13665
|
+
else if (row.entry.kind === "dir" && !isSelectable(row.entry)) navigate(row.entry.path);
|
|
13666
|
+
};
|
|
13667
|
+
const handleKeyDown = (e) => {
|
|
13668
|
+
const rows = visibleRows;
|
|
13669
|
+
if (rows.length === 0) return;
|
|
13670
|
+
const index = rows.findIndex((row) => row.entry.path === tabFocusPath);
|
|
13671
|
+
const current = index >= 0 ? rows[index] : rows[0];
|
|
13672
|
+
switch (e.key) {
|
|
13673
|
+
case "ArrowDown":
|
|
13674
|
+
e.preventDefault();
|
|
13675
|
+
if (index < rows.length - 1) moveTo(rows[index + 1]);
|
|
13676
|
+
break;
|
|
13677
|
+
case "ArrowUp":
|
|
13678
|
+
e.preventDefault();
|
|
13679
|
+
if (index > 0) moveTo(rows[index - 1]);
|
|
13680
|
+
break;
|
|
13681
|
+
case "Home":
|
|
13682
|
+
e.preventDefault();
|
|
13683
|
+
moveTo(rows[0]);
|
|
13684
|
+
break;
|
|
13685
|
+
case "End":
|
|
13686
|
+
e.preventDefault();
|
|
13687
|
+
moveTo(rows[rows.length - 1]);
|
|
13688
|
+
break;
|
|
13689
|
+
case "ArrowRight":
|
|
13690
|
+
e.preventDefault();
|
|
13691
|
+
if (current.entry.disabled) break;
|
|
13692
|
+
if (current.expandable && !current.expanded) {
|
|
13693
|
+
toggleExpanded(current.entry.path);
|
|
13694
|
+
} else if (current.expanded) {
|
|
13695
|
+
const firstChild = rows[index + 1];
|
|
13696
|
+
if (firstChild && firstChild.parentPath === current.entry.path) moveTo(firstChild);
|
|
13697
|
+
}
|
|
13698
|
+
break;
|
|
13699
|
+
case "ArrowLeft":
|
|
13700
|
+
e.preventDefault();
|
|
13701
|
+
if (current.expanded && !current.entry.disabled) {
|
|
13702
|
+
toggleExpanded(current.entry.path);
|
|
13703
|
+
} else if (current.parentPath) {
|
|
13704
|
+
const parent = rows.find((row) => row.entry.path === current.parentPath);
|
|
13705
|
+
if (parent) moveTo(parent);
|
|
13706
|
+
}
|
|
13707
|
+
break;
|
|
13708
|
+
case "Enter":
|
|
13709
|
+
e.preventDefault();
|
|
13710
|
+
activate(current);
|
|
13711
|
+
break;
|
|
13712
|
+
case " ":
|
|
13713
|
+
e.preventDefault();
|
|
13714
|
+
if (!current.entry.disabled) selectEntry(current.entry);
|
|
13715
|
+
break;
|
|
13716
|
+
}
|
|
13717
|
+
};
|
|
13718
|
+
return /* @__PURE__ */ jsxs(
|
|
13719
|
+
"div",
|
|
13720
|
+
{
|
|
13721
|
+
role: "tree",
|
|
13722
|
+
"aria-label": ariaLabel,
|
|
13723
|
+
"aria-multiselectable": multiple || void 0,
|
|
13724
|
+
"data-react-fancy-file-browser-tree": "",
|
|
13725
|
+
className: cn("min-h-0 flex-1 overflow-auto p-1", className),
|
|
13726
|
+
onKeyDown: handleKeyDown,
|
|
13727
|
+
children: [
|
|
13728
|
+
rootEntries === void 0 && hasProvider && (rootStatus === "loading" || rootStatus === "idle") && /* @__PURE__ */ jsx(FileBrowserStatusRow, { depth: 0, kind: "loading" }),
|
|
13729
|
+
rootEntries === void 0 && rootStatus === "error" && /* @__PURE__ */ jsx(
|
|
13730
|
+
FileBrowserStatusRow,
|
|
13731
|
+
{
|
|
13732
|
+
depth: 0,
|
|
13733
|
+
kind: "error",
|
|
13734
|
+
message: errorFor(path),
|
|
13735
|
+
onRetry: () => loadPath(path, { reload: true })
|
|
13736
|
+
}
|
|
13737
|
+
),
|
|
13738
|
+
rootEntries === void 0 && rootStatus === "idle" && !hasProvider && /* @__PURE__ */ jsx(FileBrowserStatusRow, { depth: 0, kind: "unknown" }),
|
|
13739
|
+
rootEntries !== void 0 && (rootChildren.length > 0 ? rootChildren.map((entry) => /* @__PURE__ */ jsx(FileBrowserNode, { entry, depth: 0 }, entry.path)) : /* @__PURE__ */ jsx(FileBrowserStatusRow, { depth: 0, kind: rootEntries.length === 0 ? "empty" : "no-matches" }))
|
|
13740
|
+
]
|
|
13741
|
+
}
|
|
13742
|
+
);
|
|
13743
|
+
}
|
|
13744
|
+
FileBrowserTree.displayName = "FileBrowserTree";
|
|
13745
|
+
var DEFAULT_SORT2 = { by: "name", direction: "asc" };
|
|
13746
|
+
function snapshotNodeToEntry(node) {
|
|
13747
|
+
const { children: _children, ...entry } = node;
|
|
13748
|
+
return entry;
|
|
13749
|
+
}
|
|
13750
|
+
function buildSnapshotMap(snapshot) {
|
|
13751
|
+
const map = /* @__PURE__ */ new Map();
|
|
13752
|
+
if (!snapshot || snapshot.length === 0) return map;
|
|
13753
|
+
for (const root of snapshot) {
|
|
13754
|
+
const parent = parentPath(root.path);
|
|
13755
|
+
const list = map.get(parent);
|
|
13756
|
+
if (list) list.push(snapshotNodeToEntry(root));
|
|
13757
|
+
else map.set(parent, [snapshotNodeToEntry(root)]);
|
|
13758
|
+
}
|
|
13759
|
+
const walk2 = (nodes) => {
|
|
13760
|
+
for (const node of nodes) {
|
|
13761
|
+
if (node.children) {
|
|
13762
|
+
map.set(node.path, node.children.map(snapshotNodeToEntry));
|
|
13763
|
+
walk2(node.children);
|
|
13764
|
+
}
|
|
13765
|
+
}
|
|
13766
|
+
};
|
|
13767
|
+
walk2(snapshot);
|
|
13768
|
+
return map;
|
|
13769
|
+
}
|
|
13770
|
+
function FileBrowserRoot({
|
|
13771
|
+
provider,
|
|
13772
|
+
snapshot,
|
|
13773
|
+
select = "file",
|
|
13774
|
+
multiple = false,
|
|
13775
|
+
value,
|
|
13776
|
+
defaultValue,
|
|
13777
|
+
onChange,
|
|
13778
|
+
path,
|
|
13779
|
+
defaultPath = "/",
|
|
13780
|
+
onPathChange,
|
|
13781
|
+
expandedPaths,
|
|
13782
|
+
defaultExpandedPaths,
|
|
13783
|
+
onExpandedChange,
|
|
13784
|
+
sort,
|
|
13785
|
+
defaultSort,
|
|
13786
|
+
onSortChange,
|
|
13787
|
+
filter,
|
|
13788
|
+
defaultFilter: defaultFilter2,
|
|
13789
|
+
onFilterChange,
|
|
13790
|
+
onError,
|
|
13791
|
+
indentSize = 16,
|
|
13792
|
+
showIcons = true,
|
|
13793
|
+
className,
|
|
13794
|
+
children
|
|
13795
|
+
}) {
|
|
13796
|
+
const [currentPath, setCurrentPath] = useControllableState(path, defaultPath, onPathChange);
|
|
13797
|
+
const [expanded, setExpanded] = useControllableState(
|
|
13798
|
+
expandedPaths,
|
|
13799
|
+
defaultExpandedPaths ?? [],
|
|
13800
|
+
onExpandedChange
|
|
13801
|
+
);
|
|
13802
|
+
const [sortState, setSortState] = useControllableState(sort, defaultSort ?? DEFAULT_SORT2, onSortChange);
|
|
13803
|
+
const [filterState, setFilterState] = useControllableState(filter, defaultFilter2 ?? "", onFilterChange);
|
|
13804
|
+
const [internalValue, setInternalValue] = useState(
|
|
13805
|
+
() => defaultValue ?? (multiple ? [] : null)
|
|
13806
|
+
);
|
|
13807
|
+
const selection = value !== void 0 ? value : internalValue;
|
|
13808
|
+
const selectedPaths = useMemo(
|
|
13809
|
+
() => selection == null ? [] : Array.isArray(selection) ? selection : [selection],
|
|
13810
|
+
[selection]
|
|
13811
|
+
);
|
|
13812
|
+
const snapshotMap = useMemo(() => buildSnapshotMap(snapshot), [snapshot]);
|
|
13813
|
+
const [loadedChildren, setLoadedChildren] = useState({});
|
|
13814
|
+
const [loadStatus, setLoadStatus] = useState({});
|
|
13815
|
+
const [loadErrors, setLoadErrors] = useState({});
|
|
13816
|
+
const seqCounter = useRef(0);
|
|
13817
|
+
const requestSeq = useRef({});
|
|
13818
|
+
const stateRef = useRef({ snapshotMap, loadedChildren, loadStatus });
|
|
13819
|
+
stateRef.current = { snapshotMap, loadedChildren, loadStatus };
|
|
13820
|
+
const providerRef = useRef(provider);
|
|
13821
|
+
providerRef.current = provider;
|
|
13822
|
+
const onErrorRef = useRef(onError);
|
|
13823
|
+
onErrorRef.current = onError;
|
|
13824
|
+
const onChangeRef = useRef(onChange);
|
|
13825
|
+
onChangeRef.current = onChange;
|
|
13826
|
+
const loadPath = useCallback((targetPath, options) => {
|
|
13827
|
+
const prov = providerRef.current;
|
|
13828
|
+
if (!prov) return;
|
|
13829
|
+
const { snapshotMap: snap, loadedChildren: loaded, loadStatus: statuses } = stateRef.current;
|
|
13830
|
+
const status = statuses[targetPath] ?? "idle";
|
|
13831
|
+
if (status === "loading") return;
|
|
13832
|
+
const known = snap.get(targetPath) ?? loaded[targetPath];
|
|
13833
|
+
if (!options?.reload && (known !== void 0 || status === "error")) return;
|
|
13834
|
+
const requestId = ++seqCounter.current;
|
|
13835
|
+
requestSeq.current[targetPath] = requestId;
|
|
13836
|
+
setLoadStatus((prev) => ({ ...prev, [targetPath]: "loading" }));
|
|
13837
|
+
prov.loadChildren(targetPath).then(
|
|
13838
|
+
(entries) => {
|
|
13839
|
+
if (requestSeq.current[targetPath] !== requestId) return;
|
|
13840
|
+
setLoadedChildren((prev) => ({ ...prev, [targetPath]: entries }));
|
|
13841
|
+
setLoadStatus((prev) => ({ ...prev, [targetPath]: "loaded" }));
|
|
13842
|
+
setLoadErrors((prev) => {
|
|
13843
|
+
if (!(targetPath in prev)) return prev;
|
|
13844
|
+
const next = { ...prev };
|
|
13845
|
+
delete next[targetPath];
|
|
13846
|
+
return next;
|
|
13847
|
+
});
|
|
13848
|
+
},
|
|
13849
|
+
(error) => {
|
|
13850
|
+
if (requestSeq.current[targetPath] !== requestId) return;
|
|
13851
|
+
setLoadStatus((prev) => ({ ...prev, [targetPath]: "error" }));
|
|
13852
|
+
setLoadErrors((prev) => ({
|
|
13853
|
+
...prev,
|
|
13854
|
+
[targetPath]: error instanceof Error ? error.message : String(error)
|
|
13855
|
+
}));
|
|
13856
|
+
onErrorRef.current?.(targetPath, error);
|
|
13857
|
+
}
|
|
13858
|
+
);
|
|
13859
|
+
}, []);
|
|
13860
|
+
const entriesFor = useCallback(
|
|
13861
|
+
(p) => snapshotMap.get(p) ?? loadedChildren[p],
|
|
13862
|
+
[snapshotMap, loadedChildren]
|
|
13863
|
+
);
|
|
13864
|
+
const statusFor = useCallback(
|
|
13865
|
+
(p) => {
|
|
13866
|
+
if (snapshotMap.has(p)) return "loaded";
|
|
13867
|
+
const status = loadStatus[p];
|
|
13868
|
+
if (status) return status;
|
|
13869
|
+
return loadedChildren[p] !== void 0 ? "loaded" : "idle";
|
|
13870
|
+
},
|
|
13871
|
+
[snapshotMap, loadStatus, loadedChildren]
|
|
13872
|
+
);
|
|
13873
|
+
const errorFor = useCallback((p) => loadErrors[p], [loadErrors]);
|
|
13874
|
+
useEffect(() => {
|
|
13875
|
+
if (!provider) return;
|
|
13876
|
+
loadPath(currentPath);
|
|
13877
|
+
for (const p of expanded) loadPath(p);
|
|
13878
|
+
}, [provider, currentPath, expanded, snapshotMap, loadPath]);
|
|
13879
|
+
const filterLower = filterState.trim().toLowerCase();
|
|
13880
|
+
const visibleChildrenFor = useCallback(
|
|
13881
|
+
(p) => {
|
|
13882
|
+
const entries = entriesFor(p);
|
|
13883
|
+
if (!entries) return [];
|
|
13884
|
+
const filtered = filterLower ? entries.filter((entry) => entryMatchesFilter(entry, filterLower, entriesFor)) : entries.slice();
|
|
13885
|
+
return filtered.sort((a, b) => compareFileEntries(a, b, sortState));
|
|
13886
|
+
},
|
|
13887
|
+
[entriesFor, filterLower, sortState]
|
|
13888
|
+
);
|
|
13889
|
+
const visibleRows = useMemo(() => {
|
|
13890
|
+
const rows = [];
|
|
13891
|
+
const visited = /* @__PURE__ */ new Set([currentPath]);
|
|
13892
|
+
const walk2 = (p, depth, parent) => {
|
|
13893
|
+
for (const entry of visibleChildrenFor(p)) {
|
|
13894
|
+
const expandable = isEntryExpandable(entry, entriesFor(entry.path), !!provider);
|
|
13895
|
+
const isOpen = expandable && expanded.includes(entry.path);
|
|
13896
|
+
rows.push({ entry, depth, expandable, expanded: isOpen, parentPath: parent });
|
|
13897
|
+
if (isOpen && !visited.has(entry.path)) {
|
|
13898
|
+
visited.add(entry.path);
|
|
13899
|
+
walk2(entry.path, depth + 1, entry.path);
|
|
13900
|
+
}
|
|
13901
|
+
}
|
|
13902
|
+
};
|
|
13903
|
+
walk2(currentPath, 0, null);
|
|
13904
|
+
return rows;
|
|
13905
|
+
}, [visibleChildrenFor, entriesFor, provider, expanded, currentPath]);
|
|
13906
|
+
const entryIndex = useMemo(() => {
|
|
13907
|
+
const map = /* @__PURE__ */ new Map();
|
|
13908
|
+
for (const list of snapshotMap.values()) {
|
|
13909
|
+
for (const entry of list) map.set(entry.path, entry);
|
|
13910
|
+
}
|
|
13911
|
+
for (const list of Object.values(loadedChildren)) {
|
|
13912
|
+
for (const entry of list) {
|
|
13913
|
+
if (!map.has(entry.path)) map.set(entry.path, entry);
|
|
13914
|
+
}
|
|
13915
|
+
}
|
|
13916
|
+
return map;
|
|
13917
|
+
}, [snapshotMap, loadedChildren]);
|
|
13918
|
+
const isSelectable = useCallback(
|
|
13919
|
+
(entry) => !entry.disabled && (select === "both" || (select === "file" ? entry.kind === "file" : entry.kind === "dir")),
|
|
13920
|
+
[select]
|
|
13921
|
+
);
|
|
13922
|
+
const isSelected = useCallback((p) => selectedPaths.includes(p), [selectedPaths]);
|
|
13923
|
+
const commitSelection = useCallback(
|
|
13924
|
+
(paths) => {
|
|
13925
|
+
const nextValue = multiple ? paths : paths[0] ?? null;
|
|
13926
|
+
if (value === void 0) setInternalValue(nextValue);
|
|
13927
|
+
onChangeRef.current?.(
|
|
13928
|
+
nextValue,
|
|
13929
|
+
paths.map((p) => entryIndex.get(p)).filter((entry) => entry !== void 0)
|
|
13930
|
+
);
|
|
13931
|
+
},
|
|
13932
|
+
[multiple, value, entryIndex]
|
|
13933
|
+
);
|
|
13934
|
+
const selectEntry = useCallback(
|
|
13935
|
+
(entry) => {
|
|
13936
|
+
if (!isSelectable(entry)) return;
|
|
13937
|
+
if (multiple) {
|
|
13938
|
+
commitSelection(
|
|
13939
|
+
selectedPaths.includes(entry.path) ? selectedPaths.filter((p) => p !== entry.path) : [...selectedPaths, entry.path]
|
|
13940
|
+
);
|
|
13941
|
+
} else {
|
|
13942
|
+
commitSelection([entry.path]);
|
|
13943
|
+
}
|
|
13944
|
+
},
|
|
13945
|
+
[isSelectable, multiple, selectedPaths, commitSelection]
|
|
13946
|
+
);
|
|
13947
|
+
const toggleExpanded = useCallback(
|
|
13948
|
+
(p) => {
|
|
13949
|
+
setExpanded((prev) => prev.includes(p) ? prev.filter((x) => x !== p) : [...prev, p]);
|
|
13950
|
+
},
|
|
13951
|
+
[setExpanded]
|
|
13952
|
+
);
|
|
13953
|
+
const [focusedPath, setFocusedPath] = useState(null);
|
|
13954
|
+
const navigate = useCallback(
|
|
13955
|
+
(p) => {
|
|
13956
|
+
setCurrentPath(p);
|
|
13957
|
+
setFilterState("");
|
|
13958
|
+
setFocusedPath(null);
|
|
13959
|
+
},
|
|
13960
|
+
[setCurrentPath, setFilterState]
|
|
13961
|
+
);
|
|
13962
|
+
const rowRefs = useRef(/* @__PURE__ */ new Map());
|
|
13963
|
+
const registerRow = useCallback((p, el) => {
|
|
13964
|
+
if (el) rowRefs.current.set(p, el);
|
|
13965
|
+
else rowRefs.current.delete(p);
|
|
13966
|
+
}, []);
|
|
13967
|
+
const focusRow = useCallback((p) => {
|
|
13968
|
+
rowRefs.current.get(p)?.focus();
|
|
13969
|
+
}, []);
|
|
13970
|
+
const tabFocusPath = useMemo(() => {
|
|
13971
|
+
if (focusedPath && visibleRows.some((row) => row.entry.path === focusedPath)) {
|
|
13972
|
+
return focusedPath;
|
|
13973
|
+
}
|
|
13974
|
+
return visibleRows[0]?.entry.path ?? null;
|
|
13975
|
+
}, [focusedPath, visibleRows]);
|
|
13976
|
+
const ctx = useMemo(
|
|
13977
|
+
() => ({
|
|
13978
|
+
entriesFor,
|
|
13979
|
+
visibleChildrenFor,
|
|
13980
|
+
statusFor,
|
|
13981
|
+
errorFor,
|
|
13982
|
+
loadPath,
|
|
13983
|
+
hasProvider: !!provider,
|
|
13984
|
+
path: currentPath,
|
|
13985
|
+
navigate,
|
|
13986
|
+
expandedPaths: expanded,
|
|
13987
|
+
toggleExpanded,
|
|
13988
|
+
select,
|
|
13989
|
+
multiple,
|
|
13990
|
+
selectedPaths,
|
|
13991
|
+
isSelected,
|
|
13992
|
+
isSelectable,
|
|
13993
|
+
selectEntry,
|
|
13994
|
+
sort: sortState,
|
|
13995
|
+
setSort: setSortState,
|
|
13996
|
+
filter: filterState,
|
|
13997
|
+
setFilter: setFilterState,
|
|
13998
|
+
visibleRows,
|
|
13999
|
+
focusedPath,
|
|
14000
|
+
setFocusedPath,
|
|
14001
|
+
tabFocusPath,
|
|
14002
|
+
focusRow,
|
|
14003
|
+
registerRow,
|
|
14004
|
+
indentSize,
|
|
14005
|
+
showIcons
|
|
14006
|
+
}),
|
|
14007
|
+
[
|
|
14008
|
+
entriesFor,
|
|
14009
|
+
visibleChildrenFor,
|
|
14010
|
+
statusFor,
|
|
14011
|
+
errorFor,
|
|
14012
|
+
loadPath,
|
|
14013
|
+
provider,
|
|
14014
|
+
currentPath,
|
|
14015
|
+
navigate,
|
|
14016
|
+
expanded,
|
|
14017
|
+
toggleExpanded,
|
|
14018
|
+
select,
|
|
14019
|
+
multiple,
|
|
14020
|
+
selectedPaths,
|
|
14021
|
+
isSelected,
|
|
14022
|
+
isSelectable,
|
|
14023
|
+
selectEntry,
|
|
14024
|
+
sortState,
|
|
14025
|
+
setSortState,
|
|
14026
|
+
filterState,
|
|
14027
|
+
setFilterState,
|
|
14028
|
+
visibleRows,
|
|
14029
|
+
focusedPath,
|
|
14030
|
+
tabFocusPath,
|
|
14031
|
+
focusRow,
|
|
14032
|
+
registerRow,
|
|
14033
|
+
indentSize,
|
|
14034
|
+
showIcons
|
|
14035
|
+
]
|
|
14036
|
+
);
|
|
14037
|
+
return /* @__PURE__ */ jsx(FileBrowserContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
14038
|
+
"div",
|
|
14039
|
+
{
|
|
14040
|
+
"data-react-fancy-file-browser": "",
|
|
14041
|
+
className: cn(
|
|
14042
|
+
"flex flex-col overflow-hidden rounded-lg border border-zinc-200 bg-white text-sm dark:border-zinc-700 dark:bg-zinc-900",
|
|
14043
|
+
className
|
|
14044
|
+
),
|
|
14045
|
+
children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
14046
|
+
/* @__PURE__ */ jsx(FileBrowserPathBar, {}),
|
|
14047
|
+
/* @__PURE__ */ jsx(FileBrowserToolbar, {}),
|
|
14048
|
+
/* @__PURE__ */ jsx(FileBrowserTree, {})
|
|
14049
|
+
] })
|
|
14050
|
+
}
|
|
14051
|
+
) });
|
|
14052
|
+
}
|
|
14053
|
+
FileBrowserRoot.displayName = "FileBrowser";
|
|
14054
|
+
var FileBrowser = Object.assign(FileBrowserRoot, {
|
|
14055
|
+
PathBar: FileBrowserPathBar,
|
|
14056
|
+
Toolbar: FileBrowserToolbar,
|
|
14057
|
+
Tree: FileBrowserTree,
|
|
14058
|
+
Node: FileBrowserNode
|
|
14059
|
+
});
|
|
13143
14060
|
|
|
13144
14061
|
// src/utils/media-type.ts
|
|
13145
14062
|
var IMAGE_EXTS = /* @__PURE__ */ new Set([
|
|
@@ -14955,6 +15872,6 @@ var MediaViewer = forwardRef(
|
|
|
14955
15872
|
);
|
|
14956
15873
|
MediaViewer.displayName = "MediaViewer";
|
|
14957
15874
|
|
|
14958
|
-
export { Accordion, AccordionPanel, AccordionPanelContent, AccordionPanelSection, AccordionPanelTrigger, Action, AudioViewer, Autocomplete, Avatar, Badge, Brand, Breadcrumbs, Button, Calendar, Callout, Card, Carousel, Chart, ChatDrawer, Checkbox, CheckboxGroup, ColorPicker, Command, Composer, ContentRenderer, ContextMenu, DatePicker, DisplayValue, Dropdown, EMOJI_CATEGORY_ORDER, EMOJI_DATA, EMOJI_ENTRIES, Editor, Emoji, EmojiSelect, FauxClient, Field, FieldModeContext, FileUpload, Form, FormProvider, Heading, Icon, ImageViewer, Input, InputTag, Kanban, MagicWand, Marquee, MediaViewer, Menu2 as Menu, MobileMenu, Modal, MoodMeter, MultiSwitch, Navbar, OtpInput, Pagination, PdfViewer, Pillbox, Popover, Portal, Profile, Progress, PromptInput, RadioGroup, ReasonTag, SKIN_TONES, Select, Separator, Sidebar, Skeleton, Slider, StickyNote, Switch, Table, Tabs, Text, Textarea, TimeGrid, TimePicker, Timeline, Toast, Tooltip, TreeNav, VideoViewer, applyTone, cn, configureIcons, contentEditableAdapter, controlledAdapter, find, hasSkinTones, inputAdapter, registerExtension, registerExtensions, registerIconAddendum, registerIconSet, registerIcons, resolve, resolveMediaType, sanitizeHref, sanitizeHtml, search, skinTones, textareaAdapter, useAccordion, useAccordionPanel, useAccordionSection, useAnimation, useCarousel, useCommand, useContextMenu, useControllableState, useDropdown, useEditor, useEscapeKey, useFieldMode, useFileUpload, useFloatingPosition, useFocusTrap, useId12 as useId, useKanban, useMenu, useMobileMenu, useModal, useNavbar, useNodeRegistry, useOutsideClick, usePanZoom, usePopover, useSidebar, useTabs, useToast, useTreeNav };
|
|
15875
|
+
export { Accordion, AccordionPanel, AccordionPanelContent, AccordionPanelSection, AccordionPanelTrigger, Action, AudioViewer, Autocomplete, Avatar, Badge, Brand, Breadcrumbs, Button, Calendar, Callout, Card, Carousel, Chart, ChatDrawer, Checkbox, CheckboxGroup, ColorPicker, Command, Composer, ContentRenderer, ContextMenu, DatePicker, DisplayValue, Dropdown, EMOJI_CATEGORY_ORDER, EMOJI_DATA, EMOJI_ENTRIES, Editor, Emoji, EmojiSelect, FauxClient, Field, FieldModeContext, FileBrowser, FileUpload, Form, FormProvider, Heading, Icon, ImageViewer, Input, InputTag, Kanban, MagicWand, Marquee, MediaViewer, Menu2 as Menu, MobileMenu, Modal, MoodMeter, MultiSwitch, Navbar, OtpInput, Pagination, PdfViewer, Pillbox, Popover, Portal, Profile, Progress, PromptInput, RadioGroup, ReasonTag, SKIN_TONES, Select, Separator, Sidebar, Skeleton, Slider, StickyNote, Switch, Table, Tabs, Text, Textarea, TimeGrid, TimePicker, Timeline, Toast, Tooltip, TreeNav, VideoViewer, applyTone, cn, configureIcons, contentEditableAdapter, controlledAdapter, find, hasSkinTones, inputAdapter, registerExtension, registerExtensions, registerIconAddendum, registerIconSet, registerIcons, resolve, resolveMediaType, sanitizeHref, sanitizeHtml, search, skinTones, textareaAdapter, useAccordion, useAccordionPanel, useAccordionSection, useAnimation, useCarousel, useCommand, useContextMenu, useControllableState, useDropdown, useEditor, useEscapeKey, useFieldMode, useFileBrowser, useFileUpload, useFloatingPosition, useFocusTrap, useId12 as useId, useKanban, useMenu, useMobileMenu, useModal, useNavbar, useNodeRegistry, useOutsideClick, usePanZoom, usePopover, useSidebar, useTabs, useToast, useTreeNav };
|
|
14959
15876
|
//# sourceMappingURL=index.js.map
|
|
14960
15877
|
//# sourceMappingURL=index.js.map
|