nuxt-media-library 0.0.48 → 0.0.49

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 (87) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/components/MLMedia/Dropzone.d.vue.ts +60 -0
  3. package/dist/runtime/components/MLMedia/Dropzone.vue +137 -0
  4. package/dist/runtime/components/MLMedia/Dropzone.vue.d.ts +60 -0
  5. package/dist/runtime/components/MLMedia/Editor.d.vue.ts +23 -0
  6. package/dist/runtime/components/MLMedia/Editor.vue +34 -0
  7. package/dist/runtime/components/MLMedia/Editor.vue.d.ts +23 -0
  8. package/dist/runtime/components/MLMedia/EditorPanel.d.vue.ts +38 -0
  9. package/dist/runtime/components/MLMedia/EditorPanel.vue +220 -0
  10. package/dist/runtime/components/MLMedia/EditorPanel.vue.d.ts +38 -0
  11. package/dist/runtime/components/MLMedia/Field.d.vue.ts +65 -0
  12. package/dist/runtime/components/MLMedia/Field.vue +282 -0
  13. package/dist/runtime/components/MLMedia/Field.vue.d.ts +65 -0
  14. package/dist/runtime/components/MLMedia/FileFacts.d.vue.ts +19 -0
  15. package/dist/runtime/components/MLMedia/FileFacts.vue +38 -0
  16. package/dist/runtime/components/MLMedia/FileFacts.vue.d.ts +19 -0
  17. package/dist/runtime/components/MLMedia/FilePicker.d.vue.ts +23 -0
  18. package/dist/runtime/components/MLMedia/FilePicker.vue +33 -0
  19. package/dist/runtime/components/MLMedia/FilePicker.vue.d.ts +23 -0
  20. package/dist/runtime/components/MLMedia/FilePickerPanel.d.vue.ts +33 -0
  21. package/dist/runtime/components/MLMedia/FilePickerPanel.vue +300 -0
  22. package/dist/runtime/components/MLMedia/FilePickerPanel.vue.d.ts +33 -0
  23. package/dist/runtime/components/MLMedia/FilterPill.d.vue.ts +30 -0
  24. package/dist/runtime/components/MLMedia/FilterPill.vue +84 -0
  25. package/dist/runtime/components/MLMedia/FilterPill.vue.d.ts +30 -0
  26. package/dist/runtime/components/MLMedia/Gallery.d.vue.ts +78 -0
  27. package/dist/runtime/components/MLMedia/Gallery.vue +256 -0
  28. package/dist/runtime/components/MLMedia/Gallery.vue.d.ts +78 -0
  29. package/dist/runtime/components/MLMedia/GallerySection.d.vue.ts +102 -0
  30. package/dist/runtime/components/MLMedia/GallerySection.vue +422 -0
  31. package/dist/runtime/components/MLMedia/GallerySection.vue.d.ts +102 -0
  32. package/dist/runtime/components/MLMedia/KindBadge.d.vue.ts +17 -0
  33. package/dist/runtime/components/MLMedia/KindBadge.vue +22 -0
  34. package/dist/runtime/components/MLMedia/KindBadge.vue.d.ts +17 -0
  35. package/dist/runtime/components/MLMedia/MediaTile.d.vue.ts +63 -0
  36. package/dist/runtime/components/MLMedia/MediaTile.vue +117 -0
  37. package/dist/runtime/components/MLMedia/MediaTile.vue.d.ts +63 -0
  38. package/dist/runtime/components/MLMedia/PickerCard.d.vue.ts +42 -0
  39. package/dist/runtime/components/MLMedia/PickerCard.vue +79 -0
  40. package/dist/runtime/components/MLMedia/PickerCard.vue.d.ts +42 -0
  41. package/dist/runtime/components/MLMedia/PickerRow.d.vue.ts +31 -0
  42. package/dist/runtime/components/MLMedia/PickerRow.vue +65 -0
  43. package/dist/runtime/components/MLMedia/PickerRow.vue.d.ts +31 -0
  44. package/dist/runtime/components/MLMedia/PreviewEye.d.vue.ts +16 -0
  45. package/dist/runtime/components/MLMedia/PreviewEye.vue +21 -0
  46. package/dist/runtime/components/MLMedia/PreviewEye.vue.d.ts +16 -0
  47. package/dist/runtime/components/MLMedia/ProgressVeil.d.vue.ts +25 -0
  48. package/dist/runtime/components/MLMedia/ProgressVeil.vue +98 -0
  49. package/dist/runtime/components/MLMedia/ProgressVeil.vue.d.ts +25 -0
  50. package/dist/runtime/components/MLMedia/StatusBadge.d.vue.ts +17 -0
  51. package/dist/runtime/components/MLMedia/StatusBadge.vue +31 -0
  52. package/dist/runtime/components/MLMedia/StatusBadge.vue.d.ts +17 -0
  53. package/dist/runtime/components/MLMedia/Thumbnail.d.vue.ts +36 -0
  54. package/dist/runtime/components/MLMedia/Thumbnail.vue +84 -0
  55. package/dist/runtime/components/MLMedia/Thumbnail.vue.d.ts +36 -0
  56. package/dist/runtime/components/MLMedia/UsageList.d.vue.ts +26 -0
  57. package/dist/runtime/components/MLMedia/UsageList.vue +66 -0
  58. package/dist/runtime/components/MLMedia/UsageList.vue.d.ts +26 -0
  59. package/dist/runtime/components/MLProcessing/StepLayout.d.vue.ts +1 -1
  60. package/dist/runtime/components/MLProcessing/StepLayout.vue.d.ts +1 -1
  61. package/dist/runtime/composables/useMLFilePicker.d.ts +20 -0
  62. package/dist/runtime/composables/useMLFilePicker.js +17 -0
  63. package/dist/runtime/composables/useMLMediaAssets.d.ts +19 -0
  64. package/dist/runtime/composables/useMLMediaAssets.js +56 -0
  65. package/dist/runtime/composables/useMLMediaBrowse.d.ts +147 -0
  66. package/dist/runtime/composables/useMLMediaBrowse.js +72 -0
  67. package/dist/runtime/composables/useMLMediaEditor.d.ts +22 -0
  68. package/dist/runtime/composables/useMLMediaEditor.js +20 -0
  69. package/dist/runtime/composables/useMLMediaField.d.ts +29 -0
  70. package/dist/runtime/composables/useMLMediaField.js +77 -0
  71. package/dist/runtime/composables/useMLMediaGallery.d.ts +55 -0
  72. package/dist/runtime/composables/useMLMediaGallery.js +134 -0
  73. package/dist/runtime/composables/useMLMediaUpload.d.ts +134 -0
  74. package/dist/runtime/composables/useMLMediaUpload.js +153 -0
  75. package/dist/runtime/index.d.ts +11 -0
  76. package/dist/runtime/index.js +11 -0
  77. package/dist/runtime/utils/assetDisplay.d.ts +8 -0
  78. package/dist/runtime/utils/assetDisplay.js +29 -0
  79. package/dist/runtime/utils/assetFormat.d.ts +30 -0
  80. package/dist/runtime/utils/assetFormat.js +69 -0
  81. package/dist/runtime/utils/assetThumbnail.d.ts +11 -0
  82. package/dist/runtime/utils/assetThumbnail.js +14 -0
  83. package/dist/runtime/utils/fileAccept.d.ts +26 -0
  84. package/dist/runtime/utils/fileAccept.js +104 -0
  85. package/dist/runtime/utils/galleryDrag.d.ts +2 -0
  86. package/dist/runtime/utils/galleryDrag.js +1 -0
  87. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "media-library",
