@lion/ui 0.2.2 → 0.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 (269) hide show
  1. package/README.md +23 -72
  2. package/components/button/src/LionButton.js +1 -1
  3. package/components/core/src/SlotMixin.js +3 -2
  4. package/components/core/test/SlotMixin.test.js +1 -1
  5. package/components/core/types/SlotMixinTypes.ts +4 -1
  6. package/components/dialog/test/lion-dialog.test.js +56 -0
  7. package/components/form-core/src/FocusMixin.js +33 -2
  8. package/components/form-core/src/LionField.js +3 -1
  9. package/components/form-core/src/validate/LionValidationFeedback.js +2 -5
  10. package/components/form-core/src/validate/ResultValidator.js +1 -1
  11. package/components/form-core/test/FocusMixin.test.js +33 -0
  12. package/components/form-core/types/validate/ValidateMixinTypes.ts +2 -2
  13. package/components/form-integrations/test/dialog-integrations.test.js +19 -0
  14. package/components/form-integrations/test/form-group-methods.test.js +11 -37
  15. package/components/form-integrations/test/form-integrations.test.js +4 -0
  16. package/components/form-integrations/test/helpers/umbrella-form.js +21 -18
  17. package/components/input-amount/src/LionInputAmount.js +13 -4
  18. package/components/input-amount/test/lion-input-amount.test.js +19 -20
  19. package/components/input-date/src/LionInputDate.js +2 -11
  20. package/components/input-datepicker/src/LionInputDatepicker.js +5 -2
  21. package/components/input-datepicker/test/lion-input-datepicker.test.js +20 -0
  22. package/components/input-file/src/FileHandle.js +98 -0
  23. package/components/input-file/src/LionInputFile.js +892 -0
  24. package/components/input-file/src/LionSelectedFileList.js +232 -0
  25. package/components/input-file/src/localizeNamespaceLoader.js +75 -0
  26. package/components/input-file/src/validators.js +114 -0
  27. package/components/input-file/test/FileValidators.test.js +170 -0
  28. package/components/input-file/test/lion-input-file.test.js +1168 -0
  29. package/components/input-file/test/lion-selected-file-list.test.js +207 -0
  30. package/components/input-file/translations/bg-BG.js +5 -0
  31. package/components/input-file/translations/bg.js +12 -0
  32. package/components/input-file/translations/cs-CZ.js +5 -0
  33. package/components/input-file/translations/cs.js +13 -0
  34. package/components/input-file/translations/de-DE.js +5 -0
  35. package/components/input-file/translations/de.js +12 -0
  36. package/components/input-file/translations/en-AU.js +5 -0
  37. package/components/input-file/translations/en-GB.js +5 -0
  38. package/components/input-file/translations/en-PH.js +5 -0
  39. package/components/input-file/translations/en-US.js +5 -0
  40. package/components/input-file/translations/en.js +12 -0
  41. package/components/input-file/translations/es-ES.js +5 -0
  42. package/components/input-file/translations/es.js +12 -0
  43. package/components/input-file/translations/fr-BE.js +5 -0
  44. package/components/input-file/translations/fr-FR.js +5 -0
  45. package/components/input-file/translations/fr.js +13 -0
  46. package/components/input-file/translations/hu-HU.js +5 -0
  47. package/components/input-file/translations/hu.js +12 -0
  48. package/components/input-file/translations/it-IT.js +5 -0
  49. package/components/input-file/translations/it.js +12 -0
  50. package/components/input-file/translations/nl-BE.js +5 -0
  51. package/components/input-file/translations/nl-NL.js +5 -0
  52. package/components/input-file/translations/nl.js +12 -0
  53. package/components/input-file/translations/pl-PL.js +5 -0
  54. package/components/input-file/translations/pl.js +12 -0
  55. package/components/input-file/translations/ro-RO.js +5 -0
  56. package/components/input-file/translations/ro.js +12 -0
  57. package/components/input-file/translations/ru-RU.js +5 -0
  58. package/components/input-file/translations/ru.js +12 -0
  59. package/components/input-file/translations/sk-SK.js +5 -0
  60. package/components/input-file/translations/sk.js +12 -0
  61. package/components/input-file/translations/uk-UA.js +5 -0
  62. package/components/input-file/translations/uk.js +12 -0
  63. package/components/input-file/translations/zh-CN.js +5 -0
  64. package/components/input-file/translations/zh.js +12 -0
  65. package/components/input-file/types/input-file.d.ts +32 -0
  66. package/components/input-tel-dropdown/src/LionInputTelDropdown.js +1 -4
  67. package/custom-elements.json +22127 -17708
  68. package/dist-types/components/core/src/SlotMixin.d.ts.map +1 -1
  69. package/dist-types/components/core/types/SlotMixinTypes.d.ts +4 -0
  70. package/dist-types/components/core/types/SlotMixinTypes.d.ts.map +1 -1
  71. package/dist-types/components/dialog/test/lion-dialog.test.d.ts.map +1 -1
  72. package/dist-types/components/form-core/src/FocusMixin.d.ts.map +1 -1
  73. package/dist-types/components/form-core/src/LionField.d.ts +2 -1
  74. package/dist-types/components/form-core/src/LionField.d.ts.map +1 -1
  75. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts +4 -11
  76. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts.map +1 -1
  77. package/dist-types/components/form-core/src/validate/ResultValidator.d.ts +1 -1
  78. package/dist-types/components/form-core/types/validate/ValidateMixinTypes.d.ts +5 -0
  79. package/dist-types/components/form-core/types/validate/ValidateMixinTypes.d.ts.map +1 -1
  80. package/dist-types/components/form-integrations/test/dialog-integrations.test.d.ts.map +1 -1
  81. package/dist-types/components/form-integrations/test/form-group-methods.test.d.ts +1 -0
  82. package/dist-types/components/form-integrations/test/form-group-methods.test.d.ts.map +1 -1
  83. package/dist-types/components/form-integrations/test/helpers/umbrella-form.d.ts.map +1 -1
  84. package/dist-types/components/input-amount/src/LionInputAmount.d.ts +2 -4
  85. package/dist-types/components/input-amount/src/LionInputAmount.d.ts.map +1 -1
  86. package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts +0 -1
  87. package/dist-types/components/input-amount/test/lion-input-amount.test.d.ts.map +1 -1
  88. package/dist-types/components/input-date/src/LionInputDate.d.ts.map +1 -1
  89. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
  90. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts.map +1 -1
  91. package/dist-types/components/input-file/src/FileHandle.d.ts +55 -0
  92. package/dist-types/components/input-file/src/FileHandle.d.ts.map +1 -0
  93. package/dist-types/components/input-file/src/LionInputFile.d.ts +230 -0
  94. package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -0
  95. package/dist-types/components/input-file/src/LionSelectedFileList.d.ts +69 -0
  96. package/dist-types/components/input-file/src/LionSelectedFileList.d.ts.map +1 -0
  97. package/dist-types/components/input-file/src/localizeNamespaceLoader.d.ts +2 -0
  98. package/dist-types/components/input-file/src/localizeNamespaceLoader.d.ts.map +1 -0
  99. package/dist-types/components/input-file/src/validators.d.ts +58 -0
  100. package/dist-types/components/input-file/src/validators.d.ts.map +1 -0
  101. package/dist-types/components/input-file/test/FileValidators.test.d.ts +2 -0
  102. package/dist-types/components/input-file/test/FileValidators.test.d.ts.map +1 -0
  103. package/dist-types/components/input-file/test/lion-input-file.test.d.ts +5 -0
  104. package/dist-types/components/input-file/test/lion-input-file.test.d.ts.map +1 -0
  105. package/dist-types/components/input-file/test/lion-selected-file-list.test.d.ts +4 -0
  106. package/dist-types/components/input-file/test/lion-selected-file-list.test.d.ts.map +1 -0
  107. package/dist-types/components/input-file/translations/bg-BG.d.ts +13 -0
  108. package/dist-types/components/input-file/translations/bg-BG.d.ts.map +1 -0
  109. package/dist-types/components/input-file/translations/bg.d.ts +13 -0
  110. package/dist-types/components/input-file/translations/bg.d.ts.map +1 -0
  111. package/dist-types/components/input-file/translations/cs-CZ.d.ts +13 -0
  112. package/dist-types/components/input-file/translations/cs-CZ.d.ts.map +1 -0
  113. package/dist-types/components/input-file/translations/cs.d.ts +13 -0
  114. package/dist-types/components/input-file/translations/cs.d.ts.map +1 -0
  115. package/dist-types/components/input-file/translations/de-DE.d.ts +13 -0
  116. package/dist-types/components/input-file/translations/de-DE.d.ts.map +1 -0
  117. package/dist-types/components/input-file/translations/de.d.ts +13 -0
  118. package/dist-types/components/input-file/translations/de.d.ts.map +1 -0
  119. package/dist-types/components/input-file/translations/en-AU.d.ts +13 -0
  120. package/dist-types/components/input-file/translations/en-AU.d.ts.map +1 -0
  121. package/dist-types/components/input-file/translations/en-GB.d.ts +13 -0
  122. package/dist-types/components/input-file/translations/en-GB.d.ts.map +1 -0
  123. package/dist-types/components/input-file/translations/en-PH.d.ts +13 -0
  124. package/dist-types/components/input-file/translations/en-PH.d.ts.map +1 -0
  125. package/dist-types/components/input-file/translations/en-US.d.ts +13 -0
  126. package/dist-types/components/input-file/translations/en-US.d.ts.map +1 -0
  127. package/dist-types/components/input-file/translations/en.d.ts +13 -0
  128. package/dist-types/components/input-file/translations/en.d.ts.map +1 -0
  129. package/dist-types/components/input-file/translations/es-ES.d.ts +13 -0
  130. package/dist-types/components/input-file/translations/es-ES.d.ts.map +1 -0
  131. package/dist-types/components/input-file/translations/es.d.ts +13 -0
  132. package/dist-types/components/input-file/translations/es.d.ts.map +1 -0
  133. package/dist-types/components/input-file/translations/fr-BE.d.ts +13 -0
  134. package/dist-types/components/input-file/translations/fr-BE.d.ts.map +1 -0
  135. package/dist-types/components/input-file/translations/fr-FR.d.ts +13 -0
  136. package/dist-types/components/input-file/translations/fr-FR.d.ts.map +1 -0
  137. package/dist-types/components/input-file/translations/fr.d.ts +13 -0
  138. package/dist-types/components/input-file/translations/fr.d.ts.map +1 -0
  139. package/dist-types/components/input-file/translations/hu-HU.d.ts +13 -0
  140. package/dist-types/components/input-file/translations/hu-HU.d.ts.map +1 -0
  141. package/dist-types/components/input-file/translations/hu.d.ts +13 -0
  142. package/dist-types/components/input-file/translations/hu.d.ts.map +1 -0
  143. package/dist-types/components/input-file/translations/it-IT.d.ts +13 -0
  144. package/dist-types/components/input-file/translations/it-IT.d.ts.map +1 -0
  145. package/dist-types/components/input-file/translations/it.d.ts +13 -0
  146. package/dist-types/components/input-file/translations/it.d.ts.map +1 -0
  147. package/dist-types/components/input-file/translations/nl-BE.d.ts +13 -0
  148. package/dist-types/components/input-file/translations/nl-BE.d.ts.map +1 -0
  149. package/dist-types/components/input-file/translations/nl-NL.d.ts +13 -0
  150. package/dist-types/components/input-file/translations/nl-NL.d.ts.map +1 -0
  151. package/dist-types/components/input-file/translations/nl.d.ts +13 -0
  152. package/dist-types/components/input-file/translations/nl.d.ts.map +1 -0
  153. package/dist-types/components/input-file/translations/pl-PL.d.ts +13 -0
  154. package/dist-types/components/input-file/translations/pl-PL.d.ts.map +1 -0
  155. package/dist-types/components/input-file/translations/pl.d.ts +13 -0
  156. package/dist-types/components/input-file/translations/pl.d.ts.map +1 -0
  157. package/dist-types/components/input-file/translations/ro-RO.d.ts +13 -0
  158. package/dist-types/components/input-file/translations/ro-RO.d.ts.map +1 -0
  159. package/dist-types/components/input-file/translations/ro.d.ts +13 -0
  160. package/dist-types/components/input-file/translations/ro.d.ts.map +1 -0
  161. package/dist-types/components/input-file/translations/ru-RU.d.ts +13 -0
  162. package/dist-types/components/input-file/translations/ru-RU.d.ts.map +1 -0
  163. package/dist-types/components/input-file/translations/ru.d.ts +13 -0
  164. package/dist-types/components/input-file/translations/ru.d.ts.map +1 -0
  165. package/dist-types/components/input-file/translations/sk-SK.d.ts +13 -0
  166. package/dist-types/components/input-file/translations/sk-SK.d.ts.map +1 -0
  167. package/dist-types/components/input-file/translations/sk.d.ts +13 -0
  168. package/dist-types/components/input-file/translations/sk.d.ts.map +1 -0
  169. package/dist-types/components/input-file/translations/uk-UA.d.ts +13 -0
  170. package/dist-types/components/input-file/translations/uk-UA.d.ts.map +1 -0
  171. package/dist-types/components/input-file/translations/uk.d.ts +13 -0
  172. package/dist-types/components/input-file/translations/uk.d.ts.map +1 -0
  173. package/dist-types/components/input-file/translations/zh-CN.d.ts +13 -0
  174. package/dist-types/components/input-file/translations/zh-CN.d.ts.map +1 -0
  175. package/dist-types/components/input-file/translations/zh.d.ts +13 -0
  176. package/dist-types/components/input-file/translations/zh.d.ts.map +1 -0
  177. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts +6 -0
  178. package/dist-types/components/input-stepper/src/LionInputStepper.d.ts.map +1 -1
  179. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts +0 -1
  180. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts.map +1 -1
  181. package/dist-types/exports/define/lion-input-file.d.ts +2 -0
  182. package/dist-types/exports/define/lion-input-file.d.ts.map +1 -0
  183. package/dist-types/exports/define/lion-selected-file-list.d.ts +2 -0
  184. package/dist-types/exports/define/lion-selected-file-list.d.ts.map +1 -0
  185. package/dist-types/exports/input-file.d.ts +4 -0
  186. package/dist-types/exports/input-file.d.ts.map +1 -0
  187. package/dist-types/tsconfig.tsbuildinfo +1 -1
  188. package/docs/components/button/extensions/bootstrap-button.mjs +240 -0
  189. package/docs/components/button/extensions.md +30 -0
  190. package/docs/components/checkbox-group/overview.md +2 -2
  191. package/docs/components/collapsible/overview.md +1 -1
  192. package/docs/components/combobox/{examples.md → extensions.md} +51 -43
  193. package/docs/components/combobox/overview.md +1 -1
  194. package/docs/components/combobox/src/google-combobox/google-combobox.js +4 -1
  195. package/docs/components/combobox/src/md-combobox/md-combobox.js +40 -38
  196. package/docs/components/combobox/src/wa-combobox/wa-combobox.js +204 -209
  197. package/docs/components/combobox/use-cases.md +1 -1
  198. package/docs/components/dialog/overview.md +1 -1
  199. package/docs/components/fieldset/overview.md +5 -5
  200. package/docs/components/form/overview.md +3 -3
  201. package/docs/components/icon/use-cases.md +1 -1
  202. package/docs/components/input/overview.md +1 -1
  203. package/docs/components/input-amount/overview.md +3 -3
  204. package/docs/components/input-amount/use-cases.md +4 -2
  205. package/docs/components/input-date/overview.md +3 -3
  206. package/docs/components/input-datepicker/overview.md +5 -5
  207. package/docs/components/input-datepicker/use-cases.md +11 -0
  208. package/docs/components/input-email/overview.md +2 -2
  209. package/docs/components/input-file/index.md +3 -0
  210. package/docs/components/input-file/overview.md +32 -0
  211. package/docs/components/input-file/use-cases.md +424 -0
  212. package/docs/components/input-iban/overview.md +2 -2
  213. package/docs/components/input-range/overview.md +2 -2
  214. package/docs/components/input-stepper/overview.md +1 -1
  215. package/docs/components/input-tel/overview.md +1 -1
  216. package/docs/components/input-tel/use-cases.md +6 -6
  217. package/docs/components/input-tel-dropdown/{examples.md → extensions.md} +1 -1
  218. package/docs/components/input-tel-dropdown/overview.md +1 -1
  219. package/docs/components/radio-group/overview.md +3 -3
  220. package/docs/components/select-rich/use-cases.md +1 -1
  221. package/docs/components/tabs/examples.md +1 -1
  222. package/docs/fundamentals/index.md +4 -4
  223. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  224. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  225. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  226. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  227. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  228. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  229. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  230. package/docs/fundamentals/rationales/accessibility.md +28 -0
  231. package/docs/fundamentals/systems/core/SlotMixin.md +148 -0
  232. package/docs/fundamentals/systems/core/overview.md +1 -1
  233. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  234. package/docs/fundamentals/systems/form/model-value.md +1 -1
  235. package/docs/fundamentals/systems/form/overview.md +28 -28
  236. package/docs/fundamentals/systems/form/use-cases.md +2 -0
  237. package/docs/fundamentals/systems/form/validate.md +1 -1
  238. package/docs/fundamentals/systems/localize/overview.md +4 -4
  239. package/docs/fundamentals/systems/overlays/_configuration-positioning.md +116 -0
  240. package/docs/fundamentals/systems/overlays/_edge-cases.md +145 -0
  241. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  242. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  243. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  244. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  245. package/docs/fundamentals/tools/singleton-manager/example-complex/index.md +21 -0
  246. package/docs/fundamentals/tools/singleton-manager/example-fail/index.md +19 -0
  247. package/docs/fundamentals/tools/singleton-manager/example-success/index.md +18 -0
  248. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  249. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  250. package/docs/guides/how-to/create-a-form.md +88 -0
  251. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  252. package/docs/guides/index.md +8 -8
  253. package/docs/guides/principles/index.md +5 -5
  254. package/docs/guides/principles/styling.md +1 -1
  255. package/exports/define/lion-input-file.js +3 -0
  256. package/exports/define/lion-selected-file-list.js +3 -0
  257. package/exports/input-file.js +3 -0
  258. package/package.json +3 -2
  259. package/docs/components/combobox/src/google-combobox/assets/googlelogo_color_272x92dp.png +0 -0
  260. package/docs/components/dialog/src/demo-dialog-style.js +0 -3
  261. package/docs/components/select-rich/src/flags.png +0 -0
  262. package/docs/components/select-rich/src/flags@2x.png +0 -0
  263. package/docs/fundamentals/systems/form/assets/FormatMixinDiagram-1.svg +0 -1
  264. package/docs/fundamentals/systems/form/assets/FormatMixinDiagram-2.svg +0 -1
  265. package/docs/fundamentals/systems/form/assets/FormatMixinDiagram-3.svg +0 -84
  266. package/docs/fundamentals/tools/ajax/assets/naga.json +0 -9
  267. package/docs/fundamentals/tools/ajax/assets/pabu.json +0 -9
  268. package/docs/fundamentals/tools/singleton-manager/.gitignore +0 -1
  269. package/docs/override.js +0 -15
