@lion/ui 0.6.0-beta.0 → 0.6.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 (151) hide show
  1. package/components/form-core/src/choice-group/ChoiceGroupMixin.js +10 -1
  2. package/components/form-core/src/validate/ValidateMixin.js +10 -0
  3. package/components/form-core/src/validate/Validator.js +6 -3
  4. package/components/form-core/test-suites/FormRegistrationMixins.suite.js +7 -7
  5. package/components/form-core/types/FormControlMixinTypes.ts +2 -0
  6. package/components/form-core/types/validate/ValidateMixinTypes.ts +2 -0
  7. package/components/form-core/types/validate/validate.ts +2 -2
  8. package/components/input-file/src/LionInputFile.js +7 -1
  9. package/components/input-tel-dropdown/src/LionInputTelDropdown.js +11 -3
  10. package/components/input-tel-dropdown/test/LionInputTelDropdown.test.js +110 -2
  11. package/components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.js +22 -0
  12. package/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.js +1 -38
  13. package/components/localize/src/LocalizeMixin.js +2 -2
  14. package/components/overlays/src/OverlayController.js +1 -1
  15. package/components/select/src/LionSelect.js +5 -0
  16. package/components/validate-messages/src/getLocalizedMessage.js +12 -1
  17. package/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.test.js +105 -0
  18. package/components/validate-messages/translations/bg.js +4 -0
  19. package/components/validate-messages/translations/cs.js +4 -0
  20. package/components/validate-messages/translations/de.js +4 -0
  21. package/components/validate-messages/translations/en.js +5 -1
  22. package/components/validate-messages/translations/es.js +4 -0
  23. package/components/validate-messages/translations/fr.js +4 -0
  24. package/components/validate-messages/translations/hu.js +4 -0
  25. package/components/validate-messages/translations/it.js +4 -0
  26. package/components/validate-messages/translations/nl.js +4 -0
  27. package/components/validate-messages/translations/pl.js +4 -0
  28. package/components/validate-messages/translations/ro.js +4 -0
  29. package/components/validate-messages/translations/ru.js +4 -0
  30. package/components/validate-messages/translations/sk.js +4 -0
  31. package/components/validate-messages/translations/uk.js +4 -0
  32. package/components/validate-messages/translations/zh.js +4 -0
  33. package/custom-elements.json +4375 -1693
  34. package/dist-types/components/form-core/src/choice-group/ChoiceGroupMixin.d.ts +1 -0
  35. package/dist-types/components/form-core/src/choice-group/ChoiceGroupMixin.d.ts.map +1 -1
  36. package/dist-types/components/form-core/src/validate/ValidateMixin.d.ts +1 -0
  37. package/dist-types/components/form-core/src/validate/ValidateMixin.d.ts.map +1 -1
  38. package/dist-types/components/form-core/src/validate/Validator.d.ts +5 -2
  39. package/dist-types/components/form-core/src/validate/Validator.d.ts.map +1 -1
  40. package/dist-types/components/form-core/types/FormControlMixinTypes.d.ts +1 -0
  41. package/dist-types/components/form-core/types/FormControlMixinTypes.d.ts.map +1 -1
  42. package/dist-types/components/form-core/types/validate/ValidateMixinTypes.d.ts +1 -0
  43. package/dist-types/components/form-core/types/validate/ValidateMixinTypes.d.ts.map +1 -1
  44. package/dist-types/components/form-core/types/validate/validate.d.ts +2 -2
  45. package/dist-types/components/input-file/src/LionInputFile.d.ts.map +1 -1
  46. package/dist-types/components/input-tel-dropdown/src/LionInputTelDropdown.d.ts.map +1 -1
  47. package/dist-types/components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.d.ts +12 -0
  48. package/dist-types/components/input-tel-dropdown/test-helpers/mimicUserChangingDropdown.d.ts.map +1 -0
  49. package/dist-types/components/input-tel-dropdown/test-suites/LionInputTelDropdown.suite.d.ts.map +1 -1
  50. package/dist-types/components/select/src/LionSelect.d.ts.map +1 -1
  51. package/dist-types/components/validate-messages/src/getLocalizedMessage.d.ts.map +1 -1
  52. package/dist-types/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.test.d.ts +4 -0
  53. package/dist-types/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.test.d.ts.map +1 -0
  54. package/dist-types/components/validate-messages/translations/bg-BG.d.ts +4 -0
  55. package/dist-types/components/validate-messages/translations/bg.d.ts +6 -0
  56. package/dist-types/components/validate-messages/translations/cs-CZ.d.ts +4 -0
  57. package/dist-types/components/validate-messages/translations/cs.d.ts +6 -0
  58. package/dist-types/components/validate-messages/translations/de-DE.d.ts +4 -0
  59. package/dist-types/components/validate-messages/translations/de.d.ts +6 -0
  60. package/dist-types/components/validate-messages/translations/en-AU.d.ts +4 -0
  61. package/dist-types/components/validate-messages/translations/en-GB.d.ts +4 -0
  62. package/dist-types/components/validate-messages/translations/en-PH.d.ts +4 -0
  63. package/dist-types/components/validate-messages/translations/en-US.d.ts +4 -0
  64. package/dist-types/components/validate-messages/translations/en.d.ts +6 -0
  65. package/dist-types/components/validate-messages/translations/es-ES.d.ts +4 -0
  66. package/dist-types/components/validate-messages/translations/es.d.ts +6 -0
  67. package/dist-types/components/validate-messages/translations/fr-BE.d.ts +4 -0
  68. package/dist-types/components/validate-messages/translations/fr-FR.d.ts +4 -0
  69. package/dist-types/components/validate-messages/translations/fr.d.ts +6 -0
  70. package/dist-types/components/validate-messages/translations/hu-HU.d.ts +4 -0
  71. package/dist-types/components/validate-messages/translations/hu.d.ts +6 -0
  72. package/dist-types/components/validate-messages/translations/it-IT.d.ts +4 -0
  73. package/dist-types/components/validate-messages/translations/it.d.ts +6 -0
  74. package/dist-types/components/validate-messages/translations/nl-BE.d.ts +4 -0
  75. package/dist-types/components/validate-messages/translations/nl-NL.d.ts +4 -0
  76. package/dist-types/components/validate-messages/translations/nl.d.ts +6 -0
  77. package/dist-types/components/validate-messages/translations/pl-PL.d.ts +4 -0
  78. package/dist-types/components/validate-messages/translations/pl.d.ts +6 -0
  79. package/dist-types/components/validate-messages/translations/ro-RO.d.ts +4 -0
  80. package/dist-types/components/validate-messages/translations/ro.d.ts +6 -0
  81. package/dist-types/components/validate-messages/translations/ru-RU.d.ts +4 -0
  82. package/dist-types/components/validate-messages/translations/ru.d.ts +6 -0
  83. package/dist-types/components/validate-messages/translations/sk-SK.d.ts +4 -0
  84. package/dist-types/components/validate-messages/translations/sk.d.ts +6 -0
  85. package/dist-types/components/validate-messages/translations/uk-UA.d.ts +4 -0
  86. package/dist-types/components/validate-messages/translations/uk.d.ts +6 -0
  87. package/dist-types/components/validate-messages/translations/zh.d.ts +6 -0
  88. package/dist-types/exports/input-tel-dropdown-test-helpers.d.ts +2 -0
  89. package/dist-types/exports/input-tel-dropdown-test-helpers.d.ts.map +1 -0
  90. package/dist-types/tsconfig.tsbuildinfo +1 -1
  91. package/docs/components/checkbox-group/overview.md +2 -2
  92. package/docs/components/collapsible/overview.md +1 -1
  93. package/docs/components/combobox/overview.md +1 -1
  94. package/docs/components/combobox/use-cases.md +1 -1
  95. package/docs/components/dialog/overview.md +1 -1
  96. package/docs/components/fieldset/overview.md +5 -5
  97. package/docs/components/form/overview.md +3 -3
  98. package/docs/components/icon/use-cases.md +1 -1
  99. package/docs/components/input/overview.md +1 -1
  100. package/docs/components/input-amount/overview.md +3 -3
  101. package/docs/components/input-date/overview.md +3 -3
  102. package/docs/components/input-datepicker/overview.md +5 -5
  103. package/docs/components/input-email/overview.md +2 -2
  104. package/docs/components/input-file/overview.md +1 -1
  105. package/docs/components/input-file/use-cases.md +8 -8
  106. package/docs/components/input-iban/overview.md +2 -2
  107. package/docs/components/input-range/overview.md +2 -2
  108. package/docs/components/input-stepper/overview.md +1 -1
  109. package/docs/components/input-tel/overview.md +1 -1
  110. package/docs/components/input-tel/use-cases.md +6 -6
  111. package/docs/components/input-tel-dropdown/overview.md +1 -1
  112. package/docs/components/listbox/use-cases.md +90 -95
  113. package/docs/components/radio-group/overview.md +3 -3
  114. package/docs/components/select/use-cases.md +34 -33
  115. package/docs/components/select-rich/use-cases.md +1 -1
  116. package/docs/components/tabs/examples.md +1 -1
  117. package/docs/fundamentals/index.md +4 -4
  118. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  119. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  120. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  121. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  122. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  123. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  124. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  125. package/docs/fundamentals/rationales/accessibility.md +1 -1
  126. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  127. package/docs/fundamentals/systems/core/overview.md +1 -1
  128. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  129. package/docs/fundamentals/systems/form/model-value.md +1 -1
  130. package/docs/fundamentals/systems/form/overview.md +28 -28
  131. package/docs/fundamentals/systems/form/styling.md +34 -39
  132. package/docs/fundamentals/systems/form/validate.md +7 -4
  133. package/docs/fundamentals/systems/localize/overview.md +4 -4
  134. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  135. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  136. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  137. package/docs/fundamentals/tools/ajax/overview.md +2 -1
  138. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  139. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  140. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  141. package/docs/guides/how-to/create-a-form.md +4 -4
  142. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  143. package/docs/guides/index.md +8 -8
  144. package/docs/guides/principles/index.md +5 -5
  145. package/docs/guides/principles/styling.md +1 -1
  146. package/exports/input-tel-dropdown-test-helpers.js +1 -0
  147. package/input-tel-dropdown-test-helpers.js +1 -0
  148. package/package.json +1 -1
  149. package/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.js +0 -51
  150. package/dist-types/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.d.ts +0 -2
  151. package/dist-types/components/validate-messages/test/loadDefaultFeedbackMessagesNoSideEffects.d.ts.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { dedupeMixin } from '@open-wc/dedupe-mixin';
