nuxt-ui-tools 1.7.0 → 1.8.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.
Files changed (161) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/module.mjs +18 -0
  3. package/dist/runtime/file-preview/components/provider/file-preview-host.d.vue.ts +7 -0
  4. package/dist/runtime/file-preview/components/provider/file-preview-host.vue +147 -0
  5. package/dist/runtime/file-preview/components/provider/file-preview-host.vue.d.ts +7 -0
  6. package/dist/runtime/file-preview/components/provider/file-preview-provider.d.vue.ts +21 -0
  7. package/dist/runtime/file-preview/components/provider/file-preview-provider.vue +23 -0
  8. package/dist/runtime/file-preview/components/provider/file-preview-provider.vue.d.ts +21 -0
  9. package/dist/runtime/file-preview/components/renderers/audio-renderer.d.vue.ts +10 -0
  10. package/dist/runtime/file-preview/components/renderers/audio-renderer.vue +83 -0
  11. package/dist/runtime/file-preview/components/renderers/audio-renderer.vue.d.ts +10 -0
  12. package/dist/runtime/file-preview/components/renderers/csv-renderer.d.vue.ts +10 -0
  13. package/dist/runtime/file-preview/components/renderers/csv-renderer.vue +116 -0
  14. package/dist/runtime/file-preview/components/renderers/csv-renderer.vue.d.ts +10 -0
  15. package/dist/runtime/file-preview/components/renderers/fallback-renderer.d.vue.ts +10 -0
  16. package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue +41 -0
  17. package/dist/runtime/file-preview/components/renderers/fallback-renderer.vue.d.ts +10 -0
  18. package/dist/runtime/file-preview/components/renderers/file-preview-code.d.vue.ts +9 -0
  19. package/dist/runtime/file-preview/components/renderers/file-preview-code.vue +83 -0
  20. package/dist/runtime/file-preview/components/renderers/file-preview-code.vue.d.ts +9 -0
  21. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.d.vue.ts +3 -0
  22. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue +14 -0
  23. package/dist/runtime/file-preview/components/renderers/file-preview-text-loading.vue.d.ts +3 -0
  24. package/dist/runtime/file-preview/components/renderers/image-renderer.d.vue.ts +10 -0
  25. package/dist/runtime/file-preview/components/renderers/image-renderer.vue +182 -0
  26. package/dist/runtime/file-preview/components/renderers/image-renderer.vue.d.ts +10 -0
  27. package/dist/runtime/file-preview/components/renderers/markdown-renderer.d.vue.ts +10 -0
  28. package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue +78 -0
  29. package/dist/runtime/file-preview/components/renderers/markdown-renderer.vue.d.ts +10 -0
  30. package/dist/runtime/file-preview/components/renderers/media-controls.d.vue.ts +10 -0
  31. package/dist/runtime/file-preview/components/renderers/media-controls.vue +145 -0
  32. package/dist/runtime/file-preview/components/renderers/media-controls.vue.d.ts +10 -0
  33. package/dist/runtime/file-preview/components/renderers/pdf-renderer.d.vue.ts +10 -0
  34. package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue +58 -0
  35. package/dist/runtime/file-preview/components/renderers/pdf-renderer.vue.d.ts +10 -0
  36. package/dist/runtime/file-preview/components/renderers/text-renderer.d.vue.ts +10 -0
  37. package/dist/runtime/file-preview/components/renderers/text-renderer.vue +41 -0
  38. package/dist/runtime/file-preview/components/renderers/text-renderer.vue.d.ts +10 -0
  39. package/dist/runtime/file-preview/components/renderers/video-renderer.d.vue.ts +10 -0
  40. package/dist/runtime/file-preview/components/renderers/video-renderer.vue +110 -0
  41. package/dist/runtime/file-preview/components/renderers/video-renderer.vue.d.ts +10 -0
  42. package/dist/runtime/file-preview/components/shell/file-preview-button.d.vue.ts +16 -0
  43. package/dist/runtime/file-preview/components/shell/file-preview-button.vue +40 -0
  44. package/dist/runtime/file-preview/components/shell/file-preview-button.vue.d.ts +16 -0
  45. package/dist/runtime/{form/components/renderer/form-field-error.d.vue.ts → file-preview/components/shell/file-preview-details.d.vue.ts} +4 -2
  46. package/dist/runtime/file-preview/components/shell/file-preview-details.vue +25 -0
  47. package/dist/runtime/{form/components/renderer/form-field-error.vue.d.ts → file-preview/components/shell/file-preview-details.vue.d.ts} +4 -2
  48. package/dist/runtime/file-preview/components/shell/file-preview-header.d.vue.ts +44 -0
  49. package/dist/runtime/file-preview/components/shell/file-preview-header.vue +163 -0
  50. package/dist/runtime/file-preview/components/shell/file-preview-header.vue.d.ts +44 -0
  51. package/dist/runtime/file-preview/components/shell/file-preview-message.d.vue.ts +21 -0
  52. package/dist/runtime/file-preview/components/shell/file-preview-message.vue +49 -0
  53. package/dist/runtime/file-preview/components/shell/file-preview-message.vue.d.ts +21 -0
  54. package/dist/runtime/file-preview/components/shell/file-preview-shell.d.vue.ts +17 -0
  55. package/dist/runtime/file-preview/components/shell/file-preview-shell.vue +280 -0
  56. package/dist/runtime/file-preview/components/shell/file-preview-shell.vue.d.ts +17 -0
  57. package/dist/runtime/file-preview/components/shell/file-preview-stage.d.vue.ts +37 -0
  58. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue +173 -0
  59. package/dist/runtime/file-preview/components/shell/file-preview-stage.vue.d.ts +37 -0
  60. package/dist/runtime/file-preview/components/shell/file-preview-thumb.d.vue.ts +14 -0
  61. package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue +69 -0
  62. package/dist/runtime/file-preview/components/shell/file-preview-thumb.vue.d.ts +14 -0
  63. package/dist/runtime/file-preview/components/shell/file-preview-tools.d.vue.ts +13 -0
  64. package/dist/runtime/file-preview/components/shell/file-preview-tools.vue +10 -0
  65. package/dist/runtime/file-preview/components/shell/file-preview-tools.vue.d.ts +13 -0
  66. package/dist/runtime/file-preview/composables/use-file-preview-api.d.ts +21 -0
  67. package/dist/runtime/file-preview/composables/use-file-preview-api.js +83 -0
  68. package/dist/runtime/file-preview/composables/use-file-preview-shell.d.ts +13 -0
  69. package/dist/runtime/file-preview/composables/use-file-preview-shell.js +30 -0
  70. package/dist/runtime/file-preview/composables/use-file-preview-source.d.ts +17 -0
  71. package/dist/runtime/file-preview/composables/use-file-preview-source.js +51 -0
  72. package/dist/runtime/file-preview/composables/use-file-preview-text.d.ts +14 -0
  73. package/dist/runtime/file-preview/composables/use-file-preview-text.js +79 -0
  74. package/dist/runtime/file-preview/index.d.ts +5 -0
  75. package/dist/runtime/file-preview/index.js +14 -0
  76. package/dist/runtime/file-preview/plugins/file-preview-api.d.ts +11 -0
  77. package/dist/runtime/file-preview/plugins/file-preview-api.js +10 -0
  78. package/dist/runtime/file-preview/types/api.d.ts +74 -0
  79. package/dist/runtime/file-preview/types/api.js +0 -0
  80. package/dist/runtime/file-preview/types/file.d.ts +77 -0
  81. package/dist/runtime/file-preview/types/file.js +0 -0
  82. package/dist/runtime/file-preview/types/index.d.ts +4 -0
  83. package/dist/runtime/file-preview/types/index.js +0 -0
  84. package/dist/runtime/file-preview/types/renderer.d.ts +49 -0
  85. package/dist/runtime/file-preview/types/renderer.js +0 -0
  86. package/dist/runtime/file-preview/types/shell.d.ts +12 -0
  87. package/dist/runtime/file-preview/types/shell.js +0 -0
  88. package/dist/runtime/file-preview/utils/container.d.ts +4 -0
  89. package/dist/runtime/file-preview/utils/container.js +10 -0
  90. package/dist/runtime/file-preview/utils/csv.d.ts +17 -0
  91. package/dist/runtime/file-preview/utils/csv.js +53 -0
  92. package/dist/runtime/file-preview/utils/download.d.ts +8 -0
  93. package/dist/runtime/file-preview/utils/download.js +40 -0
  94. package/dist/runtime/file-preview/utils/files.d.ts +7 -0
  95. package/dist/runtime/file-preview/utils/files.js +53 -0
  96. package/dist/runtime/file-preview/utils/format.d.ts +13 -0
  97. package/dist/runtime/file-preview/utils/format.js +40 -0
  98. package/dist/runtime/file-preview/utils/instance.d.ts +9 -0
  99. package/dist/runtime/file-preview/utils/instance.js +57 -0
  100. package/dist/runtime/file-preview/utils/renderers.d.ts +36 -0
  101. package/dist/runtime/file-preview/utils/renderers.js +247 -0
  102. package/dist/runtime/form/components/page/form-page-header.d.vue.ts +3 -1
  103. package/dist/runtime/form/components/page/form-page-header.vue +4 -4
  104. package/dist/runtime/form/components/page/form-page-header.vue.d.ts +3 -1
  105. package/dist/runtime/form/components/page/form-page-navigation.vue +2 -1
  106. package/dist/runtime/form/components/page/form-page-section.vue +15 -7
  107. package/dist/runtime/form/components/page/form-page.vue +0 -1
  108. package/dist/runtime/form/components/utils/form-file-preview.vue +5 -38
  109. package/dist/runtime/form/composables/use-field-control.d.ts +1 -1
  110. package/dist/runtime/form/composables/use-form-page.js +3 -1
  111. package/dist/runtime/form/composables/use-form-runtime.js +3 -0
  112. package/dist/runtime/form/composables/use-form-upload-registry.d.ts +2 -0
  113. package/dist/runtime/form/composables/use-form-upload-registry.js +11 -6
  114. package/dist/runtime/form/composables/use-form-validation.js +5 -1
  115. package/dist/runtime/form/fields/array-list/use-array-items.d.ts +1 -1
  116. package/dist/runtime/form/fields/array-table/array-table-cell.d.vue.ts +15 -0
  117. package/dist/runtime/form/fields/array-table/array-table-cell.vue +58 -0
  118. package/dist/runtime/form/fields/array-table/array-table-cell.vue.d.ts +15 -0
  119. package/dist/runtime/form/fields/array-table/component.vue +42 -26
  120. package/dist/runtime/form/fields/date/types.d.ts +6 -1
  121. package/dist/runtime/form/fields/date-family/component.vue +7 -1
  122. package/dist/runtime/form/fields/date-family/utils.d.ts +8 -1
  123. package/dist/runtime/form/fields/date-family/utils.js +10 -2
  124. package/dist/runtime/form/fields/hidden/types.d.ts +8 -1
  125. package/dist/runtime/form/fields/upload/component.vue +55 -176
  126. package/dist/runtime/form/fields/upload/types.d.ts +68 -3
  127. package/dist/runtime/form/fields/upload/upload-item.d.vue.ts +24 -0
  128. package/dist/runtime/form/fields/upload/upload-item.vue +158 -0
  129. package/dist/runtime/form/fields/upload/upload-item.vue.d.ts +24 -0
  130. package/dist/runtime/form/fields/upload/use-upload-items.d.ts +44 -0
  131. package/dist/runtime/form/fields/upload/use-upload-items.js +317 -0
  132. package/dist/runtime/form/types/callbacks.d.ts +8 -2
  133. package/dist/runtime/form/types/controller.d.ts +6 -3
  134. package/dist/runtime/form/types/field-base.d.ts +5 -5
  135. package/dist/runtime/form/types/field-output-utils.d.ts +12 -1
  136. package/dist/runtime/form/types/field-output.d.ts +1 -1
  137. package/dist/runtime/form/types/field.d.ts +3 -2
  138. package/dist/runtime/form/types/form-api.d.ts +4 -2
  139. package/dist/runtime/form/types/options-runtime.d.ts +8 -4
  140. package/dist/runtime/form/types/options.d.ts +11 -11
  141. package/dist/runtime/form/types/output.d.ts +33 -10
  142. package/dist/runtime/form/types/page.d.ts +7 -3
  143. package/dist/runtime/form/types/transform.d.ts +5 -2
  144. package/dist/runtime/form/types/ui.d.ts +7 -3
  145. package/dist/runtime/form/types/upload-runtime.d.ts +4 -0
  146. package/dist/runtime/form/utils/page.js +24 -5
  147. package/dist/runtime/form/utils/state.js +14 -11
  148. package/dist/runtime/i18n/locales/en.js +104 -1
  149. package/dist/runtime/i18n/locales/fr.js +104 -1
  150. package/dist/runtime/i18n/types.d.ts +121 -0
  151. package/dist/runtime/query-state/composables.d.ts +2 -2
  152. package/dist/runtime/shared/styles/tokens.css +1 -1
  153. package/dist/runtime/shared/types/utils.d.ts +12 -4
  154. package/dist/runtime/shared/utils/file.d.ts +11 -0
  155. package/dist/runtime/shared/utils/file.js +50 -0
  156. package/dist/runtime/table/components/table/table-renderer.vue +23 -3
  157. package/dist/runtime/table/components/utils/search-query-input.d.vue.ts +5 -1
  158. package/dist/runtime/table/components/utils/search-query-input.vue +17 -3
  159. package/dist/runtime/table/components/utils/search-query-input.vue.d.ts +5 -1
  160. package/package.json +56 -29
  161. package/dist/runtime/form/components/renderer/form-field-error.vue +0 -16
