@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,232 @@
1
+ import { uuid } from '@lion/ui/core.js';
2
+ import { LionValidationFeedback } from '@lion/ui/form-core.js';
3
+ import { LocalizeMixin } from '@lion/ui/localize.js';
4
+ import { ScopedElementsMixin } from '@open-wc/scoped-elements';
5
+ import { css, html, LitElement, nothing } from 'lit';
6
+ import { ifDefined } from 'lit/directives/if-defined.js';
7
+ import { repeat } from 'lit/directives/repeat.js';
8
+ import { localizeNamespaceLoader } from './localizeNamespaceLoader.js';
9
+
10
+ /**
11
+ * @typedef {import('lit').TemplateResult} TemplateResult
12
+ * @typedef {import('../types/input-file.js').InputFile} InputFile
13
+ * @typedef {import('../types/input-file.js').SystemFile} SystemFile
14
+ */
15
+
16
+ export class LionSelectedFileList extends LocalizeMixin(ScopedElementsMixin(LitElement)) {
17
+ static get scopedElements() {
18
+ return {
19
+ // @ts-expect-error [external] fix types scopedElements
20
+ ...super.scopedElements,
21
+ 'lion-validation-feedback': LionValidationFeedback,
22
+ };
23
+ }
24
+
25
+ static get properties() {
26
+ return {
27
+ fileList: { type: Array },
28
+ multiple: { type: Boolean },
29
+ };
30
+ }
31
+
32
+ static localizeNamespaces = [
33
+ { 'lion-input-file': localizeNamespaceLoader },
34
+ ...super.localizeNamespaces,
35
+ ];
36
+
37
+ constructor() {
38
+ super();
39
+ /**
40
+ * @type {InputFile[]}
41
+ */
42
+ this.fileList = [];
43
+ this.multiple = false;
44
+ }
45
+
46
+ /**
47
+ * @param {import('lit').PropertyValues} changedProperties
48
+ */
49
+ updated(changedProperties) {
50
+ super.updated(changedProperties);
51
+ if (changedProperties.has('fileList')) {
52
+ this._enhanceLightDomA11y();
53
+ }
54
+ }
55
+
56
+ _enhanceLightDomA11y() {
57
+ const fileFeedbackElementList = this.shadowRoot?.querySelectorAll('[id^="file-feedback"]');
58
+ // TODO: this will break as soon as a Subclasser changes the template ...
59
+ const inputFileNode = this.parentNode?.parentNode;
60
+ fileFeedbackElementList?.forEach(feedbackEl => {
61
+ // Generic focus/blur handling that works for both Fields/FormGroups
62
+ inputFileNode?.addEventListener('focusin', () => {
63
+ feedbackEl.setAttribute('aria-live', 'polite');
64
+ });
65
+ inputFileNode?.addEventListener('focusout', () => {
66
+ feedbackEl.setAttribute('aria-live', 'assertive');
67
+ });
68
+ });
69
+ }
70
+
71
+ /**
72
+ * @protected
73
+ * @param {InputFile} removedFile
74
+ */
75
+ _removeFile(removedFile) {
76
+ this.dispatchEvent(
77
+ // TODO: do we need bubble and composed for an internal event that only LionInputFile should know about?
78
+ new CustomEvent('file-remove-requested', {
79
+ // composed: true,
80
+ // bubbles: true,
81
+ detail: {
82
+ removedFile,
83
+ status: removedFile.status,
84
+ uploadResponse: removedFile.response,
85
+ },
86
+ }),
87
+ );
88
+ }
89
+
90
+ /**
91
+ * @protected
92
+ * @param {Array<import('../../form-core/types/validate/ValidateMixinTypes.js').FeedbackMessage>} validationFeedback
93
+ * @param {string} fileUuid
94
+ * @return {TemplateResult}
95
+ */
96
+ // eslint-disable-next-line class-methods-use-this
97
+ _validationFeedbackTemplate(validationFeedback, fileUuid) {
98
+ return html`
99
+ <lion-validation-feedback
100
+ id="file-feedback-${fileUuid}"
101
+ .feedbackData="${validationFeedback}"
102
+ aria-live="assertive"
103
+ ></lion-validation-feedback>
104
+ `;
105
+ }
106
+
107
+ /**
108
+ * @protected
109
+ * @param {InputFile} file
110
+ * @return {TemplateResult|nothing}
111
+ */
112
+ // eslint-disable-next-line class-methods-use-this, no-unused-vars
113
+ _listItemBeforeTemplate(file) {
114
+ return nothing;
115
+ }
116
+
117
+ /**
118
+ * @protected
119
+ * @param {InputFile} file
120
+ * @param {string} fileUuid
121
+ * @return {TemplateResult}
122
+ */
123
+ // eslint-disable-next-line no-unused-vars
124
+ _listItemAfterTemplate(file, fileUuid) {
125
+ return html`
126
+ <button
127
+ class="selected__list__item__remove-button"
128
+ aria-label="${this.msgLit('lion-input-file:removeButtonLabel', {
129
+ fileName: file.systemFile.name,
130
+ })}"
131
+ @click=${() => this._removeFile(file)}
132
+ >
133
+ ${this._removeButtonContentTemplate()}
134
+ </button>
135
+ `;
136
+ }
137
+
138
+ /**
139
+ * @protected
140
+ * @return {TemplateResult}
141
+ */
142
+ // eslint-disable-next-line class-methods-use-this
143
+ _removeButtonContentTemplate() {
144
+ return html`✖️`;
145
+ }
146
+
147
+ /**
148
+ * @protected
149
+ * @param {InputFile} file
150
+ * @return {TemplateResult}
151
+ */
152
+ _selectedListItemTemplate(file) {
153
+ const fileUuid = uuid();
154
+ return html`
155
+ <div class="selected__list__item" status="${file.status ? file.status.toLowerCase() : ''}">
156
+ <div class="selected__list__item__label">
157
+ ${this._listItemBeforeTemplate(file)}
158
+ <span id="selected-list-item-label-${fileUuid}" class="selected__list__item__label__text">
159
+ <span class="sr-only">${this.msgLit('lion-input-file:fileNameDescriptionLabel')}</span>
160
+ ${file.downloadUrl && file.status !== 'LOADING'
161
+ ? html`
162
+ <a
163
+ class="selected__list__item__label__link"
164
+ href="${file.downloadUrl}"
165
+ target="${file.downloadUrl.startsWith('blob') ? '_blank' : ''}"
166
+ rel="${ifDefined(
167
+ file.downloadUrl.startsWith('blob') ? 'noopener noreferrer' : undefined,
168
+ )}"
169
+ >${file.systemFile?.name}</a
170
+ >
171
+ `
172
+ : file.systemFile?.name}
173
+ </span>
174
+ ${this._listItemAfterTemplate(file, fileUuid)}
175
+ </div>
176
+ ${file.status === 'FAIL' && file.validationFeedback
177
+ ? html`
178
+ ${repeat(
179
+ file.validationFeedback,
180
+ validationFeedback => html`
181
+ ${this._validationFeedbackTemplate([validationFeedback], fileUuid)}
182
+ `,
183
+ )}
184
+ `
185
+ : nothing}
186
+ </div>
187
+ `;
188
+ }
189
+
190
+ render() {
191
+ return this.fileList?.length
192
+ ? html`
193
+ ${this.multiple
194
+ ? html`
195
+ <ul class="selected__list">
196
+ ${this.fileList.map(
197
+ file => html` <li>${this._selectedListItemTemplate(file)}</li> `,
198
+ )}
199
+ </ul>
200
+ `
201
+ : html` ${this._selectedListItemTemplate(this.fileList[0])} `}
202
+ `
203
+ : nothing;
204
+ }
205
+
206
+ static get styles() {
207
+ return [
208
+ css`
209
+ .selected__list {
210
+ list-style-type: none;
211
+ margin-block-start: 0;
212
+ margin-block-end: 0;
213
+ padding-inline-start: 0;
214
+ }
215
+
216
+ .sr-only {
217
+ position: absolute;
218
+ top: 0;
219
+ width: 1px;
220
+ height: 1px;
221
+ overflow: hidden;
222
+ clip-path: inset(100%);
223
+ clip: rect(1px, 1px, 1px, 1px);
224
+ white-space: nowrap;
225
+ border: 0;
226
+ margin: 0;
227
+ padding: 0;
228
+ }
229
+ `,
230
+ ];
231
+ }
232
+ }
@@ -0,0 +1,75 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
2
+ export const localizeNamespaceLoader = /** @param {string} locale */ locale => {
3
+ switch (locale) {
4
+ case 'bg-BG':
5
+ return import('@lion/ui/input-file-translations/bg-BG.js');
6
+ case 'bg':
7
+ return import('@lion/ui/input-file-translations/bg.js');
8
+ case 'cs-CZ':
9
+ return import('@lion/ui/input-file-translations/cs-CZ.js');
10
+ case 'cs':
11
+ return import('@lion/ui/input-file-translations/cs.js');
12
+ case 'de-DE':
13
+ return import('@lion/ui/input-file-translations/de-DE.js');
14
+ case 'de':
15
+ return import('@lion/ui/input-file-translations/de.js');
16
+ case 'en-AU':
17
+ return import('@lion/ui/input-file-translations/en-AU.js');
18
+ case 'en-GB':
19
+ return import('@lion/ui/input-file-translations/en-GB.js');
20
+ case 'en-US':
21
+ return import('@lion/ui/input-file-translations/en-US.js');
22
+ case 'en-PH':
23
+ case 'en':
24
+ return import('@lion/ui/input-file-translations/en.js');
25
+ case 'es-ES':
26
+ return import('@lion/ui/input-file-translations/es-ES.js');
27
+ case 'es':
28
+ return import('@lion/ui/input-file-translations/es.js');
29
+ case 'fr-FR':
30
+ return import('@lion/ui/input-file-translations/fr-FR.js');
31
+ case 'fr-BE':
32
+ return import('@lion/ui/input-file-translations/fr-BE.js');
33
+ case 'fr':
34
+ return import('@lion/ui/input-file-translations/fr.js');
35
+ case 'hu-HU':
36
+ return import('@lion/ui/input-file-translations/hu-HU.js');
37
+ case 'hu':
38
+ return import('@lion/ui/input-file-translations/hu.js');
39
+ case 'it-IT':
40
+ return import('@lion/ui/input-file-translations/it-IT.js');
41
+ case 'it':
42
+ return import('@lion/ui/input-file-translations/it.js');
43
+ case 'nl-BE':
44
+ return import('@lion/ui/input-file-translations/nl-BE.js');
45
+ case 'nl-NL':
46
+ return import('@lion/ui/input-file-translations/nl-NL.js');
47
+ case 'nl':
48
+ return import('@lion/ui/input-file-translations/nl.js');
49
+ case 'pl-PL':
50
+ return import('@lion/ui/input-file-translations/pl-PL.js');
51
+ case 'pl':
52
+ return import('@lion/ui/input-file-translations/pl.js');
53
+ case 'ro-RO':
54
+ return import('@lion/ui/input-file-translations/ro-RO.js');
55
+ case 'ro':
56
+ return import('@lion/ui/input-file-translations/ro.js');
57
+ case 'ru-RU':
58
+ return import('@lion/ui/input-file-translations/ru-RU.js');
59
+ case 'ru':
60
+ return import('@lion/ui/input-file-translations/ru.js');
61
+ case 'sk-SK':
62
+ return import('@lion/ui/input-file-translations/sk-SK.js');
63
+ case 'sk':
64
+ return import('@lion/ui/input-file-translations/sk.js');
65
+ case 'uk-UA':
66
+ return import('@lion/ui/input-file-translations/uk-UA.js');
67
+ case 'uk':
68
+ return import('@lion/ui/input-file-translations/uk.js');
69
+ case 'zh-CN':
70
+ case 'zh':
71
+ return import('@lion/ui/input-file-translations/zh.js');
72
+ default:
73
+ return import('@lion/ui/input-file-translations/en.js');
74
+ }
75
+ };
@@ -0,0 +1,114 @@
1
+ import { Validator } from '@lion/ui/form-core.js';
2
+ import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js';
3
+
4
+ /**
5
+ * @typedef {import('../../form-core/types/validate/validate.js').ValidatorConfig} ValidatorConfig
6
+ * @typedef {import('../../form-core/types/validate/validate.js').ValidatorParam} ValidatorParam
7
+ * @typedef {import('../../form-core/types/validate/validate.js').ValidatorOutcome} ValidatorOutcome
8
+ * @typedef {import('../types/input-file.js').InputFile} InputFile
9
+ */
10
+
11
+ /* eslint max-classes-per-file: ["error", 2] */
12
+ export class IsAcceptedFile extends Validator {
13
+ static validatorName = 'IsAcceptedFile';
14
+
15
+ /**
16
+ * @param {number} fileFileSize
17
+ * @param {number} maxFileSize
18
+ */
19
+ static checkFileSize(fileFileSize, maxFileSize) {
20
+ return fileFileSize <= maxFileSize;
21
+ }
22
+
23
+ /**
24
+ * Gets the extension of a file name
25
+ * @param {string} fileName like myFile.txt
26
+ * @return {string} like .txt
27
+ */
28
+ static getExtension(fileName) {
29
+ return fileName?.slice(fileName.lastIndexOf('.'));
30
+ }
31
+
32
+ /**
33
+ * @param {string} extension
34
+ * @param {string[]} allowedFileExtensions
35
+ */
36
+ static isExtensionAllowed(extension, allowedFileExtensions) {
37
+ return allowedFileExtensions?.find(
38
+ allowedExtension => allowedExtension.toUpperCase() === extension.toUpperCase(),
39
+ );
40
+ }
41
+
42
+ /**
43
+ * @param {string} type
44
+ * @param {string[]} allowedFileTypes
45
+ */
46
+ static isFileTypeAllowed(type, allowedFileTypes) {
47
+ return allowedFileTypes?.find(allowedType => allowedType.toUpperCase() === type.toUpperCase());
48
+ }
49
+
50
+ /**
51
+ * @param {Array<File>} modelValue array of file objects
52
+ * @param {{ allowedFileTypes: string[]; allowedFileExtensions: string[]; maxFileSize: number; }} params
53
+ * @returns {Boolean}
54
+ */
55
+ execute(modelValue, params = this.param) {
56
+ let isInvalidType;
57
+ let isInvalidFileExt;
58
+
59
+ const ctor = /** @type {typeof IsAcceptedFile} */ (this.constructor);
60
+ const { allowedFileTypes, allowedFileExtensions, maxFileSize } = params;
61
+ if (allowedFileTypes?.length) {
62
+ isInvalidType = modelValue.some(file => !ctor.isFileTypeAllowed(file.type, allowedFileTypes));
63
+ return isInvalidType;
64
+ }
65
+ if (allowedFileExtensions?.length) {
66
+ isInvalidFileExt = modelValue.some(
67
+ file => !ctor.isExtensionAllowed(ctor.getExtension(file.name), allowedFileExtensions),
68
+ );
69
+ return isInvalidFileExt;
70
+ }
71
+ const invalidFileSize = modelValue.findIndex(
72
+ file => !ctor.checkFileSize(file.size, maxFileSize),
73
+ );
74
+ return invalidFileSize > -1;
75
+ }
76
+
77
+ // @todo: validation should be handled with ValidateMixin so overriding getMessage to return an empty string should
78
+ // not be necessary anymore
79
+ static async getMessage() {
80
+ return '';
81
+ }
82
+ }
83
+
84
+ export class DuplicateFileNames extends Validator {
85
+ static validatorName = 'DuplicateFileNames';
86
+
87
+ /**
88
+ * @param {ValidatorParam} [param]
89
+ * @param {ValidatorConfig} [config]
90
+ */
91
+ constructor(param, config) {
92
+ super(param, config);
93
+ this.type = 'info';
94
+ }
95
+
96
+ /**
97
+ * @param {InputFile[]} modelValue
98
+ * @param {ValidatorParam} [params]
99
+ * @returns {ValidatorOutcome|Promise<ValidatorOutcome>}
100
+ */
101
+ execute(modelValue, params = this.param) {
102
+ return params.show;
103
+ }
104
+
105
+ /**
106
+ * @returns {Promise<string|Element>}
107
+ */
108
+ static async getMessage() {
109
+ const localizeManager = getLocalizeManager();
110
+ // TODO: we need to make sure namespace is loaded
111
+ // TODO: keep Validators localize system agnostic
112
+ return localizeManager.msg('lion-input-file:uploadTextDuplicateFileName');
113
+ }
114
+ }
@@ -0,0 +1,170 @@
1
+ import { expect } from '@open-wc/testing';
2
+ import { IsAcceptedFile } from '../src/validators.js';
3
+
4
+ describe('lion-input-file: IsAcceptedFile', () => {
5
+ describe('valid file type', () => {
6
+ it('should return false for allowed file extension .csv', async () => {
7
+ const fileTypeObj = new IsAcceptedFile();
8
+ // @ts-ignore ignore file typing
9
+ const returnVal = fileTypeObj.execute([{ name: 'foo.csv' }], {
10
+ allowedFileExtensions: ['.csv'],
11
+ allowedFileTypes: undefined,
12
+ });
13
+ expect(returnVal).to.be.false;
14
+ });
15
+
16
+ it('should return false for allowed file extension .csv and .pdf', async () => {
17
+ const fileTypeObj = new IsAcceptedFile();
18
+ // @ts-ignore ignore file typing
19
+ const returnVal = fileTypeObj.execute([{ name: 'foo.pdf' }], {
20
+ allowedFileExtensions: ['.csv', '.pdf'],
21
+ allowedFileTypes: undefined,
22
+ });
23
+ expect(returnVal).to.be.false;
24
+ });
25
+
26
+ it('should return true for not allowed file extension .csv and .pdf', async () => {
27
+ const fileTypeObj = new IsAcceptedFile();
28
+ // @ts-ignore ignore file typing
29
+ const returnVal = fileTypeObj.execute([{ name: 'foo.pdf' }], {
30
+ allowedFileExtensions: ['.txt'],
31
+ allowedFileTypes: undefined,
32
+ });
33
+ expect(returnVal).to.be.true;
34
+ });
35
+
36
+ it('should return false for valid file type for single file', async () => {
37
+ const fileTypeObj = new IsAcceptedFile();
38
+ // @ts-ignore ignore file typing
39
+ const returnVal = fileTypeObj.execute([{ type: 'foo' }], {
40
+ allowedFileTypes: ['foo'],
41
+ });
42
+
43
+ expect(returnVal).to.be.false;
44
+ });
45
+
46
+ it('should return false for valid file type for multiple file', async () => {
47
+ const fileTypeObj = new IsAcceptedFile();
48
+ // @ts-ignore ignore file typing
49
+ const returnVal = fileTypeObj.execute([{ type: 'foo' }, { type: 'foo' }], {
50
+ allowedFileTypes: ['foo'],
51
+ });
52
+
53
+ expect(returnVal).to.be.false;
54
+ });
55
+
56
+ it('should return false if no file types are defined, and has small file size', async () => {
57
+ const fileTypeObj = new IsAcceptedFile();
58
+ const returnVal = fileTypeObj.execute(
59
+ [
60
+ // @ts-ignore ignore file typing
61
+ { type: 'foo', size: 1 },
62
+ // @ts-ignore ignore file typing
63
+ { type: 'foo', size: 1 },
64
+ ],
65
+ {
66
+ allowedFileTypes: [],
67
+ maxFileSize: 1028,
68
+ },
69
+ );
70
+
71
+ expect(returnVal).to.be.false;
72
+ });
73
+
74
+ it('should return false for valid file type for multiple file and multiple allowed types', async () => {
75
+ const fileTypeObj = new IsAcceptedFile();
76
+ // @ts-ignore ignore file typing
77
+ const returnVal = fileTypeObj.execute([{ type: 'foo' }, { type: 'bar' }], {
78
+ allowedFileTypes: ['foo', 'bar'],
79
+ });
80
+
81
+ expect(returnVal).to.be.false;
82
+ });
83
+ });
84
+
85
+ describe('invalid file type', () => {
86
+ it('should return true for invalid file type for single file', async () => {
87
+ const fileTypeObj = new IsAcceptedFile();
88
+ // @ts-ignore ignore file typing
89
+ const returnVal = fileTypeObj.execute([{ type: 'bar' }], {
90
+ allowedFileTypes: ['foo'],
91
+ });
92
+
93
+ expect(returnVal).to.be.true;
94
+ });
95
+
96
+ it('should return false for invalid file type for multiple file', async () => {
97
+ const fileTypeObj = new IsAcceptedFile();
98
+ // @ts-ignore ignore file typing
99
+ const returnVal = fileTypeObj.execute([{ type: 'foo' }, { type: 'bar' }], {
100
+ allowedFileTypes: ['foo'],
101
+ });
102
+
103
+ expect(returnVal).to.be.true;
104
+ });
105
+
106
+ it('should return false for invalid file type for multiple file and multiple allowed types', async () => {
107
+ const fileTypeObj = new IsAcceptedFile();
108
+ // @ts-ignore ignore file typing
109
+ const returnVal = fileTypeObj.execute([{ type: 'foo' }, { type: 'pdf' }], {
110
+ allowedFileTypes: ['foo', 'bar'],
111
+ });
112
+
113
+ expect(returnVal).to.be.true;
114
+ });
115
+
116
+ it('should return true for invalid file type for empty type', async () => {
117
+ const fileTypeObj = new IsAcceptedFile();
118
+ // @ts-ignore ignore file typing
119
+ const returnVal = fileTypeObj.execute([{ type: '' }], {
120
+ allowedFileTypes: ['foo'],
121
+ });
122
+
123
+ expect(returnVal).to.be.true;
124
+ });
125
+ });
126
+
127
+ describe('valid file size', () => {
128
+ it('should return false for valid file size for single file', async () => {
129
+ const fileSizeObj = new IsAcceptedFile();
130
+ // @ts-ignore ignore file typing
131
+ const returnVal = fileSizeObj.execute([{ size: 5 }], {
132
+ maxFileSize: 10,
133
+ });
134
+
135
+ expect(returnVal).to.be.false;
136
+ });
137
+
138
+ it('should return false for valid file size for multiple file', async () => {
139
+ const fileSizeObj = new IsAcceptedFile();
140
+ // @ts-ignore ignore file typing
141
+ const returnVal = fileSizeObj.execute([{ size: 5 }, { size: 9 }], {
142
+ maxFileSize: 10,
143
+ });
144
+
145
+ expect(returnVal).to.be.false;
146
+ });
147
+ });
148
+
149
+ describe('invalid file size', () => {
150
+ it('should return true for invalid file size for single file', async () => {
151
+ const fileSizeObj = new IsAcceptedFile();
152
+ // @ts-ignore ignore file typing
153
+ const returnVal = fileSizeObj.execute([{ size: 5 }], {
154
+ maxFileSize: 3,
155
+ });
156
+
157
+ expect(returnVal).to.be.true;
158
+ });
159
+
160
+ it('should return false for invalid file size for multiple file', async () => {
161
+ const fileSizeObj = new IsAcceptedFile();
162
+ // @ts-ignore ignore file typing
163
+ const returnVal = fileSizeObj.execute([{ size: 5 }, { size: 9 }], {
164
+ maxFileSize: 7,
165
+ });
166
+
167
+ expect(returnVal).to.be.true;
168
+ });
169
+ });
170
+ });