@lion/ui 0.13.0 → 0.14.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 (219) hide show
  1. package/components/accordion/src/LionAccordion.js +3 -2
  2. package/components/button/src/LionButton.js +6 -10
  3. package/components/button/src/LionButtonReset.js +2 -0
  4. package/components/button/src/LionButtonSubmit.js +2 -0
  5. package/components/button/test-suites/LionButton.suite.js +0 -14
  6. package/components/checkbox-group/src/LionCheckbox.js +3 -0
  7. package/components/checkbox-group/src/LionCheckboxGroup.js +2 -0
  8. package/components/checkbox-group/src/LionCheckboxIndeterminate.js +3 -0
  9. package/components/collapsible/src/LionCollapsible.js +4 -1
  10. package/components/combobox/src/LionCombobox.js +5 -0
  11. package/components/dialog/src/LionDialog.js +3 -0
  12. package/components/drawer/src/LionDrawer.js +9 -0
  13. package/components/fieldset/src/LionFieldset.js +1 -1
  14. package/components/form/src/LionForm.js +0 -1
  15. package/components/form-core/src/FormControlMixin.js +10 -0
  16. package/components/form-core/src/FormatMixin.js +1 -1
  17. package/components/form-core/src/validate/LionValidationFeedback.js +8 -10
  18. package/components/form-core/src/validate/ValidateMixin.js +23 -3
  19. package/components/form-core/test/validate/lion-validation-feedback.test.js +0 -22
  20. package/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.js +119 -0
  21. package/components/form-core/types/validate/validate.ts +1 -0
  22. package/components/icon/src/LionIcon.js +2 -0
  23. package/components/input-amount-dropdown/src/LionInputAmountDropdown.js +557 -0
  24. package/components/input-amount-dropdown/src/currencyUtil.js +324 -0
  25. package/components/input-amount-dropdown/src/formatters.js +24 -0
  26. package/components/input-amount-dropdown/src/localizeNamespaceLoader.js +7 -0
  27. package/components/input-amount-dropdown/src/parsers.js +23 -0
  28. package/components/input-amount-dropdown/src/serializers.js +24 -0
  29. package/components/input-amount-dropdown/src/validators.js +22 -0
  30. package/components/input-amount-dropdown/test/LionInputAmountDropdown.test.js +199 -0
  31. package/components/input-amount-dropdown/test-helpers/mimicUserChangingDropdown.js +22 -0
  32. package/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.js +322 -0
  33. package/components/input-amount-dropdown/translations/en.js +5 -0
  34. package/components/input-amount-dropdown/types/index.ts +234 -0
  35. package/components/input-file/src/LionInputFile.js +9 -0
  36. package/components/input-tel/src/LionInputTel.js +3 -0
  37. package/components/input-tel/types/index.ts +6 -0
  38. package/components/input-tel-dropdown/src/LionInputTelDropdown.js +2 -0
  39. package/components/listbox/src/LionListbox.js +2 -0
  40. package/components/localize/src/number/getFractionDigits.js +1 -1
  41. package/components/overlays/src/OverlayMixin.js +4 -0
  42. package/components/progress-indicator/src/LionProgressIndicator.js +4 -0
  43. package/components/radio-group/src/LionRadioGroup.js +3 -1
  44. package/components/select/src/LionSelect.js +4 -0
  45. package/components/select-rich/src/LionSelectInvoker.js +2 -0
  46. package/components/select-rich/src/LionSelectRich.js +2 -0
  47. package/components/steps/src/LionStep.js +0 -1
  48. package/components/steps/src/LionSteps.js +0 -1
  49. package/components/switch/src/LionSwitch.js +3 -0
  50. package/components/switch/src/LionSwitchButton.js +5 -0
  51. package/components/tabs/src/LionTabs.js +8 -0
  52. package/custom-elements.json +5581 -1475
  53. package/dist-types/components/accordion/src/LionAccordion.d.ts +3 -2
  54. package/dist-types/components/accordion/src/LionAccordion.d.ts.map +1 -1
  55. package/dist-types/components/button/src/LionButton.d.ts +2 -1
  56. package/dist-types/components/button/src/LionButton.d.ts.map +1 -1
  57. package/dist-types/components/button/src/LionButtonReset.d.ts +2 -0
  58. package/dist-types/components/button/src/LionButtonReset.d.ts.map +1 -1
  59. package/dist-types/components/button/src/LionButtonSubmit.d.ts +2 -0
  60. package/dist-types/components/button/src/LionButtonSubmit.d.ts.map +1 -1
  61. package/dist-types/components/button/test-suites/LionButton.suite.d.ts.map +1 -1
  62. package/dist-types/components/checkbox-group/src/LionCheckbox.d.ts +3 -0
  63. package/dist-types/components/checkbox-group/src/LionCheckbox.d.ts.map +1 -1
  64. package/dist-types/components/checkbox-group/src/LionCheckboxGroup.d.ts +2 -0
  65. package/dist-types/components/checkbox-group/src/LionCheckboxGroup.d.ts.map +1 -1
  66. package/dist-types/components/checkbox-group/src/LionCheckboxIndeterminate.d.ts +3 -0
  67. package/dist-types/components/checkbox-group/src/LionCheckboxIndeterminate.d.ts.map +1 -1
  68. package/dist-types/components/collapsible/src/LionCollapsible.d.ts +3 -1
  69. package/dist-types/components/collapsible/src/LionCollapsible.d.ts.map +1 -1
  70. package/dist-types/components/combobox/src/LionCombobox.d.ts +5 -0
  71. package/dist-types/components/combobox/src/LionCombobox.d.ts.map +1 -1
  72. package/dist-types/components/dialog/src/LionDialog.d.ts +3 -0
  73. package/dist-types/components/dialog/src/LionDialog.d.ts.map +1 -1
  74. package/dist-types/components/drawer/src/LionDrawer.d.ts +9 -0
  75. package/dist-types/components/drawer/src/LionDrawer.d.ts.map +1 -1
  76. package/dist-types/components/fieldset/src/LionFieldset.d.ts +1 -1
  77. package/dist-types/components/form/src/LionForm.d.ts.map +1 -1
  78. package/dist-types/components/form-core/src/FormControlMixin.d.ts.map +1 -1
  79. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts +4 -3
  80. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts.map +1 -1
  81. package/dist-types/components/form-core/src/validate/ValidateMixin.d.ts.map +1 -1
  82. package/dist-types/components/form-core/test-suites/ValidateMixinFeedbackPart.suite.d.ts.map +1 -1
  83. package/dist-types/components/form-core/types/validate/validate.d.ts +1 -0
  84. package/dist-types/components/form-core/types/validate/validate.d.ts.map +1 -1
  85. package/dist-types/components/icon/src/LionIcon.d.ts +2 -0
  86. package/dist-types/components/icon/src/LionIcon.d.ts.map +1 -1
  87. package/dist-types/components/input-amount-dropdown/src/LionInputAmountDropdown.d.ts +221 -0
  88. package/dist-types/components/input-amount-dropdown/src/LionInputAmountDropdown.d.ts.map +1 -0
  89. package/dist-types/components/input-amount-dropdown/src/currencyUtil.d.ts +24 -0
  90. package/dist-types/components/input-amount-dropdown/src/currencyUtil.d.ts.map +1 -0
  91. package/dist-types/components/input-amount-dropdown/src/formatters.d.ts +4 -0
  92. package/dist-types/components/input-amount-dropdown/src/formatters.d.ts.map +1 -0
  93. package/dist-types/components/input-amount-dropdown/src/localizeNamespaceLoader.d.ts +2 -0
  94. package/dist-types/components/input-amount-dropdown/src/localizeNamespaceLoader.d.ts.map +1 -0
  95. package/dist-types/components/input-amount-dropdown/src/parsers.d.ts +2 -0
  96. package/dist-types/components/input-amount-dropdown/src/parsers.d.ts.map +1 -0
  97. package/dist-types/components/input-amount-dropdown/src/serializers.d.ts +3 -0
  98. package/dist-types/components/input-amount-dropdown/src/serializers.d.ts.map +1 -0
  99. package/dist-types/components/input-amount-dropdown/src/validators.d.ts +12 -0
  100. package/dist-types/components/input-amount-dropdown/src/validators.d.ts.map +1 -0
  101. package/dist-types/components/input-amount-dropdown/test/LionInputAmountDropdown.test.d.ts +7 -0
  102. package/dist-types/components/input-amount-dropdown/test/LionInputAmountDropdown.test.d.ts.map +1 -0
  103. package/dist-types/components/input-amount-dropdown/test-helpers/mimicUserChangingDropdown.d.ts +12 -0
  104. package/dist-types/components/input-amount-dropdown/test-helpers/mimicUserChangingDropdown.d.ts.map +1 -0
  105. package/dist-types/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.d.ts +13 -0
  106. package/dist-types/components/input-amount-dropdown/test-suites/LionInputAmountDropdown.suite.d.ts.map +1 -0
  107. package/dist-types/components/input-amount-dropdown/translations/en.d.ts +7 -0
  108. package/dist-types/components/input-amount-dropdown/translations/en.d.ts.map +1 -0
  109. package/dist-types/components/input-amount-dropdown/types/index.d.ts +86 -0
  110. package/dist-types/components/input-amount-dropdown/types/index.d.ts.map +1 -0
  111. package/dist-types/components/input-file/src/LionInputFile.d.ts +9 -0
  112. package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -1
  113. package/dist-types/components/input-tel/src/LionInputTel.d.ts +3 -0
  114. package/dist-types/components/input-tel/src/LionInputTel.d.ts.map +1 -1
  115. package/dist-types/components/input-tel/types/index.d.ts +1 -1
  116. package/dist-types/components/input-tel/types/index.d.ts.map +1 -1
  117. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts +2 -0
  118. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts.map +1 -1
  119. package/dist-types/components/listbox/src/LionListbox.d.ts +2 -0
  120. package/dist-types/components/listbox/src/LionListbox.d.ts.map +1 -1
  121. package/dist-types/components/localize/src/number/getFractionDigits.d.ts +1 -1
  122. package/dist-types/components/overlays/src/OverlayMixin.d.ts +4 -0
  123. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  124. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts +3 -0
  125. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts.map +1 -1
  126. package/dist-types/components/radio-group/src/LionRadioGroup.d.ts +3 -1
  127. package/dist-types/components/radio-group/src/LionRadioGroup.d.ts.map +1 -1
  128. package/dist-types/components/select/src/LionSelect.d.ts +3 -0
  129. package/dist-types/components/select/src/LionSelect.d.ts.map +1 -1
  130. package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts +2 -0
  131. package/dist-types/components/select-rich/src/LionSelectInvoker.d.ts.map +1 -1
  132. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +3 -1
  133. package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
  134. package/dist-types/components/steps/src/LionStep.d.ts +0 -1
  135. package/dist-types/components/steps/src/LionStep.d.ts.map +1 -1
  136. package/dist-types/components/steps/src/LionSteps.d.ts +0 -1
  137. package/dist-types/components/steps/src/LionSteps.d.ts.map +1 -1
  138. package/dist-types/components/switch/src/LionSwitch.d.ts +3 -0
  139. package/dist-types/components/switch/src/LionSwitch.d.ts.map +1 -1
  140. package/dist-types/components/switch/src/LionSwitchButton.d.ts +5 -0
  141. package/dist-types/components/switch/src/LionSwitchButton.d.ts.map +1 -1
  142. package/dist-types/components/tabs/src/LionTabs.d.ts +8 -0
  143. package/dist-types/components/tabs/src/LionTabs.d.ts.map +1 -1
  144. package/dist-types/exports/define/lion-input-amount-dropdown.d.ts +2 -0
  145. package/dist-types/exports/define/lion-input-amount-dropdown.d.ts.map +1 -0
  146. package/dist-types/exports/input-amount-dropdown-test-helpers.d.ts +2 -0
  147. package/dist-types/exports/input-amount-dropdown-test-helpers.d.ts.map +1 -0
  148. package/dist-types/exports/input-amount-dropdown-test-suites.d.ts +2 -0
  149. package/dist-types/exports/input-amount-dropdown-test-suites.d.ts.map +1 -0
  150. package/dist-types/exports/input-amount-dropdown.d.ts +2 -0
  151. package/dist-types/exports/input-amount-dropdown.d.ts.map +1 -0
  152. package/dist-types/tsconfig.tsbuildinfo +1 -1
  153. package/docs/components/accordion/api-table.md +8 -0
  154. package/docs/components/button/api-table.md +0 -3
  155. package/docs/components/checkbox-group/overview.md +2 -2
  156. package/docs/components/collapsible/api-table.md +7 -0
  157. package/docs/components/collapsible/overview.md +1 -1
  158. package/docs/components/combobox/api-table.md +7 -0
  159. package/docs/components/combobox/overview.md +1 -1
  160. package/docs/components/combobox/use-cases.md +1 -1
  161. package/docs/components/dialog/overview.md +1 -1
  162. package/docs/components/drawer/api-table.md +8 -0
  163. package/docs/components/fieldset/overview.md +5 -5
  164. package/docs/components/form/overview.md +3 -3
  165. package/docs/components/form/use-cases.md +2 -2
  166. package/docs/components/icon/use-cases.md +1 -1
  167. package/docs/components/input/overview.md +1 -1
  168. package/docs/components/input-amount/overview.md +3 -3
  169. package/docs/components/input-amount-dropdown/api-table.md +210 -0
  170. package/docs/components/input-amount-dropdown/index.md +13 -0
  171. package/docs/components/input-amount-dropdown/use-cases.md +103 -0
  172. package/docs/components/input-date/overview.md +3 -3
  173. package/docs/components/input-datepicker/overview.md +5 -5
  174. package/docs/components/input-email/overview.md +2 -2
  175. package/docs/components/input-file/api-table.md +7 -0
  176. package/docs/components/input-file/overview.md +1 -1
  177. package/docs/components/input-iban/overview.md +2 -2
  178. package/docs/components/input-range/overview.md +2 -2
  179. package/docs/components/input-stepper/overview.md +1 -1
  180. package/docs/components/input-tel/overview.md +1 -1
  181. package/docs/components/input-tel/use-cases.md +6 -6
  182. package/docs/components/input-tel-dropdown/overview.md +1 -1
  183. package/docs/components/listbox/use-cases.md +1 -1
  184. package/docs/components/radio-group/overview.md +3 -3
  185. package/docs/components/select/use-cases.md +1 -1
  186. package/docs/components/select-rich/api-table.md +0 -1
  187. package/docs/components/select-rich/use-cases.md +1 -1
  188. package/docs/components/tabs/api-table.md +7 -0
  189. package/docs/components/tabs/examples.md +1 -1
  190. package/docs/fundamentals/index.md +4 -4
  191. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  192. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  193. package/docs/fundamentals/rationales/accessibility.md +1 -1
  194. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  195. package/docs/fundamentals/systems/core/overview.md +1 -1
  196. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  197. package/docs/fundamentals/systems/form/model-value.md +1 -1
  198. package/docs/fundamentals/systems/form/overview.md +28 -28
  199. package/docs/fundamentals/systems/form/validate.md +19 -3
  200. package/docs/fundamentals/systems/localize/overview.md +4 -4
  201. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  202. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  203. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  204. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  205. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  206. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  207. package/docs/guides/how-to/create-a-form.md +4 -4
  208. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  209. package/docs/guides/index.md +8 -8
  210. package/docs/guides/principles/index.md +5 -5
  211. package/docs/guides/principles/styling.md +1 -1
  212. package/exports/define/lion-input-amount-dropdown.js +3 -0
  213. package/exports/input-amount-dropdown-test-helpers.js +1 -0
  214. package/exports/input-amount-dropdown-test-suites.js +1 -0
  215. package/exports/input-amount-dropdown.js +1 -0
  216. package/input-amount-dropdown-test-helpers.js +1 -0
  217. package/input-amount-dropdown-test-suites.js +1 -0
  218. package/input-amount-dropdown.js +1 -0
  219. package/package.json +2 -1
