@oslokommune/punkt-elements 16.13.2 → 16.15.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 (157) hide show
  1. package/CHANGELOG.md +35 -1
  2. package/dist/calendar-Ch1dgnBo.js +656 -0
  3. package/dist/calendar-DkjXla0I.cjs +90 -0
  4. package/dist/{card-CQYxrtL0.cjs → card-C3POnP3O.cjs} +1 -1
  5. package/dist/{card-Bir3-pl2.js → card-CEDbo1t7.js} +2 -2
  6. package/dist/{checkbox-Cspxcvt_.js → checkbox-Bjp43NxA.js} +1 -1
  7. package/dist/{checkbox-BARDb9iV.cjs → checkbox-nTbihMxW.cjs} +1 -1
  8. package/dist/{combobox-CGbFTWq_.cjs → combobox-C4Q6kQEs.cjs} +1 -1
  9. package/dist/{combobox-BcdLZRDU.js → combobox-CL9BLuxt.js} +6 -6
  10. package/dist/components/fileupload/fileupload-base.d.ts +116 -0
  11. package/dist/components/fileupload/fileupload-operations.d.ts +4 -0
  12. package/dist/components/fileupload/fileupload-renderers.d.ts +28 -0
  13. package/dist/components/fileupload/fileupload-types.d.ts +70 -0
  14. package/dist/components/fileupload/fileupload.d.ts +25 -0
  15. package/dist/components/fileupload/fileupload.test.d.ts +0 -0
  16. package/dist/components/fileupload/index.d.ts +4 -0
  17. package/dist/components/index.d.ts +2 -0
  18. package/dist/components/input-wrapper/input-wrapper.d.ts +2 -0
  19. package/dist/controllers/focus-modality-controller.d.ts +31 -0
  20. package/dist/converters-Bffq_YDm.cjs +1 -0
  21. package/dist/converters-jauYkvNh.js +1378 -0
  22. package/dist/{datepicker-OVd8pmnZ.js → datepicker-BUK1Qanw.js} +22 -21
  23. package/dist/{datepicker-DpmlsnH_.cjs → datepicker-DYd3JCs_.cjs} +10 -10
  24. package/dist/docs/components/fileupload.d.ts +27 -0
  25. package/dist/fileupload-BRqioKSY.cjs +396 -0
  26. package/dist/fileupload-Bnl303X0.js +1372 -0
  27. package/dist/fileupload.d.ts +1 -0
  28. package/dist/{header-DWgPgGfS.cjs → header-Dps8o-ZY.cjs} +1 -1
  29. package/dist/{header-a6ztVNp3.js → header-T5O6sfCN.js} +3 -3
  30. package/dist/{helptext-CCBKKLus.js → helptext-C5rG4VkA.js} +2 -2
  31. package/dist/{helptext-D-3QfZv5.cjs → helptext-DQgStO4D.cjs} +1 -1
  32. package/dist/{input-element-Bw1s6NlF.js → input-element-CjebcQd1.js} +3 -3
  33. package/dist/{input-element-B1yxcfz4.cjs → input-element-FGcr5hGd.cjs} +1 -1
  34. package/dist/input-wrapper-B_XFi211.cjs +55 -0
  35. package/dist/{input-wrapper-YfTmpSSY.js → input-wrapper-DBpMVSxt.js} +41 -15
  36. package/dist/{listbox-DuBgTFvH.js → listbox-0NHJvNjz.js} +2 -2
  37. package/dist/{listbox-DCA2JoPV.cjs → listbox-CcThiNBY.cjs} +1 -1
  38. package/dist/{modal-CBEWrAi2.cjs → modal-B6591sWa.cjs} +1 -1
  39. package/dist/{modal-pPp8nia6.js → modal-DyIC3uhs.js} +1 -1
  40. package/dist/pkt-calendar.cjs +1 -1
  41. package/dist/pkt-calendar.js +1 -1
  42. package/dist/pkt-card.cjs +1 -1
  43. package/dist/pkt-card.js +1 -1
  44. package/dist/pkt-checkbox.cjs +1 -1
  45. package/dist/pkt-checkbox.js +1 -1
  46. package/dist/pkt-combobox.cjs +1 -1
  47. package/dist/pkt-combobox.js +1 -1
  48. package/dist/pkt-consent.cjs +1 -1
  49. package/dist/pkt-consent.js +1 -1
  50. package/dist/pkt-datepicker.cjs +1 -1
  51. package/dist/pkt-datepicker.js +1 -1
  52. package/dist/pkt-fileupload.cjs +1 -0
  53. package/dist/pkt-fileupload.js +2 -0
  54. package/dist/pkt-header.cjs +1 -1
  55. package/dist/pkt-header.js +1 -1
  56. package/dist/pkt-heading.cjs +1 -1
  57. package/dist/pkt-heading.js +1 -1
  58. package/dist/pkt-helptext.cjs +1 -1
  59. package/dist/pkt-helptext.js +1 -1
  60. package/dist/pkt-index.cjs +1 -1
  61. package/dist/pkt-index.js +25 -24
  62. package/dist/pkt-input-wrapper.cjs +1 -1
  63. package/dist/pkt-input-wrapper.js +1 -1
  64. package/dist/pkt-link.cjs +1 -1
  65. package/dist/pkt-link.js +1 -1
  66. package/dist/pkt-linkcard.cjs +1 -1
  67. package/dist/pkt-linkcard.js +1 -1
  68. package/dist/pkt-listbox.cjs +1 -1
  69. package/dist/pkt-listbox.js +1 -1
  70. package/dist/pkt-loader.cjs +1 -1
  71. package/dist/pkt-loader.js +1 -1
  72. package/dist/pkt-messagebox.cjs +1 -1
  73. package/dist/pkt-messagebox.js +1 -1
  74. package/dist/pkt-modal.cjs +1 -1
  75. package/dist/pkt-modal.js +1 -1
  76. package/dist/pkt-progressbar.cjs +1 -1
  77. package/dist/pkt-progressbar.js +1 -1
  78. package/dist/pkt-radiobutton.cjs +1 -1
  79. package/dist/pkt-radiobutton.js +1 -1
  80. package/dist/pkt-select.cjs +1 -1
  81. package/dist/pkt-select.js +1 -1
  82. package/dist/pkt-tabs.cjs +1 -1
  83. package/dist/pkt-tabs.js +1 -1
  84. package/dist/pkt-tag.cjs +1 -1
  85. package/dist/pkt-tag.js +1 -1
  86. package/dist/pkt-textarea.cjs +1 -1
  87. package/dist/pkt-textarea.js +1 -1
  88. package/dist/pkt-textinput.cjs +1 -1
  89. package/dist/pkt-textinput.js +1 -1
  90. package/dist/pkt-timepicker.cjs +1 -1
  91. package/dist/pkt-timepicker.js +1 -1
  92. package/dist/{progressbar-CdXtmOBb.cjs → progressbar-CrwpnIkv.cjs} +1 -1
  93. package/dist/{progressbar-Dy9AiYQz.js → progressbar-uC-0W6o4.js} +1 -1
  94. package/dist/{radiobutton-V_KPbup_.js → radiobutton-B-8-wWFf.js} +1 -1
  95. package/dist/{radiobutton-GOnpUbCW.cjs → radiobutton-DvXr7tDk.cjs} +1 -1
  96. package/dist/{select-C2i8sOk5.cjs → select-B75wzvyz.cjs} +1 -1
  97. package/dist/{select-BSAFd8Wa.js → select-D9B8Ighz.js} +3 -3
  98. package/dist/shared-types/fileupload.d.ts +73 -0
  99. package/dist/shared-types/index.d.ts +2 -0
  100. package/dist/shared-utils/fileupload/announcements.d.ts +11 -0
  101. package/dist/shared-utils/fileupload/filename.d.ts +13 -0
  102. package/dist/shared-utils/fileupload/focus-trap.d.ts +12 -0
  103. package/dist/shared-utils/fileupload/formats.d.ts +30 -0
  104. package/dist/shared-utils/fileupload/index.d.ts +15 -0
  105. package/dist/shared-utils/fileupload/navigation.d.ts +5 -0
  106. package/dist/shared-utils/fileupload/size.d.ts +12 -0
  107. package/dist/shared-utils/fileupload/transfers.d.ts +17 -0
  108. package/dist/shared-utils/fileupload/truncate.d.ts +13 -0
  109. package/dist/shared-utils/fileupload/validation.d.ts +24 -0
  110. package/dist/tests/component-registry.d.ts +2 -0
  111. package/dist/{textarea-DYOBhJrN.js → textarea-CIWXMaAD.js} +2 -2
  112. package/dist/{textarea-D7aGOyDf.cjs → textarea-CjqprL6Y.cjs} +1 -1
  113. package/dist/{textinput-DFh8_9cx.cjs → textinput-B0VWmVIF.cjs} +1 -1
  114. package/dist/{textinput-BUgntXnM.js → textinput-CxzAxM8E.js} +2 -2
  115. package/dist/{timepicker-BIfE7KKJ.cjs → timepicker-Cv9juQeB.cjs} +1 -1
  116. package/dist/{timepicker-DaSwn1FA.js → timepicker-DXrpg5C7.js} +2 -2
  117. package/package.json +2 -2
  118. package/src/components/fileupload/fileupload-base.ts +557 -0
  119. package/src/components/fileupload/fileupload-operations.ts +213 -0
  120. package/src/components/fileupload/fileupload-renderers.ts +769 -0
  121. package/src/components/fileupload/fileupload-types.ts +88 -0
  122. package/src/components/fileupload/fileupload.test.ts +949 -0
  123. package/src/components/fileupload/fileupload.ts +469 -0
  124. package/src/components/fileupload/index.ts +34 -0
  125. package/src/components/index.ts +16 -0
  126. package/src/components/input-wrapper/input-wrapper.ts +10 -3
  127. package/dist/calendar-BzEtOJla.js +0 -2031
  128. package/dist/calendar-E5jJLt8D.cjs +0 -90
  129. package/dist/input-wrapper-BRuVELpK.cjs +0 -54
  130. /package/dist/{booleanish-jJLlnr6-.cjs → booleanish-B5EFqFpJ.cjs} +0 -0
  131. /package/dist/{booleanish-BmcOU-43.js → booleanish-CRaKZ75t.js} +0 -0
  132. /package/dist/{consent-DeB_5sWH.js → consent-f_IGyHss.js} +0 -0
  133. /package/dist/{consent-hwu7pp3S.cjs → consent-zh2npq2K.cjs} +0 -0
  134. /package/dist/{dialog-polyfill.esm-1hPr0gl9.js → dialog-polyfill.esm-BmVHGRTX.js} +0 -0
  135. /package/dist/{dialog-polyfill.esm-D3Oinvk0.cjs → dialog-polyfill.esm-CXDsb3l-.cjs} +0 -0
  136. /package/dist/{heading-DiQLPxdf.js → heading-BxvE80bb.js} +0 -0
  137. /package/dist/{heading-BgSWIRii.cjs → heading-DRJTS27k.cjs} +0 -0
  138. /package/dist/{input-wrapper-Dwd-_sKm.cjs → input-wrapper-CYCCx8MP.cjs} +0 -0
  139. /package/dist/{input-wrapper-CDyHTR1V.js → input-wrapper-kw2y6Jp_.js} +0 -0
  140. /package/dist/{link-CyTCpI0k.cjs → link-B8zYc5WK.cjs} +0 -0
  141. /package/dist/{link-CKAFMz9j.js → link-CjJcwcxR.js} +0 -0
  142. /package/dist/{linkcard-BE4-FewK.cjs → linkcard-C4x-NJMi.cjs} +0 -0
  143. /package/dist/{linkcard-B0KOGaGj.js → linkcard-Ddh5R_L-.js} +0 -0
  144. /package/dist/{loader-CX2rsC01.cjs → loader-CGQXAuyM.cjs} +0 -0
  145. /package/dist/{loader-6kFZUErT.js → loader-DeizpX45.js} +0 -0
  146. /package/dist/{messagebox-Din9X53b.cjs → messagebox-Ccu29rDv.cjs} +0 -0
  147. /package/dist/{messagebox-Cwlg5sOr.js → messagebox-DqoPqo4K.js} +0 -0
  148. /package/dist/{pkt-options-controller-CXUGx134.cjs → pkt-options-controller-BPBb_O2K.cjs} +0 -0
  149. /package/dist/{pkt-options-controller-CRrI6WMk.js → pkt-options-controller-DbLrdRwH.js} +0 -0
  150. /package/dist/{repeat-Cepr3469.js → repeat-DV1O43YU.js} +0 -0
  151. /package/dist/{repeat-CqcDYBxr.cjs → repeat-DaDFvx86.cjs} +0 -0
  152. /package/dist/{tabs-Rc9N9HWa.js → tabs-4eWA7CsK.js} +0 -0
  153. /package/dist/{tabs-BfurI6C3.cjs → tabs-RnDtIFv3.cjs} +0 -0
  154. /package/dist/{tag-DLthw5b4.cjs → tag-DWtajZk6.cjs} +0 -0
  155. /package/dist/{tag-VwykmuQR.js → tag-hl59gziI.js} +0 -0
  156. /package/dist/{utils-CUqCKGvp.js → utils-BOOZ0ppt.js} +0 -0
  157. /package/dist/{utils-CjAbIttF.cjs → utils-CM67Oudc.cjs} +0 -0
