@useupup/svelte 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/UpupUploader.svelte +96 -0
  4. package/dist/UpupUploader.svelte.d.ts +5 -0
  5. package/dist/UpupUploader.svelte.d.ts.map +1 -0
  6. package/dist/assets/logos.d.ts +5 -0
  7. package/dist/assets/logos.d.ts.map +1 -0
  8. package/dist/assets/logos.js +4 -0
  9. package/dist/components/AudioUploader.svelte +220 -0
  10. package/dist/components/AudioUploader.svelte.d.ts +4 -0
  11. package/dist/components/AudioUploader.svelte.d.ts.map +1 -0
  12. package/dist/components/AudioWaveform.svelte +72 -0
  13. package/dist/components/AudioWaveform.svelte.d.ts +7 -0
  14. package/dist/components/AudioWaveform.svelte.d.ts.map +1 -0
  15. package/dist/components/BoxUploader.svelte +17 -0
  16. package/dist/components/BoxUploader.svelte.d.ts +19 -0
  17. package/dist/components/BoxUploader.svelte.d.ts.map +1 -0
  18. package/dist/components/CameraUploader.svelte +140 -0
  19. package/dist/components/CameraUploader.svelte.d.ts +4 -0
  20. package/dist/components/CameraUploader.svelte.d.ts.map +1 -0
  21. package/dist/components/ClientBoxUploader.svelte +60 -0
  22. package/dist/components/ClientBoxUploader.svelte.d.ts +19 -0
  23. package/dist/components/ClientBoxUploader.svelte.d.ts.map +1 -0
  24. package/dist/components/ClientDropboxUploader.svelte +60 -0
  25. package/dist/components/ClientDropboxUploader.svelte.d.ts +19 -0
  26. package/dist/components/ClientDropboxUploader.svelte.d.ts.map +1 -0
  27. package/dist/components/ClientGoogleDriveUploader.svelte +57 -0
  28. package/dist/components/ClientGoogleDriveUploader.svelte.d.ts +19 -0
  29. package/dist/components/ClientGoogleDriveUploader.svelte.d.ts.map +1 -0
  30. package/dist/components/ClientOneDriveUploader.svelte +60 -0
  31. package/dist/components/ClientOneDriveUploader.svelte.d.ts +19 -0
  32. package/dist/components/ClientOneDriveUploader.svelte.d.ts.map +1 -0
  33. package/dist/components/DefaultAddMoreIcon.svelte +11 -0
  34. package/dist/components/DefaultAddMoreIcon.svelte.d.ts +8 -0
  35. package/dist/components/DefaultAddMoreIcon.svelte.d.ts.map +1 -0
  36. package/dist/components/DefaultLoaderIcon.svelte +5 -0
  37. package/dist/components/DefaultLoaderIcon.svelte.d.ts +19 -0
  38. package/dist/components/DefaultLoaderIcon.svelte.d.ts.map +1 -0
  39. package/dist/components/DropboxUploader.svelte +17 -0
  40. package/dist/components/DropboxUploader.svelte.d.ts +19 -0
  41. package/dist/components/DropboxUploader.svelte.d.ts.map +1 -0
  42. package/dist/components/EmptyIcon.svelte +3 -0
  43. package/dist/components/EmptyIcon.svelte.d.ts +19 -0
  44. package/dist/components/EmptyIcon.svelte.d.ts.map +1 -0
  45. package/dist/components/FileHero.svelte +152 -0
  46. package/dist/components/FileHero.svelte.d.ts +8 -0
  47. package/dist/components/FileHero.svelte.d.ts.map +1 -0
  48. package/dist/components/FileIcon.svelte +23 -0
  49. package/dist/components/FileIcon.svelte.d.ts +8 -0
  50. package/dist/components/FileIcon.svelte.d.ts.map +1 -0
  51. package/dist/components/FileItem.svelte +95 -0
  52. package/dist/components/FileItem.svelte.d.ts +12 -0
  53. package/dist/components/FileItem.svelte.d.ts.map +1 -0
  54. package/dist/components/FileList.svelte +442 -0
  55. package/dist/components/FileList.svelte.d.ts +4 -0
  56. package/dist/components/FileList.svelte.d.ts.map +1 -0
  57. package/dist/components/FilePreview.svelte +218 -0
  58. package/dist/components/FilePreview.svelte.d.ts +17 -0
  59. package/dist/components/FilePreview.svelte.d.ts.map +1 -0
  60. package/dist/components/FilePreviewPortal.svelte +189 -0
  61. package/dist/components/FilePreviewPortal.svelte.d.ts +12 -0
  62. package/dist/components/FilePreviewPortal.svelte.d.ts.map +1 -0
  63. package/dist/components/FilePreviewThumbnail.svelte +87 -0
  64. package/dist/components/FilePreviewThumbnail.svelte.d.ts +17 -0
  65. package/dist/components/FilePreviewThumbnail.svelte.d.ts.map +1 -0
  66. package/dist/components/FileRow.svelte +192 -0
  67. package/dist/components/FileRow.svelte.d.ts +10 -0
  68. package/dist/components/FileRow.svelte.d.ts.map +1 -0
  69. package/dist/components/GoogleDriveUploader.svelte +17 -0
  70. package/dist/components/GoogleDriveUploader.svelte.d.ts +19 -0
  71. package/dist/components/GoogleDriveUploader.svelte.d.ts.map +1 -0
  72. package/dist/components/Icon.svelte +24 -0
  73. package/dist/components/Icon.svelte.d.ts +10 -0
  74. package/dist/components/Icon.svelte.d.ts.map +1 -0
  75. package/dist/components/Icons.d.ts +10 -0
  76. package/dist/components/Icons.d.ts.map +1 -0
  77. package/dist/components/Icons.js +9 -0
  78. package/dist/components/ImageEditorStub.svelte +3 -0
  79. package/dist/components/ImageEditorStub.svelte.d.ts +19 -0
  80. package/dist/components/ImageEditorStub.svelte.d.ts.map +1 -0
  81. package/dist/components/OneDriveUploader.svelte +17 -0
  82. package/dist/components/OneDriveUploader.svelte.d.ts +19 -0
  83. package/dist/components/OneDriveUploader.svelte.d.ts.map +1 -0
  84. package/dist/components/ScreenCaptureUploader.svelte +254 -0
  85. package/dist/components/ScreenCaptureUploader.svelte.d.ts +4 -0
  86. package/dist/components/ScreenCaptureUploader.svelte.d.ts.map +1 -0
  87. package/dist/components/ServerModeDriveUploader.svelte +250 -0
  88. package/dist/components/ServerModeDriveUploader.svelte.d.ts +10 -0
  89. package/dist/components/ServerModeDriveUploader.svelte.d.ts.map +1 -0
  90. package/dist/components/SourceSelector.svelte +292 -0
  91. package/dist/components/SourceSelector.svelte.d.ts +19 -0
  92. package/dist/components/SourceSelector.svelte.d.ts.map +1 -0
  93. package/dist/components/SourceView.svelte +100 -0
  94. package/dist/components/SourceView.svelte.d.ts +4 -0
  95. package/dist/components/SourceView.svelte.d.ts.map +1 -0
  96. package/dist/components/TrashIcon.svelte +11 -0
  97. package/dist/components/TrashIcon.svelte.d.ts +8 -0
  98. package/dist/components/TrashIcon.svelte.d.ts.map +1 -0
  99. package/dist/components/UploaderPanel.svelte +363 -0
  100. package/dist/components/UploaderPanel.svelte.d.ts +4 -0
  101. package/dist/components/UploaderPanel.svelte.d.ts.map +1 -0
  102. package/dist/components/UrlUploader.svelte +71 -0
  103. package/dist/components/UrlUploader.svelte.d.ts +4 -0
  104. package/dist/components/UrlUploader.svelte.d.ts.map +1 -0
  105. package/dist/components/icons/AudioIcon.svelte +5 -0
  106. package/dist/components/icons/AudioIcon.svelte.d.ts +7 -0
  107. package/dist/components/icons/AudioIcon.svelte.d.ts.map +1 -0
  108. package/dist/components/icons/BoxIcon.svelte +5 -0
  109. package/dist/components/icons/BoxIcon.svelte.d.ts +7 -0
  110. package/dist/components/icons/BoxIcon.svelte.d.ts.map +1 -0
  111. package/dist/components/icons/CameraIcon.svelte +5 -0
  112. package/dist/components/icons/CameraIcon.svelte.d.ts +7 -0
  113. package/dist/components/icons/CameraIcon.svelte.d.ts.map +1 -0
  114. package/dist/components/icons/DropboxIcon.svelte +5 -0
  115. package/dist/components/icons/DropboxIcon.svelte.d.ts +7 -0
  116. package/dist/components/icons/DropboxIcon.svelte.d.ts.map +1 -0
  117. package/dist/components/icons/GoogleDriveIcon.svelte +5 -0
  118. package/dist/components/icons/GoogleDriveIcon.svelte.d.ts +7 -0
  119. package/dist/components/icons/GoogleDriveIcon.svelte.d.ts.map +1 -0
  120. package/dist/components/icons/LinkIcon.svelte +5 -0
  121. package/dist/components/icons/LinkIcon.svelte.d.ts +7 -0
  122. package/dist/components/icons/LinkIcon.svelte.d.ts.map +1 -0
  123. package/dist/components/icons/MyDeviceIcon.svelte +5 -0
  124. package/dist/components/icons/MyDeviceIcon.svelte.d.ts +7 -0
  125. package/dist/components/icons/MyDeviceIcon.svelte.d.ts.map +1 -0
  126. package/dist/components/icons/OneDriveIcon.svelte +5 -0
  127. package/dist/components/icons/OneDriveIcon.svelte.d.ts +7 -0
  128. package/dist/components/icons/OneDriveIcon.svelte.d.ts.map +1 -0
  129. package/dist/components/icons/ScreenCaptureIcon.svelte +5 -0
  130. package/dist/components/icons/ScreenCaptureIcon.svelte.d.ts +7 -0
  131. package/dist/components/icons/ScreenCaptureIcon.svelte.d.ts.map +1 -0
  132. package/dist/components/shared/DriveAuthFallback.svelte +60 -0
  133. package/dist/components/shared/DriveAuthFallback.svelte.d.ts +12 -0
  134. package/dist/components/shared/DriveAuthFallback.svelte.d.ts.map +1 -0
  135. package/dist/components/shared/DriveBrowser.svelte +209 -0
  136. package/dist/components/shared/DriveBrowser.svelte.d.ts +25 -0
  137. package/dist/components/shared/DriveBrowser.svelte.d.ts.map +1 -0
  138. package/dist/components/shared/DriveBrowserHeader.svelte +191 -0
  139. package/dist/components/shared/DriveBrowserHeader.svelte.d.ts +14 -0
  140. package/dist/components/shared/DriveBrowserHeader.svelte.d.ts.map +1 -0
  141. package/dist/components/shared/DriveBrowserIcon.svelte +39 -0
  142. package/dist/components/shared/DriveBrowserIcon.svelte.d.ts +8 -0
  143. package/dist/components/shared/DriveBrowserIcon.svelte.d.ts.map +1 -0
  144. package/dist/components/shared/DriveBrowserItem.svelte +63 -0
  145. package/dist/components/shared/DriveBrowserItem.svelte.d.ts +10 -0
  146. package/dist/components/shared/DriveBrowserItem.svelte.d.ts.map +1 -0
  147. package/dist/components/shared/FileSuccessCheck.svelte +47 -0
  148. package/dist/components/shared/FileSuccessCheck.svelte.d.ts +10 -0
  149. package/dist/components/shared/FileSuccessCheck.svelte.d.ts.map +1 -0
  150. package/dist/components/shared/ProgressBar.svelte +81 -0
  151. package/dist/components/shared/ProgressBar.svelte.d.ts +10 -0
  152. package/dist/components/shared/ProgressBar.svelte.d.ts.map +1 -0
  153. package/dist/components/shared/SourceViewContainer.svelte +27 -0
  154. package/dist/components/shared/SourceViewContainer.svelte.d.ts +10 -0
  155. package/dist/components/shared/SourceViewContainer.svelte.d.ts.map +1 -0
  156. package/dist/components/shared/UploaderHeader.svelte +156 -0
  157. package/dist/components/shared/UploaderHeader.svelte.d.ts +13 -0
  158. package/dist/components/shared/UploaderHeader.svelte.d.ts.map +1 -0
  159. package/dist/composables/use-tiles-per-row.d.ts +19 -0
  160. package/dist/composables/use-tiles-per-row.d.ts.map +1 -0
  161. package/dist/composables/use-tiles-per-row.js +30 -0
  162. package/dist/composables/useBox.d.ts +28 -0
  163. package/dist/composables/useBox.d.ts.map +1 -0
  164. package/dist/composables/useBox.js +61 -0
  165. package/dist/composables/useCameraUploader.d.ts +23 -0
  166. package/dist/composables/useCameraUploader.d.ts.map +1 -0
  167. package/dist/composables/useCameraUploader.js +114 -0
  168. package/dist/composables/useDropbox.d.ts +28 -0
  169. package/dist/composables/useDropbox.d.ts.map +1 -0
  170. package/dist/composables/useDropbox.js +61 -0
  171. package/dist/composables/useFetchFileByUrl.d.ts +9 -0
  172. package/dist/composables/useFetchFileByUrl.d.ts.map +1 -0
  173. package/dist/composables/useFetchFileByUrl.js +33 -0
  174. package/dist/composables/useGoogleDrive.d.ts +28 -0
  175. package/dist/composables/useGoogleDrive.d.ts.map +1 -0
  176. package/dist/composables/useGoogleDrive.js +65 -0
  177. package/dist/composables/useOneDrive.d.ts +29 -0
  178. package/dist/composables/useOneDrive.d.ts.map +1 -0
  179. package/dist/composables/useOneDrive.js +64 -0
  180. package/dist/composables/useSSEProcessing.d.ts +20 -0
  181. package/dist/composables/useSSEProcessing.d.ts.map +1 -0
  182. package/dist/composables/useSSEProcessing.js +17 -0
  183. package/dist/composables/useServerModeDrive.d.ts +17 -0
  184. package/dist/composables/useServerModeDrive.d.ts.map +1 -0
  185. package/dist/composables/useServerModeDrive.js +33 -0
  186. package/dist/composables/useSourceSelector.d.ts +13 -0
  187. package/dist/composables/useSourceSelector.d.ts.map +1 -0
  188. package/dist/composables/useSourceSelector.js +36 -0
  189. package/dist/composables/useUploaderPanel.d.ts +12 -0
  190. package/dist/composables/useUploaderPanel.d.ts.map +1 -0
  191. package/dist/composables/useUploaderPanel.js +43 -0
  192. package/dist/context/create-uploader-context.d.ts +4 -0
  193. package/dist/context/create-uploader-context.d.ts.map +1 -0
  194. package/dist/context/create-uploader-context.js +324 -0
  195. package/dist/context/source-view-header-extra.d.ts +14 -0
  196. package/dist/context/source-view-header-extra.d.ts.map +1 -0
  197. package/dist/context/source-view-header-extra.js +41 -0
  198. package/dist/context/uploader-context.d.ts +109 -0
  199. package/dist/context/uploader-context.d.ts.map +1 -0
  200. package/dist/context/uploader-context.js +96 -0
  201. package/dist/index.d.ts +10 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +5 -0
  204. package/dist/lib/constants.d.ts +13 -0
  205. package/dist/lib/constants.d.ts.map +1 -0
  206. package/dist/lib/constants.js +67 -0
  207. package/dist/lib/to-readable.d.ts +14 -0
  208. package/dist/lib/to-readable.d.ts.map +1 -0
  209. package/dist/lib/to-readable.js +15 -0
  210. package/dist/lib/view-mode.d.ts +24 -0
  211. package/dist/lib/view-mode.d.ts.map +1 -0
  212. package/dist/lib/view-mode.js +31 -0
  213. package/dist/shared/types.d.ts +15 -0
  214. package/dist/shared/types.d.ts.map +1 -0
  215. package/dist/shared/types.js +1 -0
  216. package/dist/styles.d.ts +4 -0
  217. package/dist/tailwind-prefixed.css +2479 -0
  218. package/dist/tailwind.css +74 -0
  219. package/dist/use-upup-upload.d.ts +38 -0
  220. package/dist/use-upup-upload.d.ts.map +1 -0
  221. package/dist/use-upup-upload.js +81 -0
  222. package/package.json +101 -0
