vitepress-plugin-filelist 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ interface FileListLabels {
6
6
  filter: string;
7
7
  refresh: string;
8
8
  batch: string;
9
+ /** @deprecated No longer shown; the selection count lives in the toolbar. */
9
10
  batchCount: string;
10
11
  batching: string;
11
12
  passwordPrompt: string;
@@ -33,6 +34,23 @@ interface FileListLabels {
33
34
  previewFailed: string;
34
35
  batchFailed: string;
35
36
  defaultZip: string;
37
+ /** Text button that clears the selection (wide layout). */
38
+ clear: string;
39
+ /** Accessible name of the clear-selection button. */
40
+ clearSelection: string;
41
+ /** `{size}` total, used for the selection and the footer. */
42
+ totalSize: string;
43
+ /** `{count}` files inside a loaded folder. */
44
+ folderItems: string;
45
+ /** Label of the compact sort select shown in narrow containers. */
46
+ sortBy: string;
47
+ selectAll: string;
48
+ /** Accessible name of a row checkbox, `{name}`. */
49
+ selectItem: string;
50
+ /** Accessible name of the expand button, `{name}`. */
51
+ expand: string;
52
+ /** Accessible name of the collapse button, `{name}`. */
53
+ collapse: string;
36
54
  }
37
55
  interface FileListSource {
38
56
  /** OpenList path, directory or single file. */
@@ -186,6 +204,13 @@ export declare function uniqueEntryName(name: string, used: Set<string>): string
186
204
  export declare function formatSize(size: number): string;
187
205
  export declare function formatDate(iso: string): string;
188
206
  export declare function extOf(name: string): string;
207
+ export type FileKind = 'doc' | 'sheet' | 'slide' | 'pdf' | 'image' | 'media' | 'archive' | 'code' | 'other';
208
+ /** Rough file family, used to colour the type badge. */
209
+ export declare function fileKind(name: string): FileKind;
210
+ /** Sum of the `size` of the given entries, ignoring missing or invalid sizes. */
211
+ export declare function totalSize(items: Array<{
212
+ size?: number;
213
+ }>): number;
189
214
  export declare function compareNodes(a: SortableNode, b: SortableNode, key: SortKey, ascending?: boolean): number;
190
215
  /**
191
216
  * Register `<FileList>` and `<VPFileList>` globally.
@@ -1,5 +1,5 @@
1
- import { computed, defineComponent, mergeProps, onMounted, ref, unref, useSSRContext } from "vue";
2
- import { ssrIncludeBooleanAttr, ssrInterpolate, ssrRenderAttr, ssrRenderAttrs, ssrRenderClass, ssrRenderList, ssrRenderStyle } from "vue/server-renderer";
1
+ import { computed, defineComponent, mergeProps, onBeforeUnmount, onMounted, ref, unref, useSSRContext } from "vue";
2
+ import { ssrIncludeBooleanAttr, ssrInterpolate, ssrLooseContain, ssrLooseEqual, ssrRenderAttr, ssrRenderAttrs, ssrRenderClass, ssrRenderList, ssrRenderStyle } from "vue/server-renderer";
3
3
  // src/client/locales.ts
4
4
  const fileListLocales = {
5
5
  "zh-CN": {
@@ -7,7 +7,7 @@ const fileListLocales = {
7
7
  demoBadge: "演示数据",
8
8
  filter: "筛选文件名",
9
9
  refresh: "刷新",
10
- batch: "批量下载",
10
+ batch: "下载 ZIP",
11
11
  batchCount: "批量下载({count})",
12
12
  batching: "打包中 {percent}%",
13
13
  passwordPrompt: "该目录受密码保护:",
@@ -34,7 +34,16 @@ const fileListLocales = {
34
34
  downloadFailed: "下载失败:{message}",
35
35
  previewFailed: "预览失败:{message}",
36
36
  batchFailed: "打包失败:{message}",
37
- defaultZip: "资源"
37
+ defaultZip: "资源",
38
+ clear: "清除",
39
+ clearSelection: "取消选择",
40
+ totalSize: "共 {size}",
41
+ folderItems: "{count} 项",
42
+ sortBy: "排序",
43
+ selectAll: "全选",
44
+ selectItem: "选择 {name}",
45
+ expand: "展开 {name}",
46
+ collapse: "收起 {name}"
38
47
  },
39
48
  "en-US": {
40
49
  allFiles: "All files",
@@ -68,7 +77,16 @@ const fileListLocales = {
68
77
  downloadFailed: "Download failed: {message}",
69
78
  previewFailed: "Preview failed: {message}",
70
79
  batchFailed: "Failed to create ZIP: {message}",
71
- defaultZip: "resources"
80
+ defaultZip: "resources",
81
+ clear: "Clear",
82
+ clearSelection: "Clear selection",
83
+ totalSize: "{size} total",
84
+ folderItems: "{count} items",
85
+ sortBy: "Sort",
86
+ selectAll: "Select all",
87
+ selectItem: "Select {name}",
88
+ expand: "Expand {name}",
89
+ collapse: "Collapse {name}"
72
90
  }
73
91
  };
74
92
  function formatLabel(template, values = {}) {
@@ -151,6 +169,97 @@ function extOf(name) {
151
169
  const match = name.match(/\.([a-z0-9]+)$/i);
152
170
  return match ? match[1].toUpperCase() : "";
153
171
  }
172
+ const FILE_KINDS = {};
173
+ for (const [kind, exts] of Object.entries({
174
+ doc: [
175
+ "doc",
176
+ "docx",
177
+ "odt",
178
+ "rtf",
179
+ "txt",
180
+ "md",
181
+ "pages",
182
+ "wps"
183
+ ],
184
+ sheet: [
185
+ "xls",
186
+ "xlsx",
187
+ "ods",
188
+ "csv",
189
+ "tsv",
190
+ "numbers",
191
+ "et"
192
+ ],
193
+ slide: [
194
+ "ppt",
195
+ "pptx",
196
+ "odp",
197
+ "key",
198
+ "dps"
199
+ ],
200
+ pdf: ["pdf"],
201
+ image: [
202
+ "png",
203
+ "jpg",
204
+ "jpeg",
205
+ "gif",
206
+ "webp",
207
+ "svg",
208
+ "bmp",
209
+ "ico",
210
+ "avif",
211
+ "heic"
212
+ ],
213
+ media: [
214
+ "mp3",
215
+ "wav",
216
+ "flac",
217
+ "aac",
218
+ "ogg",
219
+ "m4a",
220
+ "mp4",
221
+ "webm",
222
+ "mov",
223
+ "mkv",
224
+ "avi"
225
+ ],
226
+ archive: [
227
+ "zip",
228
+ "rar",
229
+ "7z",
230
+ "tar",
231
+ "gz",
232
+ "tgz",
233
+ "bz2",
234
+ "xz",
235
+ "iso"
236
+ ],
237
+ code: [
238
+ "js",
239
+ "ts",
240
+ "json",
241
+ "html",
242
+ "css",
243
+ "py",
244
+ "java",
245
+ "c",
246
+ "cpp",
247
+ "go",
248
+ "rs",
249
+ "sh",
250
+ "yml",
251
+ "yaml",
252
+ "xml"
253
+ ]
254
+ })) for (const ext of exts) FILE_KINDS[ext] = kind;
255
+ /** Rough file family, used to colour the type badge. */
256
+ function fileKind(name) {
257
+ return FILE_KINDS[extOf(name).toLowerCase()] ?? "other";
258
+ }
259
+ /** Sum of the `size` of the given entries, ignoring missing or invalid sizes. */
260
+ function totalSize(items) {
261
+ return items.reduce((sum, item) => sum + (Number.isFinite(item.size) && (item.size ?? 0) > 0 ? item.size : 0), 0);
262
+ }
154
263
  function compareNodes(a, b, key, ascending = true) {
155
264
  if (a.is_dir !== b.is_dir) return a.is_dir ? -1 : 1;
156
265
  let result = 0;
@@ -415,6 +524,11 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
415
524
  zipPath: [...zipParent, item.name]
416
525
  }));
