@lion/ui 0.8.0 → 0.8.2

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 (136) hide show
  1. package/README.md +7 -7
  2. package/components/calendar/src/LionCalendar.js +6 -0
  3. package/components/core/src/DisabledWithTabIndexMixin.js +7 -0
  4. package/components/core/src/ScopedElementsMixin.js +58 -37
  5. package/components/core/test/ScopedElementsMixin.test.js +131 -0
  6. package/components/core/test/ssr-definitions/ScopedElementsHost.define.js +1 -0
  7. package/components/form/src/LionForm.js +3 -12
  8. package/components/form/test/lion-form.test.js +88 -1
  9. package/components/form-core/src/FormControlMixin.js +6 -0
  10. package/components/form-core/src/registration/FormRegisteringMixin.js +1 -1
  11. package/components/form-core/src/utils/SyncUpdatableMixin.js +7 -0
  12. package/components/form-core/src/validate/LionValidationFeedback.js +4 -2
  13. package/components/form-core/test/validate/lion-validation-feedback.test.js +12 -0
  14. package/components/form-core/types/registration/FormRegisteringMixinTypes.ts +1 -1
  15. package/components/icon/src/LionIcon.js +6 -0
  16. package/components/listbox/test-suites/ListboxMixin.suite.js +2 -2
  17. package/components/localize/src/LocalizeManager.js +259 -204
  18. package/components/overlays/src/OverlayController.js +1 -1
  19. package/components/overlays/src/OverlayMixin.js +7 -0
  20. package/components/progress-indicator/src/LionProgressIndicator.js +6 -0
  21. package/components/steps/src/LionStep.js +6 -0
  22. package/components/steps/src/LionSteps.js +6 -0
  23. package/components/tabs/src/LionTabs.js +6 -0
  24. package/components/tooltip/src/LionTooltip.js +6 -0
  25. package/custom-elements.json +792 -330
  26. package/dist-types/components/calendar/src/LionCalendar.d.ts +5 -0
  27. package/dist-types/components/calendar/src/LionCalendar.d.ts.map +1 -1
  28. package/dist-types/components/core/src/DisabledWithTabIndexMixin.d.ts.map +1 -1
  29. package/dist-types/components/core/src/ScopedElementsMixin.d.ts +43 -6
  30. package/dist-types/components/core/src/ScopedElementsMixin.d.ts.map +1 -1
  31. package/dist-types/components/core/test/ScopedElementsMixin.test.d.ts +2 -0
  32. package/dist-types/components/core/test/ScopedElementsMixin.test.d.ts.map +1 -0
  33. package/dist-types/components/core/test/ssr-definitions/ScopedElementsHost.define.d.ts +2 -0
  34. package/dist-types/components/core/test/ssr-definitions/ScopedElementsHost.define.d.ts.map +1 -0
  35. package/dist-types/components/form/src/LionForm.d.ts.map +1 -1
  36. package/dist-types/components/form/test/lion-form.test.d.ts.map +1 -1
  37. package/dist-types/components/form-core/src/FormControlMixin.d.ts.map +1 -1
  38. package/dist-types/components/form-core/src/utils/SyncUpdatableMixin.d.ts.map +1 -1
  39. package/dist-types/components/form-core/src/validate/LionValidationFeedback.d.ts.map +1 -1
  40. package/dist-types/components/form-core/types/registration/FormRegisteringMixinTypes.d.ts +1 -1
  41. package/dist-types/components/form-integrations/test/helpers/helpers.d.ts +3 -3
  42. package/dist-types/components/form-integrations/test/helpers/helpers.d.ts.map +1 -1
  43. package/dist-types/components/icon/src/LionIcon.d.ts +5 -0
  44. package/dist-types/components/icon/src/LionIcon.d.ts.map +1 -1
  45. package/dist-types/components/input-datepicker/src/LionInputDatepicker.d.ts +1 -1
  46. package/dist-types/components/input-file/src/LionInputFile.d.ts +1 -1
  47. package/dist-types/components/input-file/src/LionSelectedFileList.d.ts +1 -1
  48. package/dist-types/components/localize/src/LocalizeManager.d.ts +109 -112
  49. package/dist-types/components/localize/src/LocalizeManager.d.ts.map +1 -1
  50. package/dist-types/components/overlays/src/OverlayMixin.d.ts.map +1 -1
  51. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts +5 -0
  52. package/dist-types/components/progress-indicator/src/LionProgressIndicator.d.ts.map +1 -1
  53. package/dist-types/components/select-rich/src/LionSelectRich.d.ts +1 -1
  54. package/dist-types/components/steps/src/LionStep.d.ts +5 -0
  55. package/dist-types/components/steps/src/LionStep.d.ts.map +1 -1
  56. package/dist-types/components/steps/src/LionSteps.d.ts +5 -0
  57. package/dist-types/components/steps/src/LionSteps.d.ts.map +1 -1
  58. package/dist-types/components/switch/src/LionSwitch.d.ts +1 -1
  59. package/dist-types/components/tabs/src/LionTabs.d.ts +5 -0
  60. package/dist-types/components/tabs/src/LionTabs.d.ts.map +1 -1
  61. package/dist-types/components/tooltip/src/LionTooltip.d.ts +5 -0
  62. package/dist-types/components/tooltip/src/LionTooltip.d.ts.map +1 -1
  63. package/dist-types/tsconfig.tsbuildinfo +1 -1
  64. package/docs/components/checkbox-group/api-table.md +2 -2
  65. package/docs/components/checkbox-group/overview.md +2 -2
  66. package/docs/components/collapsible/overview.md +1 -1
  67. package/docs/components/combobox/overview.md +1 -1
  68. package/docs/components/combobox/use-cases.md +1 -1
  69. package/docs/components/dialog/overview.md +1 -1
  70. package/docs/components/fieldset/overview.md +5 -5
  71. package/docs/components/form/overview.md +3 -3
  72. package/docs/components/icon/use-cases.md +1 -1
  73. package/docs/components/input/api-table.md +2 -2
  74. package/docs/components/input/overview.md +1 -1
  75. package/docs/components/input-amount/api-table.md +2 -2
  76. package/docs/components/input-amount/overview.md +3 -3
  77. package/docs/components/input-date/api-table.md +2 -2
  78. package/docs/components/input-date/overview.md +3 -3
  79. package/docs/components/input-datepicker/api-table.md +2 -2
  80. package/docs/components/input-datepicker/overview.md +5 -5
  81. package/docs/components/input-email/api-table.md +2 -2
  82. package/docs/components/input-email/overview.md +2 -2
  83. package/docs/components/input-file/api-table.md +2 -2
  84. package/docs/components/input-file/overview.md +1 -1
  85. package/docs/components/input-iban/api-table.md +2 -2
  86. package/docs/components/input-iban/overview.md +2 -2
  87. package/docs/components/input-range/api-table.md +2 -2
  88. package/docs/components/input-range/overview.md +2 -2
  89. package/docs/components/input-stepper/api-table.md +2 -2
  90. package/docs/components/input-stepper/overview.md +1 -1
  91. package/docs/components/input-tel/api-table.md +2 -2
  92. package/docs/components/input-tel/overview.md +1 -1
  93. package/docs/components/input-tel/use-cases.md +6 -6
  94. package/docs/components/input-tel-dropdown/api-table.md +2 -2
  95. package/docs/components/input-tel-dropdown/overview.md +1 -1
  96. package/docs/components/listbox/api-table.md +1 -1
  97. package/docs/components/listbox/use-cases.md +1 -1
  98. package/docs/components/radio-group/api-table.md +2 -2
  99. package/docs/components/radio-group/overview.md +3 -3
  100. package/docs/components/select/api-table.md +2 -2
  101. package/docs/components/select/use-cases.md +1 -1
  102. package/docs/components/select-rich/api-table.md +1 -1
  103. package/docs/components/select-rich/use-cases.md +1 -1
  104. package/docs/components/switch/api-table.md +2 -2
  105. package/docs/components/tabs/examples.md +1 -1
  106. package/docs/components/textarea/api-table.md +2 -2
  107. package/docs/fundamentals/index.md +4 -4
  108. package/docs/fundamentals/node-tools/providence-analytics/LocalConfiguration.md +1 -1
  109. package/docs/fundamentals/node-tools/providence-analytics/QueryResult.md +1 -1
  110. package/docs/fundamentals/node-tools/providence-analytics/analyzer.md +1 -1
  111. package/docs/fundamentals/node-tools/providence-analytics/dashboard.md +1 -1
  112. package/docs/fundamentals/node-tools/providence-analytics/overview.md +3 -3
  113. package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
  114. package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
  115. package/docs/fundamentals/rationales/accessibility.md +1 -1
  116. package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
  117. package/docs/fundamentals/systems/core/overview.md +1 -1
  118. package/docs/fundamentals/systems/form/api-table.md +1 -1
  119. package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
  120. package/docs/fundamentals/systems/form/model-value.md +1 -1
  121. package/docs/fundamentals/systems/form/overview.md +28 -28
  122. package/docs/fundamentals/systems/form/validate.md +2 -2
  123. package/docs/fundamentals/systems/localize/api-table.md +38 -40
  124. package/docs/fundamentals/systems/localize/overview.md +4 -4
  125. package/docs/fundamentals/systems/overlays/configuration.md +2 -2
  126. package/docs/fundamentals/systems/overlays/overview.md +5 -5
  127. package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
  128. package/docs/fundamentals/tools/helpers/overview.md +1 -1
  129. package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
  130. package/docs/guides/how-to/create-a-custom-field.md +5 -5
  131. package/docs/guides/how-to/create-a-form.md +4 -4
  132. package/docs/guides/how-to/extend-a-native-input.md +2 -2
  133. package/docs/guides/index.md +8 -8
  134. package/docs/guides/principles/index.md +5 -5
  135. package/docs/guides/principles/styling.md +1 -1
  136. package/package.json +1 -1