2
2
  import { FormRegistrarMixin } from '../registration/FormRegistrarMixin.js';
3
3
  import { InteractionStateMixin } from '../InteractionStateMixin.js';
4
+ import { ValidateMixin } from '../validate/ValidateMixin.js';
4
5
 
5
6
  /**
6
7
  * @typedef {import('../../types/choice-group/ChoiceGroupMixinTypes.js').ChoiceGroupMixin} ChoiceGroupMixin
@@ -8,6 +9,7 @@ import { InteractionStateMixin } from '../InteractionStateMixin.js';
8
9
  * @typedef {import('../../types/registration/FormRegistrarMixinTypes.js').ElementWithParentFormGroup} ElementWithParentFormGroup
9
10
  * @typedef {import('../../types/form-group/FormGroupMixinTypes.js').FormControl} FormControl
10
11
  * @typedef {import('../../types/choice-group/ChoiceInputMixinTypes.js').ChoiceInputHost} ChoiceInputHost
12
+ * @typedef {import('../validate/Validator.js').Validator} Validator
11
13
  */
12
14
 
13
15
  /**
@@ -22,7 +24,9 @@ import { InteractionStateMixin } from '../InteractionStateMixin.js';
22
24
  */
23
25
  const ChoiceGroupMixinImplementation = superclass =>