@@ -0,0 +1,769 @@
1
+ import { html } from 'lit'
2
+ import { classMap } from 'lit/directives/class-map.js'
3
+ import { ifDefined } from 'lit/directives/if-defined.js'
4
+
5
+ import { getDisplayFilename, splitFilenameForTruncation } from 'shared-utils/fileupload'
6
+
7
+ import type {
8
+ FileItem,
9
+ TQueueItemOperation,
10
+ TQueueOperationContext,
11
+ TTransferProgress,
12
+ } from './fileupload-types'
13
+
14
+ type TRenderQueueItemProps = {
15
+ file: FileItem
16
+ inputName: string
17
+ disabled: boolean
18
+ fileSize: string
19
+ thumbnailUrl?: string
20
+ previewEnabled?: boolean
21
+ canOpenPreview?: boolean
22
+ transferProgress: TTransferProgress
23
+ transferErrorMessage?: string
24
+ transferShowProgress?: boolean
25
+ transferLastProgress?: number
26
+ loadingText?: string
27
+ operations: TQueueItemOperation[]
28
+ activeOperationId?: string
29
+ truncateTail?: number
30
+ onActivateOperation: (fileId: string, operationId: string) => void
31
+ onCloseOperation: (fileId: string) => void
32
+ getFileAttribute: <T>(fileId: string, name: string) => T | undefined
33
+ setFileAttribute: (fileId: string, name: string, value: unknown) => void
34
+ onCancel: (fileId: string) => void
35
+ onOpenPreview?: (fileId: string) => void
36
+ onThumbnailImageError?: (fileId: string) => void
37
+ }
38
+
39
+ /**
40
+ * Render a filename with optional middle-truncation. The split-or-not decision
41
+ * lives in `splitFilenameForTruncation` (shared with React) so the threshold
42
+ * matches across both runtimes.
43
+ */
44
+ const renderTruncatedFilename = (filename: string, truncateTail?: number) => {
45
+ const split = splitFilenameForTruncation(filename, truncateTail)
46
+ if (!split) return html`<span data-pkt-truncate-part="first">${filename}</span>`
47
+ return html`<span
48
+ class="pkt-fileupload__queue-display__item__title__head"
49
+ data-pkt-truncate-part="first"
50
+ >${split.head}</span
51
+ ><span
52
+ class="pkt-fileupload__queue-display__item__title__tail"
53
+ data-pkt-truncate-part="tail"
54
+ >${split.tail}</span
55
+ >`
56
+ }
57
+
58
+ const renderTitleWithSize = (filename: string, fileSize: string, truncateTail?: number) => html`
59
+ <p class="pkt-fileupload__queue-display__item__title">
60
+ ${renderTruncatedFilename(filename, truncateTail)}${fileSize
61
+ ? html`<span class="pkt-fileupload__queue-display__item__filesize"> (${fileSize})</span>`
62
+ : null}
63
+ </p>
64
+ `
65
+
66
+ const getProgressState = (progress: TTransferProgress): 'in-progress' | 'error' | 'idle' => {
67
+ if (typeof progress === 'number') return 'in-progress'
68
+ if (progress === 'error') return 'error'
69
+ return 'idle'
70
+ }
71
+
72
+ const renderLoadingText = (loadingText?: string) =>
73
+ html`<span class="pkt-fileupload__queue-display__item__loading-text" aria-label="Laster opp fil"
74
+ >${loadingText ?? 'Laster opp...'}</span
75
+ >`
76
+
77
+ const createOperationContext = ({
78
+ operationId,
79
+ file,
80
+ inputName,
81
+ disabled,
82
+ isActive,
83
+ onActivateOperation,
84
+ onCloseOperation,
85
+ getFileAttribute,
86
+ setFileAttribute,
87
+ }: {
88
+ operationId: string
89
+ file: FileItem
90
+ inputName: string
91
+ disabled: boolean
92
+ isActive: boolean
93
+ onActivateOperation: (fileId: string, operationId: string) => void
94
+ onCloseOperation: (fileId: string) => void
95
+ getFileAttribute: <T>(fileId: string, name: string) => T | undefined
96
+ setFileAttribute: (fileId: string, name: string, value: unknown) => void
97
+ }): TQueueOperationContext => ({
98
+ file,
99
+ inputName,
100
+ disabled,
101
+ isActive,
102
+ activate: () => onActivateOperation(file.fileId, operationId),
103
+ close: () => onCloseOperation(file.fileId),
104
+ getAttribute: <T>(name: string) => getFileAttribute<T>(file.fileId, name),
105
+ setAttribute: (name: string, value: unknown) => setFileAttribute(file.fileId, name, value),
106
+ })
107
+
108
+ const renderTransferInProgress = ({
109
+ file,
110
+ fileSize,
111
+ disabled,
112
+ transferProgress,
113
+ transferShowProgress,
114
+ loadingText,
115
+ truncateTail,
116
+ onCancel,
117
+ iconName = 'document-text',
118
+ }: {
119
+ file: FileItem
120
+ fileSize: string
121
+ disabled: boolean
122
+ transferProgress: number
123
+ transferShowProgress?: boolean
124
+ loadingText?: string
125
+ truncateTail?: number
126
+ onCancel: (fileId: string) => void
127
+ iconName?: string
128
+ }) => {
129
+ const filename = getDisplayFilename(file)
130
+ const showProgressBar = transferShowProgress ?? transferProgress !== 0
131
+
132
+ return html`
133
+ <pkt-icon
134
+ name=${iconName}
135
+ class="pkt-fileupload__queue-display__item__icon pkt-icon--medium"
136
+ aria-hidden="true"
137
+ ></pkt-icon>
138
+ ${renderTitleWithSize(filename, fileSize, truncateTail)}
139
+ <button
140
+ type="button"
141
+ class="pkt-fileupload__queue-display__item__cancel-button"
142
+ aria-label="Avbryt opplasting"
143
+ ?disabled=${disabled}
144
+ @click=${() => onCancel(file.fileId)}
145
+ >
146
+ Avbryt
147
+ </button>
148
+ ${showProgressBar
149
+ ? html`
150
+ <pkt-progressbar
151
+ class="pkt-fileupload__queue-display__item__progress"
152
+ valueCurrent=${Math.round(transferProgress * 100)}
153
+ valueMax=${100}
154
+ statusType="none"
155
+ statusPlacement="following"
156
+ ></pkt-progressbar>
157
+ <span class="pkt-fileupload__queue-display__item__percentage" aria-hidden="true"
158
+ >${Math.round(transferProgress * 100)}%</span
159
+ >
160
+ `
161
+ : renderLoadingText(loadingText)}
162
+ `
163
+ }
164
+
165
+ const renderTransferError = ({
166
+ file,
167
+ fileSize,
168
+ disabled,
169
+ transferShowProgress,
170
+ transferLastProgress,
171
+ transferErrorMessage,
172
+ truncateTail,
173
+ onCancel,
174
+ }: {
175
+ file: FileItem
176
+ fileSize: string
177
+ disabled: boolean
178
+ transferShowProgress?: boolean
179
+ transferLastProgress?: number
180
+ transferErrorMessage?: string
181
+ truncateTail?: number
182
+ onCancel: (fileId: string) => void
183
+ }) => {
184
+ const filename = getDisplayFilename(file)
185
+ const showProgressError = !!transferShowProgress
186
+ const progressValue = Math.round((transferLastProgress ?? 1) * 100)
187
+
188
+ return html`
189
+ <pkt-icon
190
+ name="alert-error"
191
+ class="pkt-fileupload__queue-display__item__icon pkt-icon--medium"
192
+ aria-hidden="true"
193
+ ></pkt-icon>
194
+ ${renderTitleWithSize(filename, fileSize, truncateTail)}
195
+ <button
196
+ type="button"
197
+ class="pkt-fileupload__queue-display__item__cancel-button"
198
+ aria-label="Fjern fil som feilet under opplasting"
199
+ ?disabled=${disabled}
200
+ @click=${() => onCancel(file.fileId)}
201
+ >
202
+ Fjern
203
+ </button>
204
+ ${showProgressError
205
+ ? html`
206
+ <pkt-progressbar
207
+ class="pkt-fileupload__queue-display__item__progress pkt-fileupload__queue-display__item__progress--error"
208
+ valueCurrent=${progressValue}
209
+ valueMax=${100}
210
+ statusType="none"
211
+ statusPlacement="following"
212
+ ></pkt-progressbar>
213
+ `
214
+ : null}
215
+ ${transferErrorMessage
216
+ ? html`<span class="pkt-fileupload__queue-display__item__error-message"
217
+ >${transferErrorMessage}</span
218
+ >`
219
+ : null}
220
+ `
221
+ }
222
+
223
+ const renderOperationButtons = ({
224
+ file,
225
+ disabled,
226
+ inputName,
227
+ operations,
228
+ activeOperationId,
229
+ onActivateOperation,
230
+ onCloseOperation,
231
+ getFileAttribute,
232
+ setFileAttribute,
233
+ }: {
234
+ file: FileItem
235
+ disabled: boolean
236
+ inputName: string
237
+ operations: TQueueItemOperation[]
238
+ activeOperationId?: string
239
+ onActivateOperation: (fileId: string, operationId: string) => void
240
+ onCloseOperation: (fileId: string) => void
241
+ getFileAttribute: <T>(fileId: string, name: string) => T | undefined
242
+ setFileAttribute: (fileId: string, name: string, value: unknown) => void
243
+ }) => {
244
+ const activeOperation = operations.find((operation) => operation.id === activeOperationId)
245
+ const hasOperationUIActive = !!(
246
+ activeOperation?.renderInlineUI || activeOperation?.renderExtendedUI
247
+ )
248
+ if (hasOperationUIActive) return null
249
+
250
+ const operationButtons = operations
251
+ .map((operation) => {
252
+ const context = createOperationContext({
253
+ operationId: operation.id,
254
+ file,
255
+ inputName,
256
+ disabled,
257
+ isActive: operation.id === activeOperationId,
258
+ onActivateOperation,
259
+ onCloseOperation,
260
+ getFileAttribute,
261
+ setFileAttribute,
262
+ })
263
+ const title = typeof operation.title === 'function' ? operation.title(file) : operation.title
264
+ if (!title) return null
265
+ const ariaLabel =
266
+ typeof operation.ariaLabel === 'function' ? operation.ariaLabel(file) : operation.ariaLabel
267
+ return html`<button
268
+ type="button"
269
+ class="pkt-fileupload__queue-display__item__operation"
270
+ aria-label=${ifDefined(ariaLabel)}
271
+ ?disabled=${disabled}
272
+ @click=${() => {
273
+ if (operation.renderInlineUI || operation.renderExtendedUI) {
274
+ context.activate()
275
+ }
276
+ operation.onClick?.(context)
277
+ }}
278
+ >
279
+ ${title}
280
+ </button>`
281
+ })
282
+ .filter(Boolean)
283
+
284
+ if (operationButtons.length === 0) return null
285
+ return html`<div class="pkt-fileupload__queue-display__item__actions">${operationButtons}</div>`
286
+ }
287
+
288
+ const renderOperationContent = ({
289
+ file,
290
+ disabled,
291
+ inputName,
292
+ operations,
293
+ activeOperationId,
294
+ onActivateOperation,
295
+ onCloseOperation,
296
+ getFileAttribute,
297
+ setFileAttribute,
298
+ }: {
299
+ file: FileItem
300
+ disabled: boolean
301
+ inputName: string
302
+ operations: TQueueItemOperation[]
303
+ activeOperationId?: string
304
+ onActivateOperation: (fileId: string, operationId: string) => void
305
+ onCloseOperation: (fileId: string) => void
306
+ getFileAttribute: <T>(fileId: string, name: string) => T | undefined
307
+ setFileAttribute: (fileId: string, name: string, value: unknown) => void
308
+ }) =>
309
+ operations.map((operation) => {
310
+ if (!operation.renderContent) return null
311
+ const context = createOperationContext({
312
+ operationId: operation.id,
313
+ file,
314
+ inputName,
315
+ disabled,
316
+ isActive: operation.id === activeOperationId,
317
+ onActivateOperation,
318
+ onCloseOperation,
319
+ getFileAttribute,
320
+ setFileAttribute,
321
+ })
322
+ const content = operation.renderContent(context)
323
+ if (content === null || content === undefined) {
324
+ return null
325
+ }
326
+ return html`<div class="pkt-fileupload__queue-display__item__operation-content">
327
+ ${content}
328
+ </div>`
329
+ })
330
+
331
+ const renderExpandedOperationContent = ({
332
+ file,
333
+ disabled,
334
+ inputName,
335
+ operations,
336
+ activeOperationId,
337
+ onActivateOperation,
338
+ onCloseOperation,
339
+ getFileAttribute,
340
+ setFileAttribute,
341
+ }: {
342
+ file: FileItem
343
+ disabled: boolean
344
+ inputName: string
345
+ operations: TQueueItemOperation[]
346
+ activeOperationId?: string
347
+ onActivateOperation: (fileId: string, operationId: string) => void
348
+ onCloseOperation: (fileId: string) => void
349
+ getFileAttribute: <T>(fileId: string, name: string) => T | undefined
350
+ setFileAttribute: (fileId: string, name: string, value: unknown) => void
351
+ }) => {
352
+ if (!activeOperationId) return null
353
+ const activeOperation = operations.find((operation) => operation.id === activeOperationId)
354
+ if (!activeOperation?.renderExtendedUI) return null
355
+ const context = createOperationContext({
356
+ operationId: activeOperation.id,
357
+ file,
358
+ inputName,
359
+ disabled,
360
+ isActive: true,
361
+ onActivateOperation,
362
+ onCloseOperation,
363
+ getFileAttribute,
364
+ setFileAttribute,
365
+ })
366
+ return html`<div class="pkt-fileupload__queue-display__item__expanded-operation-ui">
367
+ ${activeOperation.renderExtendedUI(context)}
368
+ </div>`
369
+ }
370
+
371
+ const renderHiddenOperationInputs = ({
372
+ file,
373
+ disabled,
374
+ inputName,
375
+ operations,
376
+ activeOperationId,
377
+ onActivateOperation,
378
+ onCloseOperation,
379
+ getFileAttribute,
380
+ setFileAttribute,
381
+ }: {
382
+ file: FileItem
383
+ disabled: boolean
384
+ inputName: string
385
+ operations: TQueueItemOperation[]
386
+ activeOperationId?: string
387
+ onActivateOperation: (fileId: string, operationId: string) => void
388
+ onCloseOperation: (fileId: string) => void
389
+ getFileAttribute: <T>(fileId: string, name: string) => T | undefined
390
+ setFileAttribute: (fileId: string, name: string, value: unknown) => void
391
+ }) =>
392
+ operations.map((operation) => {
393
+ if (!operation.renderHidden) return null
394
+ const context = createOperationContext({
395
+ operationId: operation.id,
396
+ file,
397
+ inputName,
398
+ disabled,
399
+ isActive: operation.id === activeOperationId,
400
+ onActivateOperation,
401
+ onCloseOperation,
402
+ getFileAttribute,
403
+ setFileAttribute,
404
+ })
405
+ return operation.renderHidden(context)
406
+ })
407
+
408
+ const renderFilenameIdleMain = (file: FileItem, fileSize: string, truncateTail?: number) => {
409
+ const filename = getDisplayFilename(file)
410
+ return html`
411
+ <pkt-icon
412
+ name="document-text"
413
+ class="pkt-fileupload__queue-display__item__icon pkt-icon--medium"
414
+ aria-hidden="true"
415
+ ></pkt-icon>
416
+ ${renderTitleWithSize(filename, fileSize, truncateTail)}
417
+ `
418
+ }
419
+
420
+ const renderThumbnailIdleMain = ({
421
+ file,
422
+ thumbnailUrl,
423
+ previewEnabled,
424
+ canOpenPreview,
425
+ truncateTail,
426
+ onOpenPreview,
427
+ onThumbnailImageError,
428
+ }: {
429
+ file: FileItem
430
+ thumbnailUrl?: string
431
+ previewEnabled?: boolean
432
+ canOpenPreview?: boolean
433
+ truncateTail?: number
434
+ onOpenPreview?: (fileId: string) => void
435
+ onThumbnailImageError?: (fileId: string) => void
436
+ }) => {
437
+ const filename = getDisplayFilename(file)
438
+ const hasThumbnail = !!thumbnailUrl
439
+ const showExpandButton = !!previewEnabled && !!canOpenPreview
440
+ const isInteractive = showExpandButton && !!onOpenPreview
441
+
442
+ return html`
443
+ <div
444
+ class=${classMap({
445
+ 'pkt-fileupload__queue-display__item__thumbnail': true,
446
+ 'pkt-fileupload__queue-display__item__thumbnail--fallback': !hasThumbnail,
447
+ })}
448
+ >
449
+ <button
450
+ type="button"
451
+ class="pkt-fileupload__queue-display__item__thumbnail__image-wrapper pkt-btn pkt-btn--medium pkt-btn--secondary pkt-btn--label-only"
452
+ aria-label=${`Forhåndsvis bilde ${filename}`}
453
+ ?disabled=${!isInteractive}
454
+ @click=${() => {
455
+ if (!isInteractive || !onOpenPreview) return
456
+ onOpenPreview(file.fileId)
457
+ }}
458
+ >
459
+ <span class="pkt-btn__text">
460
+ ${showExpandButton
461
+ ? html`<pkt-icon
462
+ name="expand"
463
+ class="pkt-fileupload__queue-display__item__thumbnail__expand-icon"
464
+ aria-hidden="true"
465
+ ></pkt-icon>`
466
+ : null}
467
+ ${hasThumbnail
468
+ ? html`<img
469
+ src=${ifDefined(thumbnailUrl)}
470
+ alt=${filename}
471
+ @error=${() => onThumbnailImageError?.(file.fileId)}
472
+ />`
473
+ : html`<pkt-icon
474
+ name="document-text"
475
+ class="pkt-fileupload__queue-display__item__icon pkt-icon--medium"
476
+ aria-hidden="true"
477
+ ></pkt-icon>`}
478
+ </span>
479
+ </button>
480
+ <span class="pkt-fileupload__queue-display__item__thumbnail__title"
481
+ >${renderTruncatedFilename(filename, truncateTail)}</span
482
+ >
483
+ </div>
484
+ `
485
+ }
486
+
487
+ export const renderFilenameQueueItem = ({
488
+ file,
489
+ inputName,
490
+ disabled,
491
+ fileSize,
492
+ operations,
493
+ activeOperationId,
494
+ onActivateOperation,
495
+ onCloseOperation,
496
+ getFileAttribute,
497
+ setFileAttribute,
498
+ transferProgress,
499
+ transferErrorMessage,
500
+ transferShowProgress,
501
+ transferLastProgress,
502
+ loadingText,
503
+ truncateTail,
504
+ onCancel,
505
+ }: TRenderQueueItemProps) => {
506
+ const state = getProgressState(transferProgress)
507
+ const activeOperation = operations.find((operation) => operation.id === activeOperationId)
508
+ const inlineOperationTemplate = activeOperation?.renderInlineUI
509
+ ? activeOperation.renderInlineUI(
510
+ createOperationContext({
511
+ operationId: activeOperation.id,
512
+ file,
513
+ inputName,
514
+ disabled,
515
+ isActive: true,
516
+ onActivateOperation,
517
+ onCloseOperation,
518
+ getFileAttribute,
519
+ setFileAttribute,
520
+ }),
521
+ )
522
+ : null
523
+
524
+ return html`
525
+ <li
526
+ class=${classMap({
527
+ 'pkt-fileupload__queue-display__item': true,
528
+ 'pkt-fileupload__queue-display__item--in-progress': state === 'in-progress',
529
+ 'pkt-fileupload__queue-display__item--error': state === 'error',
530
+ [`pkt-fileupload__queue-display__item--${transferProgress}`]:
531
+ typeof transferProgress === 'string',
532
+ })}
533
+ >
534
+ ${renderHiddenOperationInputs({
535
+ file,
536
+ disabled,
537
+ inputName,
538
+ operations,
539
+ activeOperationId,
540
+ onActivateOperation,
541
+ onCloseOperation,
542
+ getFileAttribute,
543
+ setFileAttribute,
544
+ })}
545
+ ${state === 'in-progress'
546
+ ? renderTransferInProgress({
547
+ file,
548
+ fileSize,
549
+ disabled,
550
+ transferProgress: transferProgress as number,
551
+ transferShowProgress,
552
+ loadingText,
553
+ truncateTail,
554
+ onCancel,
555
+ })
556
+ : state === 'error'
557
+ ? renderTransferError({
558
+ file,
559
+ fileSize,
560
+ disabled,
561
+ transferShowProgress,
562
+ transferLastProgress,
563
+ transferErrorMessage,
564
+ truncateTail,
565
+ onCancel,
566
+ })
567
+ : inlineOperationTemplate
568
+ ? html`
569
+ <pkt-icon
570
+ name="document-text"
571
+ class="pkt-fileupload__queue-display__item__icon pkt-icon--medium"
572
+ aria-hidden="true"
573
+ ></pkt-icon>
574
+ <div class="pkt-fileupload__queue-display__item__inline-ui">
575
+ ${inlineOperationTemplate}
576
+ </div>
577
+ `
578
+ : renderFilenameIdleMain(file, fileSize, truncateTail)}
579
+ ${state === 'idle'
580
+ ? renderOperationButtons({
581
+ file,
582
+ disabled,
583
+ inputName,
584
+ operations,
585
+ activeOperationId,
586
+ onActivateOperation,
587
+ onCloseOperation,
588
+ getFileAttribute,
589
+ setFileAttribute,
590
+ })
591
+ : null}
592
+ ${state === 'idle'
593
+ ? renderOperationContent({
594
+ file,
595
+ disabled,
596
+ inputName,
597
+ operations,
598
+ activeOperationId,
599
+ onActivateOperation,
600
+ onCloseOperation,
601
+ getFileAttribute,
602
+ setFileAttribute,
603
+ })
604
+ : null}
605
+ ${state === 'idle'
606
+ ? renderExpandedOperationContent({
607
+ file,
608
+ disabled,
609
+ inputName,
610
+ operations,
611
+ activeOperationId,
612
+ onActivateOperation,
613
+ onCloseOperation,
614
+ getFileAttribute,
615
+ setFileAttribute,
616
+ })
617
+ : null}
618
+ </li>
619
+ `
620
+ }
621
+
622
+ export const renderThumbnailQueueItem = ({
623
+ file,
624
+ inputName,
625
+ disabled,
626
+ fileSize,
627
+ thumbnailUrl,
628
+ previewEnabled,
629
+ canOpenPreview,
630
+ operations,
631
+ activeOperationId,
632
+ onActivateOperation,
633
+ onCloseOperation,
634
+ getFileAttribute,
635
+ setFileAttribute,
636
+ transferProgress,
637
+ transferErrorMessage,
638
+ transferShowProgress,
639
+ transferLastProgress,
640
+ loadingText,
641
+ truncateTail,
642
+ onCancel,
643
+ onOpenPreview,
644
+ onThumbnailImageError,
645
+ }: TRenderQueueItemProps) => {
646
+ const state = getProgressState(transferProgress)
647
+ const activeOperation = operations.find((operation) => operation.id === activeOperationId)
648
+ const inlineOperationTemplate = activeOperation?.renderInlineUI
649
+ ? activeOperation.renderInlineUI(
650
+ createOperationContext({
651
+ operationId: activeOperation.id,
652
+ file,
653
+ inputName,
654
+ disabled,
655
+ isActive: true,
656
+ onActivateOperation,
657
+ onCloseOperation,
658
+ getFileAttribute,
659
+ setFileAttribute,
660
+ }),
661
+ )
662
+ : null
663
+
664
+ return html`
665
+ <li
666
+ class=${classMap({
667
+ 'pkt-fileupload__queue-display__item': true,
668
+ 'pkt-fileupload__queue-display__item--in-progress': state === 'in-progress',
669
+ 'pkt-fileupload__queue-display__item--error': state === 'error',
670
+ [`pkt-fileupload__queue-display__item--${transferProgress}`]:
671
+ typeof transferProgress === 'string',
672
+ })}
673
+ >
674
+ ${renderHiddenOperationInputs({
675
+ file,
676
+ disabled,
677
+ inputName,
678
+ operations,
679
+ activeOperationId,
680
+ onActivateOperation,
681
+ onCloseOperation,
682
+ getFileAttribute,
683
+ setFileAttribute,
684
+ })}
685
+ ${state === 'in-progress'
686
+ ? renderTransferInProgress({
687
+ file,
688
+ fileSize,
689
+ disabled,
690
+ transferProgress: transferProgress as number,
691
+ transferShowProgress,
692
+ loadingText,
693
+ truncateTail,
694
+ onCancel,
695
+ iconName: 'picture',
696
+ })
697
+ : state === 'error'
698
+ ? renderTransferError({
699
+ file,
700
+ fileSize,
701
+ disabled,
702
+ transferShowProgress,
703
+ transferLastProgress,
704
+ transferErrorMessage,
705
+ truncateTail,
706
+ onCancel,
707
+ })
708
+ : inlineOperationTemplate
709
+ ? html`
710
+ <pkt-icon
711
+ name="picture"
712
+ class="pkt-fileupload__queue-display__item__icon pkt-icon--medium"
713
+ aria-hidden="true"
714
+ ></pkt-icon>
715
+ <div class="pkt-fileupload__queue-display__item__inline-ui">
716
+ ${inlineOperationTemplate}
717
+ </div>
718
+ `
719
+ : renderThumbnailIdleMain({
720
+ file,
721
+ thumbnailUrl,
722
+ previewEnabled,
723
+ canOpenPreview,
724
+ truncateTail,
725
+ onOpenPreview,
726
+ onThumbnailImageError,
727
+ })}
728
+ ${state === 'idle'
729
+ ? renderOperationButtons({
730
+ file,
731
+ disabled,
732
+ inputName,
733
+ operations,
734
+ activeOperationId,
735
+ onActivateOperation,
736
+ onCloseOperation,
737
+ getFileAttribute,
738
+ setFileAttribute,
739
+ })
740
+ : null}
741
+ ${state === 'idle'
742
+ ? renderOperationContent({
743
+ file,
744
+ disabled,
745
+ inputName,
746
+ operations,
747
+ activeOperationId,
748
+ onActivateOperation,
749
+ onCloseOperation,
750
+ getFileAttribute,
751
+ setFileAttribute,
752
+ })
753
+ : null}
754
+ ${state === 'idle'
755
+ ? renderExpandedOperationContent({
756
+ file,
757
+ disabled,
758
+ inputName,
759
+ operations,
760
+ activeOperationId,
761
+ onActivateOperation,
762
+ onCloseOperation,
763
+ getFileAttribute,
764
+ setFileAttribute,
765
+ })
766
+ : null}
767
+ </li>
768
+ `
769
+ }