Compare commits
1 commit
master
...
chore/add-
| Author | SHA1 | Date | |
|---|---|---|---|
| 0cf0ce4fb3 |
36 changed files with 192 additions and 523 deletions
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'@lion/ui': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
[validation-message] remove the letter T from IsDateDisabled in Italian message
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'@lion/ui': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix(LionInputStepper): improve handling of decimal step values and alignment closes #2615
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
---
|
|
||||||
'@lion/ui': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
feat(LionInputStepper): implement self-destructing output content for value display
|
|
||||||
|
|
||||||
1. from <div class="input-stepper__value">${this.__valueText}</div> to <output class="input-stepper__value" for="..">${this.\_\_valueText}</output>
|
|
||||||
2. remove the \_onEnterButton() and \_onLeaveButton() logic.
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
---
|
|
||||||
'@lion/ui': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
[form-core] improve validation feedback message for screen readers to make it one sentence.
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
---
|
|
||||||
'@lion/ui': patch
|
|
||||||
---
|
|
||||||
|
|
||||||
fix(pagination): remove unnecessary ellipsis when count equals visiblePages + 1
|
|
||||||
|
|
||||||
Fixed issue where LionPagination component incorrectly displayed ellipsis when the total page count was exactly one more than the visible pages setting (e.g., showing [1, 2, 3, 4, 5, '...', 6] instead of [1, 2, 3, 4, 5, 6] when visiblePages=5 and count=6).
|
|
||||||
29
README.md
29
README.md
|
|
@ -54,35 +54,6 @@ They provide an unopinionated, white-label layer that can be extended to your ow
|
||||||
<a href="https://lion.js.org/guides/"><strong>Explore the Lion Guides ▶</strong></a>
|
<a href="https://lion.js.org/guides/"><strong>Explore the Lion Guides ▶</strong></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary><h2>Table of Contents</h2></summary>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#astro-migration"> Astro migration </a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#astro-how-to"> Astro how to </a></li>
|
|
||||||
<li><a href="#issues-which-are-not-caused-by-the-migration-not-to-be-fixed-now"> Issues which are not caused by migration </a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#how-to-install"> Installation </a></li>
|
|
||||||
<li><a href="#how-to-use"> How to use </a>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#extend-a-web-component"> Extend a web component </a></li>
|
|
||||||
<li><a href="#use-a-javascript-system"> Use a JavaScript system </a></li>
|
|
||||||
<li><a href="#use-a-web-component"> Use a Web Component </a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a href="#issues"> Issues </a></li>
|
|
||||||
<li><a href="#feature-requests"> Feature request </a></li>
|
|
||||||
<li><a href="#content"> Content </a></li>
|
|
||||||
<li><a href="#technologies"> Technologies </a></li>
|
|
||||||
<li><a href="#rationale"> Rationale </a></li>
|
|
||||||
<li><a href="#coding-guidelines"> Coding guidelines </a></li>
|
|
||||||
<li><a href="#how-to-contribute"> How to contribute </a></li>
|
|
||||||
<li><a href="#site-deployment"> Site deployment </a></li>
|
|
||||||
<li><a href="#content"> Contact </a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Astro migration
|
## Astro migration
|
||||||
|
|
||||||
- Keep using `/docs` on the root level as we used it in the `master` branch. The documentation is copied into Astro related directories on `npm run start` and when when anything in `/docs` is updated.
|
- Keep using `/docs` on the root level as we used it in the `master` branch. The documentation is copied into Astro related directories on `npm run start` and when when anything in `/docs` is updated.
|
||||||
|
|
|
||||||
|
|
@ -130,13 +130,3 @@ rocket-navigation ul > li:nth-child(n + 6).active > ul {
|
||||||
font-size: inherit;
|
font-size: inherit;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.announcement-bar {
|
|
||||||
background: var(--primary-color);
|
|
||||||
color: white;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
& a {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
<div class="announcement-bar">
|
|
||||||
<p>A new portal is coming! Check out <a href="/about/astro">the technical release of our Astro portal</a></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<header id="main-header">
|
|
||||||
<div class="content-area">
|
|
||||||
{% for blockName, blockPath in _joiningBlocks.header %}
|
|
||||||
{% include blockPath %}
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
---
|
|
||||||
title: Astro portal
|
|
||||||
description: Technically live with Astro (while still fully compatible with Rocket content!)
|
|
||||||
---
|
|
||||||
|
|
||||||
# Astro Portal Announcement
|
|
||||||
|
|
||||||
We are excited to introduce the new version of our portal, now built with the Astro framework! You can access it at <a href="https://lion.js.org/next" rel="noopener noreferrer">/next</a>.
|
|
||||||
|
|
||||||
## What’s New?
|
|
||||||
|
|
||||||
- **Modern Look & Feel:** The UI will be redesigned in close collaboration with our designers. Expect further improvements as the design team continues to enhance the user experience.
|
|
||||||
- **Search Functionality:** Quickly find components and documentation with the new search feature.
|
|
||||||
- **Upgraded Dependencies:** All major dependencies and approaches have been updated for better performance, security, and maintainability.
|
|
||||||
|
|
||||||
## Compatibility
|
|
||||||
|
|
||||||
The new Astro portal is fully compatible with the previous Rocket-based portal. Maintaining the same structure and functionality was a key challenge and priority, ensuring a seamless experience for all users. Both portals will run in parallel in the near term future. During the course of 2026 the Astro portal will become the default.
|
|
||||||
|
|
||||||
## For Developers
|
|
||||||
|
|
||||||
The technical release of the Astro portal is primarily aimed at developers. We encourage you to explore the new features and provide feedback.
|
|
||||||
|
|
||||||
**Found an issue or have a suggestion?**
|
|
||||||
Please open an issue on [GitHub](https://github.com/ing-bank/lion/issues) and use the tag `#astro`.
|
|
||||||
|
|
||||||
Stay tuned for more updates as we continue to improve the portal!
|
|
||||||
|
|
@ -81,13 +81,19 @@ You can use `slot="label"` instead of the `label` attribute for defining more co
|
||||||
```html preview-story
|
```html preview-story
|
||||||
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
<lion-checkbox-group name="scientists[]" label="Favorite scientists">
|
||||||
<lion-checkbox .choiceValue="${'Archimedes'}">
|
<lion-checkbox .choiceValue="${'Archimedes'}">
|
||||||
<label slot="label"><strong>Archimedes</strong></label>
|
<label slot="label"
|
||||||
|
><a href="https://wikipedia.org/wiki/Archimedes" target="_blank">Archimedes</a></label
|
||||||
|
>
|
||||||
</lion-checkbox>
|
</lion-checkbox>
|
||||||
<lion-checkbox .choiceValue="${'Francis Bacon'}">
|
<lion-checkbox .choiceValue="${'Francis Bacon'}">
|
||||||
<label slot="label"><strong>Francis Bacon</strong></label>
|
<label slot="label"
|
||||||
|
><a href="https://wikipedia.org/wiki/Francis_Bacon" target="_blank">Francis Bacon</a></label
|
||||||
|
>
|
||||||
</lion-checkbox>
|
</lion-checkbox>
|
||||||
<lion-checkbox .choiceValue="${'Marie Curie'}">
|
<lion-checkbox .choiceValue="${'Marie Curie'}">
|
||||||
<label slot="label"><strong>Marie Curie</strong></label>
|
<label slot="label"
|
||||||
|
><a href="https://wikipedia.org/wiki/Marie_Curie" target="_blank">Marie Curie</a></label
|
||||||
|
>
|
||||||
</lion-checkbox>
|
</lion-checkbox>
|
||||||
</lion-checkbox-group>
|
</lion-checkbox-group>
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -86,13 +86,19 @@ You can use `slot="label"` instead of the `label` attribute for defining more co
|
||||||
export const label = () => html`
|
export const label = () => html`
|
||||||
<lion-radio-group name="dinos_7" label="Favourite dinosaur">
|
<lion-radio-group name="dinos_7" label="Favourite dinosaur">
|
||||||
<lion-radio .choiceValue="${'allosaurus'}">
|
<lion-radio .choiceValue="${'allosaurus'}">
|
||||||
<label slot="label"><strong>allosaurus</strong></label>
|
<label slot="label"
|
||||||
|
><a href="https://wikipedia.org/wiki/allosaurus" target="_blank">allosaurus</a></label
|
||||||
|
>
|
||||||
</lion-radio>
|
</lion-radio>
|
||||||
<lion-radio .choiceValue="${'brontosaurus'}">
|
<lion-radio .choiceValue="${'brontosaurus'}">
|
||||||
<label slot="label"><strong>brontosaurus</strong></label>
|
<label slot="label"
|
||||||
|
><a href="https://wikipedia.org/wiki/brontosaurus" target="_blank">brontosaurus</a></label
|
||||||
|
>
|
||||||
</lion-radio>
|
</lion-radio>
|
||||||
<lion-radio .choiceValue="${'diplodocus'}">
|
<lion-radio .choiceValue="${'diplodocus'}">
|
||||||
<label slot="label"><strong>diplodocus</strong></label>
|
<label slot="label"
|
||||||
|
><a href="https://wikipedia.org/wiki/diplodocus" target="_blank">diplodocus</a></label
|
||||||
|
>
|
||||||
</lion-radio>
|
</lion-radio>
|
||||||
</lion-radio-group>
|
</lion-radio-group>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import '@lion/ui/define/lion-select.js';
|
||||||
export const main = () => html`
|
export const main = () => html`
|
||||||
<lion-select name="favoriteColor" label="Favorite color">
|
<lion-select name="favoriteColor" label="Favorite color">
|
||||||
<select slot="input">
|
<select slot="input">
|
||||||
<option selected value>Please select</option>
|
<option selected hidden value>Please select</option>
|
||||||
<option value="red">Red</option>
|
<option value="red">Red</option>
|
||||||
<option value="hotpink">Hotpink</option>
|
<option value="hotpink">Hotpink</option>
|
||||||
<option value="teal">Teal</option>
|
<option value="teal">Teal</option>
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ You can preselect an option by setting the property modelValue.
|
||||||
```html preview-story
|
```html preview-story
|
||||||
<lion-select name="favoriteColor" label="Favorite color" .modelValue="${'hotpink'}">
|
<lion-select name="favoriteColor" label="Favorite color" .modelValue="${'hotpink'}">
|
||||||
<select slot="input">
|
<select slot="input">
|
||||||
<option selected value>Please select</option>
|
<option selected hidden value>Please select</option>
|
||||||
<option value="red">Red</option>
|
<option value="red">Red</option>
|
||||||
<option value="hotpink">Hotpink</option>
|
<option value="hotpink">Hotpink</option>
|
||||||
<option value="teal">Teal</option>
|
<option value="teal">Teal</option>
|
||||||
|
|
@ -42,7 +42,7 @@ You can disable an option by adding the `disabled` attribute to an option.
|
||||||
```html preview-story
|
```html preview-story
|
||||||
<lion-select name="favoriteColor" label="Favorite color">
|
<lion-select name="favoriteColor" label="Favorite color">
|
||||||
<select slot="input">
|
<select slot="input">
|
||||||
<option selected value>Please select</option>
|
<option selected hidden value>Please select</option>
|
||||||
<option value="red">Red</option>
|
<option value="red">Red</option>
|
||||||
<option value="hotpink" disabled>Hotpink</option>
|
<option value="hotpink" disabled>Hotpink</option>
|
||||||
<option value="teal">Teal</option>
|
<option value="teal">Teal</option>
|
||||||
|
|
@ -55,7 +55,7 @@ Or by setting the `disabled` attribute on the entire `lion-select` field.
|
||||||
```html preview-story
|
```html preview-story
|
||||||
<lion-select name="favoriteColor" label="Favorite color" disabled>
|
<lion-select name="favoriteColor" label="Favorite color" disabled>
|
||||||
<select slot="input">
|
<select slot="input">
|
||||||
<option selected value>Please select</option>
|
<option selected hidden value>Please select</option>
|
||||||
<option value="red">Red</option>
|
<option value="red">Red</option>
|
||||||
<option value="hotpink">Hotpink</option>
|
<option value="hotpink">Hotpink</option>
|
||||||
<option value="teal">Teal</option>
|
<option value="teal">Teal</option>
|
||||||
|
|
@ -89,7 +89,7 @@ class MyOptions extends LitElement {
|
||||||
return html`
|
return html`
|
||||||
<lion-select name="favoriteColor" label="Favorite color" disabled>
|
<lion-select name="favoriteColor" label="Favorite color" disabled>
|
||||||
<select slot="input">
|
<select slot="input">
|
||||||
<option selected value>Please select</option>
|
<option selected hidden value>Please select</option>
|
||||||
${this.colours.map(colour => html`<option value="${colour}">${colour}</option>`)}
|
${this.colours.map(colour => html`<option value="${colour}">${colour}</option>`)}
|
||||||
</select>
|
</select>
|
||||||
</lion-select>
|
</lion-select>
|
||||||
|
|
@ -115,7 +115,7 @@ render() {
|
||||||
return html`
|
return html`
|
||||||
<lion-select name="favoriteColor" label="Favorite color" disabled>
|
<lion-select name="favoriteColor" label="Favorite color" disabled>
|
||||||
<select slot="input">
|
<select slot="input">
|
||||||
<option selected value>Please select</option>
|
<option selected hidden value>Please select</option>
|
||||||
${repeat(
|
${repeat(
|
||||||
this.colours,
|
this.colours,
|
||||||
(colour) => colour,
|
(colour) => colour,
|
||||||
|
|
|
||||||
|
|
@ -87,11 +87,11 @@ export class LionValidationFeedback extends LocalizeMixin(LitElement) {
|
||||||
${this.feedbackData &&
|
${this.feedbackData &&
|
||||||
this.feedbackData.map(
|
this.feedbackData.map(
|
||||||
({ message, type, validator }) => html`
|
({ message, type, validator }) => html`
|
||||||
<span class="validation-feedback__type">
|
<div class="validation-feedback__type">
|
||||||
${message && type
|
${message && type
|
||||||
? this._localizeManager.msg(`lion-form-core:validation${capitalize(type)}`)
|
? this._localizeManager.msg(`lion-form-core:validation${capitalize(type)}`)
|
||||||
: nothing}
|
: nothing}
|
||||||
</span>
|
</div>
|
||||||
${this._messageTemplate({ message, type, validator })}
|
${this._messageTemplate({ message, type, validator })}
|
||||||
`,
|
`,
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,12 @@ describe('lion-validation-feedback', () => {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
const validationFeedbackType = el.shadowRoot?.querySelector('.validation-feedback__type');
|
const validationFeedbackType = el.shadowRoot?.querySelector('.validation-feedback__type');
|
||||||
expect(validationFeedbackType?.textContent?.trim()).to.equal('Error,');
|
expect(validationFeedbackType?.textContent?.trim()).to.equal('Error');
|
||||||
|
|
||||||
el.feedbackData = [{ message: 'hello', type: 'info', validator: new AlwaysInvalid() }];
|
el.feedbackData = [{ message: 'hello', type: 'info', validator: new AlwaysInvalid() }];
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
expect(validationFeedbackType?.textContent?.trim()).to.equal('Info,');
|
expect(validationFeedbackType?.textContent?.trim()).to.equal('Info');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('it does not share the type if there is no message', async () => {
|
it('it does not share the type if there is no message', async () => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Грешка,',
|
validationError: 'Грешка',
|
||||||
validationWarning: 'Предупреждение,',
|
validationWarning: 'Предупреждение',
|
||||||
validationSuccess: 'Успех,',
|
validationSuccess: 'Успех',
|
||||||
validationInfo: 'Информация,',
|
validationInfo: 'Информация',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Chyba,',
|
validationError: 'Chyba',
|
||||||
validationWarning: 'Varování,',
|
validationWarning: 'Varování',
|
||||||
validationSuccess: 'Úspěch,',
|
validationSuccess: 'Úspěch',
|
||||||
validationInfo: 'Informace,',
|
validationInfo: 'Informace',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Fehler,',
|
validationError: 'Fehler',
|
||||||
validationWarning: 'Warnhinweis,',
|
validationWarning: 'Warnhinweis',
|
||||||
validationSuccess: 'Erfolgreich,',
|
validationSuccess: 'Erfolgreich',
|
||||||
validationInfo: 'Info,',
|
validationInfo: 'Info',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Error,',
|
validationError: 'Error',
|
||||||
validationWarning: 'Warning,',
|
validationWarning: 'Warning',
|
||||||
validationSuccess: 'Success,',
|
validationSuccess: 'Success',
|
||||||
validationInfo: 'Info,',
|
validationInfo: 'Info',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Error,',
|
validationError: 'Error',
|
||||||
validationWarning: 'Advertencia,',
|
validationWarning: 'Advertencia',
|
||||||
validationSuccess: 'Satisfactorio,',
|
validationSuccess: 'Satisfactorio',
|
||||||
validationInfo: 'Información,',
|
validationInfo: 'Información',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Erreur,',
|
validationError: 'Erreur',
|
||||||
validationWarning: 'Avertissement,',
|
validationWarning: 'Avertissement',
|
||||||
validationSuccess: 'Succès,',
|
validationSuccess: 'Succès',
|
||||||
validationInfo: 'Info,',
|
validationInfo: 'Info',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Hiba,',
|
validationError: 'Hiba',
|
||||||
validationWarning: 'Figyelmeztetés,',
|
validationWarning: 'Figyelmeztetés',
|
||||||
validationSuccess: 'Sikeres,',
|
validationSuccess: 'Sikeres',
|
||||||
validationInfo: 'Információ,',
|
validationInfo: 'Információ',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Errore,',
|
validationError: 'Errore',
|
||||||
validationWarning: 'Avvertenza,',
|
validationWarning: 'Avvertenza',
|
||||||
validationSuccess: 'Operazione riuscita,',
|
validationSuccess: 'Operazione riuscita',
|
||||||
validationInfo: 'Info,',
|
validationInfo: 'Info',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Fout,',
|
validationError: 'Fout',
|
||||||
validationWarning: 'Waarschuwing,',
|
validationWarning: 'Waarschuwing',
|
||||||
validationSuccess: 'Succes,',
|
validationSuccess: 'Succes',
|
||||||
validationInfo: 'Informatie,',
|
validationInfo: 'Informatie',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Błąd,',
|
validationError: 'Błąd',
|
||||||
validationWarning: 'Ostrzeżenie,',
|
validationWarning: 'Ostrzeżenie',
|
||||||
validationSuccess: 'Zrealizowano pomyślnie,',
|
validationSuccess: 'Zrealizowano pomyślnie',
|
||||||
validationInfo: 'Informacja,',
|
validationInfo: 'Informacja',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Eroare,',
|
validationError: 'Eroare',
|
||||||
validationWarning: 'Atenție,',
|
validationWarning: 'Atenție',
|
||||||
validationSuccess: 'Succes,',
|
validationSuccess: 'Succes',
|
||||||
validationInfo: 'Informații,',
|
validationInfo: 'Informații',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Ошибка,',
|
validationError: 'Ошибка',
|
||||||
validationWarning: 'Предупреждение,',
|
validationWarning: 'Предупреждение',
|
||||||
validationSuccess: 'Успешно,',
|
validationSuccess: 'Успешно',
|
||||||
validationInfo: 'Информация,',
|
validationInfo: 'Информация',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Chyba,',
|
validationError: 'Chyba',
|
||||||
validationWarning: 'Varovanie,',
|
validationWarning: 'Varovanie',
|
||||||
validationSuccess: 'Úspešné,',
|
validationSuccess: 'Úspešné',
|
||||||
validationInfo: 'Info,',
|
validationInfo: 'Info',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: 'Помилка,',
|
validationError: 'Помилка',
|
||||||
validationWarning: 'Попередження,',
|
validationWarning: 'Попередження',
|
||||||
validationSuccess: 'Успішно,',
|
validationSuccess: 'Успішно',
|
||||||
validationInfo: 'Інформація,',
|
validationInfo: 'Інформація',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
export default {
|
export default {
|
||||||
validationError: '错误,',
|
validationError: '错误',
|
||||||
validationWarning: '警告,',
|
validationWarning: '警告',
|
||||||
validationSuccess: '成功,',
|
validationSuccess: '成功',
|
||||||
validationInfo: '信息,',
|
validationInfo: '信息',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { html, css, render, nothing } from 'lit';
|
import { html, css, render } from 'lit';
|
||||||
import { formatNumber, LocalizeMixin, parseNumber } from '@lion/ui/localize-no-side-effects.js';
|
import { formatNumber, LocalizeMixin, parseNumber } from '@lion/ui/localize-no-side-effects.js';
|
||||||
import { LionInput } from '@lion/ui/input.js';
|
import { LionInput } from '@lion/ui/input.js';
|
||||||
import { IsNumber, MinNumber, MaxNumber } from '@lion/ui/form-core.js';
|
import { IsNumber, MinNumber, MaxNumber } from '@lion/ui/form-core.js';
|
||||||
|
|
@ -96,6 +96,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
|
|
||||||
this._increment = this._increment.bind(this);
|
this._increment = this._increment.bind(this);
|
||||||
this._decrement = this._decrement.bind(this);
|
this._decrement = this._decrement.bind(this);
|
||||||
|
this._onEnterButton = this._onEnterButton.bind(this);
|
||||||
|
this._onLeaveButton = this._onLeaveButton.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
|
|
@ -242,26 +244,6 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
this._inputNode.removeAttribute('aria-valuenow');
|
this._inputNode.removeAttribute('aria-valuenow');
|
||||||
this._inputNode.removeAttribute('aria-valuetext');
|
this._inputNode.removeAttribute('aria-valuetext');
|
||||||
}
|
}
|
||||||
this._destroyOutputContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
_destroyOutputContent() {
|
|
||||||
const outputElement = /** @type {HTMLElement} */ (
|
|
||||||
this.shadowRoot?.querySelector('.input-stepper__value')
|
|
||||||
);
|
|
||||||
|
|
||||||
const timeoutValue = outputElement?.dataset?.selfDestruct
|
|
||||||
? Number(outputElement.dataset.selfDestruct)
|
|
||||||
: 2000;
|
|
||||||
clearTimeout(this.timer);
|
|
||||||
if (outputElement) {
|
|
||||||
this.timer = setTimeout(() => {
|
|
||||||
if (outputElement.parentNode) {
|
|
||||||
this.__valueText = nothing;
|
|
||||||
this.requestUpdate();
|
|
||||||
}
|
|
||||||
}, timeoutValue);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -285,19 +267,12 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
_increment() {
|
_increment() {
|
||||||
const { step, min, max } = this.values;
|
const { step, min, max } = this.values;
|
||||||
const stepMin = min !== Infinity ? min : 0;
|
const stepMin = min !== Infinity ? min : 0;
|
||||||
const epsilon = 1e-10; // Tolerance for floating-point comparison
|
|
||||||
|
|
||||||
let newValue;
|
let newValue = this.currentValue + step;
|
||||||
|
|
||||||
const remainder = (this.currentValue - stepMin) % step;
|
if ((this.currentValue + stepMin) % step !== 0) {
|
||||||
const isAligned = Math.abs(remainder) < epsilon || Math.abs(remainder - step) < epsilon;
|
// If the value is not aligned to step, align it to the nearest step
|
||||||
|
newValue = Math.floor(this.currentValue / step) * step + step + (stepMin % step);
|
||||||
if (!isAligned) {
|
|
||||||
// If the value is not aligned to step, align it to the next valid step
|
|
||||||
newValue = Math.ceil((this.currentValue - stepMin) / step) * step + stepMin;
|
|
||||||
} else {
|
|
||||||
// If the value is aligned, just add the step
|
|
||||||
newValue = this.currentValue + step;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue <= max || max === Infinity) {
|
if (newValue <= max || max === Infinity) {
|
||||||
|
|
@ -314,19 +289,12 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
_decrement() {
|
_decrement() {
|
||||||
const { step, max, min } = this.values;
|
const { step, max, min } = this.values;
|
||||||
const stepMin = min !== Infinity ? min : 0;
|
const stepMin = min !== Infinity ? min : 0;
|
||||||
const epsilon = 1e-10; // Tolerance for floating-point comparison
|
|
||||||
|
|
||||||
let newValue;
|
let newValue = this.currentValue - step;
|
||||||
|
|
||||||
const remainder = (this.currentValue - stepMin) % step;
|
if ((this.currentValue + stepMin) % step !== 0) {
|
||||||
const isAligned = Math.abs(remainder) < epsilon || Math.abs(remainder - step) < epsilon;
|
// If the value is not aligned to step, align it to the nearest step
|
||||||
|
newValue = Math.floor(this.currentValue / step) * step + (stepMin % step);
|
||||||
if (!isAligned) {
|
|
||||||
// If the value is not aligned to step, align it to the previous valid step
|
|
||||||
newValue = Math.floor((this.currentValue - stepMin) / step) * step + stepMin;
|
|
||||||
} else {
|
|
||||||
// If the value is aligned, just subtract the step
|
|
||||||
newValue = this.currentValue - step;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newValue >= min || min === Infinity) {
|
if (newValue >= min || min === Infinity) {
|
||||||
|
|
@ -412,6 +380,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
<button
|
<button
|
||||||
?disabled=${this.disabled || this.readOnly}
|
?disabled=${this.disabled || this.readOnly}
|
||||||
@click=${this._decrement}
|
@click=${this._decrement}
|
||||||
|
@focus=${this._onEnterButton}
|
||||||
|
@blur=${this._onLeaveButton}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="${this.msgLit('lion-input-stepper:decrease')} ${this.fieldName}"
|
aria-label="${this.msgLit('lion-input-stepper:decrease')} ${this.fieldName}"
|
||||||
>
|
>
|
||||||
|
|
@ -430,6 +400,8 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
<button
|
<button
|
||||||
?disabled=${this.disabled || this.readOnly}
|
?disabled=${this.disabled || this.readOnly}
|
||||||
@click=${this._increment}
|
@click=${this._increment}
|
||||||
|
@focus=${this._onEnterButton}
|
||||||
|
@blur=${this._onLeaveButton}
|
||||||
type="button"
|
type="button"
|
||||||
aria-label="${this.msgLit('lion-input-stepper:increase')} ${this.fieldName}"
|
aria-label="${this.msgLit('lion-input-stepper:increase')} ${this.fieldName}"
|
||||||
>
|
>
|
||||||
|
|
@ -441,9 +413,7 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
/** @protected */
|
/** @protected */
|
||||||
_inputGroupTemplate() {
|
_inputGroupTemplate() {
|
||||||
return html`
|
return html`
|
||||||
<output for="${this._inputId}" data-self-destruct="2000" class="input-stepper__value"
|
<div class="input-stepper__value">${this.__valueText}</div>
|
||||||
>${this.__valueText}</output
|
|
||||||
>
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
${this._inputGroupBeforeTemplate()}
|
${this._inputGroupBeforeTemplate()}
|
||||||
<div class="input-group__container">
|
<div class="input-group__container">
|
||||||
|
|
@ -454,4 +424,38 @@ export class LionInputStepper extends LocalizeMixin(LionInput) {
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @param {Event} ev
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
_onEnterButton(ev) {
|
||||||
|
const valueNode = /** @type {HTMLElement} */ (
|
||||||
|
this.shadowRoot?.querySelector('.input-stepper__value')
|
||||||
|
);
|
||||||
|
valueNode.setAttribute('aria-live', 'assertive');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Redispatch leave event on host when catching leave event
|
||||||
|
* on the incrementor and decrementor button.
|
||||||
|
*
|
||||||
|
* This redispatched leave event will be caught by
|
||||||
|
* InteractionStateMixin to set "touched" state to true.
|
||||||
|
*
|
||||||
|
* Interacting with the buttons is "user interactions"
|
||||||
|
* the same way as focusing + blurring the field (native input)
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @param {Event} ev
|
||||||
|
*/
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
_onLeaveButton(ev) {
|
||||||
|
const valueNode = /** @type {HTMLElement} */ (
|
||||||
|
this.shadowRoot?.querySelector('.input-stepper__value')
|
||||||
|
);
|
||||||
|
valueNode.removeAttribute('aria-live');
|
||||||
|
this.dispatchEvent(new Event(this._leaveEvent));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { expect, fixture as _fixture, nextFrame } from '@open-wc/testing';
|
import { expect, fixture as _fixture, nextFrame } from '@open-wc/testing';
|
||||||
import { nothing } from 'lit';
|
|
||||||
import { html } from 'lit/static-html.js';
|
import { html } from 'lit/static-html.js';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
import { formatNumber } from '@lion/ui/localize-no-side-effects.js';
|
import { formatNumber } from '@lion/ui/localize-no-side-effects.js';
|
||||||
|
|
@ -179,6 +178,31 @@ describe('<lion-input-stepper>', () => {
|
||||||
expect(counter).to.equal(1);
|
expect(counter).to.equal(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fires a leave event ("blur") on button clicks', async () => {
|
||||||
|
const blurSpy = sinon.spy();
|
||||||
|
const el = await fixture(html`
|
||||||
|
<lion-input-stepper @blur=${blurSpy} name="year" label="Years"></lion-input-stepper>
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(el.value).to.equal('');
|
||||||
|
const decrementButton = el.querySelector('[slot=prefix]');
|
||||||
|
decrementButton?.dispatchEvent(new Event('focus'));
|
||||||
|
decrementButton?.dispatchEvent(new Event('click'));
|
||||||
|
decrementButton?.dispatchEvent(new Event('blur'));
|
||||||
|
expect(el.value).to.equal('−1');
|
||||||
|
expect(blurSpy.calledOnce).to.be.true;
|
||||||
|
expect(el.touched).to.be.true;
|
||||||
|
|
||||||
|
el.touched = false;
|
||||||
|
const incrementButton = el.querySelector('[slot=suffix]');
|
||||||
|
incrementButton?.dispatchEvent(new Event('focus'));
|
||||||
|
incrementButton?.dispatchEvent(new Event('click'));
|
||||||
|
incrementButton?.dispatchEvent(new Event('blur'));
|
||||||
|
expect(el.value).to.equal('0');
|
||||||
|
expect(blurSpy.calledTwice).to.be.true;
|
||||||
|
expect(el.touched).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
it('should update min and max attributes when min and max property change', async () => {
|
it('should update min and max attributes when min and max property change', async () => {
|
||||||
const el = await fixture(inputStepperWithAttrs);
|
const el = await fixture(inputStepperWithAttrs);
|
||||||
el.min = 100;
|
el.min = 100;
|
||||||
|
|
@ -335,251 +359,6 @@ describe('<lion-input-stepper>', () => {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
expect(el.modelValue).to.equal(-13, 'Fail - : (-23 > 100 by 10; val 55)'); // -23 > -13 > -3 > 7
|
expect(el.modelValue).to.equal(-13, 'Fail - : (-23 > 100 by 10; val 55)'); // -23 > -13 > -3 > 7
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles decimal step values correctly', async () => {
|
|
||||||
// Test with decimal step 0.1
|
|
||||||
let el = await fixture(
|
|
||||||
html`<lion-input-stepper step="0.1" min="0" max="9" value="5.55"></lion-input-stepper>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Test increment with decimal step
|
|
||||||
let incrementButton = el.querySelector('[slot=suffix]');
|
|
||||||
incrementButton?.dispatchEvent(new Event('click'));
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(el.modelValue).to.equal(5.6, 'Fail + : (0 > 9 by 0.1; val 5.55)');
|
|
||||||
|
|
||||||
// Test decrement with decimal step
|
|
||||||
let decrementButton = el.querySelector('[slot=prefix]');
|
|
||||||
decrementButton?.dispatchEvent(new Event('click'));
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(el.modelValue).to.equal(5.5, 'Fail - : (0 > 9 by 0.1; val 5.6)');
|
|
||||||
|
|
||||||
// Test with value that needs alignment
|
|
||||||
el = await fixture(
|
|
||||||
html`<lion-input-stepper step="0.1" min="0" max="9" value="3.27"></lion-input-stepper>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Should align to next step when incrementing
|
|
||||||
incrementButton = el.querySelector('[slot=suffix]');
|
|
||||||
incrementButton?.dispatchEvent(new Event('click'));
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(el.modelValue).to.equal(3.3, 'Fail + alignment: (0 > 9 by 0.1; val 3.27)');
|
|
||||||
|
|
||||||
// Reset and test decrement alignment
|
|
||||||
el = await fixture(
|
|
||||||
html`<lion-input-stepper step="0.1" min="0" max="9" value="3.27"></lion-input-stepper>`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Should align to previous step when decrementing
|
|
||||||
decrementButton = el.querySelector('[slot=prefix]');
|
|
||||||
decrementButton?.dispatchEvent(new Event('click'));
|
|
||||||
await el.updateComplete;
|
|
||||||
expect(el.modelValue).to.equal(3.2, 'Fail - alignment: (0 > 9 by 0.1; val 3.27)');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('_destroyOutputContent method', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
// Ensure clean state for each test
|
|
||||||
sinon.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should clear existing timer before setting a new one', async () => {
|
|
||||||
const el = await fixture(defaultInputStepper);
|
|
||||||
const clearTimeoutSpy = sinon.spy(window, 'clearTimeout');
|
|
||||||
|
|
||||||
// Set an initial timer
|
|
||||||
el.timer = setTimeout(() => {}, 1000);
|
|
||||||
const initialTimer = el.timer;
|
|
||||||
|
|
||||||
// Call _destroyOutputContent
|
|
||||||
el._destroyOutputContent();
|
|
||||||
|
|
||||||
expect(clearTimeoutSpy.calledWith(initialTimer)).to.be.true;
|
|
||||||
clearTimeoutSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should set __valueText to nothing and request update after timeout', async () => {
|
|
||||||
const el = await fixture(defaultInputStepper);
|
|
||||||
const requestUpdateSpy = sinon.spy(el, 'requestUpdate');
|
|
||||||
const clock = sinon.useFakeTimers();
|
|
||||||
|
|
||||||
// Set initial value text
|
|
||||||
el.__valueText = 'test value';
|
|
||||||
|
|
||||||
// Call _destroyOutputContent
|
|
||||||
el._destroyOutputContent();
|
|
||||||
|
|
||||||
// Fast forward time by 2000ms (default timeout)
|
|
||||||
clock.tick(2000);
|
|
||||||
|
|
||||||
await expect(el.__valueText).to.equal(nothing);
|
|
||||||
expect(requestUpdateSpy.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
clock.restore();
|
|
||||||
requestUpdateSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should use custom timeout from data-self-destruct attribute', async () => {
|
|
||||||
const el = await fixture(html`
|
|
||||||
<lion-input-stepper name="test" label="Test"> </lion-input-stepper>
|
|
||||||
`);
|
|
||||||
|
|
||||||
await el.updateComplete;
|
|
||||||
|
|
||||||
// Get the output element and set custom self-destruct value
|
|
||||||
const outputElement = /** @type {HTMLElement} */ (
|
|
||||||
el.shadowRoot?.querySelector('.input-stepper__value')
|
|
||||||
);
|
|
||||||
if (outputElement) {
|
|
||||||
outputElement.dataset.selfDestruct = '5000';
|
|
||||||
}
|
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers();
|
|
||||||
const requestUpdateSpy = sinon.spy(el, 'requestUpdate');
|
|
||||||
|
|
||||||
el.__valueText = 'test value';
|
|
||||||
el._destroyOutputContent();
|
|
||||||
|
|
||||||
// Should not trigger after default 2000ms
|
|
||||||
clock.tick(2000);
|
|
||||||
await expect(el.__valueText).to.equal('test value');
|
|
||||||
expect(requestUpdateSpy.called).to.be.false;
|
|
||||||
|
|
||||||
// Should trigger after custom 5000ms
|
|
||||||
clock.tick(3000); // Total 5000ms
|
|
||||||
await expect(el.__valueText).to.equal(nothing);
|
|
||||||
expect(requestUpdateSpy.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
clock.restore();
|
|
||||||
requestUpdateSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle invalid data-self-destruct value by using default timeout', async () => {
|
|
||||||
const el = await fixture(html`
|
|
||||||
<lion-input-stepper name="test" label="Test"> </lion-input-stepper>
|
|
||||||
`);
|
|
||||||
|
|
||||||
await el.updateComplete;
|
|
||||||
|
|
||||||
// Get the output element and set invalid self-destruct value
|
|
||||||
const outputElement = /** @type {HTMLElement} */ (
|
|
||||||
el.shadowRoot?.querySelector('.input-stepper__value')
|
|
||||||
);
|
|
||||||
if (outputElement) {
|
|
||||||
outputElement.dataset.selfDestruct = 'invalid';
|
|
||||||
}
|
|
||||||
|
|
||||||
const clock = sinon.useFakeTimers();
|
|
||||||
const requestUpdateSpy = sinon.spy(el, 'requestUpdate');
|
|
||||||
|
|
||||||
el.__valueText = 'test value';
|
|
||||||
el._destroyOutputContent();
|
|
||||||
|
|
||||||
// Should use default 2000ms when invalid value is provided
|
|
||||||
clock.tick(2000);
|
|
||||||
await expect(el.__valueText).to.equal(nothing);
|
|
||||||
expect(requestUpdateSpy.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
clock.restore();
|
|
||||||
requestUpdateSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not set timeout if output element does not exist', async () => {
|
|
||||||
const el = await fixture(defaultInputStepper);
|
|
||||||
const setTimeoutSpy = sinon.spy(window, 'setTimeout');
|
|
||||||
|
|
||||||
// Mock shadowRoot to return null for querySelector
|
|
||||||
const originalQuerySelector = el.shadowRoot?.querySelector;
|
|
||||||
if (el.shadowRoot) {
|
|
||||||
el.shadowRoot.querySelector = () => null;
|
|
||||||
}
|
|
||||||
|
|
||||||
el._destroyOutputContent();
|
|
||||||
|
|
||||||
expect(setTimeoutSpy.called).to.be.false;
|
|
||||||
|
|
||||||
// Restore original querySelector
|
|
||||||
if (el.shadowRoot && originalQuerySelector) {
|
|
||||||
el.shadowRoot.querySelector = originalQuerySelector;
|
|
||||||
}
|
|
||||||
setTimeoutSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should only execute timeout callback if output element still has parent', async () => {
|
|
||||||
const el = await fixture(defaultInputStepper);
|
|
||||||
const clock = sinon.useFakeTimers();
|
|
||||||
const requestUpdateSpy = sinon.spy(el, 'requestUpdate');
|
|
||||||
|
|
||||||
el.__valueText = 'test value';
|
|
||||||
el._destroyOutputContent();
|
|
||||||
|
|
||||||
// Remove the output element from DOM before timeout
|
|
||||||
const outputElement = el.shadowRoot?.querySelector('.input-stepper__value');
|
|
||||||
if (outputElement && outputElement.parentNode) {
|
|
||||||
outputElement.parentNode.removeChild(outputElement);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fast forward time
|
|
||||||
clock.tick(2000);
|
|
||||||
|
|
||||||
// Should not have updated since element was removed
|
|
||||||
expect(requestUpdateSpy.called).to.be.false;
|
|
||||||
expect(el.__valueText).to.equal('test value');
|
|
||||||
|
|
||||||
clock.restore();
|
|
||||||
requestUpdateSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be called when increment button is clicked', async () => {
|
|
||||||
const el = await fixture(defaultInputStepper);
|
|
||||||
const destroyOutputSpy = sinon.spy(el, '_destroyOutputContent');
|
|
||||||
|
|
||||||
const incrementButton = el.querySelector('[slot=suffix]');
|
|
||||||
incrementButton?.dispatchEvent(new Event('click'));
|
|
||||||
|
|
||||||
expect(destroyOutputSpy.calledOnce).to.be.true;
|
|
||||||
destroyOutputSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be called when decrement button is clicked', async () => {
|
|
||||||
const el = await fixture(defaultInputStepper);
|
|
||||||
const destroyOutputSpy = sinon.spy(el, '_destroyOutputContent');
|
|
||||||
|
|
||||||
const decrementButton = el.querySelector('[slot=prefix]');
|
|
||||||
decrementButton?.dispatchEvent(new Event('click'));
|
|
||||||
|
|
||||||
expect(destroyOutputSpy.calledOnce).to.be.true;
|
|
||||||
destroyOutputSpy.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should handle multiple rapid calls by clearing previous timers', async () => {
|
|
||||||
const el = await fixture(defaultInputStepper);
|
|
||||||
const clock = sinon.useFakeTimers();
|
|
||||||
const requestUpdateSpy = sinon.spy(el, 'requestUpdate');
|
|
||||||
|
|
||||||
el.__valueText = 'test value';
|
|
||||||
|
|
||||||
// Call _destroyOutputContent multiple times rapidly
|
|
||||||
el._destroyOutputContent();
|
|
||||||
clock.tick(1000); // 1 second
|
|
||||||
|
|
||||||
el._destroyOutputContent(); // This should clear the previous timer
|
|
||||||
clock.tick(1000); // Total 2 seconds from first call, 1 second from second call
|
|
||||||
|
|
||||||
// Should not have triggered yet since second call reset the timer
|
|
||||||
expect(el.__valueText).to.equal('test value');
|
|
||||||
expect(requestUpdateSpy.called).to.be.false;
|
|
||||||
|
|
||||||
clock.tick(1000); // Total 2 seconds from second call
|
|
||||||
|
|
||||||
// Should trigger now
|
|
||||||
await expect(el.__valueText).to.equal(nothing);
|
|
||||||
expect(requestUpdateSpy.calledOnce).to.be.true;
|
|
||||||
|
|
||||||
clock.restore();
|
|
||||||
requestUpdateSpy.restore();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -670,6 +449,32 @@ describe('<lion-input-stepper>', () => {
|
||||||
expect(el._inputNode.getAttribute('aria-valuemax')).to.equal('1000');
|
expect(el._inputNode.getAttribute('aria-valuemax')).to.equal('1000');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('when decrease button gets focus, it sets aria-live to input-stepper__value', async () => {
|
||||||
|
const el = await fixture(inputStepperWithAttrs);
|
||||||
|
const stepperValue = el.shadowRoot?.querySelector('.input-stepper__value');
|
||||||
|
const decrementButton = el.querySelector('[slot=prefix]');
|
||||||
|
|
||||||
|
decrementButton?.dispatchEvent(new Event('focus'));
|
||||||
|
expect(stepperValue?.hasAttribute('aria-live')).to.be.true;
|
||||||
|
expect(stepperValue?.getAttribute('aria-live')).to.equal('assertive');
|
||||||
|
|
||||||
|
decrementButton?.dispatchEvent(new Event('blur'));
|
||||||
|
expect(stepperValue?.hasAttribute('aria-live')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('when increase button gets focus, it sets aria-live to input-stepper__value', async () => {
|
||||||
|
const el = await fixture(inputStepperWithAttrs);
|
||||||
|
const stepperValue = el.shadowRoot?.querySelector('.input-stepper__value');
|
||||||
|
const incrementButton = el.querySelector('[slot=suffix]');
|
||||||
|
|
||||||
|
incrementButton?.dispatchEvent(new Event('focus'));
|
||||||
|
expect(stepperValue?.hasAttribute('aria-live')).to.be.true;
|
||||||
|
expect(stepperValue?.getAttribute('aria-live')).to.equal('assertive');
|
||||||
|
|
||||||
|
incrementButton?.dispatchEvent(new Event('blur'));
|
||||||
|
expect(stepperValue?.hasAttribute('aria-live')).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
it('decrease button should have aria-label with the component label', async () => {
|
it('decrease button should have aria-label with the component label', async () => {
|
||||||
const el = await fixture(inputStepperWithAttrs);
|
const el = await fixture(inputStepperWithAttrs);
|
||||||
const decrementButton = el.querySelector('[slot=prefix]');
|
const decrementButton = el.querySelector('[slot=prefix]');
|
||||||
|
|
|
||||||
|
|
@ -196,8 +196,7 @@ export class LionPagination extends LocalizeMixin(LitElement) {
|
||||||
const finish = this.count;
|
const finish = this.count;
|
||||||
// If there are more pages then we want to display we have to redo the list each time
|
// If there are more pages then we want to display we have to redo the list each time
|
||||||
// Else we can just return the same list every time.
|
// Else we can just return the same list every time.
|
||||||
// Allow up to 2 extra pages without ellipsis (e.g., count=6 or count=7 when visiblePages=5)
|
if (this.count > this.__visiblePages) {
|
||||||
if (this.count > this.__visiblePages + 2) {
|
|
||||||
// Calculate left side of current page and right side
|
// Calculate left side of current page and right side
|
||||||
const pos3 = this.current - 1;
|
const pos3 = this.current - 1;
|
||||||
const pos4 = this.current;
|
const pos4 = this.current;
|
||||||
|
|
|
||||||
|
|
@ -164,45 +164,4 @@ describe('Pagination', () => {
|
||||||
expect(buttons[3].getAttribute('aria-current')).to.equal('false');
|
expect(buttons[3].getAttribute('aria-current')).to.equal('false');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Ellipsis display', () => {
|
|
||||||
it('should not show ellipsis when count is visiblePages + 1 (count=6, visiblePages=5)', async () => {
|
|
||||||
const el = await fixture(html` <lion-pagination count="6" current="1"></lion-pagination> `);
|
|
||||||
const navItems = Array.from(/** @type {ShadowRoot} */ (el.shadowRoot).querySelectorAll('li'));
|
|
||||||
// Check that no ellipsis is rendered (no <span> elements with '...')
|
|
||||||
const spans = Array.from(
|
|
||||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelectorAll('li span'),
|
|
||||||
);
|
|
||||||
expect(spans.length).to.equal(0);
|
|
||||||
|
|
||||||
// There should be 8 nav items: previous button + 6 page buttons + next button
|
|
||||||
expect(navItems.length).to.equal(8);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not show ellipsis when count is visiblePages + 2 (count=7, visiblePages=5)', async () => {
|
|
||||||
const el = await fixture(html` <lion-pagination count="7" current="1"></lion-pagination> `);
|
|
||||||
// Check that no ellipsis is rendered (no <span> elements with '...')
|
|
||||||
const spans = Array.from(
|
|
||||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelectorAll('li span'),
|
|
||||||
);
|
|
||||||
expect(spans.length).to.equal(0);
|
|
||||||
|
|
||||||
// There should be 9 nav items: previous button + 7 page buttons + next button
|
|
||||||
const navItems = Array.from(/** @type {ShadowRoot} */ (el.shadowRoot).querySelectorAll('li'));
|
|
||||||
expect(navItems.length).to.equal(9);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should show ellipsis when count is visiblePages + 3 (count=8, visiblePages=5)', async () => {
|
|
||||||
const el = await fixture(html` <lion-pagination count="8" current="1"></lion-pagination> `);
|
|
||||||
// Check that ellipsis is rendered (should have <span> elements with '...')
|
|
||||||
const spans = Array.from(
|
|
||||||
/** @type {ShadowRoot} */ (el.shadowRoot).querySelectorAll('li span'),
|
|
||||||
);
|
|
||||||
expect(spans.length).to.be.greaterThan(0);
|
|
||||||
|
|
||||||
// Verify the ellipsis contains '...'
|
|
||||||
const ellipsisText = Array.from(spans).map(span => span.textContent);
|
|
||||||
expect(ellipsisText).to.include('...');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ export default {
|
||||||
MaxDate: 'Inserire un(a) {fieldName} prima di {params, date, YYYYMMDD}.',
|
MaxDate: 'Inserire un(a) {fieldName} prima di {params, date, YYYYMMDD}.',
|
||||||
MinMaxDate:
|
MinMaxDate:
|
||||||
'Inserire un(a) {fieldName} tra {params.min, date, YYYYMMDD} e {params.max, date, YYYYMMDD}.',
|
'Inserire un(a) {fieldName} tra {params.min, date, YYYYMMDD} e {params.max, date, YYYYMMDD}.',
|
||||||
IsDateDisabled: "Questa data non è disponibile, sceglierne un'altra.",
|
IsDateDisabled: "ТQuesta data non è disponibile, sceglierne un'altra.",
|
||||||
IsEmail: 'Inserire un valore valido per {fieldName} nel formato "name@example.com".',
|
IsEmail: 'Inserire un valore valido per {fieldName} nel formato "name@example.com".',
|
||||||
MatchesOption:
|
MatchesOption:
|
||||||
'Nessun risultato corrispondente. Provare con una parola chiave o una categoria diversa.',
|
'Nessun risultato corrispondente. Provare con una parola chiave o una categoria diversa.',
|
||||||
|
|
@ -41,7 +41,7 @@ export default {
|
||||||
MaxDate: 'Inserire un(a) {fieldName} prima di {params, date, YYYYMMDD}.',
|
MaxDate: 'Inserire un(a) {fieldName} prima di {params, date, YYYYMMDD}.',
|
||||||
MinMaxDate:
|
MinMaxDate:
|
||||||
'Inserire un(a) {fieldName} tra {params.min, date, YYYYMMDD} e {params.max, date, YYYYMMDD}.',
|
'Inserire un(a) {fieldName} tra {params.min, date, YYYYMMDD} e {params.max, date, YYYYMMDD}.',
|
||||||
IsDateDisabled: "Questa data non è disponibile, sceglierne un'altra.",
|
IsDateDisabled: "ТQuesta data non è disponibile, sceglierne un'altra.",
|
||||||
IsEmail: 'Inserire un valore valido per {fieldName} nel formato "name@example.com".',
|
IsEmail: 'Inserire un valore valido per {fieldName} nel formato "name@example.com".',
|
||||||
MatchesOption:
|
MatchesOption:
|
||||||
'Nessun risultato corrispondente. Provare con una parola chiave o una categoria diversa.',
|
'Nessun risultato corrispondente. Provare con una parola chiave o una categoria diversa.',
|
||||||
|
|
|
||||||
|
|
@ -34,9 +34,6 @@ export default {
|
||||||
eleventyConfig.setUseGitIgnore(false);
|
eleventyConfig.setUseGitIgnore(false);
|
||||||
eleventyConfig.addPassthroughCopy('CNAME');
|
eleventyConfig.addPassthroughCopy('CNAME');
|
||||||
},
|
},
|
||||||
checkLinks: {
|
|
||||||
ignoreLinkPatterns: ['**/astro'],
|
|
||||||
},
|
|
||||||
absoluteBaseUrl: absoluteBaseUrlNetlify('http://localhost:8080'),
|
absoluteBaseUrl: absoluteBaseUrlNetlify('http://localhost:8080'),
|
||||||
setupUnifiedPlugins: [
|
setupUnifiedPlugins: [
|
||||||
adjustPluginOptions(mdjsSetupCode, {
|
adjustPluginOptions(mdjsSetupCode, {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue