kopia lustrzana https://github.com/shoelace-style/shoelace
Merge branch 'next' into efficient-style-imports
commit
cfe836a1a3
|
@ -14,13 +14,15 @@ New versions of Shoelace are released as-needed and generally occur when a criti
|
||||||
|
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
|
- Added the Arabic translation [#1852]
|
||||||
|
- Added help text to `<sl-checkbox>` [#1860]
|
||||||
|
- Added help text to `<sl-switch>` [#1800]
|
||||||
- Fixed a bug in `<sl-option>` that caused HTML tags to be included in `getTextLabel()`
|
- Fixed a bug in `<sl-option>` that caused HTML tags to be included in `getTextLabel()`
|
||||||
|
- Fixed a bug in `<sl-carousel>` that caused slides to not switch correctly [#1862]
|
||||||
- Refactored component styles to be consumed more efficiently [#1692]
|
- Refactored component styles to be consumed more efficiently [#1692]
|
||||||
|
|
||||||
## 2.13.1
|
## 2.13.1
|
||||||
|
|
||||||
- Added help text to `<sl-checkbox>`
|
|
||||||
- Added help text to `<sl-switch>` [#1800]
|
|
||||||
- Fixed a bug where the safe triangle was always visible when selecting nested `<sl-menu>` elements [#1835]
|
- Fixed a bug where the safe triangle was always visible when selecting nested `<sl-menu>` elements [#1835]
|
||||||
|
|
||||||
## 2.13.0
|
## 2.13.0
|
||||||
|
|
|
@ -477,7 +477,7 @@ export default class SlCarousel extends ShoelaceElement {
|
||||||
this.scrollToSlide(nextSlide, prefersReducedMotion() ? 'auto' : behavior);
|
this.scrollToSlide(nextSlide, prefersReducedMotion() ? 'auto' : behavior);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async scrollToSlide(slide: HTMLElement, behavior: ScrollBehavior = 'smooth') {
|
private scrollToSlide(slide: HTMLElement, behavior: ScrollBehavior = 'smooth') {
|
||||||
const scrollContainer = this.scrollContainer;
|
const scrollContainer = this.scrollContainer;
|
||||||
const scrollContainerRect = scrollContainer.getBoundingClientRect();
|
const scrollContainerRect = scrollContainer.getBoundingClientRect();
|
||||||
const nextSlideRect = slide.getBoundingClientRect();
|
const nextSlideRect = slide.getBoundingClientRect();
|
||||||
|
@ -485,16 +485,11 @@ export default class SlCarousel extends ShoelaceElement {
|
||||||
const nextLeft = nextSlideRect.left - scrollContainerRect.left;
|
const nextLeft = nextSlideRect.left - scrollContainerRect.left;
|
||||||
const nextTop = nextSlideRect.top - scrollContainerRect.top;
|
const nextTop = nextSlideRect.top - scrollContainerRect.top;
|
||||||
|
|
||||||
// If the slide is already in view, don't need to scroll
|
scrollContainer.scrollTo({
|
||||||
if (nextLeft !== scrollContainer.scrollLeft || nextTop !== scrollContainer.scrollTop) {
|
left: nextLeft + scrollContainer.scrollLeft,
|
||||||
scrollContainer.scrollTo({
|
top: nextTop + scrollContainer.scrollTop,
|
||||||
left: nextLeft + scrollContainer.scrollLeft,
|
behavior
|
||||||
top: nextTop + scrollContainer.scrollTop,
|
});
|
||||||
behavior
|
|
||||||
});
|
|
||||||
|
|
||||||
await waitForEvent(scrollContainer, 'scrollend');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import '../../../dist/shoelace.js';
|
import '../../../dist/shoelace.js';
|
||||||
import { clickOnElement, dragElement, moveMouseOnElement } from '../../internal/test.js';
|
import { clickOnElement, dragElement, moveMouseOnElement } from '../../internal/test.js';
|
||||||
import { expect, fixture, html, oneEvent } from '@open-wc/testing';
|
import { expect, fixture, html, nextFrame, oneEvent } from '@open-wc/testing';
|
||||||
import { map } from 'lit/directives/map.js';
|
import { map } from 'lit/directives/map.js';
|
||||||
import { range } from 'lit/directives/range.js';
|
import { range } from 'lit/directives/range.js';
|
||||||
import { resetMouse } from '@web/test-runner-commands';
|
import { resetMouse } from '@web/test-runner-commands';
|
||||||
|
@ -8,10 +8,16 @@ import sinon from 'sinon';
|
||||||
import type SlCarousel from './carousel.js';
|
import type SlCarousel from './carousel.js';
|
||||||
|
|
||||||
describe('<sl-carousel>', () => {
|
describe('<sl-carousel>', () => {
|
||||||
|
const sandbox = sinon.createSandbox();
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
await resetMouse();
|
await resetMouse();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
it('should render a carousel with default configuration', async () => {
|
it('should render a carousel with default configuration', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const el = await fixture(html`
|
const el = await fixture(html`
|
||||||
|
@ -34,15 +40,11 @@ describe('<sl-carousel>', () => {
|
||||||
let clock: sinon.SinonFakeTimers;
|
let clock: sinon.SinonFakeTimers;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
clock = sinon.useFakeTimers({
|
clock = sandbox.useFakeTimers({
|
||||||
now: new Date()
|
now: new Date()
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
clock.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should scroll forwards every `autoplay-interval` milliseconds', async () => {
|
it('should scroll forwards every `autoplay-interval` milliseconds', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const el = await fixture<SlCarousel>(html`
|
const el = await fixture<SlCarousel>(html`
|
||||||
|
@ -52,7 +54,7 @@ describe('<sl-carousel>', () => {
|
||||||
<sl-carousel-item>Node 3</sl-carousel-item>
|
<sl-carousel-item>Node 3</sl-carousel-item>
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
sinon.stub(el, 'next');
|
sandbox.stub(el, 'next');
|
||||||
|
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
|
@ -73,7 +75,7 @@ describe('<sl-carousel>', () => {
|
||||||
<sl-carousel-item>Node 3</sl-carousel-item>
|
<sl-carousel-item>Node 3</sl-carousel-item>
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
sinon.stub(el, 'next');
|
sandbox.stub(el, 'next');
|
||||||
|
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
|
@ -96,7 +98,7 @@ describe('<sl-carousel>', () => {
|
||||||
<sl-carousel-item>Node 3</sl-carousel-item>
|
<sl-carousel-item>Node 3</sl-carousel-item>
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
sinon.stub(el, 'next');
|
sandbox.stub(el, 'next');
|
||||||
|
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
|
@ -183,7 +185,7 @@ describe('<sl-carousel>', () => {
|
||||||
<sl-carousel-item>Node 3</sl-carousel-item>
|
<sl-carousel-item>Node 3</sl-carousel-item>
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
sinon.stub(el, 'goToSlide');
|
sandbox.stub(el, 'goToSlide');
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -473,7 +475,7 @@ describe('<sl-carousel>', () => {
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;
|
const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;
|
||||||
sinon.stub(el, 'next');
|
sandbox.stub(el, 'next');
|
||||||
|
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
|
@ -496,7 +498,7 @@ describe('<sl-carousel>', () => {
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;
|
const nextButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--next')!;
|
||||||
sinon.stub(el, 'next');
|
sandbox.stub(el, 'next');
|
||||||
|
|
||||||
el.goToSlide(2, 'auto');
|
el.goToSlide(2, 'auto');
|
||||||
await oneEvent(el.scrollContainer, 'scrollend');
|
await oneEvent(el.scrollContainer, 'scrollend');
|
||||||
|
@ -560,7 +562,7 @@ describe('<sl-carousel>', () => {
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;
|
const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;
|
||||||
sinon.stub(el, 'previous');
|
sandbox.stub(el, 'previous');
|
||||||
|
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
|
@ -584,7 +586,7 @@ describe('<sl-carousel>', () => {
|
||||||
`);
|
`);
|
||||||
|
|
||||||
const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;
|
const previousButton: HTMLElement = el.shadowRoot!.querySelector('.carousel__navigation-button--previous')!;
|
||||||
sinon.stub(el, 'previous');
|
sandbox.stub(el, 'previous');
|
||||||
await el.updateComplete;
|
await el.updateComplete;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
@ -632,19 +634,27 @@ describe('<sl-carousel>', () => {
|
||||||
it('should scroll the carousel to the next slide', async () => {
|
it('should scroll the carousel to the next slide', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const el = await fixture<SlCarousel>(html`
|
const el = await fixture<SlCarousel>(html`
|
||||||
<sl-carousel slides-per-page="2" slides-per-move="2">
|
<sl-carousel>
|
||||||
<sl-carousel-item>Node 1</sl-carousel-item>
|
<sl-carousel-item>Node 1</sl-carousel-item>
|
||||||
<sl-carousel-item>Node 2</sl-carousel-item>
|
<sl-carousel-item>Node 2</sl-carousel-item>
|
||||||
<sl-carousel-item>Node 3</sl-carousel-item>
|
<sl-carousel-item>Node 3</sl-carousel-item>
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
sinon.stub(el, 'goToSlide');
|
sandbox.spy(el, 'goToSlide');
|
||||||
await el.updateComplete;
|
const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(2)')!;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
el.next();
|
el.next();
|
||||||
|
await oneEvent(el.scrollContainer, 'scrollend');
|
||||||
|
await el.updateComplete;
|
||||||
|
|
||||||
expect(el.goToSlide).to.have.been.calledWith(2);
|
const containerRect = el.scrollContainer.getBoundingClientRect();
|
||||||
|
const itemRect = expectedCarouselItem.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(el.goToSlide).to.have.been.calledWith(1);
|
||||||
|
expect(itemRect.top).to.be.equal(containerRect.top);
|
||||||
|
expect(itemRect.left).to.be.equal(containerRect.left);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -652,19 +662,32 @@ describe('<sl-carousel>', () => {
|
||||||
it('should scroll the carousel to the previous slide', async () => {
|
it('should scroll the carousel to the previous slide', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const el = await fixture<SlCarousel>(html`
|
const el = await fixture<SlCarousel>(html`
|
||||||
<sl-carousel slides-per-page="2" slides-per-move="2">
|
<sl-carousel>
|
||||||
<sl-carousel-item>Node 1</sl-carousel-item>
|
<sl-carousel-item>Node 1</sl-carousel-item>
|
||||||
<sl-carousel-item>Node 2</sl-carousel-item>
|
<sl-carousel-item>Node 2</sl-carousel-item>
|
||||||
<sl-carousel-item>Node 3</sl-carousel-item>
|
<sl-carousel-item>Node 3</sl-carousel-item>
|
||||||
</sl-carousel>
|
</sl-carousel>
|
||||||
`);
|
`);
|
||||||
sinon.stub(el, 'goToSlide');
|
const expectedCarouselItem: HTMLElement = el.querySelector('sl-carousel-item:nth-child(1)')!;
|
||||||
await el.updateComplete;
|
|
||||||
|
el.goToSlide(1);
|
||||||
|
|
||||||
|
await oneEvent(el.scrollContainer, 'scrollend');
|
||||||
|
await nextFrame();
|
||||||
|
|
||||||
|
sandbox.spy(el, 'goToSlide');
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
el.previous();
|
el.previous();
|
||||||
|
await oneEvent(el.scrollContainer, 'scrollend');
|
||||||
|
|
||||||
expect(el.goToSlide).to.have.been.calledWith(-2);
|
const containerRect = el.scrollContainer.getBoundingClientRect();
|
||||||
|
const itemRect = expectedCarouselItem.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(el.goToSlide).to.have.been.calledWith(0);
|
||||||
|
expect(itemRect.top).to.be.equal(containerRect.top);
|
||||||
|
expect(itemRect.left).to.be.equal(containerRect.left);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -228,6 +228,7 @@ export default class SlCheckbox extends ShoelaceElement implements ShoelaceFormC
|
||||||
.disabled=${this.disabled}
|
.disabled=${this.disabled}
|
||||||
.required=${this.required}
|
.required=${this.required}
|
||||||
aria-checked=${this.checked ? 'true' : 'false'}
|
aria-checked=${this.checked ? 'true' : 'false'}
|
||||||
|
aria-describedby="help-text"
|
||||||
@click=${this.handleClick}
|
@click=${this.handleClick}
|
||||||
@input=${this.handleInput}
|
@input=${this.handleInput}
|
||||||
@invalid=${this.handleInvalid}
|
@invalid=${this.handleInvalid}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { registerTranslation } from '@shoelace-style/localize';
|
||||||
|
import type { Translation } from '../utilities/localize.js';
|
||||||
|
|
||||||
|
const translation: Translation = {
|
||||||
|
$code: 'ar',
|
||||||
|
$name: 'العربية',
|
||||||
|
$dir: 'rtl',
|
||||||
|
|
||||||
|
carousel: 'كاروسيل',
|
||||||
|
clearEntry: 'حذف الخيارات',
|
||||||
|
close: 'اغلاق',
|
||||||
|
copied: 'تم النسخ',
|
||||||
|
copy: 'نسخ',
|
||||||
|
currentValue: 'القيمة الحالية',
|
||||||
|
error: 'خطأ',
|
||||||
|
goToSlide: (slide, count) => `عرض شريحة رقم ${slide} من ${count}`,
|
||||||
|
hidePassword: 'اخفاء كلمة المرور',
|
||||||
|
loading: 'جاري التحميل',
|
||||||
|
nextSlide: 'الشريحة التالية',
|
||||||
|
numOptionsSelected: num => {
|
||||||
|
if (num === 0) return 'لم يتم تحديد أي خيارات';
|
||||||
|
if (num === 1) return 'تم تحديد خيار واحد';
|
||||||
|
if (num === 2) return 'تم تحديد خياران';
|
||||||
|
if (num > 2 && num < 11) return `تم تحديد ${num} خيارات`;
|
||||||
|
return `تم تحديد ${num} خيار`;
|
||||||
|
},
|
||||||
|
previousSlide: 'الشريحة السابقة',
|
||||||
|
progress: 'مقدار التقدم',
|
||||||
|
remove: 'حذف',
|
||||||
|
resize: 'تغيير الحجم',
|
||||||
|
scrollToEnd: 'الانتقال الى النهاية',
|
||||||
|
scrollToStart: 'الانتقال الى البداية',
|
||||||
|
selectAColorFromTheScreen: 'اختر لون من الشاشة',
|
||||||
|
showPassword: 'عرض كلمة المرور',
|
||||||
|
slideNum: slide => `شريحة ${slide}`,
|
||||||
|
toggleColorFormat: 'تغيير صيغة عرض اللون'
|
||||||
|
};
|
||||||
|
|
||||||
|
registerTranslation(translation);
|
||||||
|
|
||||||
|
export default translation;
|
Ładowanie…
Reference in New Issue