@@ -1,202 +1,182 @@
1
- // @ts-ignore
1
+ // @ts-expect-error
2
2
  import MessageFormat from '@bundled-es-modules/message-format/MessageFormat.js';
3
+ import { isServer } from 'lit';
4
+
3
5
  import isLocalizeESModule from './isLocalizeESModule.js';
4
6
 
5
7
  /**
8
+ * @typedef {import('../types/LocalizeMixinTypes.js').NumberPostProcessor} NumberPostProcessor
9
+ * @typedef {import('../types/LocalizeMixinTypes.js').DatePostProcessor} DatePostProcessor
6
10
  * @typedef {import('../types/LocalizeMixinTypes.js').NamespaceObject} NamespaceObject
7
11
  */
8
12
 
9
- /** @typedef {import('../types/LocalizeMixinTypes.js').DatePostProcessor} DatePostProcessor */
10
- /** @typedef {import('../types/LocalizeMixinTypes.js').NumberPostProcessor} NumberPostProcessor */
13
+ /**
14
+ * We can't access `window.document.documentElement` on the server,
15
+ * so we write to and read from this object on the server.
16
+ * N.B.: for now, the goal is to make LocalizeManager not crash on the server, and let localization happen on the client.
17
+ * In the future, we might want to look into more advanced SSR of localized messages
18
+ */
19
+ const documentElement = isServer
20
+ ? { getAttribute: () => null, lang: '' }
21
+ : globalThis.document?.documentElement;
11
22
 