@@ -0,0 +1,892 @@
1
+ import { LionField } from '@lion/ui/form-core.js';
2
+ import { LocalizeMixin } from '@lion/ui/localize.js';
3
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements';
4
+ import { css, html } from 'lit';
5
+ import { ifDefined } from 'lit/directives/if-defined.js';
6
+ import { FileHandle, MAX_FILE_SIZE } from './FileHandle.js';
7
+ import { LionSelectedFileList } from './LionSelectedFileList.js';
8
+ import { localizeNamespaceLoader } from './localizeNamespaceLoader.js';
9
+ import { DuplicateFileNames, IsAcceptedFile } from './validators.js';
10
+
11
+ /**
12
+ * @typedef {import('lit').TemplateResult} TemplateResult
13
+ * @typedef {import('lit').RenderOptions} RenderOptions
14
+ * @typedef {import('../types/input-file.js').InputFile} InputFile
15
+ * @typedef {import('../types/input-file.js').SystemFile} SystemFile
16
+ * @typedef {import('../types/input-file.js').UploadResponse} UploadResponse
17
+ */
18
+
19
+ /**
20
+ * @param {number} bytes
21
+ * @param {number} decimals
22
+ */
23
+ function formatBytes(bytes, decimals = 2) {
24
+ if (!+bytes) {
25
+ return '0 Bytes';
26
+ }
27
+ const k = 1024;
28
+ const dm = decimals < 0 ? 0 : decimals;
29
+ const sizes = [' bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
30
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
31
+ return `${parseFloat((bytes / k ** i).toFixed(dm))}${sizes[i]}`;
32
+ }
33
+
34
+ export class LionInputFile extends ScopedElementsMixin(LocalizeMixin(LionField)) {
35
+ static get scopedElements() {
36
+ return {
37
+ ...super.scopedElements,
38
+ 'lion-selected-file-list': LionSelectedFileList,
39
+ };
40
+ }
41
+
42
+ static get properties() {
43
+ return {
44
+ accept: { type: String },
45
+ multiple: { type: Boolean, reflect: true },
46
+ buttonLabel: { type: String, attribute: 'button-label' },
47
+ maxFileSize: { type: Number, attribute: 'max-file-size' },
48
+ enableDropZone: { type: Boolean, attribute: 'enable-drop-zone' },
49
+ uploadOnSelect: { type: Boolean, attribute: 'upload-on-select' },
50
+ uploadResponse: { type: Array, state: false },
51
+ _selectedFilesMetaData: { type: Array, state: true },
52
+ };
53
+ }
54
+
55
+ static localizeNamespaces = [
56
+ { 'lion-input-file': localizeNamespaceLoader },
57
+ ...super.localizeNamespaces,
58
+ ];
59
+
60
+ static get validationTypes() {
61
+ return ['error', 'info'];
62
+ }
63
+
64
+ /**
65
+ * @configure SlotMixin
66
+ */
67
+ get slots() {
68
+ return {
69
+ ...super.slots,
70
+ input: () => html`<input .value="${ifDefined(this.getAttribute('value'))}" />`,
71
+ 'file-select-button': () =>
72
+ html`<button
73
+ type="button"
74
+ id="select-button-${this._inputId}"
75
+ @click="${this.__openDialogOnBtnClick}"
76
+ >
77
+ ${this.buttonLabel}
78
+ </button>`,
79
+ 'selected-file-list': () => ({
80
+ template: html`
81
+ <lion-selected-file-list
82
+ .fileList=${this._selectedFilesMetaData}
83
+ .multiple=${this.multiple}
84
+ ></lion-selected-file-list>
85
+ `,
86
+ }),
87
+ };
88
+ }
89
+
90
+ /**
91
+ * @type {HTMLInputElement}
92
+ * @protected
93
+ */
94
+ get _inputNode() {
95
+ return /** @type {HTMLInputElement} */ (super._inputNode);
96
+ }
97
+
98
+ /**
99
+ * @protected
100
+ */
101
+ get _buttonNode() {
102
+ return this.querySelector(`#select-button-${this._inputId}`);
103
+ }
104
+
105
+ /**
106
+ * The helpt text for the input node.
107
+ * When no light dom defined via [slot=help-text], this value will be used
108
+ * @type {string}
109
+ */
110
+ get buttonLabel() {
111
+ return this.__buttonLabel || this._buttonNode?.textContent || '';
112
+ }
113
+
114
+ /**
115
+ * @param {string} newValue
116
+ */
117
+ set buttonLabel(newValue) {
118
+ const oldValue = this.buttonLabel;
119
+ /** @type {string} */
120
+ this.__buttonLabel = newValue;
121
+ this.requestUpdate('buttonLabel', oldValue);
122
+ }
123
+
124
+ /**
125
+ * @protected
126
+ * @configure FocusMixin
127
+ */
128
+ // @ts-ignore
129
+ get _focusableNode() {
130
+ return this._buttonNode;
131
+ }
132
+
133
+ /**
134
+ * @protected
135
+ */
136
+ // TODO: no need to check anymore? https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/draggable
137
+ // eslint-disable-next-line class-methods-use-this
138
+ get _isDragAndDropSupported() {
139
+ return 'draggable' in document.createElement('div');
140
+ }
141
+
142
+ constructor() {
143
+ super();
144
+ this.type = 'file';
145
+ /**
146
+ * @protected
147
+ * @type {InputFile[]}
148
+ */
149
+ this._selectedFilesMetaData = [];
150
+ /**
151
+ * @type {UploadResponse[]}
152
+ */
153
+ // TODO: make readonly?
154
+ this.uploadResponse = [];
155
+ /**
156
+ * @private
157
+ */
158
+ this.__initialUploadResponse = this.uploadResponse;
159
+ // TODO: public default booleans are always false
160
+ this.uploadOnSelect = false;
161
+ this.multiple = false;
162
+ this.enableDropZone = false;
163
+ this.maxFileSize = MAX_FILE_SIZE;
164
+ this.accept = '';
165
+ /**
166
+ * @type {InputFile[]}
167
+ */
168
+ this.modelValue = [];
169
+ /**
170
+ * @protected
171
+ * @type {EventListener}
172
+ */
173
+ this._onRemoveFile = this._onRemoveFile.bind(this);
174
+
175
+ /** @private */
176
+ this.__duplicateFileNamesValidator = new DuplicateFileNames({ show: false });
177
+ }
178
+
179
+ connectedCallback() {
180
+ super.connectedCallback();
181
+ this.__initialUploadResponse = this.uploadResponse;
182
+
183
+ this._inputNode.addEventListener('change', this._onChange);
184
+ this._inputNode.addEventListener('click', this._onClick);
185
+ this.addEventListener(
186
+ 'file-remove-requested',
187
+ /** @type {EventListener} */ (this._onRemoveFile),
188
+ );
189
+ }
190
+
191
+ disconnectedCallback() {
192
+ super.disconnectedCallback();
193
+ this._inputNode.removeEventListener('change', this._onChange);
194
+ this._inputNode.removeEventListener('click', this._onClick);
195
+ this.removeEventListener(
196
+ 'file-remove-requested',
197
+ /** @type {EventListener} */ (this._onRemoveFile),
198
+ );
199
+ }
200
+
201
+ /**
202
+ * @configure LocalizeMixin
203
+ */
204
+ onLocaleUpdated() {
205
+ super.onLocaleUpdated();
206
+ if (this.multiple) {
207
+ // @ts-ignore
208
+ this.buttonLabel = this.msgLit('lion-input-file:selectTextMultipleFile');
209
+ } else {
210
+ // @ts-ignore
211
+ this.buttonLabel = this.msgLit('lion-input-file:selectTextSingleFile');
212
+ }
213
+ }
214
+
215
+ get _acceptCriteria() {
216
+ /** @type {string[]} */
217
+ let allowedFileTypes = [];
218
+ /** @type {string[]} */
219
+ let allowedFileExtensions = [];
220
+ if (this.accept) {
221
+ const acceptedFiles = this.accept.replace(/\s+/g, '').replace(/\.+/g, '').split(',');
222
+ allowedFileTypes = acceptedFiles.filter(acceptedFile => acceptedFile.includes('/'));
223
+ allowedFileExtensions = acceptedFiles.filter(acceptedFile => !acceptedFile.includes('/'));
224
+ }
225
+ return {
226
+ allowedFileTypes,
227
+ allowedFileExtensions,
228
+ maxFileSize: this.maxFileSize,
229
+ };
230
+ }
231
+
232
+ /**
233
+ * @enhance LionField
234
+ * Resets modelValue to initial value.
235
+ * Interaction states are cleared
236
+ */
237
+ reset() {
238
+ super.reset();
239
+ this._selectedFilesMetaData = [];
240
+ this.uploadResponse = this.__initialUploadResponse;
241
+ this.modelValue = [];
242
+ // TODO: find out why it stays dirty
243
+ this.dirty = false;
244
+ }
245
+
246
+ /**
247
+ * Clears modelValue.
248
+ * Interaction states are not cleared (use resetInteractionState for this)
249
+ * @override LionField
250
+ */
251
+ clear() {
252
+ this._selectedFilesMetaData = [];
253
+ this.uploadResponse = [];
254
+ this.modelValue = [];
255
+ }
256
+
257
+ /**
258
+ * @override ValidateMixin: override to hide the IsAcceptedFile feedback at component level as they are displayed at each file level in file list
259
+ * @param {string} type could be 'error', 'warning', 'info', 'success' or any other custom
260
+ * @param {object} meta meta info (interaction states etc)
261
+ * @protected
262
+ */
263
+ _showFeedbackConditionFor(type, meta) {
264
+ return (
265
+ super._showFeedbackConditionFor(type, meta) &&
266
+ !(
267
+ this.validationStates.error?.FileTypeAllowed || this.validationStates.error?.FileSizeAllowed
268
+ )
269
+ );
270
+ }
271
+
272
+ /**
273
+ * @configure FormatMixin
274
+ * @returns {InputFile[]} parsedValue
275
+ */
276
+ parser() {
277
+ // @ts-ignore
278
+ return this._inputNode.files ? Array.from(this._inputNode.files) : [];
279
+ }
280
+
281
+ /**
282
+ * @configure FormatMixin
283
+ * @param {InputFile[]} v - modelValue: File[]
284
+ * @returns {string} formattedValue
285
+ */
286
+ // eslint-disable-next-line no-unused-vars
287
+ formatter(v) {
288
+ return this._inputNode?.value || '';
289
+ }
290
+
291
+ /** @private */
292
+ __setupDragDropEventListeners() {
293
+ // TODO: this will break as soon as a Subclasser changes the template ... (changing class names is allowed, ids should be kept)
294
+ const dropZone = this.shadowRoot?.querySelector('.input-file__drop-zone');
295
+ ['dragenter', 'dragover', 'dragleave'].forEach(eventName => {
296
+ dropZone?.addEventListener(
297
+ eventName,
298
+ (/** @type {Event} */ ev) => {
299
+ ev.preventDefault();
300
+ ev.stopPropagation();
301
+ if (eventName !== 'dragleave') {
302
+ this.setAttribute('is-dragging', '');
303
+ } else {
304
+ this.removeAttribute('is-dragging');
305
+ }
306
+ },
307
+ false,
308
+ );
309
+ });
310
+
311
+ window.addEventListener(
312
+ 'drop',
313
+ ev => {
314
+ if (ev.target === this._inputNode) {
315
+ ev.preventDefault();
316
+ }
317
+ this.removeAttribute('is-dragging');
318
+ },
319
+ false,
320
+ );
321
+ }
322
+
323
+ /**
324
+ * @param {import('lit').PropertyValues } changedProperties
325
+ */
326
+ firstUpdated(changedProperties) {
327
+ super.firstUpdated(changedProperties);
328
+
329
+ this.__setupFileValidators();
330
+ // We need to update our light dom
331
+ this._enhanceSelectedList();
332
+
333
+ // TODO: if there's no inputNode by now, we should either throw an error or add a slot change listener
334
+ if (this._inputNode) {
335
+ // TODO: should it be possible to change this on the fly (in updated)?
336
+ this._inputNode.type = this.type;
337
+ this._inputNode.setAttribute('tabindex', '-1');
338
+ // TODO: should it be possible to change this on the fly (in updated)?
339
+ this._inputNode.multiple = this.multiple;
340
+ if (this.accept.length) {
341
+ // TODO: should it be possible to change this on the fly (in updated)?
342
+ this._inputNode.accept = this.accept;
343
+ }
344
+ }
345
+
346
+ if (this.enableDropZone && this._isDragAndDropSupported) {
347
+ this.__setupDragDropEventListeners();
348
+ this.setAttribute('drop-zone', '');
349
+ }
350
+ }
351
+
352
+ /**
353
+ * @param {import('lit').PropertyValues } changedProperties
354
+ */
355
+ updated(changedProperties) {
356
+ super.updated(changedProperties);
357
+
358
+ // TODO: mention code originates from LionInput, but we could not extend from it bc/o x/y/z
359
+ if (changedProperties.has('disabled')) {
360
+ this._inputNode.disabled = this.disabled;
361
+ this.validate();
362
+ }
363
+
364
+ if (changedProperties.has('buttonLabel') && this._buttonNode) {
365
+ this._buttonNode.textContent = this.buttonLabel;
366
+ }
367
+
368
+ // TODO: mention code originates from LionInput, but we could not extend from it bc/o x/y/z
369
+ if (changedProperties.has('name')) {
370
+ this._inputNode.name = this.name;
371
+ }
372
+
373
+ if (changedProperties.has('_ariaLabelledNodes')) {
374
+ this.__syncAriaLabelledByAttributesToButton();
375
+ }
376
+
377
+ if (changedProperties.has('_ariaDescribedNodes')) {
378
+ this.__syncAriaDescribedByAttributesToButton();
379
+ }
380
+
381
+ /**
382
+ * Update _selectedFilesMetaData only if:
383
+ * 1. It is invoked from the file-removed event handler.
384
+ * 2. There is a mismatch between the selected files and files on UI.
385
+ */
386
+ if (changedProperties.has('uploadResponse')) {
387
+ if (this._selectedFilesMetaData.length === 0) {
388
+ this.uploadResponse.forEach(preResponse => {
389
+ const file = {
390
+ systemFile: {
391
+ name: preResponse.name,
392
+ },
393
+ response: preResponse,
394
+ status: preResponse.status,
395
+ validationFeedback: [
396
+ {
397
+ message: preResponse.errorMessage,
398
+ },
399
+ ],
400
+ };
401
+ // @ts-ignore
402
+ this._selectedFilesMetaData = [...this._selectedFilesMetaData, file];
403
+ });
404
+ }
405
+ this._selectedFilesMetaData.forEach(file => {
406
+ if (
407
+ !this.uploadResponse.some(response => response.name === file.systemFile.name) &&
408
+ this.uploadOnSelect
409
+ ) {
410
+ this.__removeFileFromList(file);
411
+ } else {
412
+ this.uploadResponse.forEach(response => {
413
+ if (response.name === file.systemFile.name) {
414
+ // eslint-disable-next-line no-param-reassign
415
+ file.response = response;
416
+ // eslint-disable-next-line no-param-reassign
417
+ file.downloadUrl = response.downloadUrl ? response.downloadUrl : file.downloadUrl;
418
+ // eslint-disable-next-line no-param-reassign
419
+ file.status = response.status;
420
+ // eslint-disable-next-line no-param-reassign
421
+ file.validationFeedback = [
422
+ {
423
+ type: response.errorMessage?.length > 0 ? 'error' : 'success',
424
+ message: response.errorMessage,
425
+ },
426
+ ];
427
+ }
428
+ });
429
+ // this._selectedFilesMetaData = [...this._selectedFilesMetaData];
430
+ }
431
+ });
432
+ }
433
+ }
434
+
435
+ // TODO: this method also triggers a validator...
436
+ /**
437
+ * @private
438
+ * @param {InputFile[]} fileList
439
+ */
440
+ __computeNewAddedFiles(fileList) {
441
+ const computedFileList = fileList.filter(
442
+ file =>
443
+ this._selectedFilesMetaData.findIndex(
444
+ existLionFile => existLionFile.systemFile.name === file.name,
445
+ ) === -1,
446
+ );
447
+ // TODO: put this logic in the Validator itself. Changing the param should trigger a re-validate
448
+ this.__duplicateFileNamesValidator.param = {
449
+ show: fileList.length !== computedFileList.length,
450
+ };
451
+ this.validate();
452
+
453
+ return computedFileList;
454
+ }
455
+
456
+ /**
457
+ * @param {DragEvent} ev
458
+ * @protected
459
+ */
460
+ _processDroppedFiles(ev) {
461
+ ev.preventDefault();
462
+ this.removeAttribute('is-dragging');
463
+
464
+ const isDraggingMultipleWhileNotSupported =
465
+ ev.dataTransfer && ev.dataTransfer.items.length > 1 && !this.multiple;
466
+ if (isDraggingMultipleWhileNotSupported || !ev.dataTransfer?.files) {
467
+ return;
468
+ }
469
+
470
+ this._inputNode.files = ev.dataTransfer.files;
471
+ // @ts-ignore
472
+ this.modelValue = Array.from(ev.dataTransfer.files);
473
+ // if same file is selected again, e.dataTransfer.files lists that file.
474
+ // So filter if the file already exists
475
+ // @ts-ignore
476
+ // const newFiles = this.__computeNewAddedFiles(Array.from(ev.dataTransfer.files));
477
+ // if (newFiles.length > 0) {
478
+ // this._processFiles(newFiles);
479
+ // }
480
+ this._processFiles(Array.from(ev.dataTransfer.files));
481
+ }
482
+
483
+ /**
484
+ * @override
485
+ * @param {Event=} ev
486
+ * @protected
487
+ */
488
+
489
+ _onChange(ev) {
490
+ // Here, we take over the responsibility of InteractionStateMixin, as _leaveEvent is not the best trigger in this case.
491
+ // Giving feedback right after the file dialog is closed results in best UX.
492
+ this.touched = true;
493
+ // Here, we connect ourselves to the FormatMixin flow...
494
+ // TODO: should we call super._onChange(ev) here instead?
495
+ this._onUserInputChanged();
496
+ this._processFiles(/** @type {HTMLInputElement & {files:InputFile[]}} */ (ev?.target)?.files);
497
+ }
498
+
499
+ /**
500
+ * Clear _inputNode.value to make sure onChange is called even for duplicate files
501
+ * @param {Event} ev
502
+ * @protected
503
+ */
504
+ // eslint-disable-next-line class-methods-use-this
505
+ _onClick(ev) {
506
+ // @ts-ignore
507
+ ev.target.value = ''; // eslint-disable-line no-param-reassign
508
+ }
509
+
510
+ /**
511
+ * @protected
512
+ */
513
+ _enhanceSelectedList() {
514
+ /**
515
+ * @type {LionSelectedFileList | null}
516
+ */
517
+ const selectedFileList = this.querySelector('[slot="selected-file-list"]');
518
+ if (selectedFileList) {
519
+ selectedFileList.setAttribute('id', `selected-file-list-${this._inputId}`);
520
+ this.addToAriaDescribedBy(selectedFileList, { idPrefix: 'selected-file-list' });
521
+ }
522
+ }
523
+
524
+ /**
525
+ * @protected
526
+ */
527
+ __syncAriaLabelledByAttributesToButton() {
528
+ if (this._inputNode.hasAttribute('aria-labelledby')) {
529
+ const ariaLabelledBy = this._inputNode.getAttribute('aria-labelledby');
530
+ this._buttonNode?.setAttribute(
531
+ 'aria-labelledby',
532
+ `select-button-${this._inputId} ${ariaLabelledBy}`,
533
+ );
534
+ }
535
+ }
536
+
537
+ /**
538
+ * @protected
539
+ */
540
+ __syncAriaDescribedByAttributesToButton() {
541
+ if (this._inputNode.hasAttribute('aria-describedby')) {
542
+ const ariaDescribedby = this._inputNode.getAttribute('aria-describedby') || '';
543
+ this._buttonNode?.setAttribute('aria-describedby', ariaDescribedby);
544
+ }
545
+ }
546
+
547
+ /**
548
+ * @private
549
+ */
550
+ __setupFileValidators() {
551
+ // TODO: update .param when _acceptCriteria changes
552
+ this.defaultValidators = [
553
+ new IsAcceptedFile(this._acceptCriteria),
554
+ this.__duplicateFileNamesValidator,
555
+ ];
556
+ }
557
+
558
+ /**
559
+ * Runs on drag or change event
560
+ *
561
+ * @param {InputFile[]} selectedFiles
562
+ * @protected
563
+ */
564
+ _processFiles(selectedFiles) {
565
+ // file size and type validators are required only when file is selected and not in case of prefill
566
+ // TODO: is this needed every time?
567
+ const newFiles = this.__computeNewAddedFiles(Array.from(selectedFiles));
568
+ if (!this.multiple && newFiles.length > 0) {
569
+ this._selectedFilesMetaData = [];
570
+ this.uploadResponse = [];
571
+ }
572
+
573
+ /**
574
+ * @type {InputFile}
575
+ */
576
+ let fileObj;
577
+ for (const selectedFile of newFiles.values()) {
578
+ // @ts-ignore
579
+ fileObj = new FileHandle(selectedFile, this._acceptCriteria);
580
+ if (fileObj.failedProp?.length) {
581
+ this._handleErroredFiles(fileObj);
582
+ this.uploadResponse = [
583
+ ...this.uploadResponse,
584
+ {
585
+ name: fileObj.systemFile.name,
586
+ status: 'FAIL',
587
+ // @ts-expect-error
588
+ errorMessage: fileObj.validationFeedback[0].message,
589
+ },
590
+ ];
591
+ } else {
592
+ this.uploadResponse = [
593
+ ...this.uploadResponse,
594
+ {
595
+ name: fileObj.systemFile.name,
596
+ status: 'SUCCESS',
597
+ },
598
+ ];
599
+ }
600
+ this._selectedFilesMetaData = [...this._selectedFilesMetaData, fileObj];
601
+ this._handleErrors();
602
+ }
603
+
604
+ // only send error-free files to file-list-changed event
605
+ const _successFiles = this._selectedFilesMetaData
606
+ .filter(
607
+ ({ systemFile, status }) =>
608
+ newFiles.includes(/** @type {InputFile} */ (systemFile)) && status === 'SUCCESS',
609
+ )
610
+ .map(({ systemFile }) => /** @type {InputFile} */ (systemFile));
611
+
612
+ if (_successFiles.length > 0) {
613
+ this._dispatchFileListChangeEvent(_successFiles);
614
+ }
615
+ }
616
+
617
+ /**
618
+ * @param {InputFile[]} newFiles
619
+ * @protected
620
+ */
621
+ _dispatchFileListChangeEvent(newFiles) {
622
+ this.dispatchEvent(
623
+ new CustomEvent('file-list-changed', {
624
+ // TODO: check if composed and bubbles are needed
625
+ // composed: true,
626
+ // bubbles: true,
627
+ detail: {
628
+ newFiles,
629
+ },
630
+ }),
631
+ );
632
+ }
633
+
634
+ /**
635
+ * @protected
636
+ */
637
+ _handleErrors() {
638
+ let hasErrors = false;
639
+ this._selectedFilesMetaData.forEach(fileObj => {
640
+ if (fileObj.failedProp && fileObj.failedProp.length > 0) {
641
+ hasErrors = true;
642
+ }
643
+ });
644
+
645
+ // TODO: handle via ValidateMixin (otherwise it breaks as soon as private ValidateMixin internals change)
646
+ if (hasErrors) {
647
+ this.hasFeedbackFor?.push('error');
648
+ // @ts-ignore use private property
649
+ this.shouldShowFeedbackFor.push('error');
650
+ } else if (this._prevHasErrors && this.hasFeedbackFor.includes('error')) {
651
+ const hasFeedbackForIndex = this.hasFeedbackFor.indexOf('error');
652
+ this.hasFeedbackFor.slice(hasFeedbackForIndex, hasFeedbackForIndex + 1);
653
+ // @ts-ignore use private property
654
+ const shouldShowFeedbackForIndex = this.shouldShowFeedbackFor.indexOf('error');
655
+ // @ts-ignore use private property
656
+ this.shouldShowFeedbackFor.slice(shouldShowFeedbackForIndex, shouldShowFeedbackForIndex + 1);
657
+ }
658
+ this._prevHasErrors = hasErrors;
659
+ }
660
+
661
+ /**
662
+ * @param {InputFile} fileObj
663
+ * @protected
664
+ */
665
+ /* eslint-disable no-param-reassign */
666
+ _handleErroredFiles(fileObj) {
667
+ fileObj.validationFeedback = [];
668
+ const { allowedFileExtensions, allowedFileTypes } = this._acceptCriteria;
669
+ /**
670
+ * @type {string[]}
671
+ */
672
+ let array = [];
673
+ let arrayLength = 0;
674
+ let lastItem;
675
+
676
+ if (allowedFileExtensions.length) {
677
+ array = allowedFileExtensions;
678
+ // eslint-disable-next-line no-return-assign
679
+ array = array.map(item => (item = `.${item}`));
680
+ lastItem = array.pop();
681
+ arrayLength = array.length;
682
+ } else if (allowedFileTypes.length) {
683
+ allowedFileTypes.forEach(MIMETypes => {
684
+ if (MIMETypes.endsWith('/*')) {
685
+ array.push(MIMETypes.slice(0, -2));
686
+ } else if (MIMETypes === 'text/plain') {
687
+ array.push('text');
688
+ } else {
689
+ const index = MIMETypes.indexOf('/');
690
+ const subTypes = MIMETypes.slice(index + 1);
691
+
692
+ if (!subTypes.includes('+')) {
693
+ array.push(`.${subTypes}`);
694
+ } else {
695
+ const subType = subTypes.split('+');
696
+ array.push(`.${subType[0]}`);
697
+ }
698
+ }
699
+ });
700
+ lastItem = array.pop();
701
+ arrayLength = array.length;
702
+ }
703
+ let message = '';
704
+ if (!lastItem) {
705
+ message = `${this.msgLit('lion-input-file:allowedFileSize', {
706
+ maxSize: formatBytes(this.maxFileSize),
707
+ })}`;
708
+ } else if (!arrayLength) {
709
+ message = `${this.msgLit('lion-input-file:allowedFileValidatorSimple', {
710
+ allowedType: lastItem,
711
+ maxSize: formatBytes(this.maxFileSize),
712
+ })}`;
713
+ } else {
714
+ message = `${this.msgLit('lion-input-file:allowedFileValidatorComplex', {
715
+ allowedTypesArray: array.join(', '),
716
+ allowedTypesLastItem: lastItem,
717
+ maxSize: formatBytes(this.maxFileSize),
718
+ })}`;
719
+ }
720
+
721
+ const errorObj = {
722
+ message,
723
+ type: 'error',
724
+ };
725
+ fileObj.validationFeedback?.push(errorObj);
726
+ }
727
+
728
+ /**
729
+ * @private
730
+ * @param {InputFile} removedFile
731
+ */
732
+ __removeFileFromList(removedFile) {
733
+ this._selectedFilesMetaData = this._selectedFilesMetaData.filter(
734
+ currentFile => currentFile.systemFile.name !== removedFile.systemFile.name,
735
+ );
736
+ // checks if the file is not a pre-filled file
737
+ if (this.modelValue) {
738
+ this.modelValue = this.modelValue.filter(
739
+ (/** @type {InputFile} */ currentFile) => currentFile.name !== removedFile.systemFile.name,
740
+ );
741
+ }
742
+ this._inputNode.value = '';
743
+ this._handleErrors();
744
+ }
745
+
746
+ /**
747
+ * @param {CustomEvent} ev
748
+ * @protected
749
+ */
750
+ _onRemoveFile(ev) {
751
+ if (this.disabled) {
752
+ return;
753
+ }
754
+ const { removedFile } = ev.detail;
755
+ if (!this.uploadOnSelect && removedFile) {
756
+ this.__removeFileFromList(removedFile);
757
+ }
758
+
759
+ this._removeFile(removedFile);
760
+ }
761
+
762
+ // TODO: this doesn't remove the file from the list, but fires an event
763
+ /**
764
+ * @param {InputFile} removedFile
765
+ * @protected
766
+ */
767
+ _removeFile(removedFile) {
768
+ this.dispatchEvent(
769
+ // TODO: check if composed and bubbles are needed
770
+ new CustomEvent('file-removed', {
771
+ // bubbles: true,
772
+ // composed: true,
773
+ detail: {
774
+ removedFile,
775
+ status: removedFile.status,
776
+ uploadResponse: removedFile.response,
777
+ },
778
+ }),
779
+ );
780
+ }
781
+
782
+ /**
783
+ * Every time .formattedValue is attempted to sync to the view value (on change/blur and on
784
+ * modelValue change), this condition is checked. In case of the input-file we don't want
785
+ * this sync to happen, since the view value is already correct.
786
+ * @override FormatMixin
787
+ * @return {boolean}
788
+ * @protected
789
+ */
790
+ // eslint-disable-next-line class-methods-use-this
791
+ _reflectBackOn() {
792
+ return false;
793
+ }
794
+
795
+ /**
796
+ * Helper method for the mutually exclusive Required Validator
797
+ * @override ValidateMixin
798
+ */
799
+ _isEmpty() {
800
+ return this.modelValue?.length === 0;
801
+ }
802
+
803
+ /**
804
+ * @return {TemplateResult}
805
+ * @protected
806
+ */
807
+ _dropZoneTemplate() {
808
+ return html`
809
+ <div @drop="${this._processDroppedFiles}" class="input-file__drop-zone">
810
+ <div class="input-file__drop-zone__text">
811
+ ${this.msgLit('lion-input-file:dragAndDropText')}
812
+ </div>
813
+ <slot name="file-select-button"></slot>
814
+ </div>
815
+ `;
816
+ }
817
+
818
+ /**
819
+ * @override FormControlMixin
820
+ * @return {TemplateResult}
821
+ * @protected
822
+ */
823
+ // eslint-disable-next-line class-methods-use-this
824
+ _inputGroupAfterTemplate() {
825
+ return html` <slot name="selected-file-list"></slot> `;
826
+ }
827
+
828
+ /**
829
+ * @override FormControlMixin
830
+ * @return {TemplateResult}
831
+ * @protected
832
+ */
833
+ _inputGroupInputTemplate() {
834
+ return html`
835
+ <slot name="input"> </slot>
836
+ ${this.enableDropZone && this._isDragAndDropSupported
837
+ ? this._dropZoneTemplate()
838
+ : html`
839
+ <div class="input-group__file-select-button">
840
+ <slot name="file-select-button"></slot>
841
+ </div>
842
+ `}
843
+ `;
844
+ }
845
+
846
+ static get styles() {
847
+ return [
848
+ super.styles,
849
+ css`
850
+ .input-group__container {
851
+ position: relative;
852
+ display: flex;
853
+ flex-direction: column;
854
+ width: fit-content;
855
+ }
856
+
857
+ :host([drop-zone]) .input-group__container {
858
+ width: auto;
859
+ }
860
+
861
+ .input-group__container ::slotted(input[type='file']) {
862
+ /** Invisible, since means of interaction is button */
863
+ position: absolute;
864
+ opacity: 0;
865
+ /** Full cover positioned, so it will be a drag and drop surface */
866
+ top: 0;
867
+ left: 0;
868
+ right: 0;
869
+ bottom: 0;
870
+ }
871
+
872
+ .input-file__drop-zone {
873
+ display: flex;
874
+ position: relative;
875
+ flex-direction: column;
876
+ align-items: center;
877
+ border: dashed 2px black;
878
+ padding: 24px 0;
879
+ }
880
+ `,
881
+ ];
882
+ }
883
+
884
+ /**
885
+ * @param {MouseEvent} ev
886
+ */
887
+ __openDialogOnBtnClick(ev) {
888
+ ev.preventDefault();
889
+ ev.stopPropagation();
890
+ this._inputNode.click();
891
+ }
892
+ }