@@ -0,0 +1,442 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte'
3
+ import { get } from 'svelte/store'
4
+ import { createVirtualizer } from '@tanstack/svelte-virtual'
5
+ import { formatUiMessage as t, pluralUiMessage as plural, UploadStatus } from '@useupup/core'
6
+ import { isUploadActive, cn } from '@useupup/core/internal'
7
+ import {
8
+ useUploaderFiles,
9
+ useUploaderI18n,
10
+ useUploaderOptions,
11
+ useUploaderSource,
12
+ useUploaderTheme,
13
+ useUploaderUploadControls,
14
+ useUploaderView,
15
+ } from '../context/uploader-context'
16
+ import Icon from './Icon.svelte'
17
+ import FileItem from './FileItem.svelte'
18
+ import FileHero from './FileHero.svelte'
19
+ import UploaderHeader from './shared/UploaderHeader.svelte'
20
+ import ProgressBar from './shared/ProgressBar.svelte'
21
+ import FileSuccessCheck from './shared/FileSuccessCheck.svelte'
22
+ import { isListViewForced } from '../lib/view-mode'
23
+ import { useTilesPerRow } from '../composables/use-tiles-per-row'
24
+
25
+ const VIRTUAL_SCROLL_THRESHOLD = 20
26
+ const ESTIMATED_ITEM_HEIGHT = 76
27
+
28
+ function formatBytes(bytes: number): string {
29
+ if (bytes === 0) return '0 B'
30
+ const k = 1024
31
+ const sizes = ['B', 'KB', 'MB', 'GB']
32
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
33
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
34
+ }
35
+
36
+ function formatEta(seconds: number): string {
37
+ if (seconds <= 0 || !isFinite(seconds)) return ''
38
+ const m = Math.floor(seconds / 60)
39
+ const s = seconds % 60
40
+ if (m > 0) return `${m}m ${s}s left`
41
+ return `${s}s left`
42
+ }
43
+
44
+ const { viewMode, sourceOverlayOpen, openSourceOverlay } = useUploaderView()
45
+ const { activeSource } = useUploaderSource()
46
+ const { files, leavingFileIds } = useUploaderFiles()
47
+ const { translations: tr } = useUploaderI18n()
48
+ const {
49
+ upload: {
50
+ startUpload,
51
+ retryUpload,
52
+ uploadStatus,
53
+ uploadError,
54
+ uploadErrorCode,
55
+ totalProgress,
56
+ uploadSpeed,
57
+ uploadEta,
58
+ uploadedBytes,
59
+ totalBytes,
60
+ },
61
+ handleDone,
62
+ handleCancel,
63
+ handlePause,
64
+ handleResume,
65
+ } = useUploaderUploadControls()
66
+ const {
67
+ isProcessing,
68
+ resumable,
69
+ limit,
70
+ quietCompletion,
71
+ icons: { ContainerAddMoreIcon },
72
+ } = useUploaderOptions()
73
+ const { isDark: dark, slotOverrides: slotClasses, slots: themeSlots } = useUploaderTheme()
74
+
75
+ // Scroll element — null on SSR; set via bind:this after mount
76
+ let scrollEl: HTMLDivElement | null = $state(null)
77
+ let mounted = $state(false)
78
+
79
+ // Render in CORE's insertion order (FileManager's Map preserves it, stable
80
+ // across renders and unchanged by in-place updateFile). A file added after
81
+ // others must appear LAST, never re-sorted into the middle alphabetically
82
+ // (round-7 item 3). Folder uploads already add folder-by-folder, so grouping
83
+ // survives without a sort.
84
+ const orderedFiles = $derived(Array.from($files.values()))
85
+
86
+ const isSingle = $derived(orderedFiles.length === 1)
87
+
88
+ // Adaptive layout rule: the square-tile grid is only honored when every tile
89
+ // fits in ONE row of the fixed-height panel. tilesPerRow is measured from the
90
+ // scroll container (160px min tile + 16px gap); past it the row list is FORCED
91
+ // and UploaderHeader hides the grid/list toggle.
92
+ const { tilesPerRow, observe: observeTiles } = useTilesPerRow()
93
+ $effect(() => {
94
+ observeTiles(scrollEl)
95
+ })
96
+ const forcedList = $derived(
97
+ isListViewForced(orderedFiles.length, $tilesPerRow),
98
+ )
99
+ const effectiveViewMode = $derived(forcedList ? 'list' : $viewMode)
100
+
101
+ const shouldVirtualize = $derived(
102
+ orderedFiles.length >= VIRTUAL_SCROLL_THRESHOLD &&
103
+ effectiveViewMode !== 'grid',
104
+ )
105
+
106
+ const isUploading = $derived(isUploadActive($uploadStatus))
107
+ // When the add-more source surface is up (overlay open, or a source chosen
108
+ // while files exist), this list stays mounted but dimmed and inert behind it.
109
+ const dimmed = $derived($sourceOverlayOpen || !!$activeSource)
110
+ const heroLeaving = $derived(
111
+ isSingle && !!orderedFiles[0] && $leavingFileIds.has(orderedFiles[0].id),
112
+ )
113
+ // Quiet completion (item 7): a successful run under `quietCompletion` shows
114
+ // ONLY the checkmark overlay — Done/add-more/CTA are all suppressed and the
115
+ // host takes over via the completion callbacks. Wins over 6a's continue flow.
116
+ const quietDone = $derived(
117
+ !!quietCompletion && $uploadStatus === UploadStatus.SUCCESSFUL,
118
+ )
119
+ const canAddMore = $derived(
120
+ limit > 1 && $files.size < limit && !isUploading && !isProcessing && !quietDone,
121
+ )
122
+
123
+ const isGrid = $derived($files.size > 1 && effectiveViewMode === 'grid')
124
+ const gridStyle = $derived(
125
+ isGrid
126
+ ? 'grid-template-columns: repeat(auto-fit, minmax(160px, 1fr))'
127
+ : undefined,
128
+ )
129
+
130
+ // Create virtualizer store — getScrollElement returns null on SSR (safe)
131
+ // count starts at 0; we update via setOptions whenever orderedFiles or
132
+ // shouldVirtualize changes (Svelte $effect runs client-side).
133
+ const virtualizer = createVirtualizer<HTMLDivElement, HTMLDivElement>({
134
+ count: 0,
135
+ getScrollElement: () => scrollEl,
136
+ estimateSize: () => ESTIMATED_ITEM_HEIGHT,
137
+ overscan: 5,
138
+ enabled: false,
139
+ })
140
+
141
+ // Keep virtualizer options in sync with reactive state (runs only on client).
142
+ // CRITICAL: depend ONLY on the inputs (count/shouldVirtualize/scrollEl) and
143
+ // push them through a NON-tracking get(virtualizer) read. We must NOT read
144
+ // $virtualizer here: setOptions() internally calls virtualizerWritable.set(),
145
+ // which would re-emit the derived store; if this effect were subscribed to it
146
+ // (via $virtualizer auto-subscription) that emission would re-run the effect →
147
+ // setOptions → emit → infinite loop. get(store) does a synchronous
148
+ // subscribe+unsubscribe and is NOT registered as an effect dependency, so
149
+ // writing via it cannot re-trigger this effect.
150
+ $effect(() => {
151
+ if (!mounted) return
152
+ const count = orderedFiles.length // dependency: file list changes
153
+ const sv = shouldVirtualize // dependency: virtualization gate
154
+ void scrollEl // dependency: element binds after mount
155
+ get(virtualizer).setOptions({
156
+ count,
157
+ getScrollElement: () => scrollEl,
158
+ estimateSize: () => ESTIMATED_ITEM_HEIGHT,
159
+ overscan: 5,
160
+ enabled: sv,
161
+ })
162
+ })
163
+
164
+ onMount(() => {
165
+ mounted = true
166
+ })
167
+
168
+ function onUploadClick() {
169
+ void startUpload().catch(() => undefined)
170
+ }
171
+
172
+ function onRetryClick() {
173
+ void retryUpload().catch(() => undefined)
174
+ }
175
+ </script>
176
+
177
+ <div
178
+ data-testid="upup-file-list"
179
+ data-upup-slot="file-list"
180
+ inert={dimmed || undefined}
181
+ class={cn(
182
+ 'upup-relative upup-flex upup-h-full upup-flex-col upup-rounded-lg upup-shadow',
183
+ {
184
+ 'upup-hidden': !$files.size,
185
+ 'upup-opacity-50 upup-blur-[2px] upup-pointer-events-none': dimmed,
186
+ },
187
+ $themeSlots?.fileList?.root,
188
+ )}
189
+ >
190
+ <div role="status" aria-live="polite" class="upup-sr-only">
191
+ {t(plural(tr, 'filesSelected', $files.size), { count: $files.size })}
192
+ </div>
193
+
194
+ <UploaderHeader {handleCancel} {forcedList} hideAddMore={quietDone} />
195
+
196
+ <!-- Quiet-completion overlay (item 7): checkmark only, no CTAs. The fx
197
+ classes on FileSuccessCheck die under data-motion='off' (it then simply
198
+ appears drawn), so this satisfies the static-state rule. -->
199
+ {#if quietDone}
200
+ <div
201
+ data-testid="upup-complete-check"
202
+ data-upup-slot="complete-check"
203
+ role="status"
204
+ class={cn(
205
+ 'upup-absolute upup-inset-0 upup-z-20 upup-flex upup-flex-col upup-items-center upup-justify-center upup-gap-2 upup-rounded-lg upup-backdrop-blur-[1px]',
206
+ $dark ? 'upup-bg-[#04080f]/40' : 'upup-bg-white/50',
207
+ )}
208
+ >
209
+ <FileSuccessCheck size={56} />
210
+ <span
211
+ class={cn(
212
+ 'upup-text-sm upup-font-medium',
213
+ $dark ? 'upup-text-[#e2e8f0]' : 'upup-text-[#1e293b]',
214
+ )}
215
+ >
216
+ {tr.announceUploadComplete}
217
+ </span>
218
+ </div>
219
+ {/if}
220
+
221
+ <div
222
+ bind:this={scrollEl}
223
+ class={cn(
224
+ 'upup-preview-scroll upup-flex upup-flex-1 upup-flex-col upup-overflow-y-auto upup-p-3',
225
+ $dark ? 'upup-bg-transparent' : 'upup-bg-black/[0.075]',
226
+ $slotClasses.fileListContainer,
227
+ )}
228
+ >
229
+ {#if isSingle}
230
+ <!-- Single-file HERO: one visual fills the content area. -->
231
+ <div
232
+ role="list"
233
+ class={cn(
234
+ 'upup-animate-fx-enter upup-flex upup-min-h-0 upup-flex-1 upup-flex-col',
235
+ heroLeaving && 'upup-animate-fx-exit upup-overflow-hidden',
236
+ )}
237
+ >
238
+ <FileHero file={orderedFiles[0]!} />
239
+ </div>
240
+ {:else if shouldVirtualize && mounted}
241
+ <!-- Virtualized list -->
242
+ <div
243
+ role="list"
244
+ data-upup-slot="file-list-virtual"
245
+ style={`height: ${$virtualizer.getTotalSize()}px; position: relative;`}
246
+ class={cn(
247
+ isProcessing && 'upup-pointer-events-none upup-opacity-75',
248
+ 'upup-font-[Arial,Helvetica,sans-serif]',
249
+ )}
250
+ >
251
+ {#each $virtualizer.getVirtualItems() as virtualItem (String(virtualItem.key))}
252
+ {@const file = orderedFiles[virtualItem.index]}
253
+ {#if file}
254
+ <div
255
+ data-index={virtualItem.index}
256
+ style={`position: absolute; top: 0; left: 0; width: 100%; transform: translateY(${virtualItem.start}px); padding-bottom: 12px;`}
257
+ >
258
+ <FileItem {file} index={virtualItem.index} {forcedList} />
259
+ </div>
260
+ {/if}
261
+ {/each}
262
+ </div>
263
+ {:else}
264
+ <!-- Standard rendering for small lists and grid mode. Grid is a single row
265
+ of auto-fit tiles that STRETCH to fill the row (minmax(160px,1fr) — no
266
+ dead columns at 2/3 files). It is only reached when the tiles fit one
267
+ row (else forcedList swaps to the list), so the grid can never wrap
268
+ the fixed panel. -->
269
+ <div
270
+ role="list"
271
+ style={gridStyle}
272
+ class={cn(
273
+ isProcessing && 'upup-pointer-events-none upup-opacity-75',
274
+ 'upup-font-[Arial,Helvetica,sans-serif]',
275
+ isGrid ? 'upup-grid upup-gap-4' : 'upup-flex upup-flex-col upup-gap-3',
276
+ {
277
+ [$slotClasses.fileListContainerInnerMultiple ?? '']:
278
+ !!$slotClasses.fileListContainerInnerMultiple && $files.size > 1,
279
+ [$slotClasses.fileListContainerInnerSingle ?? '']:
280
+ !!$slotClasses.fileListContainerInnerSingle && $files.size === 1,
281
+ },
282
+ )}
283
+ >
284
+ {#each orderedFiles as file, index (file.id)}
285
+ <FileItem {file} {index} {forcedList} />
286
+ {/each}
287
+ </div>
288
+ {/if}
289
+
290
+ <!-- Full-width dashed "Add more" row (spec §4 state 3): a second
291
+ add-more affordance beneath the list/hero, opening the overlay. -->
292
+ {#if canAddMore}
293
+ <button
294
+ data-testid="upup-add-more"
295
+ data-placement="footer"
296
+ data-upup-slot="add-more"
297
+ class={cn(
298
+ 'upup-fx-hover-lift upup-fx-press upup-mt-2.5 upup-flex upup-flex-none upup-items-center upup-justify-center upup-gap-2 upup-whitespace-nowrap upup-rounded-xl upup-border-[1.5px] upup-border-dashed upup-px-3 upup-py-2 upup-text-[13px] upup-font-medium',
299
+ $dark
300
+ ? 'upup-border-white/[0.16] upup-text-[#94a3b8]'
301
+ : 'upup-border-black/[0.16] upup-text-gray-500',
302
+ $slotClasses.containerAddMoreButton,
303
+ )}
304
+ onclick={openSourceOverlay}
305
+ disabled={isUploading || isProcessing}
306
+ >
307
+ <ContainerAddMoreIcon />
308
+ {tr.addMore}
309
+ </button>
310
+ {/if}
311
+ </div>
312
+
313
+ <div
314
+ class={cn(
315
+ 'upup-shadow-top upup-flex upup-items-center upup-gap-3 upup-rounded-b-lg upup-bg-black/[0.025] upup-px-3 upup-py-2',
316
+ { 'upup-bg-white/5 dark:upup-bg-white/5': $dark },
317
+ $slotClasses.fileListFooter,
318
+ )}
319
+ >
320
+ <!-- Primary CTA only in a pre-run/idle state. Once a run is active it
321
+ becomes Cancel; when PAUSED it becomes Resume; SUCCESSFUL shows Done and
322
+ FAILED shows Retry (below). -->
323
+ {#if $uploadStatus !== UploadStatus.SUCCESSFUL && $uploadStatus !== UploadStatus.FAILED && $uploadStatus !== UploadStatus.PAUSED && !isUploading}
324
+ <button
325
+ data-testid="upup-upload-btn"
326
+ class={cn(
327
+ 'upup-fx-sheen-sweep upup-fx-press upup-disabled:animate-pulse upup-ml-auto upup-rounded-full upup-bg-[#0ea5e9] upup-px-4 upup-py-2 upup-text-sm upup-font-medium upup-text-white',
328
+ { 'upup-bg-[#38bdf8] dark:upup-bg-[#38bdf8]': $dark },
329
+ $slotClasses.uploadButton,
330
+ )}
331
+ onclick={onUploadClick}
332
+ disabled={isUploadActive($uploadStatus) || isProcessing}
333
+ >
334
+ {t(plural(tr, 'uploadFiles', $files.size), { count: $files.size })}
335
+ </button>
336
+ {/if}
337
+
338
+ {#if $uploadStatus === UploadStatus.FAILED && $uploadError}
339
+ <p
340
+ data-testid="upup-upload-error"
341
+ data-upup-slot="upload-error"
342
+ title={$uploadErrorCode}
343
+ class="upup-mr-auto upup-text-sm upup-text-red-600 dark:upup-text-red-400"
344
+ >
345
+ {$uploadErrorCode ? t(tr.uploadFailedWithCode, { code: $uploadErrorCode }) : t(tr.uploadFailed, { message: $uploadError })}
346
+ </p>
347
+ {/if}
348
+
349
+ {#if $uploadStatus === UploadStatus.FAILED}
350
+ <button
351
+ data-testid="upup-retry-btn"
352
+ class={cn(
353
+ 'upup-fx-press upup-disabled:animate-pulse upup-ml-auto upup-rounded-full upup-bg-red-600 upup-px-4 upup-py-2 upup-text-sm upup-font-medium upup-text-white',
354
+ { 'upup-bg-red-500 dark:upup-bg-red-500': $dark },
355
+ $slotClasses.uploadButton,
356
+ )}
357
+ onclick={onRetryClick}
358
+ >
359
+ {resumable?.protocol === 'multipart' ? tr.resumeUpload : tr.retryUpload}
360
+ </button>
361
+ {/if}
362
+
363
+ {#if $uploadStatus === UploadStatus.SUCCESSFUL && !quietCompletion}
364
+ <button
365
+ class={cn(
366
+ 'upup-fx-sheen-sweep upup-fx-press upup-disabled:animate-pulse upup-ml-auto upup-rounded-lg upup-bg-[#0ea5e9] upup-px-3 upup-py-2 upup-text-sm upup-font-medium upup-text-white',
367
+ { 'upup-bg-[#38bdf8] dark:upup-bg-[#38bdf8]': $dark },
368
+ $slotClasses.uploadDoneButton,
369
+ )}
370
+ onclick={handleDone}
371
+ >
372
+ {tr.done}
373
+ </button>
374
+ {/if}
375
+
376
+ <div class="upup-flex upup-flex-1 upup-flex-col upup-gap-1">
377
+ <div class="upup-flex upup-items-center upup-gap-2">
378
+ <!-- Cancel an in-flight run: aborts the active request(s) but KEEPS the
379
+ files (status → PAUSED) so the run can be resumed. A true discard is
380
+ the header's "remove all". -->
381
+ {#if $uploadStatus === UploadStatus.UPLOADING}
382
+ <button
383
+ data-testid="upup-upload-cancel"
384
+ class={cn(
385
+ 'upup-fx-press upup-flex upup-h-7 upup-items-center upup-gap-1 upup-whitespace-nowrap upup-rounded-full upup-bg-red-100 upup-px-3 upup-text-xs upup-font-medium upup-text-red-700 upup-transition-colors hover:upup-bg-red-200',
386
+ { 'upup-bg-red-500/20 upup-text-red-100 hover:upup-bg-red-500/30': $dark },
387
+ )}
388
+ onclick={handlePause}
389
+ aria-label={tr.cancel}
390
+ title={tr.cancel}
391
+ >
392
+ <Icon name="x" size={14} />
393
+ {tr.cancel}
394
+ </button>
395
+ {/if}
396
+ {#if $uploadStatus === UploadStatus.PAUSED}
397
+ <button
398
+ data-testid="upup-upload-resume"
399
+ class={cn(
400
+ 'upup-fx-press upup-flex upup-h-7 upup-items-center upup-gap-1 upup-whitespace-nowrap upup-rounded-full upup-bg-[#0ea5e9] upup-px-3 upup-text-xs upup-font-medium upup-text-white upup-transition-colors',
401
+ { 'upup-bg-[#38bdf8] dark:upup-bg-[#38bdf8]': $dark },
402
+ )}
403
+ onclick={handleResume}
404
+ aria-label={tr.resumeUpload}
405
+ title={tr.resumeUpload}
406
+ >
407
+ <Icon name="player-play" size={14} />
408
+ {tr.resumeUpload}
409
+ </button>
410
+ {/if}
411
+ <ProgressBar
412
+ class="upup-flex-1"
413
+ progressBarClassName="upup-rounded"
414
+ progress={$totalProgress}
415
+ showValue={true}
416
+ />
417
+ </div>
418
+
419
+ {#if (isUploadActive($uploadStatus) || $uploadStatus === UploadStatus.PAUSED) && $totalBytes > 0}
420
+ <div
421
+ class={cn(
422
+ 'upup-flex upup-items-center upup-justify-between upup-text-[11px] upup-text-gray-500',
423
+ { 'upup-text-gray-400': $dark },
424
+ )}
425
+ >
426
+ <span>
427
+ {formatBytes($uploadedBytes)} of {formatBytes($totalBytes)}
428
+ {#if $uploadSpeed > 0}
429
+ &middot; {formatBytes($uploadSpeed)}/s
430
+ {/if}
431
+ </span>
432
+ {#if isUploadActive($uploadStatus) && $uploadEta > 0}
433
+ <span>{formatEta($uploadEta)}</span>
434
+ {/if}
435
+ {#if $uploadStatus === UploadStatus.PAUSED}
436
+ <span>{tr.paused}</span>
437
+ {/if}
438
+ </div>
439
+ {/if}
440
+ </div>
441
+ </div>
442
+ </div>
@@ -0,0 +1,4 @@
1
+ declare const FileList: import("svelte").Component<Record<string, never>, {}, "">;
2
+ type FileList = ReturnType<typeof FileList>;
3
+ export default FileList;
4
+ //# sourceMappingURL=FileList.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FileList.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FileList.svelte.ts"],"names":[],"mappings":"AAuXA,QAAA,MAAM,QAAQ,2DAAwC,CAAC;AACvD,KAAK,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAC5C,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,218 @@
1
+ <script lang="ts">
2
+ import { UploadStatus, type Translations } from '@useupup/core'
3
+ import { fileCanPreviewText, fileGetIsImage, fileGetIsPdf, fileGetIsText, cn, isFileRemovalLocked } from '@useupup/core/internal'
4
+ import {
5
+ useUploaderFiles,
6
+ useUploaderI18n,
7
+ useUploaderOptions,
8
+ useUploaderTheme,
9
+ useUploaderUploadControls,
10
+ } from '../context/uploader-context'
11
+ import FilePreviewThumbnail from './FilePreviewThumbnail.svelte'
12
+ import ProgressBar from './shared/ProgressBar.svelte'
13
+ import FileSuccessCheck from './shared/FileSuccessCheck.svelte'
14
+
15
+ let {
16
+ fileName,
17
+ fileType,
18
+ fileId,
19
+ fileUrl,
20
+ fileSize,
21
+ canPreview,
22
+ onRequestPreview,
23
+ onUpdateCanPreview,
24
+ onclick,
25
+ index = 0,
26
+ }: {
27
+ fileName: string
28
+ fileType: string
29
+ fileId: string
30
+ fileUrl: string
31
+ fileSize?: number
32
+ canPreview: boolean
33
+ onRequestPreview?: () => void
34
+ onUpdateCanPreview?: (val: boolean) => void
35
+ onclick?: () => void
36
+ /** Position in the sorted list — drives the completion-check stagger. */
37
+ index?: number
38
+ } = $props()
39
+
40
+ const { handleFileRemove, files } = useUploaderFiles()
41
+ const { translations: tr } = useUploaderI18n()
42
+ const { upload: { filesProgressMap } } = useUploaderUploadControls()
43
+ const {
44
+ icons: { FileDeleteIcon },
45
+ allowPreview,
46
+ } = useUploaderOptions()
47
+ const {
48
+ isDark: isDarkTheme,
49
+ slotOverrides: slotClasses,
50
+ slots: themeSlots,
51
+ } = useUploaderTheme()
52
+
53
+ const isImage = $derived(fileGetIsImage(fileType))
54
+ const isVideo = $derived(fileType.startsWith('video/'))
55
+ const isPdf = $derived(fileGetIsPdf(fileType, fileName))
56
+ const isText = $derived(fileGetIsText(fileType, fileName))
57
+ const canPreviewText = $derived(fileCanPreviewText(fileType, fileName, fileSize))
58
+
59
+ // Auto-signal canPreview when applicable
60
+ $effect(() => {
61
+ if ((isImage || isPdf || (isText && canPreviewText)) && !canPreview) {
62
+ onUpdateCanPreview?.(true)
63
+ }
64
+ })
65
+
66
+ const progress = $derived.by(() => {
67
+ const fileProgress = $filesProgressMap[fileId]
68
+ const loaded = fileProgress?.loaded ?? NaN
69
+ const total = fileProgress?.total ?? NaN
70
+ const pct = Math.floor((loaded / total) * 100)
71
+ // No progress entry ⇒ NaN; total === 0 ⇒ Infinity. Either would render
72
+ // width:NaN%/aria-valuenow=NaN in ProgressBar while an upload is active.
73
+ return Number.isFinite(pct) ? pct : 0
74
+ })
75
+
76
+ const fileStatus = $derived($files.get(fileId)?.status)
77
+
78
+ const isSuccessful = $derived(fileStatus === UploadStatus.SUCCESSFUL)
79
+
80
+ function formatFileSize(bytes: number | undefined, tr: Translations) {
81
+ if (!bytes || bytes === 0) return tr.zeroBytes
82
+ const k = 1024
83
+ const sizes = [tr.bytes, tr.kb, tr.mb, tr.gb]
84
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
85
+ return Math.round((bytes / Math.pow(k, i)) * 10) / 10 + ' ' + sizes[i]
86
+ }
87
+
88
+ function onHandleFileRemove(e: MouseEvent) {
89
+ e.stopPropagation()
90
+ handleFileRemove(fileId)
91
+ }
92
+ </script>
93
+
94
+ <div
95
+ class={cn('upup-block upup-w-full', $themeSlots?.filePreview?.root)}
96
+ data-testid="upup-file-preview"
97
+ data-upup-slot="file-preview"
98
+ >
99
+ <div
100
+ class={cn(
101
+ 'upup-fx-hover-lift upup-relative upup-h-[160px] upup-w-full upup-overflow-hidden upup-rounded-xl upup-ring-1',
102
+ 'upup-bg-contain upup-bg-center upup-bg-no-repeat',
103
+ $isDarkTheme
104
+ ? 'upup-bg-white/[0.055] upup-ring-white/[0.08]'
105
+ : 'upup-bg-black/[0.04] upup-ring-black/[0.06]',
106
+ {
107
+ [$slotClasses.fileThumbnailMultiple ?? '']: !!$slotClasses.fileThumbnailMultiple && $files.size > 1,
108
+ [$slotClasses.fileThumbnailSingle ?? '']: !!$slotClasses.fileThumbnailSingle && $files.size === 1,
109
+ },
110
+ $themeSlots?.filePreview?.thumbnail,
111
+ )}
112
+ style={isImage ? `background-image: url(${fileUrl})` : undefined}
113
+ >
114
+ <button
115
+ type="button"
116
+ aria-label={fileName}
117
+ class="upup-absolute upup-inset-0 upup-z-0 upup-cursor-pointer"
118
+ {onclick}
119
+ ></button>
120
+ {#if isVideo}
121
+ <!-- First-frame video thumbnail — no controls chrome (the native
122
+ player pill read as broken CSS in a 145px tile). -->
123
+ <video
124
+ src={fileUrl}
125
+ muted
126
+ playsinline
127
+ preload="metadata"
128
+ class="upup-pointer-events-none upup-absolute upup-inset-0 upup-h-full upup-w-full upup-object-cover"
129
+ ></video>
130
+ {/if}
131
+ {#if !isImage && !isVideo}
132
+ <div class="upup-flex upup-h-full upup-items-center upup-justify-center upup-p-6">
133
+ <FilePreviewThumbnail
134
+ {canPreview}
135
+ {fileType}
136
+ {fileName}
137
+ {fileUrl}
138
+ {fileSize}
139
+ {allowPreview}
140
+ slotClasses={$slotClasses}
141
+ labels={tr}
142
+ onUpdateCanPreview={onUpdateCanPreview}
143
+ />
144
+ </div>
145
+ {/if}
146
+
147
+ <!-- Delete disappears once the file has uploaded successfully — the
148
+ completion check is then the only overlay affordance. -->
149
+ {#if !isSuccessful}
150
+ <button
151
+ class={cn(
152
+ 'upup-fx-remove upup-fx-press upup-absolute upup-right-1.5 upup-top-1.5 upup-z-10',
153
+ 'upup-flex upup-h-[30px] upup-w-[30px] upup-items-center upup-justify-center',
154
+ 'upup-rounded-[8px] upup-bg-[#04080f]/40 upup-text-[#e2e8f0]',
155
+ 'hover:upup-bg-[#04080f]/65',
156
+ 'disabled:upup-cursor-not-allowed disabled:upup-opacity-50',
157
+ $slotClasses.fileDeleteButton,
158
+ $themeSlots?.filePreview?.deleteButton,
159
+ )}
160
+ onclick={onHandleFileRemove}
161
+ type="button"
162
+ disabled={isFileRemovalLocked(progress, fileStatus ?? UploadStatus.IDLE)}
163
+ aria-label={tr.removeFile}
164
+ data-testid="upup-file-remove"
165
+ >
166
+ <FileDeleteIcon class="upup-h-5 upup-w-5" />
167
+ </button>
168
+ {/if}
169
+
170
+ {#if isSuccessful}
171
+ <FileSuccessCheck
172
+ {index}
173
+ size={20}
174
+ class="upup-absolute upup-left-1.5 upup-top-1.5 upup-z-10"
175
+ />
176
+ {/if}
177
+
178
+ <ProgressBar
179
+ class="upup-absolute upup-bottom-0 upup-left-0 upup-right-0"
180
+ progressBarClassName="upup-rounded-t-none upup-rounded-b-md"
181
+ {progress}
182
+ />
183
+ </div>
184
+
185
+ <div class="upup-mt-1 upup-px-0.5">
186
+ <div
187
+ class={cn(
188
+ 'upup-truncate upup-text-[13px] upup-font-normal upup-leading-tight upup-text-gray-900',
189
+ { 'upup-text-white': $isDarkTheme },
190
+ $themeSlots?.filePreview?.name,
191
+ )}
192
+ >
193
+ {fileName}
194
+ </div>
195
+ <div
196
+ class={cn(
197
+ 'upup-mt-0.5 upup-text-[11px] upup-leading-tight upup-text-gray-500',
198
+ { 'upup-text-gray-400': $isDarkTheme },
199
+ $themeSlots?.filePreview?.size,
200
+ )}
201
+ >
202
+ {formatFileSize(fileSize, tr)}
203
+ </div>
204
+ {#if allowPreview && canPreview}
205
+ <button
206
+ type="button"
207
+ class={cn(
208
+ 'upup-mt-1 upup-text-[11px] upup-font-normal upup-leading-tight upup-text-[#0284c7] upup-transition-all hover:upup-text-[#0284c7] hover:upup-underline',
209
+ { 'upup-text-[#38bdf8] hover:upup-text-[#7dd3fc]': $isDarkTheme },
210
+ $themeSlots?.filePreview?.previewButton,
211
+ )}
212
+ onclick={() => onRequestPreview?.()}
213
+ >
214
+ {tr.clickToPreview}
215
+ </button>
216
+ {/if}
217
+ </div>
218
+ </div>
@@ -0,0 +1,17 @@
1
+ type $$ComponentProps = {
2
+ fileName: string;
3
+ fileType: string;
4
+ fileId: string;
5
+ fileUrl: string;
6
+ fileSize?: number;
7
+ canPreview: boolean;
8
+ onRequestPreview?: () => void;
9
+ onUpdateCanPreview?: (val: boolean) => void;
10
+ onclick?: () => void;
11
+ /** Position in the sorted list — drives the completion-check stagger. */
12
+ index?: number;
13
+ };
14
+ declare const FilePreview: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type FilePreview = ReturnType<typeof FilePreview>;
16
+ export default FilePreview;
17
+ //# sourceMappingURL=FilePreview.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FilePreview.svelte.d.ts","sourceRoot":"","sources":["../../src/components/FilePreview.svelte.ts"],"names":[],"mappings":"AAgBC,KAAK,gBAAgB,GAAI;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,OAAO,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC7B,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IAC3C,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,CAAC;AAuJJ,QAAA,MAAM,WAAW,sDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}