417
526
  }
527
+ function resortAll() {
528
+ if (props.sources.length) {
529
+ for (const root of roots.value) if (root.children.length) sortTree(root.children);
530
+ } else sortTree(roots.value);
531
+ }
418
532
  function sourceName(path) {
419
533
  const parts = path.split("/").filter(Boolean);
420
534
  return parts[parts.length - 1] || path;
@@ -531,6 +645,36 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
531
645
  const allFiles = computed(() => roots.value.flatMap(descendantFiles));
532
646
  const allChecked = computed(() => allFiles.value.length > 0 && allFiles.value.every((file) => selected.value.has(file.path)));
533
647
  const someChecked = computed(() => !allChecked.value && allFiles.value.some((file) => selected.value.has(file.path)));
648
+ const selectedFiles = computed(() => allFiles.value.filter((file) => selected.value.has(file.path)));
649
+ const selectedBytes = computed(() => totalSize(selectedFiles.value));
650
+ const loadedBytes = computed(() => totalSize(allFiles.value));
651
+ function folderFiles(node) {
652
+ return node.is_dir && node.loaded ? descendantFiles(node) : [];
653
+ }
654
+ /** Compact sort control for narrow containers, where header cells are hidden. */
655
+ const sortOptions = computed(() => [
656
+ "name",
657
+ "size",
658
+ "modified"
659
+ ].flatMap((key) => {
660
+ const label = t(key === "name" ? "columnName" : key === "size" ? "columnSize" : "columnDate");
661
+ return [{
662
+ value: `${key}:asc`,
663
+ label: `${label} ↑`
664
+ }, {
665
+ value: `${key}:desc`,
666
+ label: `${label} ↓`
667
+ }];
668
+ }));
669
+ const sortValue = computed({
670
+ get: () => `${sortKey.value}:${sortAsc.value ? "asc" : "desc"}`,
671
+ set: (value) => {
672
+ const [key, direction] = value.split(":");
673
+ sortKey.value = key;
674
+ sortAsc.value = direction !== "desc";
675
+ resortAll();
676
+ }
677
+ });
534
678
  const unloadedFolders = computed(() => {
535
679
  let count = 0;
536
680
  const walk = (nodes) => {
@@ -547,18 +691,52 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
547
691
  return !node.is_dir && previewable.test(node.name);
548
692
  }
549
693
  const displayTitle = computed(() => props.title || siteTitle.value || (props.path === "/" ? t("allFiles") : props.path));
694
+ const stickyTop = ref(0);
695
+ function measureStickyTop() {
696
+ if (typeof document === "undefined") return;
697
+ let top = 0;
698
+ for (const el of document.querySelectorAll(".VPNav, .VPLocalNav")) {
699
+ const style = getComputedStyle(el);
700
+ if (style.display === "none" || style.position !== "fixed" && style.position !== "sticky") continue;
701
+ top = Math.max(top, (Number.parseFloat(style.top) || 0) + el.offsetHeight);
702
+ }
703
+ stickyTop.value = top;
704
+ }
550
705
  onMounted(() => {
551
706
  loadRoot();
552
707
  loadSiteTitle();
708
+ measureStickyTop();
709
+ window.addEventListener("resize", measureStickyTop, { passive: true });
710
+ });
711
+ onBeforeUnmount(() => {
712
+ window.removeEventListener("resize", measureStickyTop);
713
+ clearTimeout(toastTimer);
553
714
  });
554
715
  return (_ctx, _push, _parent, _attrs) => {
555
- _push(`<div${ssrRenderAttrs(mergeProps({ class: "fl-root" }, _attrs))}><div class="fl-bar"><div class="fl-title"><span class="fl-title-icon">🗂️</span><span class="fl-title-text">${ssrInterpolate(displayTitle.value)}</span>`);
556
- if (demo.value) _push(`<span class="fl-demo-badge">${ssrInterpolate(t("demoBadge"))}</span>`);
716
+ _push(`<div${ssrRenderAttrs(mergeProps({
717
+ class: ["fl-root", { "fl-selecting": selected.value.size > 0 }],
718
+ style: { "--fl-sticky-top-auto": `${stickyTop.value}px` }
719
+ }, _attrs))}><div class="fl-bar">`);
720
+ if (selected.value.size) _push(`<div class="fl-selection" role="status"><span class="fl-selection-count">${ssrInterpolate(t("selected", { count: selected.value.size }))}</span><span class="fl-selection-size">${ssrInterpolate(t("totalSize", { size: unref(formatSize)(selectedBytes.value) }))}</span></div>`);
721
+ else {
722
+ _push(`<div class="fl-title"><span class="fl-title-text">${ssrInterpolate(displayTitle.value)}</span>`);
723
+ if (demo.value) _push(`<span class="fl-demo-badge">${ssrInterpolate(t("demoBadge"))}</span>`);
724
+ else _push(`<!---->`);
725
+ _push(`</div>`);
726
+ }
727
+ if (selected.value.size) _push(`<button class="fl-clear" type="button"${ssrRenderAttr("aria-label", t("clearSelection"))}${ssrRenderAttr("title", t("clearSelection"))}><svg class="fl-clear-icon" viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12"></path><path d="M18 6 6 18"></path></svg><span class="fl-clear-text">${ssrInterpolate(t("clear"))}</span></button>`);
557
728
  else _push(`<!---->`);
558
- _push(`</div><div class="fl-tools"><input${ssrRenderAttr("value", keyword.value)} class="fl-search" type="text"${ssrRenderAttr("placeholder", t("filter"))}><button class="fl-btn" type="button">${ssrInterpolate(t("refresh"))}</button><button class="fl-btn fl-btn-primary" type="button"${ssrIncludeBooleanAttr(selected.value.size === 0 || batchBusy.value) ? " disabled" : ""}>${ssrInterpolate(batchBusy.value ? t("batching", { percent: batchProgress.value }) : selected.value.size ? t("batchCount", { count: selected.value.size }) : t("batch"))}</button></div></div>`);
559
- if (needsPassword.value) _push(`<div class="fl-password"><span>${ssrInterpolate(t("passwordPrompt"))}</span><input${ssrRenderAttr("value", passwordInput.value)} type="password"${ssrRenderAttr("placeholder", t("passwordPlaceholder"))}><button class="fl-btn fl-btn-primary" type="button">${ssrInterpolate(t("open"))}</button></div>`);
729
+ _push(`<div class="fl-tools"><label class="fl-search"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m20 20-3.5-3.5"></path></svg><input${ssrRenderAttr("value", keyword.value)} type="text"${ssrRenderAttr("placeholder", t("filter"))}${ssrRenderAttr("aria-label", t("filter"))}></label><button class="fl-icon-btn" type="button"${ssrRenderAttr("aria-label", t("refresh"))}${ssrRenderAttr("title", t("refresh"))}><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12a9 9 0 1 1-3-6.7"></path><path d="M21 4v5h-5"></path></svg></button></div>`);
730
+ if (selected.value.size) _push(`<button class="fl-btn fl-btn-primary" type="button"${ssrIncludeBooleanAttr(batchBusy.value) ? " disabled" : ""}><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12"></path><path d="m7 10 5 5 5-5"></path><path d="M5 21h14"></path></svg> ${ssrInterpolate(batchBusy.value ? t("batching", { percent: batchProgress.value }) : t("batch"))}</button>`);
731
+ else _push(`<!---->`);
732
+ _push(`</div>`);
733
+ if (needsPassword.value) _push(`<div class="fl-password"><span>${ssrInterpolate(t("passwordPrompt"))}</span><input${ssrRenderAttr("value", passwordInput.value)} type="password"${ssrRenderAttr("placeholder", t("passwordPlaceholder"))}${ssrRenderAttr("aria-label", t("passwordPlaceholder"))}><button class="fl-btn fl-btn-primary" type="button">${ssrInterpolate(t("open"))}</button></div>`);
560
734
  else _push(`<!---->`);
561
- _push(`<div class="fl-row fl-head"><span class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(allChecked.value) ? " checked" : ""}${ssrRenderAttr("indeterminate", someChecked.value)}></span><span class="fl-cell fl-cell-name">${ssrInterpolate(t("columnName"))}<span class="fl-sort">${ssrInterpolate(sortKey.value === "name" ? sortAsc.value ? "▲" : "▼" : "")}</span></span><span class="fl-cell fl-cell-size">${ssrInterpolate(t("columnSize"))}<span class="fl-sort">${ssrInterpolate(sortKey.value === "size" ? sortAsc.value ? "▲" : "▼" : "")}</span></span><span class="fl-cell fl-cell-date">${ssrInterpolate(t("columnDate"))}<span class="fl-sort">${ssrInterpolate(sortKey.value === "modified" ? sortAsc.value ? "▲" : "▼" : "")}</span></span><span class="fl-cell fl-cell-op"></span></div>`);
735
+ _push(`<div class="fl-row fl-head"><label class="fl-cell fl-cell-check"><input type="checkbox"${ssrRenderAttr("aria-label", t("selectAll"))}${ssrIncludeBooleanAttr(allChecked.value) ? " checked" : ""}${ssrRenderAttr("indeterminate", someChecked.value)}></label><span class="fl-cell fl-cell-name"><button class="fl-sort-btn" type="button"${ssrRenderAttr("aria-sort", sortKey.value === "name" ? sortAsc.value ? "ascending" : "descending" : void 0)}>${ssrInterpolate(t("columnName"))}<span class="fl-sort" aria-hidden="true">${ssrInterpolate(sortKey.value === "name" ? sortAsc.value ? "▲" : "▼" : "")}</span></button></span><span class="fl-cell fl-cell-size"><button class="fl-sort-btn" type="button">${ssrInterpolate(t("columnSize"))}<span class="fl-sort" aria-hidden="true">${ssrInterpolate(sortKey.value === "size" ? sortAsc.value ? "▲" : "▼" : "")}</span></button></span><span class="fl-cell fl-cell-date"><button class="fl-sort-btn" type="button">${ssrInterpolate(t("columnDate"))}<span class="fl-sort" aria-hidden="true">${ssrInterpolate(sortKey.value === "modified" ? sortAsc.value ? "▲" : "▼" : "")}</span></button></span><span class="fl-cell fl-cell-op"><label class="fl-sort-select"><span>${ssrInterpolate(t("sortBy"))}</span><span class="fl-sort-current" aria-hidden="true">${ssrInterpolate(sortOptions.value.find((option) => option.value === sortValue.value)?.label)}</span><select${ssrRenderAttr("aria-label", t("sortBy"))}><!--[-->`);
736
+ ssrRenderList(sortOptions.value, (option) => {
737
+ _push(`<option${ssrRenderAttr("value", option.value)}${ssrIncludeBooleanAttr(Array.isArray(sortValue.value) ? ssrLooseContain(sortValue.value, option.value) : ssrLooseEqual(sortValue.value, option.value)) ? " selected" : ""}>${ssrInterpolate(option.label)}</option>`);
738
+ });
739
+ _push(`<!--]--></select></label></span></div>`);
562
740
  if (loadingRoot.value) _push(`<div class="fl-status">${ssrInterpolate(t("loading"))}</div>`);
563
741
  else if (errorMsg.value) {
564
742
  _push(`<div class="fl-status fl-error">${ssrInterpolate(t("loadFailed", { message: errorMsg.value }))} `);
@@ -574,30 +752,39 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
574
752
  _push(`<div class="${ssrRenderClass([{
575
753
  "fl-row-dir": node.is_dir,
576
754
  "fl-row-root": node.isRoot,
577
- "fl-row-selected": selected.value.has(node.path)
578
- }, "fl-row"])}"><span class="fl-cell fl-cell-check"><input type="checkbox"${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></span><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)} style="${ssrRenderStyle({ paddingLeft: `${node.depth * 22}px` })}">`);
579
- if (node.is_dir) _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-arrow"])}">▸</button>`);
580
- else _push(`<span class="fl-arrow-placeholder"></span>`);
581
- _push(`<span class="${ssrRenderClass([{ "fl-name-link": node.is_dir }, "fl-name"])}">`);
582
- if (node.is_dir) _push(`<span class="fl-folder-icon">📁</span>`);
583
- else _push(`<span class="fl-ext-badge">${ssrInterpolate(unref(extOf)(node.name) || "FILE")}</span>`);
584
- _push(` ${ssrInterpolate(node.name)} `);
585
- if (node.loading) _push(`<span class="fl-loading">…</span>`);
755
+ "fl-row-selected": !node.is_dir && selected.value.has(node.path)
756
+ }, "fl-row"])}" style="${ssrRenderStyle({ "--fl-depth": node.depth })}"><label class="fl-cell fl-cell-check"><input type="checkbox"${ssrRenderAttr("aria-label", t("selectItem", { name: node.name }))}${ssrIncludeBooleanAttr(isChecked(node)) ? " checked" : ""}${ssrRenderAttr("indeterminate", isIndeterminate(node))}></label><span class="fl-cell fl-cell-name"${ssrRenderAttr("title", node.isRoot ? node.path : void 0)}><span class="fl-indent" aria-hidden="true"></span>`);
757
+ if (node.is_dir) _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-arrow"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"></path></svg></button>`);
758
+ else _push(`<span class="fl-arrow-placeholder" aria-hidden="true"></span>`);
759
+ if (node.is_dir) _push(`<svg class="fl-folder-icon" viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path></svg>`);
760
+ else _push(`<span class="fl-type"${ssrRenderAttr("data-kind", unref(fileKind)(node.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(node.name) || "FILE")}</span>`);
761
+ _push(`<span class="fl-label"><span class="${ssrRenderClass([{ "fl-name-dir": node.is_dir }, "fl-name"])}">${ssrInterpolate(node.name)}</span><span class="fl-meta">`);
762
+ if (node.is_dir) {
763
+ _push(`<!--[-->`);
764
+ if (node.loaded) _push(`<!--[-->${ssrInterpolate(t("folderItems", { count: folderFiles(node).length }))} · ${ssrInterpolate(unref(formatSize)(unref(totalSize)(folderFiles(node))))}<!--]-->`);
765
+ else _push(`<!---->`);
766
+ _push(`<!--]-->`);
767
+ } else _push(`<!--[-->${ssrInterpolate(unref(formatSize)(node.size))} · ${ssrInterpolate(unref(formatDate)(node.modified))}<!--]-->`);
768
+ _push(`</span></span>`);
769
+ if (node.is_dir && node.loaded) _push(`<span class="fl-count">${ssrInterpolate(t("folderItems", { count: folderFiles(node).length }))}</span>`);
586
770
  else _push(`<!---->`);
587
- _push(`</span></span><span class="fl-cell fl-cell-size">${ssrInterpolate(node.is_dir ? "-" : unref(formatSize)(node.size))}</span><span class="fl-cell fl-cell-date">${ssrInterpolate(unref(formatDate)(node.modified))}</span><span class="fl-cell fl-cell-op">`);
588
- if (!node.is_dir) {
771
+ if (node.loading) _push(`<span class="fl-loading" aria-hidden="true">…</span>`);
772
+ else _push(`<!---->`);
773
+ _push(`</span><span class="fl-cell fl-cell-size">${ssrInterpolate(node.is_dir ? "—" : unref(formatSize)(node.size))}</span><span class="fl-cell fl-cell-date">${ssrInterpolate(unref(formatDate)(node.modified))}</span><span class="fl-cell fl-cell-op">`);
774
+ if (node.is_dir) _push(`<button type="button" class="${ssrRenderClass([{ "fl-arrow-open": node.expanded }, "fl-action fl-arrow-end"])}"${ssrRenderAttr("aria-label", t(node.expanded ? "collapse" : "expand", { name: node.name }))}${ssrRenderAttr("aria-expanded", node.expanded)}><svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"></path></svg></button>`);
775
+ else {
589
776
  _push(`<!--[-->`);
590
- if (canPreview(node)) _push(`<button class="fl-download" type="button"${ssrRenderAttr("title", t("preview"))}><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg></button>`);
777
+ if (canPreview(node)) _push(`<button class="fl-action fl-download" type="button"${ssrRenderAttr("aria-label", `${t("preview")} ${node.name}`)}${ssrRenderAttr("title", t("preview"))}><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7Z"></path><circle cx="12" cy="12" r="3"></circle></svg></button>`);
591
778
  else _push(`<!---->`);
592
- _push(`<button class="fl-download" type="button"${ssrRenderAttr("title", t("download"))}><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12"></path><path d="m7 10 5 5 5-5"></path><path d="M5 21h14"></path></svg></button><!--]-->`);
593
- } else _push(`<!---->`);
779
+ _push(`<button class="fl-action fl-download" type="button"${ssrRenderAttr("aria-label", `${t("download")} ${node.name}`)}${ssrRenderAttr("title", t("download"))}><svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v12"></path><path d="m7 10 5 5 5-5"></path><path d="M5 21h14"></path></svg></button><!--]-->`);
780
+ }
594
781
  _push(`</span></div>`);
595
782
  });
596
- _push(`<!--]--><div class="fl-foot"><span>${ssrInterpolate(t("loaded", { count: allFiles.value.length }))} · ${ssrInterpolate(t("selected", { count: selected.value.size }))}</span>`);
597
- if (unloadedFolders.value) _push(`<span> · ${ssrInterpolate(t("unloaded", { count: unloadedFolders.value }))}</span>`);
783
+ _push(`<!--]--><div class="fl-foot"><span>${ssrInterpolate(t("loaded", { count: allFiles.value.length }))} · ${ssrInterpolate(t("totalSize", { size: unref(formatSize)(loadedBytes.value) }))}</span>`);
784
+ if (unloadedFolders.value) _push(`<span class="fl-foot-hint">${ssrInterpolate(t("unloaded", { count: unloadedFolders.value }))}</span>`);
598
785
  else _push(`<!---->`);
599
786
  _push(`</div>`);
600
- if (toast.value) _push(`<div class="fl-toast">${ssrInterpolate(toast.value)}</div>`);
787
+ if (toast.value) _push(`<div class="fl-toast" role="status">${ssrInterpolate(toast.value)}</div>`);
601
788
  else _push(`<!---->`);
602
789
  _push(`</div>`);
603
790
  };
@@ -630,4 +817,4 @@ function enhanceAppWithFileList({ app }) {
630
817
  if (!app._context.components.FileList) app.component("FileList", _sfc_main);
631
818
  if (!app._context.components.VPFileList) app.component("VPFileList", _sfc_main);
632
819
  }
633
- export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileListLocales, formatDate, formatLabel, formatSize, joinPath, resolveLabels, uniqueEntryName, zipEntryName, zipSegment };
820
+ export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileKind, fileListLocales, formatDate, formatLabel, formatSize, joinPath, resolveLabels, totalSize, uniqueEntryName, zipEntryName, zipSegment };