vitepress-plugin-filelist 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;
@@ -24,6 +25,7 @@ interface FileListLabels {
24
25
  download: string;
25
26
  loaded: string;
26
27
  selected: string;
28
+ /** @deprecated No longer shown; the footer only shows loaded files and size. */
27
29
  unloaded: string;
28
30
  folderFailed: string;
29
31
  demoDownload: string;
@@ -33,6 +35,23 @@ interface FileListLabels {
33
35
  previewFailed: string;
34
36
  batchFailed: string;
35
37
  defaultZip: string;
38
+ /** Text button that clears the selection (wide layout). */
39
+ clear: string;
40
+ /** Accessible name of the clear-selection button. */
41
+ clearSelection: string;
42
+ /** `{size}` total, used for the selection and the footer. */
43
+ totalSize: string;
44
+ /** `{count}` files inside a loaded folder. */
45
+ folderItems: string;
46
+ /** Label of the compact sort select shown in narrow containers. */
47
+ sortBy: string;
48
+ selectAll: string;
49
+ /** Accessible name of a row checkbox, `{name}`. */
50
+ selectItem: string;
51
+ /** Accessible name of the expand button, `{name}`. */
52
+ expand: string;
53
+ /** Accessible name of the collapse button, `{name}`. */
54
+ collapse: string;
36
55
  }
37
56
  interface FileListSource {
38
57
  /** OpenList path, directory or single file. */
@@ -186,6 +205,13 @@ export declare function uniqueEntryName(name: string, used: Set<string>): string
186
205
  export declare function formatSize(size: number): string;
187
206
  export declare function formatDate(iso: string): string;
188
207
  export declare function extOf(name: string): string;
208
+ export type FileKind = 'doc' | 'sheet' | 'slide' | 'pdf' | 'image' | 'media' | 'archive' | 'code' | 'other';
209
+ /** Rough file family, used to colour the type badge. */
210
+ export declare function fileKind(name: string): FileKind;
211
+ /** Sum of the `size` of the given entries, ignoring missing or invalid sizes. */
212
+ export declare function totalSize(items: Array<{
213
+ size?: number;
214
+ }>): number;
189
215
  export declare function compareNodes(a: SortableNode, b: SortableNode, key: SortKey, ascending?: boolean): number;
190
216
  /**
191
217
  * 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,34 +645,87 @@ 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)));
534
- const unloadedFolders = computed(() => {
535
- let count = 0;
536
- const walk = (nodes) => {
537
- for (const node of nodes) {
538
- if (node.is_dir && !node.loaded) count += 1;
539
- if (node.children.length) walk(node.children);
540
- }
541
- };
542
- walk(roots.value);
543
- return count;
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
+ }
544
677
  });
545
678
  const previewable = /\.(?:pdf|png|jpe?g|gif|webp|svg|txt|md|json|mp3|wav|mp4|webm|mov)$/i;
546
679
  function canPreview(node) {
547
680
  return !node.is_dir && previewable.test(node.name);
548
681
  }
549
682
  const displayTitle = computed(() => props.title || siteTitle.value || (props.path === "/" ? t("allFiles") : props.path));
683
+ const stickyTop = ref(0);
684
+ function measureStickyTop() {
685
+ if (typeof document === "undefined") return;
686
+ let top = 0;
687
+ for (const el of document.querySelectorAll(".VPNav, .VPLocalNav")) {
688
+ const style = getComputedStyle(el);
689
+ if (style.display === "none" || style.position !== "fixed" && style.position !== "sticky") continue;
690
+ top = Math.max(top, (Number.parseFloat(style.top) || 0) + el.offsetHeight);
691
+ }
692
+ stickyTop.value = top;
693
+ }
550
694
  onMounted(() => {
551
695
  loadRoot();
552
696
  loadSiteTitle();
697
+ measureStickyTop();
698
+ window.addEventListener("resize", measureStickyTop, { passive: true });
699
+ });
700
+ onBeforeUnmount(() => {
701
+ window.removeEventListener("resize", measureStickyTop);
702
+ clearTimeout(toastTimer);
553
703
  });
554
704
  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>`);
705
+ _push(`<div${ssrRenderAttrs(mergeProps({
706
+ class: ["fl-root", { "fl-selecting": selected.value.size > 0 }],
707
+ style: { "--fl-sticky-top-auto": `${stickyTop.value}px` }
708
+ }, _attrs))}><div class="fl-bar">`);
709
+ 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>`);
710
+ else {
711
+ _push(`<div class="fl-title"><span class="fl-title-text">${ssrInterpolate(displayTitle.value)}</span>`);
712
+ if (demo.value) _push(`<span class="fl-demo-badge">${ssrInterpolate(t("demoBadge"))}</span>`);
713
+ else _push(`<!---->`);
714
+ _push(`</div>`);
715
+ }
716
+ 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
717
  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>`);
718
+ _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>`);
719
+ 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>`);
560
720
  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>`);
721
+ _push(`</div>`);
722
+ 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>`);
723
+ else _push(`<!---->`);
724
+ _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"))}><!--[-->`);
725
+ ssrRenderList(sortOptions.value, (option) => {
726
+ _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>`);
727
+ });
728
+ _push(`<!--]--></select></label></span></div>`);
562
729
  if (loadingRoot.value) _push(`<div class="fl-status">${ssrInterpolate(t("loading"))}</div>`);
563
730
  else if (errorMsg.value) {
564
731
  _push(`<div class="fl-status fl-error">${ssrInterpolate(t("loadFailed", { message: errorMsg.value }))} `);
@@ -574,30 +741,36 @@ const _sfc_main = /*@__PURE__*/ defineComponent({
574
741
  _push(`<div class="${ssrRenderClass([{
575
742
  "fl-row-dir": node.is_dir,
576
743
  "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>`);
744
+ "fl-row-selected": !node.is_dir && selected.value.has(node.path)
745
+ }, "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>`);
746
+ 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>`);
747
+ else _push(`<span class="fl-arrow-placeholder" aria-hidden="true"></span>`);
748
+ 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>`);
749
+ else _push(`<span class="fl-type"${ssrRenderAttr("data-kind", unref(fileKind)(node.name))} aria-hidden="true">${ssrInterpolate(unref(extOf)(node.name) || "FILE")}</span>`);
750
+ _push(`<span class="fl-label"><span class="${ssrRenderClass([{ "fl-name-dir": node.is_dir }, "fl-name"])}">${ssrInterpolate(node.name)}</span><span class="fl-meta">`);
751
+ if (node.is_dir) {
752
+ _push(`<!--[-->`);
753
+ if (node.loaded) _push(`<!--[-->${ssrInterpolate(t("folderItems", { count: folderFiles(node).length }))} · ${ssrInterpolate(unref(formatSize)(unref(totalSize)(folderFiles(node))))}<!--]-->`);
754
+ else _push(`<!---->`);
755
+ _push(`<!--]-->`);
756
+ } else _push(`<!--[-->${ssrInterpolate(unref(formatSize)(node.size))} · ${ssrInterpolate(unref(formatDate)(node.modified))}<!--]-->`);
757
+ _push(`</span></span>`);
758
+ if (node.is_dir && node.loaded) _push(`<span class="fl-count">${ssrInterpolate(t("folderItems", { count: folderFiles(node).length }))}</span>`);
759
+ else _push(`<!---->`);
760
+ if (node.loading) _push(`<span class="fl-loading" aria-hidden="true">…</span>`);
586
761
  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) {
762
+ _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">`);
763
+ 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>`);
764
+ else {
589
765
  _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>`);
766
+ 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
767
  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(`<!---->`);
768
+ _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><!--]-->`);
769
+ }
594
770
  _push(`</span></div>`);
595
771
  });
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>`);
598
- else _push(`<!---->`);
599
- _push(`</div>`);
600
- if (toast.value) _push(`<div class="fl-toast">${ssrInterpolate(toast.value)}</div>`);
772
+ _push(`<!--]--><div class="fl-foot"><span>${ssrInterpolate(t("loaded", { count: allFiles.value.length }))} · ${ssrInterpolate(t("totalSize", { size: unref(formatSize)(loadedBytes.value) }))}</span></div>`);
773
+ if (toast.value) _push(`<div class="fl-toast" role="status">${ssrInterpolate(toast.value)}</div>`);
601
774
  else _push(`<!---->`);
602
775
  _push(`</div>`);
603
776
  };
@@ -630,4 +803,4 @@ function enhanceAppWithFileList({ app }) {
630
803
  if (!app._context.components.FileList) app.component("FileList", _sfc_main);
631
804
  if (!app._context.components.VPFileList) app.component("VPFileList", _sfc_main);
632
805
  }
633
- export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileListLocales, formatDate, formatLabel, formatSize, joinPath, resolveLabels, uniqueEntryName, zipEntryName, zipSegment };
806
+ export { _sfc_main as FileList, _sfc_main as VPFileList, compareNodes, encodePath, enhanceAppWithFileList, extOf, fileKind, fileListLocales, formatDate, formatLabel, formatSize, joinPath, resolveLabels, totalSize, uniqueEntryName, zipEntryName, zipSegment };