3
3
  "configKey": "mediaLibrary",
4
- "version": "0.0.48",
4
+ "version": "0.0.49",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -0,0 +1,60 @@
1
+ import { type RejectedFile } from '../../utils/fileAccept.js';
2
+ type __VLS_Props = {
3
+ /** Browser accept syntax ("image/*,.glb"). Build one from kinds with acceptForKinds(). */
4
+ accept?: string;
5
+ multiple?: boolean;
6
+ /** Bytes. Larger files are rejected with reason "size". */
7
+ maxSize?: number | null;
8
+ /** How many more files fit. Extra files are rejected with reason "count". */
9
+ maxFiles?: number | null;
10
+ disabled?: boolean;
11
+ /**
12
+ * `area`: a dashed box with an upload button (fields, picker).
13
+ * `overlay`: wraps the default slot and only shows a highlight while files are dragged over it (gallery).
14
+ */
15
+ variant?: 'area' | 'overlay';
16
+ title?: string;
17
+ hint?: string;
18
+ /** The button that opens the computer's file dialog. Defaults to "Upload file(s)". */
19
+ buttonLabel?: string;
20
+ /** One short row instead of a box, for places where the drop zone is secondary (the picker). */
21
+ compact?: boolean;
22
+ };
23
+ type __VLS_Slots = {
24
+ default?: (props: {
25
+ isDragging: boolean;
26
+ open: () => void;
27
+ }) => unknown;
28
+ actions?: (props: {
29
+ open: () => void;
30
+ }) => unknown;
31
+ };
32
+ declare function open(): void;
33
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
34
+ open: typeof open;
35
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ files: (files: File[]) => any;
37
+ rejected: (files: RejectedFile[]) => any;
38
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
39
+ onFiles?: ((files: File[]) => any) | undefined;
40
+ onRejected?: ((files: RejectedFile[]) => any) | undefined;
41
+ }>, {
42
+ title: string;
43
+ accept: string;
44
+ maxSize: number | null;
45
+ variant: "area" | "overlay";
46
+ compact: boolean;
47
+ disabled: boolean;
48
+ multiple: boolean;
49
+ maxFiles: number | null;
50
+ hint: string;
51
+ buttonLabel: string;
52
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
53
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
54
+ declare const _default: typeof __VLS_export;
55
+ export default _default;
56
+ type __VLS_WithSlots<T, S> = T & {
57
+ new (): {
58
+ $slots: S;
59
+ };
60
+ };
@@ -0,0 +1,137 @@
1
+ <script setup>
2
+ import { computed, ref } from "vue";
3
+ import { useFileUpload } from "#imports";
4
+ import { formatBytes } from "../../utils/assetFormat";
5
+ import { partitionFiles } from "../../utils/fileAccept";
6
+ const props = defineProps({
7
+ accept: { type: String, required: false, default: "*" },
8
+ multiple: { type: Boolean, required: false, default: true },
9
+ maxSize: { type: [Number, null], required: false, default: null },
10
+ maxFiles: { type: [Number, null], required: false, default: null },
11
+ disabled: { type: Boolean, required: false, default: false },
12
+ variant: { type: String, required: false, default: "area" },
13
+ title: { type: String, required: false, default: void 0 },
14
+ hint: { type: String, required: false, default: void 0 },
15
+ buttonLabel: { type: String, required: false, default: void 0 },
16
+ compact: { type: Boolean, required: false, default: false }
17
+ });
18
+ const emit = defineEmits(["files", "rejected"]);
19
+ defineSlots();
20
+ const { isDragging, dropzoneRef } = useFileUpload({
21
+ accept: "*",
22
+ multiple: true,
23
+ dropzone: true,
24
+ onUpdate: (files) => handle(files)
25
+ });
26
+ const input = ref();
27
+ function handle(files) {
28
+ if (props.disabled || !files.length) return;
29
+ const max = props.multiple ? props.maxFiles : Math.min(1, props.maxFiles ?? 1);
30
+ const { accepted, rejected } = partitionFiles(files, { accept: props.accept, maxSize: props.maxSize, maxFiles: max });
31
+ if (accepted.length) emit("files", accepted);
32
+ if (rejected.length) emit("rejected", rejected);
33
+ }
34
+ function open() {
35
+ if (!props.disabled) input.value?.click();
36
+ }
37
+ function onInput(e) {
38
+ const el = e.target;
39
+ handle(Array.from(el.files ?? []));
40
+ el.value = "";
41
+ }
42
+ const carriesFiles = ref(false);
43
+ function onDragEnter(e) {
44
+ carriesFiles.value = !!e.dataTransfer && Array.from(e.dataTransfer.types).includes("Files");
45
+ }
46
+ const active = computed(() => isDragging.value && carriesFiles.value && !props.disabled);
47
+ const sizeHint = computed(() => props.maxSize ? `Up to ${formatBytes(props.maxSize)}` : "");
48
+ defineExpose({ open });
49
+ </script>
50
+
51
+ <template>
52
+ <div
53
+ ref="dropzoneRef"
54
+ class="relative"
55
+ @dragenter.capture="onDragEnter"
56
+ :data-dragging="active || void 0"
57
+ :aria-disabled="disabled || void 0"
58
+ >
59
+ <input
60
+ ref="input"
61
+ type="file"
62
+ class="sr-only"
63
+ tabindex="-1"
64
+ aria-hidden="true"
65
+ :accept="accept === '*' ? void 0 : accept"
66
+ :multiple="multiple"
67
+ :disabled="disabled"
68
+ @change="onInput"
69
+ >
70
+
71
+ <template v-if="variant === 'overlay'">
72
+ <slot
73
+ :is-dragging="active"
74
+ :open="open"
75
+ />
76
+ <div
77
+ v-if="active"
78
+ class="pointer-events-none absolute inset-0 z-20 grid place-items-center rounded-[inherit] border-2 border-dashed border-primary bg-primary/10 backdrop-blur-[1px]"
79
+ >
80
+ <span class="flex items-center gap-2 rounded-md bg-default px-3 py-1.5 text-sm font-semibold text-primary shadow-sm">
81
+ <UIcon
82
+ name="i-lucide-upload"
83
+ class="size-4"
84
+ />
85
+ {{ title ?? "Drop to upload" }}
86
+ </span>
87
+ </div>
88
+ </template>
89
+
90
+ <div
91
+ v-else
92
+ class="flex items-center rounded-lg border-[1.5px] border-dashed text-center transition-colors motion-reduce:transition-none"
93
+ :class="[
94
+ compact ? 'flex-row flex-wrap justify-center gap-x-3 gap-y-1.5 px-3 py-2.5' : 'flex-col gap-2.5 px-4 py-5',
95
+ active ? 'border-primary bg-primary/10' : 'border-accented bg-default',
96
+ disabled ? 'opacity-60' : ''
97
+ ]"
98
+ >
99
+ <slot
100
+ :is-dragging="active"
101
+ :open="open"
102
+ >
103
+ <UIcon
104
+ v-if="!compact"
105
+ name="i-lucide-upload"
106
+ class="size-5"
107
+ :class="active ? 'text-primary' : 'text-dimmed'"
108
+ />
109
+ <p class="text-sm">
110
+ <span :class="compact ? 'text-muted' : 'font-semibold'">{{ title ?? (multiple ? "Drop files here" : "Drop a file here") }}</span>
111
+ <span class="text-muted"> or</span>
112
+ </p>
113
+ <div class="flex flex-wrap justify-center gap-1.5">
114
+ <UButton
115
+ size="sm"
116
+ color="neutral"
117
+ variant="outline"
118
+ icon="i-lucide-upload"
119
+ :label="buttonLabel ?? (multiple ? 'Upload files' : 'Upload file')"
120
+ :disabled="disabled"
121
+ @click="open"
122
+ />
123
+ <slot
124
+ name="actions"
125
+ :open="open"
126
+ />
127
+ </div>
128
+ <p
129
+ v-if="hint || sizeHint"
130
+ class="text-xs text-dimmed"
131
+ >
132
+ {{ [hint, sizeHint].filter(Boolean).join(" \xB7 ") }}
133
+ </p>
134
+ </slot>
135
+ </div>
136
+ </div>
137
+ </template>
@@ -0,0 +1,60 @@
1
+ import { type RejectedFile } from '../../utils/fileAccept.js';
2
+ type __VLS_Props = {
3
+ /** Browser accept syntax ("image/*,.glb"). Build one from kinds with acceptForKinds(). */
4
+ accept?: string;
5
+ multiple?: boolean;
6
+ /** Bytes. Larger files are rejected with reason "size". */
7
+ maxSize?: number | null;
8
+ /** How many more files fit. Extra files are rejected with reason "count". */
9
+ maxFiles?: number | null;
10
+ disabled?: boolean;
11
+ /**
12
+ * `area`: a dashed box with an upload button (fields, picker).
13
+ * `overlay`: wraps the default slot and only shows a highlight while files are dragged over it (gallery).
14
+ */
15
+ variant?: 'area' | 'overlay';
16
+ title?: string;
17
+ hint?: string;
18
+ /** The button that opens the computer's file dialog. Defaults to "Upload file(s)". */
19
+ buttonLabel?: string;
20
+ /** One short row instead of a box, for places where the drop zone is secondary (the picker). */
21
+ compact?: boolean;
22
+ };
23
+ type __VLS_Slots = {
24
+ default?: (props: {
25
+ isDragging: boolean;
26
+ open: () => void;
27
+ }) => unknown;
28
+ actions?: (props: {
29
+ open: () => void;
30
+ }) => unknown;
31
+ };
32
+ declare function open(): void;
33
+ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
34
+ open: typeof open;
35
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
36
+ files: (files: File[]) => any;
37
+ rejected: (files: RejectedFile[]) => any;
38
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
39
+ onFiles?: ((files: File[]) => any) | undefined;
40
+ onRejected?: ((files: RejectedFile[]) => any) | undefined;
41
+ }>, {
42
+ title: string;
43
+ accept: string;
44
+ maxSize: number | null;
45
+ variant: "area" | "overlay";
46
+ compact: boolean;
47
+ disabled: boolean;
48
+ multiple: boolean;
49
+ maxFiles: number | null;
50
+ hint: string;
51
+ buttonLabel: string;
52
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
53
+ declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
54
+ declare const _default: typeof __VLS_export;
55
+ export default _default;
56
+ type __VLS_WithSlots<T, S> = T & {
57
+ new (): {
58
+ $slots: S;
59
+ };
60
+ };
@@ -0,0 +1,23 @@
1
+ import type { MediaUsage } from './UsageList.vue.js';
2
+ export type MediaEditorResult = {
3
+ action: 'save';
4
+ alt: Record<string, string>;
5
+ } | {
6
+ action: 'remove';
7
+ assetId: string;
8
+ };
9
+ type __VLS_Props = {
10
+ assetId: string;
11
+ siblings?: readonly string[];
12
+ alt?: Readonly<Record<string, string>>;
13
+ readonly?: boolean;
14
+ removeLabel?: string;
15
+ usages?: Readonly<Record<string, MediaUsage[]>>;
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
18
+ close: (result: MediaEditorResult | null) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ onClose?: ((result: MediaEditorResult | null) => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
@@ -0,0 +1,34 @@
1
+ <script setup>
2
+ defineProps({
3
+ assetId: { type: String, required: true },
4
+ siblings: { type: Array, required: false },
5
+ alt: { type: Object, required: false },
6
+ readonly: { type: Boolean, required: false },
7
+ removeLabel: { type: String, required: false },
8
+ usages: { type: Object, required: false }
9
+ });
10
+ const emit = defineEmits(["close"]);
11
+ </script>
12
+
13
+ <template>
14
+ <UModal
15
+ :title="readonly ? 'Preview' : 'Edit media'"
16
+ :close="{ onClick: () => emit('close', null) }"
17
+ :ui="{ content: 'sm:max-w-5xl', body: 'p-0 sm:p-0' }"
18
+ @update:open="(open) => !open && emit('close', null)"
19
+ >
20
+ <template #body>
21
+ <MLMediaEditorPanel
22
+ :asset-id="assetId"
23
+ :siblings="siblings"
24
+ :alt="alt"
25
+ :readonly="readonly"
26
+ :remove-label="removeLabel"
27
+ :usages="usages"
28
+ @save="({ alt: a }) => emit('close', { action: 'save', alt: a })"
29
+ @remove="(id) => emit('close', { action: 'remove', assetId: id })"
30
+ @cancel="emit('close', null)"
31
+ />
32
+ </template>
33
+ </UModal>
34
+ </template>
@@ -0,0 +1,23 @@
1
+ import type { MediaUsage } from './UsageList.vue.js';
2
+ export type MediaEditorResult = {
3
+ action: 'save';
4
+ alt: Record<string, string>;
5
+ } | {
6
+ action: 'remove';
7
+ assetId: string;
8
+ };
9
+ type __VLS_Props = {
10
+ assetId: string;
11
+ siblings?: readonly string[];
12
+ alt?: Readonly<Record<string, string>>;
13
+ readonly?: boolean;
14
+ removeLabel?: string;
15
+ usages?: Readonly<Record<string, MediaUsage[]>>;
16
+ };
17
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
18
+ close: (result: MediaEditorResult | null) => any;
19
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
20
+ onClose?: ((result: MediaEditorResult | null) => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
@@ -0,0 +1,38 @@
1
+ import type { MediaUsage } from './UsageList.vue.js';
2
+ type __VLS_Props = {
3
+ /** The file to show first. */
4
+ assetId: string;
5
+ /** The record's other files, in order. Enables previous / next. */
6
+ siblings?: readonly string[];
7
+ /** Current alt text per asset ID. Alt text lives on the host record, not the file. */
8
+ alt?: Readonly<Record<string, string>>;
9
+ /** Preview only: no alt editing, no Save. Used from the picker. */
10
+ readonly?: boolean;
11
+ /** Label for the remove button, e.g. "Remove from product". Empty hides it. */
12
+ removeLabel?: string;
13
+ /** Where each file is used, per asset ID. Omit to hide the section. */
14
+ usages?: Readonly<Record<string, MediaUsage[]>>;
15
+ };
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
17
+ currentId: import("vue").ComputedRef<string>;
18
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ remove: (assetId: string) => any;
20
+ cancel: () => any;
21
+ save: (result: {
22
+ alt: Record<string, string>;
23
+ }) => any;
24
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
25
+ onRemove?: ((assetId: string) => any) | undefined;
26
+ onCancel?: (() => any) | undefined;
27
+ onSave?: ((result: {
28
+ alt: Record<string, string>;
29
+ }) => any) | undefined;
30
+ }>, {
31
+ usages: Readonly<Record<string, MediaUsage[]>>;
32
+ siblings: readonly string[];
33
+ alt: Readonly<Record<string, string>>;
34
+ readonly: boolean;
35
+ removeLabel: string;
36
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
37
+ declare const _default: typeof __VLS_export;
38
+ export default _default;
@@ -0,0 +1,220 @@
1
+ <script setup>
2
+ import { computed, ref, watch } from "vue";
3
+ import { useMLMediaAssets } from "../../composables/useMLMediaAssets";
4
+ import { assetName } from "../../utils/assetFormat";
5
+ const props = defineProps({
6
+ assetId: { type: String, required: true },
7
+ siblings: { type: Array, required: false, default: () => [] },
8
+ alt: { type: Object, required: false, default: () => ({}) },
9
+ readonly: { type: Boolean, required: false, default: false },
10
+ removeLabel: { type: String, required: false, default: "" },
11
+ usages: { type: Object, required: false, default: void 0 }
12
+ });
13
+ const emit = defineEmits(["save", "remove", "cancel"]);
14
+ const list = computed(() => {
15
+ const s = props.siblings.filter(Boolean);
16
+ return s.includes(props.assetId) ? s : [props.assetId, ...s];
17
+ });
18
+ const index = ref(Math.max(0, list.value.indexOf(props.assetId)));
19
+ const currentId = computed(() => list.value[index.value] ?? props.assetId);
20
+ const library = useMLMediaAssets(list);
21
+ const asset = computed(() => library.get(currentId.value));
22
+ const lookup = computed(() => library.lookup(currentId.value));
23
+ const name = computed(() => asset.value ? assetName(asset.value) : "");
24
+ const kind = computed(() => (asset.value?.kind ?? "").toLowerCase());
25
+ const takesAlt = computed(() => kind.value === "image" || kind.value === "video");
26
+ const alts = ref({ ...props.alt });
27
+ const dirty = computed(() => list.value.some((id) => (alts.value[id] ?? "") !== (props.alt[id] ?? "")));
28
+ const altModel = computed({
29
+ get: () => alts.value[currentId.value] ?? "",
30
+ set: (v) => {
31
+ alts.value = { ...alts.value, [currentId.value]: v };
32
+ }
33
+ });
34
+ const canPrev = computed(() => index.value > 0);
35
+ const canNext = computed(() => index.value < list.value.length - 1);
36
+ const go = (step) => {
37
+ index.value = Math.min(list.value.length - 1, Math.max(0, index.value + step));
38
+ };
39
+ function onKey(e) {
40
+ const t = e.target;
41
+ if (t && (t.tagName === "TEXTAREA" || t.tagName === "INPUT" || t.isContentEditable)) return;
42
+ if (e.key === "ArrowLeft" && canPrev.value) go(-1);
43
+ else if (e.key === "ArrowRight" && canNext.value) go(1);
44
+ }
45
+ const confirmRemove = ref(false);
46
+ watch(currentId, () => {
47
+ confirmRemove.value = false;
48
+ });
49
+ const usagesHere = computed(() => props.usages?.[currentId.value]);
50
+ const originalUrl = computed(() => asset.value?.previewUrl ?? null);
51
+ defineExpose({ currentId });
52
+ </script>
53
+
54
+ <template>
55
+ <div
56
+ class="flex min-h-0 flex-col"
57
+ @keydown="onKey"
58
+ >
59
+ <div class="grid min-h-0 md:grid-cols-[minmax(0,1fr)_18rem]">
60
+ <!-- Stage -->
61
+ <div class="relative grid min-h-72 place-items-center bg-neutral-950 px-12 py-10 md:min-h-[26rem]">
62
+ <USkeleton
63
+ v-if="lookup === 'loading'"
64
+ class="aspect-[4/3] w-full max-w-lg rounded-md bg-white/10"
65
+ />
66
+ <div
67
+ v-else-if="!asset"
68
+ class="flex flex-col items-center gap-2 text-center text-sm text-neutral-300"
69
+ >
70
+ <UIcon
71
+ name="i-lucide-file-x"
72
+ class="size-8 text-neutral-400"
73
+ />
74
+ {{ lookup === "forbidden" ? "You don't have access to this file." : "This file could not be found." }}
75
+ </div>
76
+ <div
77
+ v-else
78
+ :key="currentId"
79
+ class="h-[min(52vh,26rem)] w-full max-w-2xl"
80
+ >
81
+ <MLAssetManagerAssetPreview :asset="asset" />
82
+ </div>
83
+
84
+ <UButton
85
+ v-if="list.length > 1"
86
+ icon="i-lucide-chevron-left"
87
+ color="neutral"
88
+ variant="soft"
89
+ class="absolute left-2 top-1/2 -translate-y-1/2 rounded-full"
90
+ aria-label="Previous file"
91
+ :disabled="!canPrev"
92
+ @click="go(-1)"
93
+ />
94
+ <UButton
95
+ v-if="list.length > 1"
96
+ icon="i-lucide-chevron-right"
97
+ color="neutral"
98
+ variant="soft"
99
+ class="absolute right-2 top-1/2 -translate-y-1/2 rounded-full"
100
+ aria-label="Next file"
101
+ :disabled="!canNext"
102
+ @click="go(1)"
103
+ />
104
+ <span
105
+ v-if="list.length > 1"
106
+ class="absolute bottom-2.5 left-1/2 -translate-x-1/2 text-xs text-neutral-300 tabular-nums"
107
+ aria-live="polite"
108
+ >{{ index + 1 }} of {{ list.length }}</span>
109
+ </div>
110
+
111
+ <!-- Side panel -->
112
+ <div class="flex min-w-0 flex-col gap-5 border-t border-default p-4 md:border-l md:border-t-0">
113
+ <div class="flex min-w-0 items-start gap-2">
114
+ <div class="flex min-w-0 flex-1 flex-col gap-1">
115
+ <span class="truncate text-sm font-semibold">{{ name || "\u2014" }}</span>
116
+ <div
117
+ v-if="asset"
118
+ class="flex flex-wrap gap-1"
119
+ >
120
+ <MLMediaKindBadge :kind="asset.kind" />
121
+ <MLMediaStatusBadge
122
+ v-if="asset.status !== 'ready'"
123
+ :status="asset.status"
124
+ />
125
+ </div>
126
+ </div>
127
+ <UButton
128
+ v-if="originalUrl"
129
+ :to="originalUrl"
130
+ target="_blank"
131
+ rel="noopener"
132
+ icon="i-lucide-external-link"
133
+ color="neutral"
134
+ variant="ghost"
135
+ size="sm"
136
+ aria-label="Open the original in a new tab"
137
+ />
138
+ </div>
139
+
140
+ <UFormField
141
+ v-if="takesAlt && !readonly"
142
+ label="Alt text"
143
+ :help="kind === 'image' ? 'Describes the image for screen readers and search.' : 'Describes the video for screen readers.'"
144
+ >
145
+ <UTextarea
146
+ v-model="altModel"
147
+ :rows="3"
148
+ autoresize
149
+ class="w-full"
150
+ :placeholder="kind === 'image' ? 'e.g. Oak lounge chair, front view on a light background' : ''"
151
+ />
152
+ </UFormField>
153
+ <p
154
+ v-else-if="takesAlt && altModel"
155
+ class="text-sm"
156
+ >
157
+ <span class="block text-[11px] font-semibold uppercase tracking-wider text-dimmed">Alt text</span>
158
+ {{ altModel }}
159
+ </p>
160
+
161
+ <MLMediaFileFacts :asset="asset" />
162
+
163
+ <MLMediaUsageList
164
+ v-if="usagesHere"
165
+ :usages="usagesHere"
166
+ />
167
+ </div>
168
+ </div>
169
+
170
+ <div class="flex flex-wrap items-center gap-2 border-t border-default px-4 py-3">
171
+ <template v-if="removeLabel && !readonly">
172
+ <UButton
173
+ v-if="!confirmRemove"
174
+ color="error"
175
+ variant="ghost"
176
+ icon="i-lucide-x"
177
+ :label="removeLabel"
178
+ @click="confirmRemove = true"
179
+ />
180
+ <template v-else>
181
+ <span class="text-sm text-muted">The file stays in the library.</span>
182
+ <UButton
183
+ color="error"
184
+ variant="soft"
185
+ :label="removeLabel"
186
+ @click="emit('remove', currentId)"
187
+ />
188
+ <UButton
189
+ color="neutral"
190
+ variant="ghost"
191
+ label="Keep"
192
+ @click="confirmRemove = false"
193
+ />
194
+ </template>
195
+ </template>
196
+ <span class="me-auto" />
197
+ <template v-if="readonly">
198
+ <UButton
199
+ color="neutral"
200
+ variant="outline"
201
+ label="Close"
202
+ @click="emit('cancel')"
203
+ />
204
+ </template>
205
+ <template v-else>
206
+ <UButton
207
+ color="neutral"
208
+ variant="outline"
209
+ label="Cancel"
210
+ @click="emit('cancel')"
211
+ />
212
+ <UButton
213
+ label="Save"
214
+ :disabled="!dirty"
215
+ @click="emit('save', { alt: { ...alts } })"
216
+ />
217
+ </template>
218
+ </div>
219
+ </div>
220
+ </template>
@@ -0,0 +1,38 @@
1
+ import type { MediaUsage } from './UsageList.vue.js';
2
+ type __VLS_Props = {
3
+ /** The file to show first. */
4
+ assetId: string;
5
+ /** The record's other files, in order. Enables previous / next. */
6
+ siblings?: readonly string[];
7
+ /** Current alt text per asset ID. Alt text lives on the host record, not the file. */
8
+ alt?: Readonly<Record<string, string>>;
9
+ /** Preview only: no alt editing, no Save. Used from the picker. */
10
+ readonly?: boolean;
11
+ /** Label for the remove button, e.g. "Remove from product". Empty hides it. */
12
+ removeLabel?: string;
13
+ /** Where each file is used, per asset ID. Omit to hide the section. */
14
+ usages?: Readonly<Record<string, MediaUsage[]>>;
15
+ };
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
17
+ currentId: import("vue").ComputedRef<string>;
18
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
19
+ remove: (assetId: string) => any;
20
+ cancel: () => any;
21
+ save: (result: {
22
+ alt: Record<string, string>;
23
+ }) => any;
24
+ }, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
25
+ onRemove?: ((assetId: string) => any) | undefined;
26
+ onCancel?: (() => any) | undefined;
27
+ onSave?: ((result: {
28
+ alt: Record<string, string>;
29
+ }) => any) | undefined;
30
+ }>, {
31
+ usages: Readonly<Record<string, MediaUsage[]>>;
32
+ siblings: readonly string[];
33
+ alt: Readonly<Record<string, string>>;
34
+ readonly: boolean;
35
+ removeLabel: string;
36
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
37
+ declare const _default: typeof __VLS_export;
38
+ export default _default;