@lion/ui 0.13.1 → 0.14.1

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