12
23
  /**
13
24
  * `LocalizeManager` manages your translations (includes loading)
14
25
  */
15
- export class LocalizeManager {
16
- // eslint-disable-line no-unused-vars
17
- constructor({
18
- autoLoadOnLocaleChange = false,
19
- fallbackLocale = '',
20
- showKeyAsFallback = false,
21
- allowOverridesForExistingNamespaces = false,
22
- } = {}) {
23
- /** @private */
24
- this.__delegationTarget = document.createDocumentFragment();
25
- /** @protected */
26
- this._autoLoadOnLocaleChange = !!autoLoadOnLocaleChange;
27
- /** @protected */
28
- this._fallbackLocale = fallbackLocale;
29
- /** @protected */
30
- this._showKeyAsFallback = showKeyAsFallback;
31
-
32
- /** @private */
33
- this.__allowOverridesForExistingNamespaces = allowOverridesForExistingNamespaces;
34
-
35
- /**
36
- * @type {Object.<string, Object.<string, Object>>}
37
- * @private
38
- */
39
- this.__storage = {};
40
-
41
- /**
42
- * @type {Map.<RegExp|string, function>}
43
- * @private
44
- */
45
- this.__namespacePatternsMap = new Map();
46
-
47
- /**
48
- * @type {Object.<string, function|null>}
49
- * @private
50
- */
51
- this.__namespaceLoadersCache = {};
26
+ export class LocalizeManager extends EventTarget {
27
+ /**
28
+ * The localize system uses (normalized) Intl for formatting numbers.
29
+ * It's possible to customize this output per locale
30
+ */
31
+ formatNumberOptions = {
32
+ returnIfNaN: '',
33
+ /** @type {Map<string, NumberPostProcessor>} */
34
+ postProcessors: new Map(),
35
+ };
52
36
 
53
- /**
54
- * @type {Object.<string, Object.<string, Promise.<Object|void>>>}
55
- * @private
56
- */
57
- this.__namespaceLoaderPromisesCache = {};
37
+ /**
38
+ * The localize system uses (normalized) Intl for formatting dates.
39
+ * It's possible to customize this output per locale
40
+ */
41
+ formatDateOptions = {
42
+ /** @type {Map<string, DatePostProcessor>} */
43
+ postProcessors: new Map(),
44
+ };
58
45
 
59
- this.formatNumberOptions = {
60
- returnIfNaN: '',
61
- /** @type {Map<string,DatePostProcessor>} */
62
- postProcessors: new Map(),
63
- };
64
-
65
- this.formatDateOptions = {
66
- /** @type {Map<string,DatePostProcessor>} */
67
- postProcessors: new Map(),
68
- };
69
-
70
- /**
71
- * Via html[data-localize-lang], developers are allowed to set the initial locale, without
72
- * having to worry about whether locale is initialized before 3rd parties like Google Translate.
73
- * When this value differs from html[lang], we assume the 3rd party took
74
- * control over the page language and we set this._langAttrSetByTranslationTool to html[lang]
75
- */
76
- const initialLocale = document.documentElement.getAttribute('data-localize-lang');
46
+ /**
47
+ * Although it's common to configure the language via the html[lang] attribute,
48
+ * the lang attribute can be changed by 3rd party translation tools like Google Translate.
49
+ *
50
+ * ## Why is this a potential problem?
51
+ * The localize system reads from html[lang] for its original configuration. Let's say it's
52
+ * configured as "en-US" by the developer. This means all translation data are fetched for locale "en-US".
53
+ *
54
+ * Now the Google Translate plugin kicks in. It will automatically translate all English texts found into
55
+ * Chinese texts. Everything looks fine... But Google Translate also sets html[lang] to "zh-CN":
56
+ * our localize system responds by trying to fetch Chinese translation data.
57
+ * Two problems can occur here:
58
+ * - the Chinese translations don't exist
59
+ * - Google Translate expects to translate from English to Chinese... and now we suddenly serve Chinese text
60
+ * ourselves... not what we intended.
61
+ *
62
+ * ## How can we solve this?
63
+ * Via `html[data-localize-lang]`, developers are allowed to set the initial locale, without
64
+ * having to worry about whether locale is initialized before 3rd parties like Google Translate.
65
+ * When this value differs from html[lang], we assume the 3rd party took
66
+ * control over the page language and we set this.#localeSetByTranslationTool to html[lang]
67
+ */
68
+ #shouldHandleTranslationTools = false;
77
69
 
78
- /** @protected */
79
- this._supportExternalTranslationTools = Boolean(initialLocale);
70
+ /**
71
+ * The locale that is configured on html[data-localize-lang]
72
+ */
73
+ #localeProvidedViaDataLangAttr = '';
80
74
 
81
- if (this._supportExternalTranslationTools) {
82
- this.locale = initialLocale || 'en-GB';
83
- this._setupTranslationToolSupport();
84
- }
75
+ /**
76
+ * The locale that is set on html[lang] by a 3rd party translation tool like Googl Translate
77
+ * @type {string|null}
78
+ */
79
+ #localeSetByTranslationTool = null;
85
80
 
86
- if (!document.documentElement.lang) {
87
- document.documentElement.lang = this.locale || 'en-GB';
88
- }
81
+ /**
82
+ * @type {Object<string, Object<string, Object>>}
83
+ * @private
84
+ */
85
+ __storage = {};
89
86
 
90
- /** @protected */
91
- this._setupHtmlLangAttributeObserver();
92
- }
87
+ /**
88
+ * @type {Map<RegExp|string, function>}
89
+ * @private
90
+ */
91
+ __namespacePatternsMap = new Map();
93
92
 
94
- /** @protected */
95
- _setupTranslationToolSupport() {
96
- /**
97
- * This value allows for support for Google Translate (or other 3rd parties taking control
98
- * of the html[lang] attribute).
99
- *
100
- * Have the following scenario in mind:
101
- * 1. locale is initialized by developer via html[data-localize-lang="en-US"] and
102
- * html[lang="en-US"]. When localize is loaded (note that this also can be after step 2 below),
103
- * it will sync its initial state from html[data-localize-lang]
104
- * 2. Google Translate kicks in for the French language. It will set html[lang="fr"].
105
- * This new language is not one known by us, so we most likely don't have translations for
106
- * this file. Therefore, we do NOT sync this value to LocalizeManager. The manager should
107
- * still ask for known resources (in this case for locale 'en-US')
108
- * 3. locale is changed (think of a language dropdown)
109
- * It's a bit of a weird case, because we would not expect an end user to do this. If he/she
110
- * does, make sure that we do not go against Google Translate, so we maintain accessibility
111
- * (by not altering html[lang]). We detect this by reading _langAttrSetByTranslationTool:
112
- * when its value is null, we consider Google translate 'not active'.
113
- *
114
- * When Google Translate is turned off by the user (html[lang=auto]),
115
- * `localize.locale` will be synced to html[lang] again
116
- *
117
- * Keep in mind that all of the above also works with other tools than Google Translate,
118
- * but this is the most widely used tool and therefore used as an example.
119
- */
120
- this._langAttrSetByTranslationTool = document.documentElement.lang || null;
121
- }
93
+ /**
94
+ * @type {Object<string, function|null>}
95
+ * @private
96
+ */
97
+ __namespaceLoadersCache = {};
122
98
 
123
- teardown() {
124
- this._teardownHtmlLangAttributeObserver();
125
- }
99
+ /**
100
+ * @type {Object<string, Object<string, Promise<Object|void>>>}
101
+ * @private
102
+ */
103
+ __namespaceLoaderPromisesCache = {};
126
104
 
127
105
  /**
128
106
  * @returns {string}
129
107
  */
130
108
  get locale() {
131
- if (this._supportExternalTranslationTools) {
132
- return this.__locale || '';
109
+ if (!this.#shouldHandleTranslationTools) {
110
+ return documentElement.lang || '';
133
111
  }
134
- return document.documentElement.lang;
112
+ return this.#localeProvidedViaDataLangAttr || '';
135
113
  }
136
114
 
137
115
  /**
138
- * @param {string} value
116
+ * @param {string} newLocale
139
117
  */
140
- set locale(value) {
141
- /** @type {string} */
142
- let oldLocale;
143
- if (this._supportExternalTranslationTools) {
144
- oldLocale = /** @type {string} */ (this.__locale);
145
- this.__locale = value;
146
- if (this._langAttrSetByTranslationTool === null) {
147
- this._setHtmlLangAttribute(value);
148
- }
149
- } else {
150
- oldLocale = document.documentElement.lang;
151
- this._setHtmlLangAttribute(value);
152
- }
118
+ set locale(newLocale) {
119
+ this.#assertCorrectLocale(newLocale);
153
120
 
154
- if (!value.includes('-')) {
155
- this.__handleLanguageOnly(value);
121
+ if (!this.#shouldHandleTranslationTools) {
122
+ const oldLocale = documentElement.lang;
123
+ this._setHtmlLangAttribute(newLocale);
124
+ this._onLocaleChanged(newLocale, oldLocale);
125
+ return;
156
126
  }
157
127
 
158
- this._onLocaleChanged(value, oldLocale);
128
+ const oldLocale = /** @type {string} */ (this.#localeProvidedViaDataLangAttr);
129
+ this.#localeProvidedViaDataLangAttr = newLocale;
130
+ const isLangAutoOrTranslationToolNotProcessed = this.#localeSetByTranslationTool === null;
131
+ if (isLangAutoOrTranslationToolNotProcessed) {
132
+ this._setHtmlLangAttribute(newLocale);
133
+ }
134
+ this._onLocaleChanged(newLocale, oldLocale);
159
135
  }
160
136
 
161
137
  /**
162
- * @param {string} locale
163
- * @protected
138
+ * @readonly
139
+ * @returns {Promise<Object|void>}
164
140
  */
165
- _setHtmlLangAttribute(locale) {
166
- this._teardownHtmlLangAttributeObserver();
167
- document.documentElement.lang = locale;
168
- this._setupHtmlLangAttributeObserver();
169
- }
141
+ get loadingComplete() {
142
+ const hasPendingCacheForLocale =
143
+ typeof this.__namespaceLoaderPromisesCache[this.locale] === 'object';
170
144
 
171
- /**
172
- * @param {string} value
173
- * @throws {Error} Language only locales are not allowed(Use 'en-GB' instead of 'en')
174
- * @private
175
- */
176
- // eslint-disable-next-line class-methods-use-this
177
- __handleLanguageOnly(value) {
178
- throw new Error(`
179
- Locale was set to ${value}.
180
- Language only locales are not allowed, please use the full language locale e.g. 'en-GB' instead of 'en'.
181
- See https://github.com/ing-bank/lion/issues/187 for more information.
182
- `);
145
+ return !hasPendingCacheForLocale
146
+ ? Promise.resolve()
147
+ : Promise.all(Object.values(this.__namespaceLoaderPromisesCache[this.locale]));
183
148
  }
184
149
 
185
- /**
186
- * @returns {Promise.<Object|void>}
187
- */
188
- get loadingComplete() {
189
- if (typeof this.__namespaceLoaderPromisesCache[this.locale] === 'object') {
190
- return Promise.all(Object.values(this.__namespaceLoaderPromisesCache[this.locale]));
150
+ constructor({
151
+ allowOverridesForExistingNamespaces = false,
152
+ autoLoadOnLocaleChange = false,
153
+ showKeyAsFallback = false,
154
+ fallbackLocale = '',
155
+ } = {}) {
156
+ super();
157
+
158
+ /** @private */
159
+ this.__allowOverridesForExistingNamespaces = allowOverridesForExistingNamespaces;
160
+ /** @protected */
161
+ this._autoLoadOnLocaleChange = !!autoLoadOnLocaleChange;
162
+ /** @protected */
163
+ this._showKeyAsFallback = showKeyAsFallback;
164
+ /** @protected */
165
+ this._fallbackLocale = fallbackLocale;
166
+
167
+ const localeProvidedViaDataLangAttr = documentElement.getAttribute('data-localize-lang');
168
+ this.#shouldHandleTranslationTools = Boolean(localeProvidedViaDataLangAttr);
169
+
170
+ if (this.#shouldHandleTranslationTools) {
171
+ this.locale = /** @type {string} */ (localeProvidedViaDataLangAttr);
172
+ this._setupTranslationToolSupport();
191
173
  }
192
- return Promise.resolve();
193
- }
194
174
 
195
- reset() {
196
- this.__storage = {};
197
- this.__namespacePatternsMap = new Map();
198
- this.__namespaceLoadersCache = {};
199
- this.__namespaceLoaderPromisesCache = {};
175
+ if (!documentElement.lang) {
176
+ documentElement.lang = this.locale || 'en-GB';
177
+ }
178
+
179
+ this._setupHtmlLangAttributeObserver();
200
180
  }
201
181
 
202
182
  /**
@@ -240,7 +220,7 @@ export class LocalizeManager {
240
220
  * @param {NamespaceObject[]} namespaces
241
221
  * @param {Object} options
242
222
  * @param {string} [options.locale]
243
- * @returns {Promise.<Object>}
223
+ * @returns {Promise<Object>}
244
224
  */
245
225
  loadNamespaces(namespaces, { locale } = {}) {
246
226
  return Promise.all(
@@ -255,7 +235,7 @@ export class LocalizeManager {
255
235
  * @param {NamespaceObject} namespaceObj
256
236
  * @param {Object} options
257
237
  * @param {string} [options.locale]
258
- * @returns {Promise.<Object|void>}
238
+ * @returns {Promise<Object|void>}
259
239
  */
260
240
  loadNamespace(namespaceObj, { locale = this.locale } = { locale: this.locale }) {
261
241
  const isDynamicImport = typeof namespaceObj === 'object';
@@ -278,7 +258,7 @@ export class LocalizeManager {
278
258
 
279
259
  /**
280
260
  * @param {string | string[]} keys
281
- * @param {Object.<string,?>} [vars]
261
+ * @param {Object<string,?>} [vars]
282
262
  * @param {Object} [opts]
283
263
  * @param {string} [opts.locale]
284
264
  * @returns {string}
@@ -293,18 +273,84 @@ export class LocalizeManager {
293
273
  return formatter.format(vars);
294
274
  }
295
275
 
276
+ teardown() {
277
+ this._teardownHtmlLangAttributeObserver();
278
+ }
279
+
280
+ reset() {
281
+ this.__storage = {};
282
+ this.__namespacePatternsMap = new Map();
283
+ this.__namespaceLoadersCache = {};
284
+ this.__namespaceLoaderPromisesCache = {};
285
+ }
286
+
287
+ /**
288
+ * @param {{locale:string, postProcessor:DatePostProcessor}} options
289
+ */
290
+ setDatePostProcessorForLocale({ locale, postProcessor }) {
291
+ this.formatDateOptions?.postProcessors.set(locale, postProcessor);
292
+ }
293
+
294
+ /**
295
+ * @param {{locale:string, postProcessor:NumberPostProcessor}} options
296
+ */
297
+ setNumberPostProcessorForLocale({ locale, postProcessor }) {
298
+ this.formatNumberOptions?.postProcessors.set(locale, postProcessor);
299
+ }
300
+
301
+ /**
302
+ * This value allows for support for Google Translate (or other 3rd parties taking control
303
+ * of the html[lang] attribute).
304
+ *
305
+ * Have the following scenario in mind:
306
+ * 1. locale is initialized by developer via html[data-localize-lang="en-US"] and
307
+ * html[lang="en-US"]. When localize is loaded (note that this also can be after step 2 below),
308
+ * it will sync its initial state from html[data-localize-lang]
309
+ * 2. Google Translate kicks in for the French language. It will set html[lang="fr"].
310
+ * This new language is not one known by us, so we most likely don't have translations for
311
+ * this file. Therefore, we do NOT sync this value to LocalizeManager. The manager should
312
+ * still ask for known resources (in this case for locale 'en-US')
313
+ * 3. locale is changed (think of a language dropdown)
314
+ * It's a bit of a weird case, because we would not expect an end user to do this. If he/she
315
+ * does, make sure that we do not go against Google Translate, so we maintain accessibility
316
+ * (by not altering html[lang]). We detect this by reading #localeSetByTranslationTool:
317
+ * when its value is null, we consider Google translate 'not active'.
318
+ *
319
+ * When Google Translate is turned off by the user (html[lang=auto]),
320
+ * `localize.locale` will be synced to html[lang] again
321
+ *
322
+ * Keep in mind that all of the above also works with other tools than Google Translate,
323
+ * but this is the most widely used tool and therefore used as an example.
324
+ * @protected
325
+ */
326
+ _setupTranslationToolSupport() {
327
+ this.#localeSetByTranslationTool = documentElement.lang || null;
328
+ }
329
+
330
+ /**
331
+ * @param {string} locale
332
+ * @protected
333
+ */
334
+ _setHtmlLangAttribute(locale) {
335
+ this._teardownHtmlLangAttributeObserver();
336
+ documentElement.lang = locale;
337
+ this._setupHtmlLangAttributeObserver();
338
+ }
339
+
296
340
  /** @protected */
297
341
  _setupHtmlLangAttributeObserver() {
342
+ if (isServer) return;
343
+
298
344
  if (!this._htmlLangAttributeObserver) {
299
345
  this._htmlLangAttributeObserver = new MutationObserver(mutations => {
300
346
  mutations.forEach(mutation => {
301
- if (this._supportExternalTranslationTools) {
302
- if (document.documentElement.lang === 'auto') {
347
+ if (this.#shouldHandleTranslationTools) {
348
+ if (documentElement.lang === 'auto') {
303
349
  // Google Translate is switched off
304
- this._langAttrSetByTranslationTool = null;
350
+ this.#localeSetByTranslationTool = null;
305
351
  this._setHtmlLangAttribute(this.locale);
306
352
  } else {
307
- this._langAttrSetByTranslationTool = document.documentElement.lang;
353
+ this.#localeSetByTranslationTool = document.documentElement.lang;
308
354
  }
309
355
  } else {
310
356
  this._onLocaleChanged(document.documentElement.lang, mutation.oldValue || '');
@@ -352,7 +398,7 @@ export class LocalizeManager {
352
398
  * @param {NamespaceObject} namespaceObj
353
399
  * @param {boolean} isDynamicImport
354
400
  * @param {string} namespace
355
- * @returns {Promise.<Object|void>}
401
+ * @returns {Promise<Object|void>}
356
402
  * @protected
357
403
  */
358
404
  _loadNamespaceData(locale, namespaceObj, isDynamicImport, namespace) {
@@ -388,7 +434,7 @@ export class LocalizeManager {
388
434
  let loader = this.__namespaceLoadersCache[namespace];
389
435
  if (!loader) {
390
436
  if (isDynamicImport) {
391
- const _namespaceObj = /** @type {Object.<string,function>} */ (namespaceObj);
437
+ const _namespaceObj = /** @type {Object<string,function>} */ (namespaceObj);
392
438
  loader = _namespaceObj[namespace];
393
439
  this.__namespaceLoadersCache[namespace] = loader;
394
440
  } else {
@@ -411,7 +457,7 @@ export class LocalizeManager {
411
457
  * @param {string} locale
412
458
  * @param {string} namespace
413
459
  * @param {string} [fallbackLocale]
414
- * @returns {Promise.<any>}
460
+ * @returns {Promise<any>}
415
461
  * @throws {Error} Data for namespace and (locale or fallback locale) could not be loaded.
416
462
  * @protected
417
463
  */
@@ -441,7 +487,7 @@ export class LocalizeManager {
441
487
  /**
442
488
  * @param {string} locale
443
489
  * @param {string} namespace
444
- * @param {Promise.<Object|void>} promise
490
+ * @param {Promise<Object|void>} promise
445
491
  * @protected
446
492
  */
447
493
  _cacheNamespaceLoaderPromise(locale, namespace, promise) {
@@ -480,31 +526,6 @@ export class LocalizeManager {
480
526
  return locale.substring(0, 2);
481
527
  }
482
528
 
483
- /**
484
- * @param {string} type
485
- * @param {EventListener} listener
486
- * @param {...Object} options
487
- */
488
- addEventListener(type, listener, ...options) {
489
- this.__delegationTarget.addEventListener(type, listener, ...options);
490
- }
491
-
492
- /**
493
- * @param {string} type
494
- * @param {EventListener} listener
495
- * @param {...Object} options
496
- */
497
- removeEventListener(type, listener, ...options) {
498
- this.__delegationTarget.removeEventListener(type, listener, ...options);
499
- }
500
-
501
- /**
502
- * @param {CustomEvent} event
503
- */
504
- dispatchEvent(event) {
505
- this.__delegationTarget.dispatchEvent(event);
506
- }
507
-
508
529
  /**
509
530
  * @param {string} newLocale
510
531
  * @param {string} oldLocale
@@ -514,9 +535,8 @@ export class LocalizeManager {
514
535
  _onLocaleChanged(newLocale, oldLocale) {
515
536
  // Event firing immediately, does not wait for loading the translations
516
537
  this.dispatchEvent(new CustomEvent('__localeChanging'));
517
- if (newLocale === oldLocale) {
518
- return;
519
- }
538
+ if (newLocale === oldLocale) return;
539
+
520
540
  if (this._autoLoadOnLocaleChange) {
521
541
  this._loadAllMissing(newLocale, oldLocale);
522
542
  this.loadingComplete.then(() => {
@@ -588,7 +608,7 @@ export class LocalizeManager {
588
608
  const names = namesString.split('.');
589
609
  const result = names.reduce(
590
610
  /**
591
- * @param {Object.<string, any> | string} message
611
+ * @param {Object<string, any> | string} message
592
612
  * @param {string} name
593
613
  * @returns {string}
594
614
  */
@@ -600,16 +620,51 @@ export class LocalizeManager {
600
620
  }
601
621
 
602
622
  /**
603
- * @param {{locale:string, postProcessor:DatePostProcessor}} options
623
+ * @param {string} value
624
+ * @throws {Error} Language only locales are not allowed(Use 'en-GB' instead of 'en')
604
625
  */
605
- setDatePostProcessorForLocale({ locale, postProcessor }) {
606
- this.formatDateOptions.postProcessors.set(locale, postProcessor);
626
+ // eslint-disable-next-line class-methods-use-this
627
+ #assertCorrectLocale(value) {
628
+ if (value.includes('-')) return;
629
+
630
+ throw new Error(`
631
+ Locale was set to ${value}.
632
+ Language only locales are not allowed, please use the full language locale e.g. 'en-GB' instead of 'en'.
633
+ See https://github.com/ing-bank/lion/issues/187 for more information.
634
+ `);
607
635
  }
608
636
 
637
+ // === TODO: delete below in a next breaking release ---
638
+
609
639
  /**
610
- * @param {{locale:string, postProcessor:NumberPostProcessor}} options
640
+ * @deprecated
641
+ * @protected
611
642
  */
612
- setNumberPostProcessorForLocale({ locale, postProcessor }) {
613
- this.formatNumberOptions.postProcessors.set(locale, postProcessor);
643
+ get _supportExternalTranslationTools() {
644
+ return this.#shouldHandleTranslationTools;
645
+ }
646
+
647
+ /**
648
+ * @deprecated
649
+ * @protected
650
+ */
651
+ set _supportExternalTranslationTools(supportsThem) {
652
+ this.#shouldHandleTranslationTools = supportsThem;
653
+ }
654
+
655
+ /**
656
+ * @deprecated
657
+ * @protected
658
+ */
659
+ get _langAttrSetByTranslationTool() {
660
+ return this.#localeProvidedViaDataLangAttr;
661
+ }
662
+
663
+ /**
664
+ * @deprecated
665
+ * @protected
666
+ */
667
+ set _langAttrSetByTranslationTool(newValue) {
668
+ this.#localeProvidedViaDataLangAttr = newValue;
614
669
  }
615
670
  }
@@ -578,7 +578,7 @@ export class OverlayController extends EventTarget {
578
578
  // The role="dialog" is set on the contentNode (or another role), so role="none"
579
579
  // is valid here, although AXE complains about this setup.
580
580
  // For now we need to add `ignoredRules: ['aria-allowed-role']` in your AXE tests.
581
- // see: https://lion-web.netlify.app/fundamentals/systems/overlays/rationale/#considerations
581
+ // see: https://lion.js.org/fundamentals/systems/overlays/rationale/#considerations
582
582
  wrappingDialogElement.setAttribute('role', 'none');
583
583
  wrappingDialogElement.setAttribute('data-overlay-outer-wrapper', '');
584
584
  // N.B. position: fixed is needed to escape out of 'overflow: hidden'
@@ -189,6 +189,13 @@ export const OverlayMixinImplementation = superclass =>
189
189
  }
190
190
  }
191
191
 
192
+ /**
193
+ * // TODO: check if this is a false positive or if we can improve
194
+ * @configure ReactiveElement
195
+ */
196
+ // @ts-expect-error
197
+ static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
198
+
192
199
  get _overlayInvokerNode() {
193
200
  return /** @type {HTMLElement | undefined} */ (
194
201
  Array.from(this.children).find(child => child.slot === 'invoker')
@@ -93,6 +93,12 @@ export class LionProgressIndicator extends LocalizeMixin(LitElement) {
93
93
  return !this.hasAttribute('value');
94
94
  }
95
95
 
96
+ /**
97
+ * // TODO: check if this is a false positive or if we can improve
98
+ * @configure ReactiveElement
99
+ */
100
+ static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
101
+
96
102
  /**
97
103
  * In case of a determinate progress-indicator it returns the progress percentage
98
104
  * based on value, min & max.
@@ -115,4 +115,10 @@ export class LionStep extends LitElement {
115
115
  const result = this.condition(data);
116
116
  return this.invertCondition ? !result : result;
117
117
  }
118
+
119
+ /**
120
+ * // TODO: check if this is a false positive or if we can improve
121
+ * @configure ReactiveElement
122
+ */
123
+ static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
118
124
  }
@@ -108,6 +108,12 @@ export class LionSteps extends LitElement {
108
108
  );
109
109
  }
110
110
 
111
+ /**
112
+ * // TODO: check if this is a false positive or if we can improve
113
+ * @configure ReactiveElement
114
+ */
115
+ static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
116
+
111
117
  /**
112
118
  * @param {number} newCurrent
113
119
  * @param {number} oldCurrent
@@ -188,6 +188,12 @@ export class LionTabs extends LitElement {
188
188
  );
189
189
  }
190
190
 
191
+ /**
192
+ * // TODO: check if this is a false positive or if we can improve
193
+ * @configure ReactiveElement
194
+ */
195
+ static enabledWarnings = super.enabledWarnings?.filter(w => w !== 'change-in-update') || [];
196
+
191
197
  /** @private */
192
198
  __setupSlots() {
193
199
  if (this.shadowRoot) {