@@ -0,0 +1,557 @@
1
+ import { html, css } from 'lit';
2
+ import { ref, createRef } from 'lit/directives/ref.js';
3
+ import { ifDefined } from 'lit/directives/if-defined.js';
4
+
5
+ import { LionInputAmount } from '@lion/ui/input-amount.js';
6
+ import { currencyUtil } from './currencyUtil.js';
7
+ import { parseAmount } from './parsers.js';
8
+ import { formatAmount } from './formatters.js';
9
+ import { deserializer, serializer } from './serializers.js';
10
+ import { CurrencyAndAmount } from './validators.js';
11
+ import { localizeNamespaceLoader } from './localizeNamespaceLoader.js';
12
+
13
+ /**
14
+ * Note: one could consider to implement LionInputAmountDropdown as a
15
+ * [combobox](https://www.w3.org/TR/wai-aria-practices-1.2/#combobox).
16
+ * However, the currency dropdown does not directly set the textbox value, it only determines
17
+ * its region code. Therefore it does not comply to this criterium:
18
+ * "A combobox is an input widget with an associated popup that enables users to select a value for
19
+ * the combobox from a collection of possible values. In some implementations,
20
+ * the popup presents allowed values, while in other implementations, the popup presents suggested
21
+ * values, and users may either select one of the suggestions or type a value".
22
+ * We therefore decided to consider the dropdown a helper mechanism that does not set, but
23
+ * contributes to and helps format and validate the actual value.
24
+ */
25
+
26
+ /**
27
+ * @typedef {import('lit/directives/ref.js').Ref} Ref
28
+ * @typedef {import('lit').RenderOptions} RenderOptions
29
+ * @typedef {import('../../form-core/types/FormatMixinTypes.js').FormatHost} FormatHost
30
+ * @typedef {import('../../input-tel/types/index.js').RegionCode} RegionCode
31
+ * @typedef {import('../types/index.js').TemplateDataForDropdownInputAmount} TemplateDataForDropdownInputAmount
32
+ * @typedef {import('../types/index.js').OnDropdownChangeEvent} OnDropdownChangeEvent
33
+ * @typedef {import('../types/index.js').DropdownRef} DropdownRef
34
+ * @typedef {import('../types/index.js').RegionMeta} RegionMeta
35
+ * @typedef {import('../types/index.js').CurrencyCode} CurrencyCode
36
+ * @typedef {import('../../select-rich/src/LionSelectRich.js').LionSelectRich} LionSelectRich
37
+ * @typedef {import('../../overlays/src/OverlayController.js').OverlayController} OverlayController
38
+ * @typedef {import('../../form-core/types/FormatMixinTypes.js').FormatOptions} FormatOptions
39
+ * @typedef {FormatOptions & {locale?:string;currency:string|undefined}} AmountFormatOptions
40
+ * @typedef {TemplateDataForDropdownInputAmount & {data: {regionMetaList:RegionMeta[]}}} TemplateDataForIntlInputAmount
41
+ */
42
+
43
+ /**
44
+ * LionInputAmountDropdown renders a dropdown like element next to the text field, inside the
45
+ * prefix, or suffix, slot. This could be a LionSelect, a LionSelectRich or a native select.
46
+ * By default, the native `<select>` element is used for this, so that it's as lightweight as
47
+ * possible. Also, it doesn't need to be a `FormControl`, because it's purely a helper element
48
+ * to provide better UX: the modelValue (the text field) contains all needed info:
49
+ * the currency code following ISO 4217 and its corresponding currency symbol using Intl.
50
+ *
51
+ * @customElement lion-input-amount-dropdown
52
+ */
53
+ // @ts-expect-error - The types returned by 'parser(...)' are incompatible between these types. AmountDropdownModelValue' is not assignable to type 'number' */
54
+ export class LionInputAmountDropdown extends LionInputAmount {
55
+ /**
56
+ * @configure LitElement
57
+ * @type {any}
58
+ */
59
+ static properties = {
60
+ preferredCurrencies: { type: Array },
61
+ allowedCurrencies: { type: Array },
62
+ __dropdownSlot: { type: String },
63
+ };
64
+
65
+ static localizeNamespaces = [
66
+ { 'lion-input-amount-dropdown': localizeNamespaceLoader },
67
+ ...super.localizeNamespaces,
68
+ ];
69
+
70
+ refs = {
71
+ /** @type {DropdownRef} */
72
+ dropdown: /** @type {DropdownRef} */ (createRef()),
73
+ };
74
+
75
+ /**
76
+ * This method provides a TemplateData object to be fed to pure template functions, a.k.a.
77
+ * Pure Templates™. The goal is to totally decouple presentation from logic here, so that
78
+ * Subclassers can override all content without having to loose private info contained
79
+ * within the template function that was overridden.
80
+ *
81
+ * Subclassers would need to make sure all the contents of the TemplateData object are implemented
82
+ * by making sure they are coupled to the right 'ref' ([data-ref=dropdown] in this example),
83
+ * with the help of lit's spread operator directive.
84
+ * To enhance this process, the TemplateData object is completely typed. Ideally, this would be
85
+ * enhanced by providing linters that make sure all of their required members are implemented by
86
+ * a Subclasser.
87
+ * When a Subclasser wants to add more data, this can be done via:
88
+ * @example
89
+ * ```js
90
+ * get _templateDataDropdown() {
91
+ * return {
92
+ * ...super._templateDataDropdown,
93
+ * myExtraData: { x: 1, y: 2 },
94
+ * }
95
+ * }
96
+ * ```
97
+ * @overridable
98
+ * @type {TemplateDataForDropdownInputAmount}
99
+ */
100
+ get _templateDataDropdown() {
101
+ const refs = {
102
+ dropdown: {
103
+ ref: this.refs.dropdown,
104
+ props: {
105
+ style: `height: 100%;`,
106
+ },
107
+ listeners: {
108
+ change: this._onDropdownValueChange,
109
+ 'model-value-changed': this._onDropdownValueChange,
110
+ },
111
+ labels: {
112
+ selectCurrency: this._localizeManager.msg('lion-input-amount-dropdown:selectCurrency'),
113
+ allCurrencies:
114
+ this._allCurrenciesLabel ||
115
+ this._localizeManager.msg('lion-input-amount-dropdown:allCurrencies'),
116
+ preferredCurrencies:
117
+ this._preferredCurrenciesLabel ||
118
+ this._localizeManager.msg('lion-input-amount-dropdown:suggestedCurrencies'),
119
+ },
120
+ },
121
+ input: this._inputNode,
122
+ };
123
+
124
+ return {
125
+ refs,
126
+ data: {
127
+ // @ts-expect-error - cannot cast string to CurrencyCode outside a TS file
128
+ currency: this.currency,
129
+ regionMetaList: this.__regionMetaList,
130
+ regionMetaListPreferred: this.__regionMetaListPreferred,
131
+ },
132
+ };
133
+ }
134
+
135
+ /**
136
+ * @returns {string}
137
+ */
138
+ get _dropdownSlot() {
139
+ return /** @type {string} */ this.__dropdownSlot;
140
+ }
141
+
142
+ set _dropdownSlot(position) {
143
+ if (position !== 'suffix' && position !== 'prefix') {
144
+ throw new Error('Only the suffix and prefix slots are valid positions for the dropdown.');
145
+ }
146
+
147
+ this.__dropdownSlot = position;
148
+ }
149
+
150
+ static templates = {
151
+ dropdown: (/** @type {TemplateDataForDropdownInputAmount} */ templateDataForDropdown) => {
152
+ const { refs, data } = templateDataForDropdown;
153
+ const renderOption = (/** @type {RegionMeta} */ regionMeta) =>
154
+ html`${this.templates.dropdownOption(templateDataForDropdown, regionMeta)} `;
155
+
156
+ // TODO: once spread directive available, use it per ref
157
+ return html`
158
+ <select
159
+ ${ref(refs?.dropdown?.ref)}
160
+ aria-label="${refs?.dropdown?.labels?.selectCurrency}"
161
+ @change="${refs?.dropdown?.listeners?.change}"
162
+ style="${refs?.dropdown?.props?.style}"
163
+ >
164
+ ${data?.regionMetaListPreferred?.length
165
+ ? html`
166
+ <optgroup label="${refs?.dropdown?.labels?.preferredCurrencies}">
167
+ ${data.regionMetaListPreferred.map(renderOption)}
168
+ </optgroup>
169
+ <optgroup label="${refs?.dropdown?.labels?.allCurrencies}">
170
+ ${data?.regionMetaList?.map(renderOption)}
171
+ </optgroup>
172
+ `
173
+ : html` ${data?.regionMetaList?.map(renderOption)}`}
174
+ </select>
175
+ `;
176
+ },
177
+ /**
178
+ * @param {TemplateDataForDropdownInputAmount} templateDataForDropdown
179
+ * @param {RegionMeta} contextData
180
+ */
181
+ // eslint-disable-next-line class-methods-use-this
182
+ dropdownOption: (
183
+ templateDataForDropdown,
184
+ { currencyCode, nameForLocale, currencySymbol },
185
+ ) => html`
186
+ <option
187
+ value="${currencyCode}"
188
+ aria-label="${ifDefined(
189
+ nameForLocale && currencySymbol ? `${nameForLocale}, ${currencySymbol}` : '',
190
+ )}"
191
+ >
192
+ ${currencyCode} (${currencySymbol})&nbsp;
193
+ </option>
194
+ `,
195
+ };
196
+
197
+ /**
198
+ * @configure LitElement
199
+ * @enhance LionInputAmountDropdown
200
+ */
201
+ static styles = [
202
+ super.styles,
203
+ css`
204
+ /**
205
+ * We need to align the height of the dropdown with the height of the text field.
206
+ * We target the HTMLDivElement (render wrapper from SlotMixin) here. Its child,
207
+ * [data-ref=dropdown], receives a 100% height as well via inline styles (since we
208
+ * can't target from shadow styles).
209
+ */
210
+ ::slotted([slot='prefix']),
211
+ ::slotted([slot='suffix']) {
212
+ height: 100%;
213
+ }
214
+
215
+ /**
216
+ * visually hiding the 'after' slot, leaving it as sr-only (screen-reader only)
217
+ * source: https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html
218
+ */
219
+ ::slotted([slot='after']:not(:focus):not(:active)) {
220
+ clip: rect(0 0 0 0);
221
+ clip-path: inset(50%);
222
+ height: 1px;
223
+ overflow: hidden;
224
+ position: absolute;
225
+ white-space: nowrap;
226
+ width: 1px;
227
+ }
228
+ `,
229
+ ];
230
+
231
+ /**
232
+ * @configure SlotMixin
233
+ */
234
+ get slots() {
235
+ return {
236
+ ...super.slots,
237
+ [this._dropdownSlot]: () => {
238
+ const ctor = /** @type {typeof LionInputAmountDropdown} */ (this.constructor);
239
+ const { templates } = ctor;
240
+
241
+ return {
242
+ template: templates.dropdown(this._templateDataDropdown),
243
+ renderAsDirectHostChild: true,
244
+ };
245
+ },
246
+ };
247
+ }
248
+
249
+ /**
250
+ * @configure LocalizeMixin
251
+ */
252
+ onLocaleUpdated() {
253
+ super.onLocaleUpdated();
254
+
255
+ const localeSplitted = this._localizeManager.locale.split('-');
256
+ /**
257
+ * @protected
258
+ * @type {RegionCode}
259
+ */
260
+ this._langIso = /** @type {RegionCode} */ (
261
+ localeSplitted[localeSplitted.length - 1].toUpperCase()
262
+ );
263
+
264
+ this.__namesForLocale = new Intl.DisplayNames([this._langIso], {
265
+ type: 'currency',
266
+ });
267
+
268
+ this.__calculateActiveCurrency();
269
+ this.__createCurrencyMeta();
270
+ }
271
+
272
+ /**
273
+ * @lifecycle platform
274
+ */
275
+ constructor() {
276
+ super();
277
+
278
+ this.parser = parseAmount;
279
+
280
+ /**
281
+ * @param {import("../types/index.js").AmountDropdownModelValue} modelValue
282
+ * @param {import('../../localize/types/LocalizeMixinTypes.js').FormatNumberOptions} [givenOptions] Locale Options
283
+ */
284
+ this.formatter = (modelValue, givenOptions) => formatAmount(modelValue, givenOptions, this);
285
+ this.serializer = serializer;
286
+ this.deserializer = deserializer;
287
+
288
+ this.defaultValidators = [new CurrencyAndAmount()];
289
+
290
+ /**
291
+ * Slot position to render the dropdown in
292
+ * @type {string}
293
+ */
294
+ this.__dropdownSlot = 'prefix';
295
+
296
+ /**
297
+ * Regions that will be shown on top of the dropdown
298
+ * @type {CurrencyCode[]}
299
+ */
300
+ this.preferredCurrencies = [];
301
+
302
+ /**
303
+ * Regions that are allowed to be selected in the dropdown.
304
+ * @type {CurrencyCode[]}
305
+ */
306
+ this.allowedCurrencies = [];
307
+
308
+ /**
309
+ * Group label for all countries, when preferredCountries are shown
310
+ * @protected
311
+ */
312
+ this._allCurrenciesLabel = '';
313
+ /**
314
+ * Group label for preferred countries, when preferredCountries are shown
315
+ * @protected
316
+ */
317
+ this._preferredCurrenciesLabel = '';
318
+
319
+ /**
320
+ * Contains everything needed for rendering region options:
321
+ * region code, currency code, display name according to locale, display name
322
+ * @private
323
+ * @type {RegionMeta[]}
324
+ */
325
+ this.__regionMetaList = [];
326
+
327
+ /**
328
+ * A filtered `this.__regionMetaList`, containing all regions provided in `preferredCurrencies`
329
+ * @private
330
+ * @type {RegionMeta[]}
331
+ */
332
+ this.__regionMetaListPreferred = [];
333
+
334
+ /**
335
+ * @protected
336
+ * @type {EventListener}
337
+ */
338
+ this._onDropdownValueChange = this._onDropdownValueChange.bind(this);
339
+ /**
340
+ * @private
341
+ * @type {EventListener}
342
+ */
343
+ this.__syncCurrencyWithDropdown = this.__syncCurrencyWithDropdown.bind(this);
344
+
345
+ this._currencyUtil = currencyUtil;
346
+ }
347
+
348
+ /**
349
+ * @lifecycle LitElement
350
+ * @param {import('lit-element').PropertyValues } changedProperties
351
+ */
352
+ willUpdate(changedProperties) {
353
+ super.willUpdate(changedProperties);
354
+
355
+ if (changedProperties.has('allowedCurrencies')) {
356
+ this.__createCurrencyMeta();
357
+ }
358
+ }
359
+
360
+ /**
361
+ * @param {import('lit-element').PropertyValues } changedProperties
362
+ */
363
+ updated(changedProperties) {
364
+ super.updated(changedProperties);
365
+
366
+ this.__syncCurrencyWithDropdown();
367
+
368
+ if (changedProperties.has('disabled') || changedProperties.has('readOnly')) {
369
+ if (this.disabled || this.readOnly) {
370
+ this.refs.dropdown?.value?.setAttribute('disabled', '');
371
+ } else {
372
+ this.refs.dropdown?.value?.removeAttribute('disabled');
373
+ }
374
+ }
375
+
376
+ if (changedProperties.has('allowedCurrencies') && this.allowedCurrencies.length > 0) {
377
+ this.__calculateActiveCurrency();
378
+ }
379
+ }
380
+
381
+ /**
382
+ * @lifecycle LitElement
383
+ * @param {import('lit-element').PropertyValues } changedProperties
384
+ */
385
+ firstUpdated(changedProperties) {
386
+ super.firstUpdated?.(changedProperties);
387
+ this._initModelValueBasedOnDropdown();
388
+ }
389
+
390
+ /**
391
+ * @protected
392
+ */
393
+ _initModelValueBasedOnDropdown() {
394
+ if (!this._initialModelValue && !this.dirty) {
395
+ this.__initializedCurrencyCode = this.currency;
396
+ this._initialModelValue = { currency: this.currency };
397
+ this.modelValue = this._initialModelValue;
398
+ this.initInteractionState();
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Used for Required validation and computation of interaction states.
404
+ * We need to override this, because we prefill the input with the currency code, but for proper UX,
405
+ * we don't consider this as having interaction state `prefilled`
406
+ * @param {string} modelValue
407
+ * @return {boolean}
408
+ * @protected
409
+ */
410
+ _isEmpty(modelValue = this.modelValue) {
411
+ return super._isEmpty(modelValue) || this.currency === this.__initializedCurrencyCode;
412
+ }
413
+
414
+ /**
415
+ * @protected
416
+ * @param {OnDropdownChangeEvent} event
417
+ */
418
+ _onDropdownValueChange(event) {
419
+ const isInitializing = event.detail?.initialize;
420
+ const dropdownElement = event.target;
421
+ const dropdownValue = /** @type {RegionCode} */ (
422
+ dropdownElement.modelValue || dropdownElement.value
423
+ );
424
+ if (isInitializing || this.currency === dropdownValue) {
425
+ return;
426
+ }
427
+
428
+ const prevCurrency = this.currency;
429
+
430
+ /** @type {RegionCode | string} */
431
+ this.currency = dropdownValue;
432
+
433
+ if (prevCurrency !== this.currency && !this.focused) {
434
+ if (!this.value) {
435
+ this.modelValue = { currency: this.currency, amount: this.value };
436
+ } else {
437
+ /** @type {AmountFormatOptions} */
438
+ (this.formatOptions).currency = this.currency;
439
+ this.modelValue = this._callParser(this.value);
440
+ }
441
+ }
442
+ }
443
+
444
+ /**
445
+ * @private
446
+ */
447
+ __syncCurrencyWithDropdown(currencyCode = this.currency) {
448
+ const dropdownElement = this.refs.dropdown?.value;
449
+ if (!dropdownElement || !currencyCode) {
450
+ return;
451
+ }
452
+
453
+ if ('modelValue' in dropdownElement) {
454
+ const dropdownCurrencyCode = dropdownElement.modelValue;
455
+ if (dropdownCurrencyCode === currencyCode) {
456
+ return;
457
+ }
458
+ /** @type {* & FormatHost} */ (dropdownElement).modelValue = currencyCode;
459
+ } else {
460
+ const dropdownCurrencyCode = dropdownElement.value;
461
+ if (dropdownCurrencyCode === currencyCode) {
462
+ return;
463
+ }
464
+ /** @type {HTMLSelectElement} */ (dropdownElement).value = currencyCode;
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Prepares data for options, like "Greece (Ελλάδα)", where "Greece" is `nameForLocale` and
470
+ * "Ελλάδα" `nameForRegion`.
471
+ * This should be run on change of:
472
+ * - allowedCurrencies
473
+ * - locale
474
+ * @private
475
+ */
476
+ __createCurrencyMeta() {
477
+ if (!this._allowedOrAllCurrencies?.length || !this.__namesForLocale) {
478
+ return;
479
+ }
480
+
481
+ this.__regionMetaList = [];
482
+ this.__regionMetaListPreferred = [];
483
+
484
+ this._allowedOrAllCurrencies.forEach(currencyCode => {
485
+ const destinationList = this.preferredCurrencies.includes(currencyCode)
486
+ ? this.__regionMetaListPreferred
487
+ : this.__regionMetaList;
488
+
489
+ destinationList.push({
490
+ currencyCode,
491
+ nameForLocale: this.__namesForLocale?.of(currencyCode),
492
+ currencySymbol: this._currencyUtil.getCurrencySymbol(currencyCode, this._langIso ?? ''),
493
+ });
494
+ });
495
+ }
496
+
497
+ /**
498
+ * Usually, we don't use composition in regular LionFields (non choice-groups). Here we use a LionSelect(Rich) inside.
499
+ * We don't want to repropagate any children, since an Application Developer is not concerned with these internals (see repropate logic in FormControlMixin)
500
+ * Also, we don't want to give (wrong) info to InteractionStateMixin, that will set the wrong interaction states based on child info.
501
+ * TODO: Make "this._repropagationRole !== 'child'" the default for FormControlMixin
502
+ * (so that FormControls used within are never repropagated for LionFields)
503
+ * @protected
504
+ * @configure FormControlMixin: don't repropagate any children
505
+ */
506
+ // eslint-disable-next-line class-methods-use-this
507
+ _repropagationCondition() {
508
+ return false;
509
+ }
510
+
511
+ __calculateActiveCurrency() {
512
+ // 1. Get the currency from pre-configured allowed currencies (if one entry)
513
+ if (this.allowedCurrencies?.length === 1) {
514
+ [this.currency] = this.allowedCurrencies;
515
+ return;
516
+ }
517
+
518
+ // 2. Try to get the currency from user input
519
+ if (this.modelValue?.currency && this.allowedCurrencies?.includes(this.modelValue?.currency)) {
520
+ this.currency = this.modelValue.currency;
521
+ return;
522
+ }
523
+
524
+ // 3. Try to get the currency from the preferred currencies
525
+ if (this.preferredCurrencies?.length > 0) {
526
+ [this.currency] = this.preferredCurrencies;
527
+ return;
528
+ }
529
+
530
+ // 4. Try to get the currency from locale
531
+ if (
532
+ this._langIso &&
533
+ this._currencyUtil?.countryToCurrencyMap.has(this._langIso) &&
534
+ this._allowedOrAllCurrencies.includes(
535
+ // @ts-expect-error - Set.get always returns a CurrencyCode.
536
+ this._currencyUtil?.countryToCurrencyMap.get(this._langIso),
537
+ )
538
+ ) {
539
+ this.currency = this._currencyUtil?.countryToCurrencyMap.get(this._langIso);
540
+ return;
541
+ }
542
+
543
+ // 5. Not derivable
544
+ this.currency = undefined;
545
+ }
546
+
547
+ /**
548
+ * Used for rendering the region/currency list
549
+ * @property _allowedOrAllRegions
550
+ * @type {CurrencyCode[]}
551
+ */
552
+ get _allowedOrAllCurrencies() {
553
+ return this.allowedCurrencies?.length
554
+ ? this.allowedCurrencies
555
+ : Array.from(this._currencyUtil?.allCurrencies) || [];
556
+ }
557
+ }