@@ -0,0 +1,83 @@
1
+ <script setup>
2
+ import { useClipboard } from "@vueuse/core";
3
+ import { computed, ref } from "vue";
4
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
5
+ import { tintFilePreviewJsonLine } from "../../utils/format";
6
+ import FilePreviewButton from "../shell/file-preview-button.vue";
7
+ import FilePreviewTools from "../shell/file-preview-tools.vue";
8
+ const {
9
+ json,
10
+ text,
11
+ tools = true
12
+ } = defineProps({
13
+ text: { type: String, required: true },
14
+ json: { type: Boolean, required: true },
15
+ facts: { type: Array, required: true },
16
+ tools: { type: Boolean, required: false }
17
+ });
18
+ const { t } = useUiToolsLocale();
19
+ const wrap = ref(false);
20
+ const clipboard = useClipboard({ copiedDuring: 1500, legacy: true });
21
+ const source = computed(() => {
22
+ if (!json) return { formatted: false, text };
23
+ try {
24
+ return { formatted: true, text: JSON.stringify(JSON.parse(text), null, 2) };
25
+ } catch {
26
+ return { formatted: false, text };
27
+ }
28
+ });
29
+ const lines = computed(() => source.value.text.split(/\r?\n/u));
30
+ const tinted = computed(
31
+ () => source.value.formatted ? lines.value.map(tintFilePreviewJsonLine) : null
32
+ );
33
+ </script>
34
+
35
+ <template>
36
+ <div class="absolute inset-0 flex flex-col bg-default" data-file-preview-code="">
37
+ <div
38
+ class="nut-fp-code min-h-0 flex-1 overflow-auto font-mono text-[12.5px] leading-[1.65] text-default [tab-size:2]"
39
+ >
40
+ <div :class="['py-2.5', wrap ? '' : 'min-w-max']">
41
+ <div v-for="(line, position) in lines" :key="position" class="flex">
42
+ <span
43
+ class="sticky left-0 w-13 shrink-0 bg-default pe-3.5 text-end text-dimmed tabular-nums select-none"
44
+ aria-hidden="true"
45
+ >
46
+ {{ position + 1 }}
47
+ </span>
48
+ <!-- eslint-disable-next-line vue/no-v-html -- each line is escaped before tinting -->
49
+ <span
50
+ v-if="tinted"
51
+ :class="['pe-6', wrap ? 'break-all whitespace-pre-wrap' : 'whitespace-pre']"
52
+ v-html="tinted[position] || ' '"
53
+ />
54
+ <span v-else :class="['pe-6', wrap ? 'break-all whitespace-pre-wrap' : 'whitespace-pre']">
55
+ {{ line || " " }}
56
+ </span>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ <div
61
+ class="flex shrink-0 flex-wrap gap-x-3.5 gap-y-1 border-t border-default bg-elevated px-3.5 py-1.5 text-[11.5px] text-muted tabular-nums"
62
+ data-file-preview-facts=""
63
+ >
64
+ <span>{{ t("filePreview.text.lines", { count: lines.length.toLocaleString() }) }}</span>
65
+ <span v-if="source.formatted">{{ t("filePreview.text.formatted") }}</span>
66
+ <span v-for="fact in facts" :key="fact">{{ fact }}</span>
67
+ </div>
68
+
69
+ <FilePreviewTools v-if="tools">
70
+ <FilePreviewButton
71
+ icon="i-lucide-wrap-text"
72
+ :label="t('filePreview.text.wrap')"
73
+ :pressed="wrap"
74
+ @click="wrap = !wrap"
75
+ />
76
+ <FilePreviewButton
77
+ :icon="clipboard.copied.value ? 'i-lucide-check' : 'i-lucide-copy'"
78
+ :label="clipboard.copied.value ? t('filePreview.text.copied') : t('filePreview.text.copy')"
79
+ @click="clipboard.copy(source.text)"
80
+ />
81
+ </FilePreviewTools>
82
+ </div>
83
+ </template>
@@ -0,0 +1,9 @@
1
+ type __VLS_Props = {
2
+ text: string;
3
+ json: boolean;
4
+ facts: readonly string[];
5
+ tools?: boolean;
6
+ };
7
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
8
+ declare const _default: typeof __VLS_export;
9
+ export default _default;
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,14 @@
1
+ <script setup>
2
+ const WIDTHS = [62, 84, 45, 91, 70, 38, 77, 58, 88, 49, 66, 80];
3
+ </script>
4
+
5
+ <template>
6
+ <div class="absolute inset-0 flex flex-col gap-2.5 bg-default px-6 py-5" aria-hidden="true">
7
+ <span
8
+ v-for="(width, position) in WIDTHS"
9
+ :key="position"
10
+ class="block h-2.5 animate-pulse rounded bg-accented"
11
+ :style="{ width: `${width}%` }"
12
+ />
13
+ </div>
14
+ </template>
@@ -0,0 +1,3 @@
1
+ declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ declare const _default: typeof __VLS_export;
3
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import type { FilePreviewRendererProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<FilePreviewRendererProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
3
+ ready: (details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any;
4
+ error: (error: import("../../types/index.js").FilePreviewRendererError) => any;
5
+ }, string, import("vue").PublicProps, Readonly<FilePreviewRendererProps> & Readonly<{
6
+ onReady?: ((details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any) | undefined;
7
+ onError?: ((error: import("../../types/index.js").FilePreviewRendererError) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,182 @@
1
+ <script setup>
2
+ import { useElementSize, useEventListener } from "@vueuse/core";
3
+ import { computed, ref, shallowRef, useTemplateRef } from "vue";
4
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
5
+ import { onFilePreviewKey } from "../../composables/use-file-preview-shell";
6
+ import FilePreviewButton from "../shell/file-preview-button.vue";
7
+ import FilePreviewTools from "../shell/file-preview-tools.vue";
8
+ const TRANSPARENT = /* @__PURE__ */ new Set(["apng", "avif", "gif", "ico", "png", "svg", "webp"]);
9
+ const PADDING = 24;
10
+ const MAX_ZOOM = 4;
11
+ const props = defineProps({
12
+ file: { type: Object, required: true },
13
+ url: { type: String, required: true },
14
+ blob: { type: null, required: true },
15
+ container: { type: String, required: true }
16
+ });
17
+ const emit = defineEmits(["ready", "error"]);
18
+ const { t } = useUiToolsLocale();
19
+ const viewport = useTemplateRef("viewport");
20
+ const { height, width } = useElementSize(viewport);
21
+ const natural = shallowRef(null);
22
+ const scale = ref(1);
23
+ const offset = ref({ x: 0, y: 0 });
24
+ const rotation = ref(0);
25
+ const dragging = ref(false);
26
+ const transparent = computed(
27
+ () => props.file.mime === "image/svg+xml" || TRANSPARENT.has(props.file.extension ?? "")
28
+ );
29
+ const turned = computed(() => rotation.value % 180 !== 0);
30
+ const fit = computed(() => {
31
+ const size = natural.value;
32
+ if (!size || width.value <= 0 || height.value <= 0) return 1;
33
+ const shownWidth = turned.value ? size.height : size.width;
34
+ const shownHeight = turned.value ? size.width : size.height;
35
+ return Math.min(
36
+ (width.value - PADDING * 2) / shownWidth,
37
+ (height.value - PADDING * 2) / shownHeight,
38
+ 1
39
+ );
40
+ });
41
+ const maxScale = computed(() => Math.max(MAX_ZOOM / fit.value, 2));
42
+ const zoomed = computed(() => scale.value > 1.001);
43
+ const percent = computed(() => `${Math.round(fit.value * scale.value * 100)}%`);
44
+ const imageStyle = computed(() => {
45
+ const size = natural.value;
46
+ return {
47
+ height: size ? `${size.height * fit.value}px` : void 0,
48
+ transform: `translate(-50%, -50%) translate(${offset.value.x}px, ${offset.value.y}px) scale(${scale.value}) rotate(${rotation.value}deg)`,
49
+ width: size ? `${size.width * fit.value}px` : void 0
50
+ };
51
+ });
52
+ function zoomTo(next, point) {
53
+ const target = Math.min(Math.max(next, 1), maxScale.value);
54
+ const box = viewport.value?.getBoundingClientRect();
55
+ const x = point && box ? point.x - (box.left + box.width / 2) : 0;
56
+ const y = point && box ? point.y - (box.top + box.height / 2) : 0;
57
+ const ratio = target / scale.value;
58
+ offset.value = target <= 1.001 ? { x: 0, y: 0 } : { x: x - (x - offset.value.x) * ratio, y: y - (y - offset.value.y) * ratio };
59
+ scale.value = target <= 1.001 ? 1 : target;
60
+ }
61
+ function reset() {
62
+ scale.value = 1;
63
+ offset.value = { x: 0, y: 0 };
64
+ }
65
+ function rotate() {
66
+ rotation.value = (rotation.value + 90) % 360;
67
+ reset();
68
+ }
69
+ function handleLoad(event) {
70
+ if (!(event.target instanceof HTMLImageElement)) return;
71
+ const size = {
72
+ height: event.target.naturalHeight || 600,
73
+ width: event.target.naturalWidth || 800
74
+ };
75
+ natural.value = size;
76
+ emit("ready", [
77
+ { label: t("filePreview.fields.dimensions"), value: `${size.width} \xD7 ${size.height} px` }
78
+ ]);
79
+ }
80
+ function toggleZoom(event) {
81
+ if (zoomed.value) return reset();
82
+ zoomTo(Math.max(2.5, 1 / fit.value), { x: event.clientX, y: event.clientY });
83
+ }
84
+ function startDrag(event) {
85
+ if (!zoomed.value || !(event.currentTarget instanceof HTMLElement)) return;
86
+ const origin = { x: event.clientX - offset.value.x, y: event.clientY - offset.value.y };
87
+ const surface = event.currentTarget;
88
+ surface.setPointerCapture(event.pointerId);
89
+ dragging.value = true;
90
+ const move = (moved) => {
91
+ offset.value = { x: moved.clientX - origin.x, y: moved.clientY - origin.y };
92
+ };
93
+ const stop = () => {
94
+ dragging.value = false;
95
+ surface.removeEventListener("pointermove", move);
96
+ surface.removeEventListener("pointerup", stop);
97
+ surface.removeEventListener("pointercancel", stop);
98
+ };
99
+ surface.addEventListener("pointermove", move);
100
+ surface.addEventListener("pointerup", stop);
101
+ surface.addEventListener("pointercancel", stop);
102
+ }
103
+ useEventListener(
104
+ viewport,
105
+ "wheel",
106
+ (event) => {
107
+ event.preventDefault();
108
+ const speed = event.ctrlKey ? 0.01 : 18e-4;
109
+ zoomTo(scale.value * Math.exp(-event.deltaY * speed), { x: event.clientX, y: event.clientY });
110
+ },
111
+ { passive: false }
112
+ );
113
+ onFilePreviewKey(["+", "=", "-", "0", "r", "R"], (event) => {
114
+ if (event.key === "+" || event.key === "=") zoomTo(scale.value * 1.5);
115
+ else if (event.key === "-") zoomTo(scale.value / 1.5);
116
+ else if (event.key === "0") reset();
117
+ else rotate();
118
+ });
119
+ </script>
120
+
121
+ <template>
122
+ <div
123
+ ref="viewport"
124
+ :class="[
125
+ 'absolute inset-0 touch-none overflow-hidden select-none',
126
+ transparent ? 'nut-fp-checker' : '',
127
+ zoomed ? dragging ? 'cursor-grabbing' : 'cursor-grab' : 'cursor-zoom-in'
128
+ ]"
129
+ :data-file-preview-zoomed="zoomed ? '' : void 0"
130
+ data-file-preview-image=""
131
+ @dblclick="toggleZoom"
132
+ @pointerdown="startDrag"
133
+ >
134
+ <img
135
+ :src="url"
136
+ :alt="file.name"
137
+ draggable="false"
138
+ :class="[
139
+ 'absolute top-1/2 left-1/2 max-w-none origin-center',
140
+ dragging ? '' : 'transition-transform duration-150 ease-out motion-reduce:transition-none',
141
+ natural ? '' : 'opacity-0',
142
+ transparent ? '' : 'shadow-[0_1px_3px_rgba(0,0,0,0.12)]'
143
+ ]"
144
+ :style="imageStyle"
145
+ @load="handleLoad"
146
+ @error="emit('error', { reason: 'decode' })"
147
+ />
148
+
149
+ <FilePreviewTools>
150
+ <FilePreviewButton
151
+ icon="i-lucide-zoom-out"
152
+ :label="t('filePreview.image.zoomOut')"
153
+ :kbds="['-']"
154
+ :disabled="!zoomed"
155
+ @click="zoomTo(scale / 1.5)"
156
+ />
157
+ <button
158
+ type="button"
159
+ class="min-w-12 rounded-md px-1.5 py-1 text-xs font-medium text-toned tabular-nums hover:bg-elevated"
160
+ :aria-label="t('filePreview.image.fit')"
161
+ :title="t('filePreview.image.fit')"
162
+ data-file-preview-zoom=""
163
+ @click="reset"
164
+ >
165
+ {{ percent }}
166
+ </button>
167
+ <FilePreviewButton
168
+ icon="i-lucide-zoom-in"
169
+ :label="t('filePreview.image.zoomIn')"
170
+ :kbds="['+']"
171
+ :disabled="scale >= maxScale"
172
+ @click="zoomTo(scale * 1.5)"
173
+ />
174
+ <FilePreviewButton
175
+ icon="i-lucide-rotate-cw"
176
+ :label="t('filePreview.image.rotate')"
177
+ :kbds="['R']"
178
+ @click="rotate"
179
+ />
180
+ </FilePreviewTools>
181
+ </div>
182
+ </template>
@@ -0,0 +1,10 @@
1
+ import type { FilePreviewRendererProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<FilePreviewRendererProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
3
+ ready: (details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any;
4
+ error: (error: import("../../types/index.js").FilePreviewRendererError) => any;
5
+ }, string, import("vue").PublicProps, Readonly<FilePreviewRendererProps> & Readonly<{
6
+ onReady?: ((details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any) | undefined;
7
+ onError?: ((error: import("../../types/index.js").FilePreviewRendererError) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import type { FilePreviewRendererProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<FilePreviewRendererProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
3
+ ready: (details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any;
4
+ error: (error: import("../../types/index.js").FilePreviewRendererError) => any;
5
+ }, string, import("vue").PublicProps, Readonly<FilePreviewRendererProps> & Readonly<{
6
+ onReady?: ((details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any) | undefined;
7
+ onError?: ((error: import("../../types/index.js").FilePreviewRendererError) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,78 @@
1
+ <script setup>
2
+ import { computed, ref, watch } from "vue";
3
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
4
+ import { useFilePreviewShell } from "../../composables/use-file-preview-shell";
5
+ import { useFilePreviewText } from "../../composables/use-file-preview-text";
6
+ import FilePreviewTools from "../shell/file-preview-tools.vue";
7
+ import FilePreviewCode from "./file-preview-code.vue";
8
+ import FilePreviewTextLoading from "./file-preview-text-loading.vue";
9
+ const props = defineProps({
10
+ file: { type: Object, required: true },
11
+ url: { type: String, required: true },
12
+ blob: { type: null, required: true },
13
+ container: { type: String, required: true }
14
+ });
15
+ const emit = defineEmits(["ready", "error"]);
16
+ const { t } = useUiToolsLocale();
17
+ const shell = useFilePreviewShell();
18
+ const content = useFilePreviewText(props);
19
+ const view = ref(shell.markdown ? "preview" : "source");
20
+ const html = ref(null);
21
+ const facts = computed(() => content.notice.value ? [content.notice.value] : []);
22
+ watch(
23
+ content.status,
24
+ async (status) => {
25
+ if (status === "error")
26
+ emit("error", { message: content.error.value?.message, reason: "source" });
27
+ if (status !== "ready") return;
28
+ if (shell.markdown) {
29
+ try {
30
+ html.value = await shell.markdown(content.text.value);
31
+ } catch {
32
+ view.value = "source";
33
+ }
34
+ }
35
+ emit("ready");
36
+ },
37
+ { immediate: true }
38
+ );
39
+ </script>
40
+
41
+ <template>
42
+ <template v-if="content.status.value === 'ready'">
43
+ <div
44
+ v-if="view === 'preview' && html !== null"
45
+ class="absolute inset-0 overflow-auto bg-default px-6 py-7 sm:px-10"
46
+ data-file-preview-markdown=""
47
+ >
48
+ <!-- eslint-disable-next-line vue/no-v-html -- the app's markdown renderer returns sanitized HTML -->
49
+ <article class="nut-fp-prose mx-auto max-w-[68ch]" v-html="html" />
50
+ </div>
51
+ <FilePreviewCode
52
+ v-else
53
+ :text="content.text.value"
54
+ :json="false"
55
+ :facts="facts"
56
+ :tools="view === 'source'"
57
+ />
58
+
59
+ <FilePreviewTools v-if="shell.markdown">
60
+ <div class="flex gap-0.5 rounded-md border border-default bg-elevated p-0.5" role="group">
61
+ <button
62
+ v-for="option in ['preview', 'source']"
63
+ :key="option"
64
+ type="button"
65
+ :aria-pressed="view === option"
66
+ :class="[
67
+ 'rounded px-2 py-0.5 text-xs font-medium transition',
68
+ view === option ? 'bg-default text-highlighted shadow-xs ring ring-default' : 'text-muted hover:text-highlighted'
69
+ ]"
70
+ @click="view = option"
71
+ >
72
+ {{ t(`filePreview.markdown.${option}`) }}
73
+ </button>
74
+ </div>
75
+ </FilePreviewTools>
76
+ </template>
77
+ <FilePreviewTextLoading v-else />
78
+ </template>
@@ -0,0 +1,10 @@
1
+ import type { FilePreviewRendererProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<FilePreviewRendererProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
3
+ ready: (details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any;
4
+ error: (error: import("../../types/index.js").FilePreviewRendererError) => any;
5
+ }, string, import("vue").PublicProps, Readonly<FilePreviewRendererProps> & Readonly<{
6
+ onReady?: ((details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any) | undefined;
7
+ onError?: ((error: import("../../types/index.js").FilePreviewRendererError) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import type { useFullscreen, useMediaControls } from '@vueuse/core';
2
+ type __VLS_Props = {
3
+ controls: ReturnType<typeof useMediaControls>;
4
+ variant: 'video' | 'audio';
5
+ fullscreen?: ReturnType<typeof useFullscreen> | null;
6
+ compact?: boolean;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,145 @@
1
+ <script setup>
2
+ import UButton from "@nuxt/ui/components/Button.vue";
3
+ import UDropdownMenu from "@nuxt/ui/components/DropdownMenu.vue";
4
+ import USlider from "@nuxt/ui/components/Slider.vue";
5
+ import { computed } from "vue";
6
+ import { useUiToolsLocale } from "../../../i18n/use-locale";
7
+ import { formatFilePreviewDuration } from "../../utils/format";
8
+ import FilePreviewButton from "../shell/file-preview-button.vue";
9
+ const RATES = [0.5, 0.75, 1, 1.25, 1.5, 2];
10
+ const props = defineProps({
11
+ controls: { type: null, required: true },
12
+ variant: { type: String, required: true },
13
+ fullscreen: { type: null, required: false },
14
+ compact: { type: Boolean, required: false }
15
+ });
16
+ const { t } = useUiToolsLocale();
17
+ const tone = computed(() => props.variant === "video" ? "inverted" : "default");
18
+ const time = computed(
19
+ () => `${formatFilePreviewDuration(props.controls.currentTime.value)} / ${formatFilePreviewDuration(props.controls.duration.value)}`
20
+ );
21
+ const silent = computed(() => props.controls.muted.value || props.controls.volume.value === 0);
22
+ const captions = computed(() => props.controls.selectedTrack.value !== -1);
23
+ const sliderUi = computed(
24
+ () => props.variant === "video" ? { range: "bg-white", thumb: "bg-white ring-white", track: "bg-white/30" } : void 0
25
+ );
26
+ const rates = computed(
27
+ () => RATES.map((rate) => ({
28
+ checked: props.controls.rate.value === rate,
29
+ label: `${rate}\xD7`,
30
+ onSelect: () => {
31
+ props.controls.rate.value = rate;
32
+ },
33
+ type: "checkbox"
34
+ }))
35
+ );
36
+ function toggle() {
37
+ props.controls.playing.value = !props.controls.playing.value;
38
+ }
39
+ function seek(value) {
40
+ const next = Array.isArray(value) ? value[0] : value;
41
+ if (next !== void 0) props.controls.currentTime.value = next;
42
+ }
43
+ function setVolume(value) {
44
+ const next = Array.isArray(value) ? value[0] : value;
45
+ if (next === void 0) return;
46
+ props.controls.volume.value = next;
47
+ props.controls.muted.value = next === 0;
48
+ }
49
+ function toggleCaptions() {
50
+ if (captions.value) props.controls.disableTrack();
51
+ else props.controls.enableTrack(0);
52
+ }
53
+ </script>
54
+
55
+ <template>
56
+ <div
57
+ :class="[
58
+ 'flex items-center gap-1',
59
+ variant === 'video' ? 'bg-linear-to-t from-black/75 via-black/40 to-transparent px-2.5 pt-10 pb-2 text-white' : 'rounded-full border border-default bg-elevated py-1 ps-1 pe-2 text-toned'
60
+ ]"
61
+ data-file-preview-media-controls=""
62
+ >
63
+ <FilePreviewButton
64
+ :icon="controls.playing.value ? 'i-lucide-pause' : 'i-lucide-play'"
65
+ :label="controls.playing.value ? t('filePreview.media.pause') : t('filePreview.media.play')"
66
+ :kbds="['space']"
67
+ :tone="tone"
68
+ data-file-preview-play=""
69
+ @click="toggle"
70
+ />
71
+ <span class="shrink-0 px-1 text-xs whitespace-nowrap tabular-nums" data-file-preview-time="">
72
+ {{ time }}
73
+ </span>
74
+ <USlider
75
+ :model-value="controls.currentTime.value"
76
+ :min="0"
77
+ :max="Math.max(controls.duration.value, 0.1)"
78
+ :step="0.1"
79
+ size="xs"
80
+ color="neutral"
81
+ :aria-label="t('filePreview.media.seek')"
82
+ :ui="sliderUi"
83
+ class="mx-1.5 min-w-16 flex-1"
84
+ @update:model-value="seek"
85
+ />
86
+ <FilePreviewButton
87
+ :icon="silent ? 'i-lucide-volume-x' : 'i-lucide-volume-2'"
88
+ :label="silent ? t('filePreview.media.unmute') : t('filePreview.media.mute')"
89
+ :kbds="['M']"
90
+ :tone="tone"
91
+ @click="controls.muted.value = !controls.muted.value"
92
+ />
93
+ <USlider
94
+ v-if="!compact"
95
+ :model-value="silent ? 0 : controls.volume.value"
96
+ :min="0"
97
+ :max="1"
98
+ :step="0.05"
99
+ size="xs"
100
+ color="neutral"
101
+ :aria-label="t('filePreview.media.volume')"
102
+ :ui="sliderUi"
103
+ class="w-16 shrink-0"
104
+ @update:model-value="setVolume"
105
+ />
106
+ <UDropdownMenu :items="rates" :content="{ align: 'end', side: 'top' }">
107
+ <UButton
108
+ :label="`${controls.rate.value}\xD7`"
109
+ :aria-label="t('filePreview.media.speed')"
110
+ color="neutral"
111
+ variant="ghost"
112
+ size="sm"
113
+ :class="[
114
+ 'min-w-10 shrink-0 justify-center text-xs tabular-nums',
115
+ variant === 'video' ? 'text-white hover:bg-white/15 hover:text-white' : 'text-muted'
116
+ ]"
117
+ />
118
+ </UDropdownMenu>
119
+ <FilePreviewButton
120
+ v-if="controls.tracks.value.length > 0"
121
+ icon="i-lucide-captions"
122
+ :label="t('filePreview.media.captions')"
123
+ :pressed="captions"
124
+ :tone="tone"
125
+ @click="toggleCaptions"
126
+ />
127
+ <FilePreviewButton
128
+ v-if="variant === 'video' && controls.supportsPictureInPicture"
129
+ icon="i-lucide-picture-in-picture-2"
130
+ :label="t('filePreview.media.pictureInPicture')"
131
+ :tone="tone"
132
+ @click="controls.togglePictureInPicture()"
133
+ />
134
+ <FilePreviewButton
135
+ v-if="fullscreen?.isSupported.value"
136
+ :icon="fullscreen.isFullscreen.value ? 'i-lucide-minimize' : 'i-lucide-maximize'"
137
+ :label="
138
+ fullscreen.isFullscreen.value ? t('filePreview.media.exitFullscreen') : t('filePreview.media.fullscreen')
139
+ "
140
+ :kbds="['F']"
141
+ :tone="tone"
142
+ @click="fullscreen.toggle()"
143
+ />
144
+ </div>
145
+ </template>
@@ -0,0 +1,10 @@
1
+ import type { useFullscreen, useMediaControls } from '@vueuse/core';
2
+ type __VLS_Props = {
3
+ controls: ReturnType<typeof useMediaControls>;
4
+ variant: 'video' | 'audio';
5
+ fullscreen?: ReturnType<typeof useFullscreen> | null;
6
+ compact?: boolean;
7
+ };
8
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import type { FilePreviewRendererProps } from '../../types/index.js';
2
+ declare const __VLS_export: import("vue").DefineComponent<FilePreviewRendererProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
3
+ ready: (details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any;
4
+ error: (error: import("../../types/index.js").FilePreviewRendererError) => any;
5
+ }, string, import("vue").PublicProps, Readonly<FilePreviewRendererProps> & Readonly<{
6
+ onReady?: ((details?: readonly import("../../types/index.js").FilePreviewDetail[] | undefined) => any) | undefined;
7
+ onError?: ((error: import("../../types/index.js").FilePreviewRendererError) => any) | undefined;
8
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;