@lion/ui 0.10.1 → 0.11.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.
- package/README.md +2 -2
- package/components/checkbox-group/src/LionCheckboxIndeterminate.js +39 -16
- package/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.js +189 -1
- package/components/form-core/src/FocusMixin.js +4 -4
- package/components/form-core/src/LionField.js +1 -1
- package/components/input-amount/test/lion-input-amount.test.js +48 -1
- package/components/input-amount/test/parsers.test.js +1 -1
- package/components/localize/src/number/parseNumber.js +21 -12
- package/components/localize/test/number/parseNumber.test.js +1 -1
- package/components/localize/types/LocalizeMixinTypes.ts +0 -5
- package/components/select-rich/src/LionSelectRich.js +18 -2
- package/components/select-rich/test/lion-select-rich-interaction.test.js +139 -20
- package/components/select-rich/test/lion-select-rich.test.js +13 -1
- package/components/validate-messages/translations/pl.js +4 -4
- package/custom-elements.json +47661 -47626
- package/dist-types/components/checkbox-group/src/LionCheckboxIndeterminate.d.ts +10 -0
- package/dist-types/components/checkbox-group/src/LionCheckboxIndeterminate.d.ts.map +1 -1
- package/dist-types/components/checkbox-group/test-suites/CheckboxIndeterminate.suite.d.ts.map +1 -1
- package/dist-types/components/localize/src/number/parseNumber.d.ts.map +1 -1
- package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts +0 -4
- package/dist-types/components/localize/types/LocalizeMixinTypes.d.ts.map +1 -1
- package/dist-types/components/select-rich/src/LionSelectRich.d.ts.map +1 -1
- package/dist-types/tsconfig.tsbuildinfo +1 -1
- package/docs/components/accordion/api-table.md +6 -6
- package/docs/components/button/api-table.md +27 -30
- package/docs/components/calendar/api-table.md +13 -13
- package/docs/components/checkbox-group/api-table.md +305 -329
- package/docs/components/checkbox-group/overview.md +2 -2
- package/docs/components/collapsible/api-table.md +5 -5
- package/docs/components/collapsible/overview.md +1 -1
- package/docs/components/combobox/api-table.md +150 -154
- package/docs/components/combobox/overview.md +1 -1
- package/docs/components/combobox/use-cases.md +1 -1
- package/docs/components/dialog/api-table.md +12 -12
- package/docs/components/dialog/overview.md +1 -1
- package/docs/components/drawer/api-table.md +9 -10
- package/docs/components/fieldset/api-table.md +104 -118
- package/docs/components/fieldset/overview.md +5 -5
- package/docs/components/form/overview.md +3 -3
- package/docs/components/form/use-cases.md +2 -2
- package/docs/components/icon/api-table.md +3 -3
- package/docs/components/icon/use-cases.md +1 -1
- package/docs/components/input/api-table.md +110 -120
- package/docs/components/input/overview.md +1 -1
- package/docs/components/input-amount/api-table.md +129 -142
- package/docs/components/input-amount/overview.md +3 -3
- package/docs/components/input-date/api-table.md +110 -120
- package/docs/components/input-date/overview.md +3 -3
- package/docs/components/input-datepicker/api-table.md +148 -160
- package/docs/components/input-datepicker/overview.md +5 -5
- package/docs/components/input-email/api-table.md +110 -120
- package/docs/components/input-email/overview.md +2 -2
- package/docs/components/input-file/api-table.md +132 -143
- package/docs/components/input-file/overview.md +1 -1
- package/docs/components/input-iban/api-table.md +110 -120
- package/docs/components/input-iban/overview.md +2 -2
- package/docs/components/input-range/api-table.md +116 -126
- package/docs/components/input-range/overview.md +2 -2
- package/docs/components/input-stepper/api-table.md +123 -133
- package/docs/components/input-stepper/overview.md +1 -1
- package/docs/components/input-tel/api-table.md +118 -128
- package/docs/components/input-tel/overview.md +1 -1
- package/docs/components/input-tel/use-cases.md +6 -6
- package/docs/components/input-tel-dropdown/api-table.md +125 -135
- package/docs/components/input-tel-dropdown/overview.md +1 -1
- package/docs/components/listbox/api-table.md +126 -131
- package/docs/components/listbox/use-cases.md +1 -1
- package/docs/components/pagination/api-table.md +5 -5
- package/docs/components/progress-indicator/api-table.md +9 -10
- package/docs/components/radio-group/api-table.md +234 -258
- package/docs/components/radio-group/overview.md +3 -3
- package/docs/components/select/api-table.md +106 -116
- package/docs/components/select/use-cases.md +1 -1
- package/docs/components/select-rich/api-table.md +169 -178
- package/docs/components/select-rich/use-cases.md +1 -1
- package/docs/components/steps/api-table.md +6 -6
- package/docs/components/switch/api-table.md +117 -128
- package/docs/components/tabs/api-table.md +1 -1
- package/docs/components/tabs/examples.md +1 -1
- package/docs/components/textarea/api-table.md +114 -124
- package/docs/components/tooltip/api-table.md +13 -13
- package/docs/fundamentals/index.md +4 -4
- package/docs/fundamentals/node-tools/publish-docs/overview.md +3 -3
- package/docs/fundamentals/node-tools/rocket-preset-extend-lion-docs/overview.md +2 -2
- package/docs/fundamentals/rationales/accessibility.md +1 -1
- package/docs/fundamentals/systems/core/SlotMixin.md +1 -1
- package/docs/fundamentals/systems/core/api-table.md +8 -11
- package/docs/fundamentals/systems/core/overview.md +1 -1
- package/docs/fundamentals/systems/form/api-table.md +77 -82
- package/docs/fundamentals/systems/form/formatting-and-parsing.md +4 -4
- package/docs/fundamentals/systems/form/model-value.md +1 -1
- package/docs/fundamentals/systems/form/overview.md +28 -28
- package/docs/fundamentals/systems/form/validate.md +2 -2
- package/docs/fundamentals/systems/localize/api-table.md +19 -19
- package/docs/fundamentals/systems/localize/overview.md +4 -4
- package/docs/fundamentals/systems/overlays/api-table.md +45 -46
- package/docs/fundamentals/systems/overlays/configuration.md +2 -2
- package/docs/fundamentals/systems/overlays/overview.md +5 -5
- package/docs/fundamentals/systems/overlays/use-cases.md +2 -2
- package/docs/fundamentals/tools/helpers/overview.md +1 -1
- package/docs/fundamentals/tools/singleton-manager/overview.md +4 -4
- package/docs/guides/how-to/create-a-custom-field.md +5 -5
- package/docs/guides/how-to/create-a-form.md +4 -4
- package/docs/guides/how-to/extend-a-native-input.md +2 -2
- package/docs/guides/index.md +8 -8
- package/docs/guides/principles/index.md +5 -5
- package/docs/guides/principles/styling.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@ They provide an unopinionated, white-label layer that can be extended to your ow
|
|
|
45
45
|
- **Accessibility:** Aimed at compliance with the WCAG 2.2 AA standard to create components that are accessible for everybody.
|
|
46
46
|
- **Flexibility:** Provides solutions through Web Components and JavaScript classes which can be used, adopted and extended to fit all needs.
|
|
47
47
|
- **Modern Code:** Lion is distributes as pure es modules.
|
|
48
|
-
- **Exposes functions/classes and Web Components:** Ships a functionality in
|
|
48
|
+
- **Exposes functions/classes and Web Components:** Ships a functionality in its most appropriate form.
|
|
49
49
|
|
|
50
50
|
> Note: Our demo examples might look simple and not very stylish. This is on purpose. They are designed to be basic so you can easily add your own styles to them to match your intended design, without dealing with styles that are already there.
|
|
51
51
|
|
|
@@ -113,7 +113,7 @@ You can also use the lion elements directly, although this is likely not a commo
|
|
|
113
113
|
|
|
114
114
|
## Issues
|
|
115
115
|
|
|
116
|
-
If you encounter an issue with any of the packages we are offering please open a [new bug issue](https://github.com/ing-bank/lion/issues/new?assignees=&labels=&template=bug_report.md&title=). Be sure to include a description of the expected and the current behavior -
|
|
116
|
+
If you encounter an issue with any of the packages we are offering please open a [new bug issue](https://github.com/ing-bank/lion/issues/new?assignees=&labels=&template=bug_report.md&title=). Be sure to include a description of the expected and the current behavior - additionally adding a [reproduction](https://webcomponents.dev/edit/kpZmz1CJN580OaXsk56f?pm=1) always helps.
|
|
117
117
|
|
|
118
118
|
## Feature requests
|
|
119
119
|
|
|
@@ -49,17 +49,7 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|
|
49
49
|
* @protected
|
|
50
50
|
*/
|
|
51
51
|
get _subCheckboxes() {
|
|
52
|
-
|
|
53
|
-
if (
|
|
54
|
-
this._checkboxGroupNode &&
|
|
55
|
-
this._checkboxGroupNode.formElements &&
|
|
56
|
-
this._checkboxGroupNode.formElements.length > 0
|
|
57
|
-
) {
|
|
58
|
-
checkboxes = this._checkboxGroupNode.formElements.filter(
|
|
59
|
-
checkbox => checkbox !== this && this.contains(checkbox),
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
return /** @type LionCheckbox[] */ (checkboxes);
|
|
52
|
+
return /** @type LionCheckbox[] */ (this.__subCheckboxes);
|
|
63
53
|
}
|
|
64
54
|
|
|
65
55
|
_storeIndeterminateState() {
|
|
@@ -97,9 +87,14 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|
|
97
87
|
this.indeterminate = false;
|
|
98
88
|
this.checked = false;
|
|
99
89
|
break;
|
|
100
|
-
default:
|
|
90
|
+
default: {
|
|
101
91
|
this.indeterminate = true;
|
|
102
|
-
|
|
92
|
+
const disabledUncheckedElements = subCheckboxes.filter(
|
|
93
|
+
checkbox => checkbox.disabled && checkbox.checked === false,
|
|
94
|
+
);
|
|
95
|
+
this.checked =
|
|
96
|
+
subCheckboxes.length - checkedElements.length - disabledUncheckedElements.length === 0;
|
|
97
|
+
}
|
|
103
98
|
}
|
|
104
99
|
this.updateComplete.then(() => {
|
|
105
100
|
this.__settingOwnChecked = false;
|
|
@@ -155,13 +150,12 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|
|
155
150
|
subCheckboxes.length > 0 && subCheckboxes.length === checkedElements.length;
|
|
156
151
|
const allDisabled =
|
|
157
152
|
subCheckboxes.length > 0 && subCheckboxes.length === disabledElements.length;
|
|
158
|
-
const hasDisabledElements = disabledElements.length > 0;
|
|
159
153
|
|
|
160
154
|
if (allDisabled) {
|
|
161
155
|
this.checked = allChecked;
|
|
162
156
|
}
|
|
163
157
|
|
|
164
|
-
if (this.indeterminate &&
|
|
158
|
+
if (this.indeterminate && this.mixedState) {
|
|
165
159
|
this._subCheckboxes.forEach((checkbox, i) => {
|
|
166
160
|
// eslint-disable-next-line no-param-reassign
|
|
167
161
|
checkbox.checked = this._indeterminateSubStates[i];
|
|
@@ -211,6 +205,7 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|
|
211
205
|
if (!(/** @type {HTMLElement} */ (ev.target).hasAttribute('role'))) {
|
|
212
206
|
/** @type {HTMLElement} */ (ev.target)?.setAttribute('role', 'listitem');
|
|
213
207
|
}
|
|
208
|
+
this.__addToSubCheckboxes(/** @type {CustomEvent} */ (ev).detail.element);
|
|
214
209
|
this._setOwnCheckedState();
|
|
215
210
|
}
|
|
216
211
|
|
|
@@ -223,6 +218,26 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|
|
223
218
|
if (/** @type {HTMLElement} */ (ev.target).getAttribute('role') === 'listitem') {
|
|
224
219
|
/** @type {HTMLElement} */ (ev.target)?.removeAttribute('role');
|
|
225
220
|
}
|
|
221
|
+
this.__removeFromSubCheckboxes(/** @type {CustomEvent} */ (ev).detail.element);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @param {HTMLElement} element
|
|
226
|
+
*/
|
|
227
|
+
__addToSubCheckboxes(element) {
|
|
228
|
+
if (element !== this && this.contains(element)) {
|
|
229
|
+
this.__subCheckboxes.push(element);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* @param {HTMLElement} element
|
|
235
|
+
*/
|
|
236
|
+
__removeFromSubCheckboxes(element) {
|
|
237
|
+
const index = this.__subCheckboxes.indexOf(element);
|
|
238
|
+
if (index !== -1) {
|
|
239
|
+
this.__subCheckboxes.splice(index, 1);
|
|
240
|
+
}
|
|
226
241
|
}
|
|
227
242
|
|
|
228
243
|
constructor() {
|
|
@@ -230,6 +245,8 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|
|
230
245
|
this.indeterminate = false;
|
|
231
246
|
this._onRequestToAddFormElement = this._onRequestToAddFormElement.bind(this);
|
|
232
247
|
this.__onModelValueChanged = this.__onModelValueChanged.bind(this);
|
|
248
|
+
/** @type {HTMLElement[]} */
|
|
249
|
+
this.__subCheckboxes = [];
|
|
233
250
|
/** @type {boolean[]} */
|
|
234
251
|
this._indeterminateSubStates = [];
|
|
235
252
|
this.mixedState = false;
|
|
@@ -259,7 +276,13 @@ export class LionCheckboxIndeterminate extends LionCheckbox {
|
|
|
259
276
|
/** @param {import('lit-element').PropertyValues } changedProperties */
|
|
260
277
|
updated(changedProperties) {
|
|
261
278
|
super.updated(changedProperties);
|
|
262
|
-
|
|
279
|
+
|
|
280
|
+
// When 1. sub checkboxes have disabled elements and 2. some elements are checked while the others are unchecked
|
|
281
|
+
// both this._inputNode.indeterminate and this.indeterminate are already true.
|
|
282
|
+
// If user clicks the input node, this._inputNode.indeterminate is turned to false by the browser
|
|
283
|
+
// while this.indeterminate is still true and the 'indeterminate' is not in the changedProperties
|
|
284
|
+
// because it hasn't been updated (true -> true) but checked would have been updated (false -> true).
|
|
285
|
+
if (changedProperties.has('indeterminate') || changedProperties.has('checked')) {
|
|
263
286
|
this._inputNode.indeterminate = this.indeterminate;
|
|
264
287
|
}
|
|
265
288
|
}
|
|
@@ -205,7 +205,84 @@ export function runCheckboxIndeterminateSuite(customConfig) {
|
|
|
205
205
|
|
|
206
206
|
// Assert
|
|
207
207
|
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true;
|
|
208
|
-
expect(elIndeterminate?.checked).to.be.
|
|
208
|
+
expect(elIndeterminate?.checked).to.be.true;
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should be checked when all children are prechecked', async () => {
|
|
212
|
+
// Arrange
|
|
213
|
+
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
|
|
214
|
+
<${groupTag} name="scientists[]">
|
|
215
|
+
<${tag} label="Favorite scientists">
|
|
216
|
+
<${childTag} checked label="Archimedes"></${childTag}>
|
|
217
|
+
<${childTag} checked label="Francis Bacon"></${childTag}>
|
|
218
|
+
<${childTag} checked label="Marie Curie"></${childTag}>
|
|
219
|
+
</${tag}>
|
|
220
|
+
</${groupTag}>
|
|
221
|
+
`);
|
|
222
|
+
|
|
223
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
224
|
+
el.querySelector(`${cfg.tagString}`)
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
// Assert
|
|
228
|
+
expect(elIndeterminate?.hasAttribute('checked')).to.be.true;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should be checked when it has one prechecked child', async () => {
|
|
232
|
+
// Arrange
|
|
233
|
+
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
|
|
234
|
+
<${groupTag} name="scientists[]">
|
|
235
|
+
<${tag} label="Favorite scientists">
|
|
236
|
+
<${childTag} checked label="Archimedes"></${childTag}>
|
|
237
|
+
</${tag}>
|
|
238
|
+
</${groupTag}>
|
|
239
|
+
`);
|
|
240
|
+
|
|
241
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
242
|
+
el.querySelector(`${cfg.tagString}`)
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Assert
|
|
246
|
+
expect(elIndeterminate?.hasAttribute('checked')).to.be.true;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should be unchecked when it has an disabled checked child and an unchecked child', async () => {
|
|
250
|
+
// Arrange
|
|
251
|
+
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
|
|
252
|
+
<${groupTag} name="scientists[]">
|
|
253
|
+
<${tag} label="Favorite scientists">
|
|
254
|
+
<${childTag} disabled checked label="Archimedes"></${childTag}>
|
|
255
|
+
<${childTag} label="Francis Bacon"></${childTag}>
|
|
256
|
+
</${tag}>
|
|
257
|
+
</${groupTag}>
|
|
258
|
+
`);
|
|
259
|
+
|
|
260
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
261
|
+
el.querySelector(`${cfg.tagString}`)
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Assert
|
|
265
|
+
expect(elIndeterminate?.hasAttribute('checked')).to.be.false;
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should be indeterminated when some of the children are prechecked', async () => {
|
|
269
|
+
// Arrange
|
|
270
|
+
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
|
|
271
|
+
<${groupTag} name="scientists[]">
|
|
272
|
+
<${tag} label="Favorite scientists">
|
|
273
|
+
<${childTag} checked label="Archimedes"></${childTag}>
|
|
274
|
+
<${childTag} checked label="Francis Bacon"></${childTag}>
|
|
275
|
+
<${childTag} label="Marie Curie"></${childTag}>
|
|
276
|
+
</${tag}>
|
|
277
|
+
</${groupTag}>
|
|
278
|
+
`);
|
|
279
|
+
|
|
280
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
281
|
+
el.querySelector(`${cfg.tagString}`)
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
// Assert
|
|
285
|
+
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
|
|
209
286
|
});
|
|
210
287
|
|
|
211
288
|
it('should sync all children when parent is checked (from indeterminate to checked)', async () => {
|
|
@@ -384,6 +461,65 @@ export function runCheckboxIndeterminateSuite(customConfig) {
|
|
|
384
461
|
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.false;
|
|
385
462
|
});
|
|
386
463
|
|
|
464
|
+
it('should sync all prechecked children when parent is indeterminate and some of the children are disabled (from checked to unchecked)', async () => {
|
|
465
|
+
// Arrange
|
|
466
|
+
const el = /** @type {LionCheckboxGroup} */ (
|
|
467
|
+
await fixture(html`
|
|
468
|
+
<${groupTag} name="scientists[]">
|
|
469
|
+
<${tag} label="Favorite scientists">
|
|
470
|
+
<${childTag} checked label="Archimedes"></${childTag}>
|
|
471
|
+
<${childTag} disabled label="Francis Bacon"></${childTag}>
|
|
472
|
+
<${childTag} checked label="Marie Curie"></${childTag}>
|
|
473
|
+
</${tag}>
|
|
474
|
+
</${groupTag}>
|
|
475
|
+
`)
|
|
476
|
+
);
|
|
477
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
478
|
+
el.querySelector(`${cfg.tagString}`)
|
|
479
|
+
);
|
|
480
|
+
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
|
|
481
|
+
|
|
482
|
+
// Act
|
|
483
|
+
_inputNode.click();
|
|
484
|
+
await elIndeterminate.updateComplete;
|
|
485
|
+
|
|
486
|
+
// Assert
|
|
487
|
+
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.false;
|
|
488
|
+
expect(_inputNode?.hasAttribute('indeterminate')).to.be.false;
|
|
489
|
+
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.false;
|
|
490
|
+
expect(_subCheckboxes[1].hasAttribute('checked')).to.be.false;
|
|
491
|
+
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.false;
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
it('should stay as indeterminated after it is clicked, when it is interminated already and some children are disabled', async () => {
|
|
495
|
+
// Arrange
|
|
496
|
+
const el = /** @type {LionCheckboxGroup} */ (
|
|
497
|
+
await fixture(html`
|
|
498
|
+
<${groupTag} name="scientists[]">
|
|
499
|
+
<${tag} label="Favorite scientists">
|
|
500
|
+
<${childTag} checked label="Archimedes"></${childTag}>
|
|
501
|
+
<${childTag} disabled label="Francis Bacon"></${childTag}>
|
|
502
|
+
<${childTag} label="Marie Curie"></${childTag}>
|
|
503
|
+
</${tag}>
|
|
504
|
+
</${groupTag}>
|
|
505
|
+
`)
|
|
506
|
+
);
|
|
507
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
508
|
+
el.querySelector(`${cfg.tagString}`)
|
|
509
|
+
);
|
|
510
|
+
const { _subCheckboxes, _inputNode } = getCheckboxIndeterminateMembers(elIndeterminate);
|
|
511
|
+
|
|
512
|
+
// Act
|
|
513
|
+
_inputNode.click();
|
|
514
|
+
await elIndeterminate.updateComplete;
|
|
515
|
+
|
|
516
|
+
// Assert
|
|
517
|
+
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
|
|
518
|
+
expect(_inputNode.indeterminate).to.be.true;
|
|
519
|
+
expect(_subCheckboxes[0].hasAttribute('checked')).to.be.true;
|
|
520
|
+
expect(_subCheckboxes[2].hasAttribute('checked')).to.be.true;
|
|
521
|
+
});
|
|
522
|
+
|
|
387
523
|
it('should work as expected with siblings checkbox-indeterminate', async () => {
|
|
388
524
|
// Arrange
|
|
389
525
|
const el = /** @type {LionCheckboxGroup} */ (
|
|
@@ -450,6 +586,58 @@ export function runCheckboxIndeterminateSuite(customConfig) {
|
|
|
450
586
|
expect(elSecondSubCheckboxes._subCheckboxes[1].hasAttribute('checked')).to.be.false;
|
|
451
587
|
});
|
|
452
588
|
|
|
589
|
+
it('should work as expected when new checkbox was added', async () => {
|
|
590
|
+
// Arrange
|
|
591
|
+
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
|
|
592
|
+
<${groupTag} name="scientists[]">
|
|
593
|
+
<${tag} label="Favorite scientists">
|
|
594
|
+
<${childTag} checked label="Archimedes"></${childTag}>
|
|
595
|
+
<${childTag} checked label="Francis Bacon" checked></${childTag}>
|
|
596
|
+
<${childTag} checked label="Marie Curie"></${childTag}>
|
|
597
|
+
</${tag}>
|
|
598
|
+
</${groupTag}>
|
|
599
|
+
`);
|
|
600
|
+
|
|
601
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
602
|
+
el.querySelector(`${cfg.tagString}`)
|
|
603
|
+
);
|
|
604
|
+
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.false;
|
|
605
|
+
|
|
606
|
+
// Act
|
|
607
|
+
const newChild = document.createElement(/** @type {string} */ (cfg.childTagString));
|
|
608
|
+
elIndeterminate.appendChild(newChild);
|
|
609
|
+
await elIndeterminate.updateComplete;
|
|
610
|
+
|
|
611
|
+
// Assert
|
|
612
|
+
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
it('should work as expected when an existing checkbox was removed', async () => {
|
|
616
|
+
// Arrange
|
|
617
|
+
const el = /** @type {LionCheckboxGroup} */ await fixture(html`
|
|
618
|
+
<${groupTag} name="scientists[]">
|
|
619
|
+
<${tag} label="Favorite scientists">
|
|
620
|
+
<${childTag} checked label="Archimedes"></${childTag}>
|
|
621
|
+
<${childTag} checked label="Francis Bacon" checked></${childTag}>
|
|
622
|
+
<${childTag} label="Marie Curie"></${childTag}>
|
|
623
|
+
</${tag}>
|
|
624
|
+
</${groupTag}>
|
|
625
|
+
`);
|
|
626
|
+
|
|
627
|
+
const elIndeterminate = /** @type {LionCheckboxIndeterminate} */ (
|
|
628
|
+
el.querySelector(`${cfg.tagString}`)
|
|
629
|
+
);
|
|
630
|
+
expect(elIndeterminate.hasAttribute('indeterminate')).to.be.true;
|
|
631
|
+
expect(elIndeterminate.checked).to.be.false;
|
|
632
|
+
|
|
633
|
+
// Act
|
|
634
|
+
elIndeterminate.removeChild(/** @type {ChildNode} */ (elIndeterminate.lastChild));
|
|
635
|
+
await elIndeterminate.updateComplete;
|
|
636
|
+
|
|
637
|
+
// Assert
|
|
638
|
+
expect(elIndeterminate?.hasAttribute('indeterminate')).to.be.true;
|
|
639
|
+
});
|
|
640
|
+
|
|
453
641
|
it('should work as expected with nested indeterminate checkboxes', async () => {
|
|
454
642
|
// Arrange
|
|
455
643
|
const el = /** @type {LionCheckboxGroup} */ (
|
|
@@ -198,19 +198,19 @@ const FocusMixinImplementation = superclass =>
|
|
|
198
198
|
* @private
|
|
199
199
|
*/
|
|
200
200
|
__teardownEventsForFocusMixin() {
|
|
201
|
-
this._focusableNode
|
|
201
|
+
this._focusableNode?.removeEventListener(
|
|
202
202
|
'focus',
|
|
203
203
|
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchFocus),
|
|
204
204
|
);
|
|
205
|
-
this._focusableNode
|
|
205
|
+
this._focusableNode?.removeEventListener(
|
|
206
206
|
'blur',
|
|
207
207
|
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchBlur),
|
|
208
208
|
);
|
|
209
|
-
this._focusableNode
|
|
209
|
+
this._focusableNode?.removeEventListener(
|
|
210
210
|
'focusin',
|
|
211
211
|
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchFocusin),
|
|
212
212
|
);
|
|
213
|
-
this._focusableNode
|
|
213
|
+
this._focusableNode?.removeEventListener(
|
|
214
214
|
'focusout',
|
|
215
215
|
/** @type {EventListenerOrEventListenerObject} */ (this.__redispatchFocusout),
|
|
216
216
|
);
|
|
@@ -44,7 +44,7 @@ export class LionField extends FormControlMixin(
|
|
|
44
44
|
|
|
45
45
|
disconnectedCallback() {
|
|
46
46
|
super.disconnectedCallback();
|
|
47
|
-
this._inputNode
|
|
47
|
+
this._inputNode?.removeEventListener('change', this._onChange);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
resetInteractionState() {
|
|
@@ -131,7 +131,7 @@ describe('<lion-input-amount>', () => {
|
|
|
131
131
|
expect(_inputNode.value).to.equal('100.12');
|
|
132
132
|
});
|
|
133
133
|
|
|
134
|
-
it('
|
|
134
|
+
it('formats with locale when formatOptions.mode is "user-edited" and value has three decimal places', async () => {
|
|
135
135
|
const el = /** @type {LionInputAmount} */ (
|
|
136
136
|
await fixture(
|
|
137
137
|
html`<lion-input-amount
|
|
@@ -154,6 +154,14 @@ describe('<lion-input-amount>', () => {
|
|
|
154
154
|
expect(el.modelValue).to.equal(123456);
|
|
155
155
|
expect(el.formattedValue).to.equal('123.456,00');
|
|
156
156
|
|
|
157
|
+
// When editing an already existing value, we interpet the separators as they are.
|
|
158
|
+
// However, only when the decimal places are 3 or more.
|
|
159
|
+
mimicUserInput(el, '123.456.00');
|
|
160
|
+
expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
|
161
|
+
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
|
162
|
+
expect(el.modelValue).to.equal(123456);
|
|
163
|
+
expect(el.formattedValue).to.equal('123.456,00');
|
|
164
|
+
|
|
157
165
|
// Formatting should only affect values that should be formatted / parsed as a consequence of user input.
|
|
158
166
|
// When a user finished editing, the default should be restored.
|
|
159
167
|
// (think of a programmatically set modelValue, that should behave idempotent, regardless of when it is set)
|
|
@@ -162,6 +170,45 @@ describe('<lion-input-amount>', () => {
|
|
|
162
170
|
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('auto');
|
|
163
171
|
});
|
|
164
172
|
|
|
173
|
+
it('formats with heuristic when formatOptions.mode is "user-edited" and value has two decimal places', async () => {
|
|
174
|
+
const el = /** @type {LionInputAmount} */ (
|
|
175
|
+
await fixture(
|
|
176
|
+
html`<lion-input-amount
|
|
177
|
+
.modelValue=${64}
|
|
178
|
+
currency="EUR"
|
|
179
|
+
.formatOptions="${{ locale: 'en-GB' }}"
|
|
180
|
+
></lion-input-amount>`,
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
const parserSpy = sinon.spy(el, 'parser');
|
|
184
|
+
const formatterSpy = sinon.spy(el, 'formatter');
|
|
185
|
+
|
|
186
|
+
expect(el.formattedValue).to.equal('64.00');
|
|
187
|
+
// @ts-expect-error [allow-protected] in test
|
|
188
|
+
expect(el._inputNode.value).to.equal('64.00');
|
|
189
|
+
|
|
190
|
+
// When editing an already existing value, we interpret the separators based on decimal places when there's 1 or
|
|
191
|
+
// less separator in total (otherwise we would accidentally multiply or divide by 1000)
|
|
192
|
+
mimicUserInput(el, '64,00');
|
|
193
|
+
expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
|
194
|
+
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
|
195
|
+
expect(el.modelValue).to.equal(64);
|
|
196
|
+
expect(el.formattedValue).to.equal('64.00');
|
|
197
|
+
|
|
198
|
+
mimicUserInput(el, '64,0');
|
|
199
|
+
expect(parserSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
|
200
|
+
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('user-edited');
|
|
201
|
+
expect(el.modelValue).to.equal(64);
|
|
202
|
+
expect(el.formattedValue).to.equal('64.00');
|
|
203
|
+
|
|
204
|
+
// Formatting should only affect values that should be formatted / parsed as a consequence of user input.
|
|
205
|
+
// When a user finished editing, the default should be restored.
|
|
206
|
+
// (think of a programmatically set modelValue, that should behave idempotent, regardless of when it is set)
|
|
207
|
+
el.modelValue = 1234;
|
|
208
|
+
expect(el.formattedValue).to.equal('1,234.00');
|
|
209
|
+
expect(formatterSpy.lastCall.args[1]?.mode).to.equal('auto');
|
|
210
|
+
});
|
|
211
|
+
|
|
165
212
|
it('sets inputmode attribute to decimal', async () => {
|
|
166
213
|
const el = /** @type {LionInputAmount} */ (
|
|
167
214
|
await fixture(`<lion-input-amount></lion-input-amount>`)
|
|
@@ -69,6 +69,6 @@ describe('parseAmount()', async () => {
|
|
|
69
69
|
expect(parseAmount('123.456,78', { mode: 'auto' })).to.equal(123456.78);
|
|
70
70
|
expect(
|
|
71
71
|
parseAmount('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
|
72
|
-
).to.equal(
|
|
72
|
+
).to.equal(123456.78);
|
|
73
73
|
});
|
|
74
74
|
});
|
|
@@ -20,25 +20,34 @@ import { getDecimalSeparator } from './getDecimalSeparator.js';
|
|
|
20
20
|
* @return {string} unparseable|withLocale|heuristic
|
|
21
21
|
*/
|
|
22
22
|
function getParseMode(value, { mode = 'auto', viewValueStates } = {}) {
|
|
23
|
-
const
|
|
23
|
+
const separatorsRe = /[., ]/g;
|
|
24
|
+
const separators = value.match(separatorsRe) || [];
|
|
25
|
+
// @ts-expect-error [wait-for-platform-types]
|
|
26
|
+
const decimalPlaces = (value.split(separatorsRe) || []).at(-1).length;
|
|
27
|
+
const isCurViewFormattedAndUserIsEditing =
|
|
28
|
+
viewValueStates?.includes('formatted') && mode === 'user-edited';
|
|
24
29
|
|
|
25
30
|
// When a user edits an existing value, we already formatted it with a certain locale.
|
|
26
|
-
// For best UX, we stick with this locale
|
|
27
|
-
|
|
28
|
-
|
|
31
|
+
// For best UX, we stick with this locale. However, we only do this when the user has
|
|
32
|
+
// entered at least 3 decimal places.
|
|
33
|
+
const shouldUseCurLocale = isCurViewFormattedAndUserIsEditing && decimalPlaces > 2;
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
const shouldUseLocale =
|
|
36
|
+
shouldUseCurLocale ||
|
|
37
|
+
!separators.length ||
|
|
38
|
+
(mode === 'auto' && separators.length === 1 && decimalPlaces >= 3);
|
|
39
|
+
if (shouldUseLocale) {
|
|
31
40
|
return 'withLocale';
|
|
32
41
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (separators.length === 1 || separators[0] !== separators[separators.length - 1]) {
|
|
42
|
+
|
|
43
|
+
const shouldUseHeuristic =
|
|
44
|
+
(isCurViewFormattedAndUserIsEditing && decimalPlaces <= 2) ||
|
|
45
|
+
separators.length === 1 ||
|
|
46
|
+
separators[0] !== separators[separators.length - 1];
|
|
47
|
+
if (shouldUseHeuristic) {
|
|
40
48
|
return 'heuristic';
|
|
41
49
|
}
|
|
50
|
+
|
|
42
51
|
return 'unparseable';
|
|
43
52
|
}
|
|
44
53
|
|
|
@@ -82,7 +82,7 @@ describe('parseNumber()', () => {
|
|
|
82
82
|
expect(parseNumber('123.456,78', { mode: 'auto' })).to.equal(123456.78);
|
|
83
83
|
expect(
|
|
84
84
|
parseNumber('123.456,78', { mode: 'user-edited', viewValueStates: ['formatted'] }),
|
|
85
|
-
).to.equal(
|
|
85
|
+
).to.equal(123456.78);
|
|
86
86
|
});
|
|
87
87
|
|
|
88
88
|
it('detects separators unparseable when there are 2 same ones e.g. 1.234.56', () => {
|
|
@@ -67,11 +67,6 @@ export declare class LocalizeMixinHost {
|
|
|
67
67
|
|
|
68
68
|
public localizeNamespacesLoaded: Promise<Object> | undefined;
|
|
69
69
|
|
|
70
|
-
/**
|
|
71
|
-
* Hook into LitElement to only render once all translations are loaded
|
|
72
|
-
*/
|
|
73
|
-
public performUpdate(): Promise<void>;
|
|
74
|
-
|
|
75
70
|
public onLocaleReady(): void;
|
|
76
71
|
public onLocaleChanged(newLocale: string, oldLocale: string): void;
|
|
77
72
|
public onLocaleUpdated(): void;
|
|
@@ -489,9 +489,9 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|
|
489
489
|
* @param {KeyboardEvent} ev
|
|
490
490
|
* @protected
|
|
491
491
|
*/
|
|
492
|
-
// TODO: rename to
|
|
492
|
+
// TODO: rename to #invokerOnKeyUp() (and move event listener to the invoker) in v1
|
|
493
493
|
__onKeyUp(ev) {
|
|
494
|
-
if (this.disabled || this.readOnly) {
|
|
494
|
+
if (this.disabled || this.readOnly || this.singleOption) {
|
|
495
495
|
return;
|
|
496
496
|
}
|
|
497
497
|
|
|
@@ -529,6 +529,22 @@ export class LionSelectRich extends SlotMixin(ScopedElementsMixin(OverlayMixin(L
|
|
|
529
529
|
this.opened = true;
|
|
530
530
|
}
|
|
531
531
|
break;
|
|
532
|
+
case 'ArrowLeft':
|
|
533
|
+
ev.preventDefault();
|
|
534
|
+
if (this.navigateWithinInvoker) {
|
|
535
|
+
this.setCheckedIndex(
|
|
536
|
+
this._getPreviousEnabledOption(/** @type {number} */ (this.checkedIndex)),
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
break;
|
|
540
|
+
case 'ArrowRight':
|
|
541
|
+
ev.preventDefault();
|
|
542
|
+
if (this.navigateWithinInvoker) {
|
|
543
|
+
this.setCheckedIndex(
|
|
544
|
+
this._getNextEnabledOption(/** @type {number} */ (this.checkedIndex)),
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
break;
|
|
532
548
|
default:
|
|
533
549
|
if (!this._noTypeAhead) {
|
|
534
550
|
this._handleTypeAhead(ev, { setAsChecked: true });
|