24
26
  // @ts-ignore https://github.com/microsoft/TypeScript/issues/36821#issuecomment-588375051
25
- class ChoiceGroupMixin extends FormRegistrarMixin(InteractionStateMixin(superclass)) {
27
+ class ChoiceGroupMixin extends FormRegistrarMixin(
28
+ ValidateMixin(InteractionStateMixin(superclass)),
29
+ ) {
26
30
  /** @type {any} */
27
31
  static get properties() {
28
32
  return {
@@ -123,6 +127,11 @@ const ChoiceGroupMixinImplementation = superclass =>
123
127
  }
124
128
  }
125
129
 
130
+ get operationMode() {
131
+ // @ts-ignore
132
+ return this._repropagationRole === 'choice-group' ? 'select' : 'enter';
133
+ }
134
+
126
135
  constructor() {
127
136
  super();
128
137
 
@@ -20,6 +20,7 @@ import { FormControlMixin } from '../FormControlMixin.js';
20
20
  * @typedef {import('../../types/validate/ValidateMixinTypes.js').ValidateMixin} ValidateMixin
21
21
  * @typedef {import('../../types/validate/ValidateMixinTypes.js').ValidationType} ValidationType
22
22
  * @typedef {import('../../types/validate/ValidateMixinTypes.js').ValidateHost} ValidateHost
23
+ * @typedef {import('../../types/validate/ValidateMixinTypes.js').OperationMode} OperationMode
23
24
  * @typedef {import('../../types/validate/index.js').ValidatorOutcome} ValidatorOutcome
24
25
  * @typedef {typeof import('../../types/validate/ValidateMixinTypes.js').ValidateHost} ValidateHostConstructor
25
26
  * @typedef {{validator:Validator; outcome:boolean|string}} ValidationResultEntry
@@ -98,6 +99,15 @@ export const ValidateMixinImplementation = superclass =>
98
99
  return ['error'];
99
100
  }
100
101
 
102
+ /**
103
+ * Types of input interaction of the FormControl (for instance 'enter'|'select'|'upload')
104
+ * @overridable
105
+ * @type {OperationMode}
106
+ */
107
+ get operationMode() {
108
+ return 'enter';
109
+ }
110
+
101
111
  /**
102
112
  * Adds "._feedbackNode" as described below
103
113
  * @public
@@ -27,12 +27,15 @@ export class Validator extends EventTarget {
27
27
  this.__param = param;
28
28
  /** @type {ValidatorConfig} */
29
29
  this.__config = config || {};
30
- /** @type {ValidationType} */
31
- this.type = config?.type || 'error'; // Default type supported by ValidateMixin
30
+ /**
31
+ * Default type supported by ValidateMixin
32
+ * @type {ValidationType}
33
+ */
34
+ this.type = config?.type || 'error';
32
35
  }
33
36
 
34
37
  /**
35
- * The name under which validation results get registered. For convience and predictability, this
38
+ * The name under which validation results get registered. For convenience and predictability, this
36
39
  * should always be the same as the constructor name (since it will be obfuscated in js builds,
37
40
  * we need to provide it separately).
38
41
  * @type {ValidatorName}
@@ -163,20 +163,20 @@ export const runRegistrationSuite = customConfig => {
163
163
  });
164
164
 
165
165
  it('works for components that have a delayed render', async () => {
166
- class PerformUpdate extends FormRegistrarMixin(LitElement) {
167
- async performUpdate() {
166
+ class ScheduleUpdate extends FormRegistrarMixin(LitElement) {
167
+ async scheduleUpdate() {
168
168
  // eslint-disable-next-line no-promise-executor-return
169
169
  await new Promise(resolve => setTimeout(() => resolve(undefined), 10));
170
- await super.performUpdate();
170
+ await super.scheduleUpdate();
171
171
  }
172
172
 
173
173
  render() {
174
174
  return html`<slot></slot>`;
175
175
  }
176
176
  }
177
- const tagWrapperString = defineCE(PerformUpdate);
177
+ const tagWrapperString = defineCE(ScheduleUpdate);
178
178
  const tagWrapper = unsafeStatic(tagWrapperString);
179
- const el = /** @type {PerformUpdate} */ (
179
+ const el = /** @type {ScheduleUpdate} */ (
180
180
  await fixture(html`
181
181
  <${tagWrapper}>
182
182
  <${childTag}></${childTag}>
@@ -359,10 +359,10 @@ export const runRegistrationSuite = customConfig => {
359
359
  it('works for portals that have a delayed render', async () => {
360
360
  const delayedPortalString = defineCE(
361
361
  class extends FormRegistrarPortalMixin(LitElement) {
362
- async performUpdate() {
362
+ async scheduleUpdate() {
363
363
  // eslint-disable-next-line no-promise-executor-return
364
364
  await new Promise(resolve => setTimeout(() => resolve(undefined), 10));
365
- await super.performUpdate();
365
+ await super.scheduleUpdate();
366
366
  }
367
367
 
368
368
  render() {
@@ -115,6 +115,8 @@ export declare class FormControlHost {
115
115
  set fieldName(arg: string);
116
116
  get fieldName(): string;
117
117
 
118
+ get operationMode(): string;
119
+
118
120
  /**
119
121
  * Allows to add extra element references to aria-labelledby attribute.
120
122
  */
@@ -16,6 +16,8 @@ export type FeedbackMessage = {
16
16
 
17
17
  export type ValidationType = 'error' | 'warning' | 'info' | 'success' | string;
18
18
 
19
+ export type OperationMode = 'enter' | 'select' | 'upload';
20
+
19
21
  export declare class ValidateHost {
20
22
  /**
21
23
  * Used by Application Developers to add Validators to a FormControl.
@@ -2,7 +2,7 @@ import { FormControlHost } from '../../src/FormControlMixin.js';
2
2
  import { ValidationType } from './ValidateMixinTypes.js';
3
3
 
4
4
  /**
5
- * The name under which validation results get registered. For convience and predictability, this
5
+ * The name under which validation results get registered. For convenience and predictability, this
6
6
  * should always be the same as the constructor name (since it will be obfuscated in js builds,
7
7
  * we need to provide it separately).
8
8
  * @example
@@ -34,7 +34,7 @@ export type ValidatorConfig = {
34
34
  };
35
35
 
36
36
  /**
37
- * Output of the `execute` function that returns a validity outcome. When we need to shpw feedback,
37
+ * Output of the `execute` function that returns a validity outcome. When we need to show feedback,
38
38
  * it should return true, otherwise false. So when an error\info|warning|success message
39
39
  * needs to be shown, return true.
40
40
  * It's also possible to return an enum. Let's say that a phone number can have multiple
@@ -215,6 +215,11 @@ export class LionInputFile extends ScopedElementsMixin(LocalizeMixin(LionField))
215
215
  }
216
216
  }
217
217
 
218
+ // eslint-disable-next-line class-methods-use-this
219
+ get operationMode() {
220
+ return 'upload';
221
+ }
222
+
218
223
  get _acceptCriteria() {
219
224
  /** @type {string[]} */
220
225
  let allowedFileTypes = [];
@@ -433,7 +438,8 @@ export class LionInputFile extends ScopedElementsMixin(LocalizeMixin(LionField))
433
438
  ];
434
439
  }
435
440
  });
436
- // this._selectedFilesMetaData = [...this._selectedFilesMetaData];
441
+ // TODO: add a unit test for this
442
+ this._selectedFilesMetaData = [...this._selectedFilesMetaData];
437
443
  }
438
444
  });
439
445
  }
@@ -324,7 +324,10 @@ export class LionInputTelDropdown extends LionInputTel {
324
324
  */
325
325
  _onDropdownValueChange(event) {
326
326
  const isInitializing = event.detail?.initialize || !this._phoneUtil;
327
- const dropdownValue = /** @type {RegionCode} */ (event.target.modelValue || event.target.value);
327
+ const dropdownElement = event.target;
328
+ const dropdownValue = /** @type {RegionCode} */ (
329
+ dropdownElement.modelValue || dropdownElement.value
330
+ );
328
331
 
329
332
  if (isInitializing || this.activeRegion === dropdownValue) {
330
333
  return;
@@ -355,8 +358,13 @@ export class LionInputTelDropdown extends LionInputTel {
355
358
  }
356
359
 
357
360
  // Put focus on text box
358
- const overlayController = event.target._overlayCtrl;
359
- if (overlayController?.isShown) {
361
+ //
362
+ // A LionSelectRich with interactionMode set on windows/linux
363
+ // will set each item on arrow key up/down to activeElement
364
+ // which causes the focus to jump every time to the inputNode
365
+ const overlayController = dropdownElement._overlayCtrl;
366
+ // @ts-ignore interactionMode only exists on LionSelectRich not on HTMLSelectElement
367
+ if (overlayController?.isShown && dropdownElement.interactionMode !== 'windows/linux') {
360
368
  setTimeout(() => {
361
369
  this._inputNode.focus();
362
370
  });
@@ -1,12 +1,12 @@
1
1
  import { runInputTelSuite } from '@lion/ui/input-tel-test-suites.js';
2
- import { html } from 'lit';
3
2
  import { repeat } from 'lit/directives/repeat.js';
4
3
  import { ref } from 'lit/directives/ref.js';
5
-
4
+ import { aTimeout, expect, fixture, html } from '@open-wc/testing';
6
5
  import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
7
6
  import { LionOption } from '@lion/ui/listbox.js';
8
7
  import { LionSelectRich } from '@lion/ui/select-rich.js';
9
8
  import { runInputTelDropdownSuite } from '@lion/ui/input-tel-dropdown-test-suites.js';
9
+ import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
10
10
  import { ScopedElementsMixin } from '../../core/src/ScopedElementsMixin.js';
11
11
 
12
12
  /**
@@ -62,4 +62,112 @@ describe('WithFormControlInputTelDropdown', () => {
62
62
  // @ts-expect-error
63
63
  // Runs it for LionSelectRich, which uses .modelValue/@model-value-changed instead of .value/@change
64
64
  runInputTelDropdownSuite({ klass: WithFormControlInputTelDropdown });
65
+
66
+ it('focuses the textbox right after selection if selected via opened dropdown if interaction-mode is mac', async () => {
67
+ class InputTelDropdownMac extends LionInputTelDropdown {
68
+ static templates = {
69
+ ...(super.templates || {}),
70
+ /**
71
+ * @param {TemplateDataForDropdownInputTel} templateDataForDropdown
72
+ */
73
+ dropdown: templateDataForDropdown => {
74
+ const { refs, data } = templateDataForDropdown;
75
+ // TODO: once spread directive available, use it per ref (like ref(refs?.dropdown?.ref))
76
+ return html`
77
+ <lion-select-rich
78
+ ${ref(refs?.dropdown?.ref)}
79
+ label="${refs?.dropdown?.labels?.country}"
80
+ label-sr-only
81
+ @model-value-changed="${refs?.dropdown?.listeners['model-value-changed']}"
82
+ style="${refs?.dropdown?.props?.style}"
83
+ interaction-mode="mac"
84
+ >
85
+ ${repeat(
86
+ data.regionMetaList,
87
+ regionMeta => regionMeta.regionCode,
88
+ regionMeta =>
89
+ html` <lion-option .choiceValue="${regionMeta.regionCode}"> </lion-option> `,
90
+ )}
91
+ </lion-select-rich>
92
+ `;
93
+ },
94
+ };
95
+ }
96
+ customElements.define('input-tel-dropdown-mac', InputTelDropdownMac);
97
+ const el = /** @type {LionInputTelDropdown} */ (
98
+ await fixture(
99
+ html`
100
+ <input-tel-dropdown-mac
101
+ .allowedRegions="${['NL', 'BE']}"
102
+ .modelValue="${'+31612345678'}"
103
+ ></input-tel-dropdown-mac>
104
+ `,
105
+ )
106
+ );
107
+ const dropdownElement = el.refs.dropdown.value;
108
+ // @ts-expect-error [allow-protected-in-tests]
109
+ if (dropdownElement?._overlayCtrl) {
110
+ // @ts-expect-error [allow-protected-in-tests]
111
+ dropdownElement._overlayCtrl.show();
112
+ mimicUserChangingDropdown(dropdownElement, 'BE');
113
+ await el.updateComplete;
114
+ await aTimeout(0);
115
+ // @ts-expect-error [allow-protected-in-tests]
116
+ expect(el._inputNode).to.equal(document.activeElement);
117
+ }
118
+ });
119
+
120
+ it('does not focus the textbox right after selection if selected via opened dropdown if interaction-mode is windows/linux', async () => {
121
+ class InputTelDropdownWindows extends LionInputTelDropdown {
122
+ static templates = {
123
+ ...(super.templates || {}),
124
+ /**
125
+ * @param {TemplateDataForDropdownInputTel} templateDataForDropdown
126
+ */
127
+ dropdown: templateDataForDropdown => {
128
+ const { refs, data } = templateDataForDropdown;
129
+ // TODO: once spread directive available, use it per ref (like ref(refs?.dropdown?.ref))
130
+ return html`
131
+ <lion-select-rich
132
+ ${ref(refs?.dropdown?.ref)}
133
+ label="${refs?.dropdown?.labels?.country}"
134
+ label-sr-only
135
+ @model-value-changed="${refs?.dropdown?.listeners['model-value-changed']}"
136
+ style="${refs?.dropdown?.props?.style}"
137
+ interaction-mode="windows/linux"
138
+ >
139
+ ${repeat(
140
+ data.regionMetaList,
141
+ regionMeta => regionMeta.regionCode,
142
+ regionMeta =>
143
+ html` <lion-option .choiceValue="${regionMeta.regionCode}"> </lion-option> `,
144
+ )}
145
+ </lion-select-rich>
146
+ `;
147
+ },
148
+ };
149
+ }
150
+ customElements.define('input-tel-dropdown-windows', InputTelDropdownWindows);
151
+ const el = /** @type {LionInputTelDropdown} */ (
152
+ await fixture(
153
+ html`
154
+ <input-tel-dropdown-windows
155
+ .allowedRegions="${['NL', 'BE']}"
156
+ .modelValue="${'+31612345678'}"
157
+ ></input-tel-dropdown-windows>
158
+ `,
159
+ )
160
+ );
161
+ const dropdownElement = el.refs.dropdown.value;
162
+ // @ts-expect-error [allow-protected-in-tests]
163
+ if (dropdownElement?._overlayCtrl) {
164
+ // @ts-expect-error [allow-protected-in-tests]
165
+ dropdownElement._overlayCtrl.show();
166
+ mimicUserChangingDropdown(dropdownElement, 'BE');
167
+ await el.updateComplete;
168
+ await aTimeout(0);
169
+ // @ts-expect-error [allow-protected-in-tests]
170
+ expect(el._inputNode).to.not.equal(document.activeElement);
171
+ }
172
+ });
65
173
  });
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @typedef {HTMLSelectElement|HTMLElement & {modelValue:string}} DropdownElement
3
+ */
4
+
5
+ /**
6
+ * @param {DropdownElement} dropdownEl
7
+ * @param {string} value
8
+ */
9
+ export function mimicUserChangingDropdown(dropdownEl, value) {
10
+ if ('modelValue' in dropdownEl) {
11
+ // eslint-disable-next-line no-param-reassign
12
+ dropdownEl.modelValue = value;
13
+ dropdownEl.dispatchEvent(
14
+ new CustomEvent('model-value-changed', { detail: { isTriggeredByUser: true } }),
15
+ );
16
+ } else {
17
+ // eslint-disable-next-line no-param-reassign
18
+ dropdownEl.value = value;
19
+ dropdownEl.dispatchEvent(new Event('change'));
20
+ dropdownEl.dispatchEvent(new Event('input'));
21
+ }
22
+ }
@@ -11,6 +11,7 @@ import {
11
11
  } from '@open-wc/testing';
12
12
  import sinon from 'sinon';
13
13
  import { LionInputTelDropdown } from '@lion/ui/input-tel-dropdown.js';
14
+ import { mimicUserChangingDropdown } from '@lion/ui/input-tel-dropdown-test-helpers.js';
14
15
 
15
16
  /**
16
17
  * @typedef {import('lit').TemplateResult} TemplateResult
@@ -36,24 +37,6 @@ function getDropdownValue(dropdownEl) {
36
37
  return dropdownEl.value;
37
38
  }
38
39
 
39
- /**
40
- * @param {DropdownElement} dropdownEl
41
- * @param {string} value
42
- */
43
- function mimicUserChangingDropdown(dropdownEl, value) {
44
- if ('modelValue' in dropdownEl) {
45
- // eslint-disable-next-line no-param-reassign
46
- dropdownEl.modelValue = value;
47
- dropdownEl.dispatchEvent(
48
- new CustomEvent('model-value-changed', { detail: { isTriggeredByUser: true } }),
49
- );
50
- } else {
51
- // eslint-disable-next-line no-param-reassign
52
- dropdownEl.value = value;
53
- dropdownEl.dispatchEvent(new Event('change'));
54
- }
55
- }
56
-
57
40
  /**
58
41
  * @param {{ klass:LionInputTelDropdown }} config
59
42
  */
@@ -335,26 +318,6 @@ export function runInputTelDropdownSuite({ klass } = { klass: LionInputTelDropdo
335
318
  expect(el.value).to.equal('+32');
336
319
  });
337
320
 
338
- it('focuses the textbox right after selection if selected via opened dropdown', async () => {
339
- const el = await fixture(
340
- html` <${tag} .allowedRegions="${[
341
- 'NL',
342
- 'BE',
343
- ]}" .modelValue="${'+31612345678'}"></${tag}> `,
344
- );
345
- const dropdownElement = el.refs.dropdown.value;
346
- // @ts-expect-error [allow-protected-in-tests]
347
- if (dropdownElement?._overlayCtrl) {
348
- // @ts-expect-error [allow-protected-in-tests]
349
- dropdownElement._overlayCtrl.show();
350
- mimicUserChangingDropdown(dropdownElement, 'BE');
351
- await el.updateComplete;
352
- await aTimeout(0);
353
- // @ts-expect-error [allow-protected-in-tests]
354
- expect(el._inputNode).to.equal(document.activeElement);
355
- }
356
- });
357
-
358
321
  it('keeps focus on dropdownElement after selection if selected via unopened dropdown', async () => {
359
322
  const el = await fixture(
360
323
  html` <${tag} .allowedRegions="${[
@@ -64,11 +64,11 @@ const LocalizeMixinImplementation = superclass =>
64
64
  * hook into LitElement to only render once all translations are loaded
65
65
  * @returns {Promise.<void>}
66
66
  */
67
- async performUpdate() {
67
+ async scheduleUpdate() {
68
68
  if (Object.getPrototypeOf(this).constructor.waitForLocalizeNamespaces) {
69
69
  await this.localizeNamespacesLoaded;
70
70
  }
71
- super.performUpdate();
71
+ super.scheduleUpdate();
72
72
  }
73
73
 
74
74
  connectedCallback() {
@@ -931,7 +931,7 @@ export class OverlayController extends EventTarget {
931
931
  return;
932
932
  }
933
933
 
934
- if (this.elementToFocusAfterHide) {
934
+ if (this.elementToFocusAfterHide instanceof HTMLElement) {
935
935
  this.elementToFocusAfterHide.focus();
936
936
  this.elementToFocusAfterHide.scrollIntoView({ block: 'nearest' });
937
937
  } else {
@@ -57,6 +57,11 @@ class LionFieldWithSelect extends LionField {
57
57
  * @customElement lion-select
58
58
  */
59
59
  export class LionSelect extends LionFieldWithSelect {
60
+ // eslint-disable-next-line class-methods-use-this
61
+ get operationMode() {
62
+ return 'select';
63
+ }
64
+
60
65
  connectedCallback() {
61
66
  super.connectedCallback();
62
67
  this._inputNode.addEventListener('change', this._proxyChangeEvent);
@@ -6,6 +6,11 @@
6
6
  /** @type {Promise<void | object>} */
7
7
  let pendingPromise;
8
8
 
9
+ /**
10
+ * @param {string} string
11
+ */
12
+ const capitalize = string => (string && string[0].toUpperCase() + string.slice(1)) || '';
13
+
9
14
  /**
10
15
  * @param {{localize: LocalizeManager}} opts
11
16
  */
@@ -105,7 +110,13 @@ export async function loadValidateNamespace({ localize }) {
105
110
  export const getLocalizedMessage = async ({ data, localize }) => {
106
111
  await loadValidateNamespace({ localize });
107
112
  if (data) {
108
- return localize.msg(`lion-validate:${data.type}.${data.name}`, data);
113
+ const operationMode =
114
+ data.formControl?.operationMode !== 'enter' && data.name === 'Required'
115
+ ? capitalize(data.formControl?.operationMode)
116
+ : undefined;
117
+ const validatorName = operationMode ? `_${data.name}${operationMode}` : data.name;
118
+
119
+ return localize.msg(`lion-validate:${data.type}.${validatorName}`, data);
109
120
  }
110
121
  return '';
111
122
  };
@@ -0,0 +1,105 @@
1
+ /* eslint-disable no-unused-vars, no-param-reassign */
2
+ import { expect } from '@open-wc/testing';
3
+ import { getLocalizeManager } from '@lion/ui/localize-no-side-effects.js';
4
+ import { MinDate, MinLength, Required } from '@lion/ui/form-core.js';
5
+ import { loadDefaultFeedbackMessagesNoSideEffects } from '@lion/ui/validate-messages-no-side-effects.js';
6
+
7
+ /**
8
+ * @typedef {import('../../form-core/src/validate/Validator.js').Validator} Validator
9
+ */
10
+
11
+ /**
12
+ * @param {Validator} validatorEl
13
+ */
14
+ function getProtectedMembers(validatorEl) {
15
+ // @ts-ignore protected members allowed in test
16
+ return {
17
+ // @ts-ignore
18
+ getMessage: (...args) => validatorEl._getMessage(...args),
19
+ };
20
+ }
21
+
22
+ /**
23
+ * @typedef {import('@lion/ui/types/form-core.js').FormControlHost} FormControlHost
24
+ * @typedef {ArrayConstructor | ObjectConstructor | NumberConstructor | BooleanConstructor | StringConstructor | DateConstructor | 'iban' | 'email'} modelValueType
25
+ */
26
+
27
+ describe('loadDefaultFeedbackMessagesNoSideEffects', () => {
28
+ const localizeManager = getLocalizeManager();
29
+
30
+ it('will set default feedback message for Required', async () => {
31
+ const el = new Required();
32
+ const { getMessage } = getProtectedMembers(el);
33
+ expect(await getMessage()).to.equals(
34
+ 'Please configure an error message for "Required" by overriding "static async getMessage()"',
35
+ );
36
+
37
+ loadDefaultFeedbackMessagesNoSideEffects({ localize: localizeManager });
38
+ expect(await getMessage({ fieldName: 'password' })).to.equal('Please enter a(n) password.');
39
+ });
40
+
41
+ it('passes data to the feedback message', async () => {
42
+ const el = new MinLength(10);
43
+ const { getMessage } = getProtectedMembers(el);
44
+
45
+ loadDefaultFeedbackMessagesNoSideEffects({ localize: localizeManager });
46
+ expect(await getMessage({ fieldName: 'password' })).to.equal(
47
+ 'Please enter a correct password (at least 10 characters).',
48
+ );
49
+
50
+ const el2 = new MinDate(new Date('2024-01-29'));
51
+ // @ts-ignore protected members allowed in test
52
+ expect(await el2._getMessage({ fieldName: 'date' })).to.equal(
53
+ 'Please enter a(n) date after or equal to 29/01/2024.',
54
+ );
55
+ });
56
+
57
+ it('will set select specific feedback message for Required when operationMode is set', async () => {
58
+ const el = new Required();
59
+ loadDefaultFeedbackMessagesNoSideEffects({ localize: localizeManager });
60
+ const { getMessage } = getProtectedMembers(el);
61
+ const formControl = { operationMode: 'select' };
62
+ expect(await getMessage({ fieldName: 'password', formControl })).to.equal(
63
+ 'Please select a(n) password.',
64
+ );
65
+ });
66
+
67
+ it('will set upload specific feedback message for Required when operationMode is set', async () => {
68
+ const el = new Required();
69
+ const { getMessage } = getProtectedMembers(el);
70
+
71
+ loadDefaultFeedbackMessagesNoSideEffects({ localize: localizeManager });
72
+ const formControl = { operationMode: 'upload' };
73
+ expect(await getMessage({ fieldName: 'password', formControl })).to.equal(
74
+ 'Please upload a(n) password.',
75
+ );
76
+ });
77
+
78
+ it('will set ignore the operationMode for any other validator then Required, e.g. MinLength', async () => {
79
+ const el = new MinLength(10);
80
+ const { getMessage } = getProtectedMembers(el);
81
+
82
+ loadDefaultFeedbackMessagesNoSideEffects({ localize: localizeManager });
83
+ const formControl = { operationMode: 'select' };
84
+ expect(await getMessage({ fieldName: 'password', formControl })).to.equal(
85
+ 'Please enter a correct password (at least 10 characters).',
86
+ );
87
+ });
88
+
89
+ it('will await loading of translations when switching locale', async () => {
90
+ const el = new Required();
91
+ const { getMessage } = getProtectedMembers(el);
92
+ loadDefaultFeedbackMessagesNoSideEffects({ localize: localizeManager });
93
+ expect(await getMessage({ fieldName: 'password' })).to.equal('Please enter a(n) password.');
94
+ expect(await getMessage({ fieldName: 'user name' })).to.equal('Please enter a(n) user name.');
95
+
96
+ localizeManager.locale = 'de-DE';
97
+ await localizeManager.loadingComplete;
98
+ expect(await getMessage({ fieldName: 'Password' })).to.equal(
99
+ 'Password muss ausgefüllt werden.',
100
+ );
101
+ expect(await getMessage({ fieldName: 'Benutzername' })).to.equal(
102
+ 'Benutzername muss ausgefüllt werden.',
103
+ );
104
+ });
105
+ });
@@ -1,6 +1,8 @@
1
1
  export default {
2
2
  error: {
3
3
  Required: 'Моля, въведете също {fieldName}.',
4
+ _RequiredUpload: 'Моля, качете {fieldName}.',
5
+ _RequiredSelect: 'Wählen Sie auch einen Wert für {fieldName} aus.',
4
6
  EqualsLength: 'Моля, въведете правилно {fieldName} от точно {params} знака.',
5
7
  MinLength: 'Моля, въведете правилен {fieldName} (поне {params}).',
6
8
  MaxLength: 'Моля, въведете правилен {fieldName} (до {params} знака).',
@@ -22,6 +24,8 @@ export default {
22
24
  },
23
25
  warning: {
24
26
  Required: 'Моля, въведете също {fieldName}.',
27
+ _RequiredUpload: 'Моля, качете {fieldName}.',
28
+ _RequiredSelect: 'Wählen Sie auch einen Wert für {fieldName} aus.',
25
29
  EqualsLength: 'Моля, въведете правилно {fieldName} от точно {params} знака.',
26
30
  MinLength: 'Моля, въведете правилен {fieldName} (поне {params}).',
27
31
  MaxLength: 'Моля, въведете правилен {fieldName} (до {params} знака).',
@@ -1,6 +1,8 @@
1
1
  export default {
2
2
  error: {
3
3
  Required: 'Zadejte rovněž {fieldName}.',
4
+ _RequiredUpload: 'Nahrajte {fieldname}.',
5
+ _RequiredSelect: 'Vyberte rovněž {fieldname}.',
4
6
  EqualsLength: 'Zadejte správné {fieldName}, přesně {params} znaků.',
5
7
  MinLength: 'Zadejte správné {fieldName} (alespoň {params}).',
6
8
  MaxLength: 'Zadejte správné {fieldName} (až {params} znaků).',
@@ -21,6 +23,8 @@ export default {
21
23
  },
22
24
  warning: {
23
25
  Required: 'Zadejte rovněž {fieldName}.',
26
+ _RequiredUpload: 'Nahrajte {fieldname}.',
27
+ _RequiredSelect: 'Vyberte rovněž {fieldname}.',
24
28
  EqualsLength: 'Zadejte správné {fieldName}, přesně {params} znaků.',
25
29
  MinLength: 'Zadejte správné {fieldName} (alespoň {params}).',
26
30
  MaxLength: 'Zadejte správné {fieldName} (až {params} znaků).',
@@ -1,6 +1,8 @@
1
1
  export default {
2
2
  error: {
3
3
  Required: '{fieldName} muss ausgefüllt werden.',
4
+ _RequiredUpload: 'Bitte laden Sie ein/e/n {fieldName} hoch.',
5
+ _RequiredSelect: 'Wählen Sie auch einen Wert für {fieldName} aus.',
4
6
  EqualsLength: 'Geben Sie einen korrekten Wert für {fieldName} mit exakt {params} Zeichen ein.',
5
7
  MinLength: 'Du musst mindestens {params} Zeichen eingeben.',
6
8
  MaxLength: 'Du kannst maximal {params} Zeichen eingeben.',
@@ -23,6 +25,8 @@ export default {
23
25
  },
24
26
  warning: {
25
27
  Required: '{fieldName} sollte ausgefüllt werden.',
28
+ _RequiredUpload: 'Bitte laden Sie ein/e/n {fieldName} hoch.',
29
+ _RequiredSelect: 'Wählen Sie auch einen Wert für {fieldName} aus.',
26
30
  EqualsLength: 'Geben Sie einen korrekten Wert für {fieldName} mit exakt {params} Zeichen ein.',
27
31
  MinLength: 'Du solltest mindestens {params} Zeichen eingeben.',
28
32
  MaxLength: 'Du kannst maximal {params} Zeichen eingeben.',