kopia lustrzana https://github.com/shoelace-style/shoelace
Porównaj commity
13 Commity
600c83ee30
...
d0204c2297
Autor | SHA1 | Data |
---|---|---|
konnorrogers | d0204c2297 | |
konnorrogers | f10282f1e6 | |
Cory LaViska | 0b7e70bccf | |
Alessandro | 31f2600816 | |
Cory LaViska | f6d5344c44 | |
Nic Newdigate | 5a89439e14 | |
Cory LaViska | 2878957ef5 | |
Konnor Rogers | cd5a6486da | |
Cory LaViska | 77d6f27248 | |
Konnor Rogers | 7a62a87b9b | |
Cory LaViska | 0ac61a6a22 | |
Matt Walkland | acf76cf359 | |
Cory LaViska | 3451ec753c |
|
@ -95,6 +95,23 @@
|
|||
</sl-dropdown>
|
||||
</div>
|
||||
|
||||
<a
|
||||
class="ks-banner{% if toc %} with-toc{% endif %}"
|
||||
href="https://www.kickstarter.com/projects/fontawesome/web-awesome?ref=71ihfk"
|
||||
target="_blank"
|
||||
>
|
||||
<span>
|
||||
<svg viewBox="0 0 20 16" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#f36944" d="M11.63 1.625C11.63 2.27911 11.2435 2.84296 10.6865 3.10064L14 6L17.2622 5.34755C17.0968 5.10642 17 4.81452 17 4.5C17 3.67157 17.6716 3 18.5 3C19.3284 3 20 3.67157 20 4.5C20 5.31157 19.3555 5.9726 18.5504 5.99917L15.0307 13.8207C14.7077 14.5384 13.9939 15 13.2068 15H6.79317C6.00615 15 5.29229 14.5384 4.96933 13.8207L1.44963 5.99917C0.64452 5.9726 0 5.31157 0 4.5C0 3.67157 0.671573 3 1.5 3C2.32843 3 3 3.67157 3 4.5C3 4.81452 2.9032 5.10642 2.73777 5.34755L6 6L9.31702 3.09761C8.76346 2.83855 8.38 2.27656 8.38 1.625C8.38 0.727537 9.10754 0 10.005 0C10.9025 0 11.63 0.727537 11.63 1.625Z"/>
|
||||
</svg>
|
||||
<span>
|
||||
<strong style="white-space: nowrap;">Get ready for more awesome!</strong>
|
||||
Web Awesome, the next iteration of Shoelace, is on Kickstarter.
|
||||
</span>
|
||||
</span>
|
||||
<span class="faux-button">Read Our Story</span>
|
||||
</a>
|
||||
|
||||
<aside id="sidebar" data-preserve-scroll>
|
||||
<header>
|
||||
<a href="/">
|
||||
|
|
|
@ -1413,3 +1413,95 @@ body[data-page^='/tokens/'] .table-wrapper td:first-child code {
|
|||
grid-column-start: span 6;
|
||||
}
|
||||
}
|
||||
|
||||
.ks-banner {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
width: 950px;
|
||||
left: calc(50% - 475px);
|
||||
font-size: 0.9375rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background: #1a3256;
|
||||
border-radius: var(--sl-border-radius-large);
|
||||
padding: 1rem 1.25rem;
|
||||
color: #fdfdfd;
|
||||
text-decoration: none;
|
||||
line-height: 1.4;
|
||||
z-index: 2;
|
||||
margin-left: 160px;
|
||||
}
|
||||
|
||||
.ks-banner:hover {
|
||||
color: #fdfdfd;
|
||||
}
|
||||
|
||||
.ks-banner > span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.ks-banner svg {
|
||||
flex: 0 0 1.5rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.ks-banner .faux-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
background: white;
|
||||
border: solid 1px #d4d4d4;
|
||||
border-radius: var(--sl-border-radius-medium);
|
||||
font-size: 0.8375rem;
|
||||
color: #353439;
|
||||
padding: 0.5rem 1rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ks-banner.with-toc {
|
||||
width: 1100px;
|
||||
left: calc(50% - 550px);
|
||||
margin-left: 140px;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: 70px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1650px) {
|
||||
.ks-banner,
|
||||
.ks-banner.with-toc {
|
||||
width: 540px !important;
|
||||
top: 50px;
|
||||
left: calc(50% - 270px);
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 900px) {
|
||||
.ks-banner,
|
||||
.ks-banner.with-toc {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 680px) {
|
||||
.ks-banner,
|
||||
.ks-banner.with-toc {
|
||||
width: calc(100% - 2rem) !important;
|
||||
left: 1rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: 150px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,10 @@ New versions of Shoelace are released as-needed and generally occur when a criti
|
|||
|
||||
- Added the Slovenian translation [#1893]
|
||||
- Added support for `contextElement` to `VirtualElements` in `<sl-popup>` [#1874]
|
||||
- Fixed a bug in `.sl-scroll-lock` causing layout shifts. [#1895]
|
||||
- Added the `spinner` and `spinner__base` parts to `<sl-tree-item>` [#1937]
|
||||
- Added the `sync` property to `<sl-dropdown>` so the menu can easily sync sizes with the trigger element [#1935]
|
||||
- Fixed a bug in `<sl-icon>` that did not properly apply mutators to spritesheets [#1927]
|
||||
- Fixed a bug in `.sl-scroll-lock` causing layout shifts [#1895]
|
||||
- Fixed a bug in `<sl-rating>` that caused the rating to not reset in some circumstances [#1877]
|
||||
- Fixed a bug in `<sl-select>` that caused the menu to not close when rendered in a shadow root [#1878]
|
||||
- Fixed a bug in `<sl-tree>` that caused a new stacking context resulting in tooltips being clipped [#1709]
|
||||
|
@ -25,6 +28,8 @@ New versions of Shoelace are released as-needed and generally occur when a criti
|
|||
- Fixed a bug in `<sl-select>` where the tag size wouldn't update with the control's size [#1886]
|
||||
- Fixed a bug in `<sl-checkbox>` and `<sl-switch>` where the color of the required content wasn't applying correctly
|
||||
- Fixed a bug in `<sl-checkbox>` where help text was incorrectly styled [#1897]
|
||||
- Fixed a bug in `<sl-input>` that prevented the control from receiving focus when clicking over the clear button
|
||||
- Fixed a bug in `<sl-carousel>` that caused the carousel to be out of sync when used with reduced motion settings [#1887]
|
||||
|
||||
## 2.14.0
|
||||
|
||||
|
@ -44,6 +49,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti
|
|||
- Added the `hover-bridge` feature to `<sl-popup>` to support better tooltip accessibility [#1734]
|
||||
- Added the `loading` attribute and the `spinner` and `spinner__base` part to `<sl-menu-item>` [#1700]
|
||||
- Fixed files that did not have `.js` extensions. [#1770]
|
||||
- Fixed a bug in `<sl-tree>` when providing custom expand / collapse icons [#1922]
|
||||
- Fixed `<sl-dialog>` not accounting for elements with hidden dialog controls like `<video>` [#1755]
|
||||
- Fixed focus trapping not scrolling elements into view. [#1750]
|
||||
- Fixed more performance issues with focus trapping performance. [#1750]
|
||||
|
|
|
@ -30,22 +30,22 @@ export default class SlButtonGroup extends ShoelaceElement {
|
|||
|
||||
private handleFocus(event: Event) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.add('sl-button-group__button--focus');
|
||||
button?.toggleAttribute('data-sl-button-group__button--focus', true);
|
||||
}
|
||||
|
||||
private handleBlur(event: Event) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.remove('sl-button-group__button--focus');
|
||||
button?.toggleAttribute('data-sl-button-group__button--focus', false);
|
||||
}
|
||||
|
||||
private handleMouseOver(event: Event) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.add('sl-button-group__button--hover');
|
||||
button?.toggleAttribute('data-sl-button-group__button--hover', true);
|
||||
}
|
||||
|
||||
private handleMouseOut(event: Event) {
|
||||
const button = findButton(event.target as HTMLElement);
|
||||
button?.classList.remove('sl-button-group__button--hover');
|
||||
button?.toggleAttribute('data-sl-button-group__button--hover', true);
|
||||
}
|
||||
|
||||
private handleSlotChange() {
|
||||
|
@ -56,11 +56,14 @@ export default class SlButtonGroup extends ShoelaceElement {
|
|||
const button = findButton(el);
|
||||
|
||||
if (button) {
|
||||
button.classList.add('sl-button-group__button');
|
||||
button.classList.toggle('sl-button-group__button--first', index === 0);
|
||||
button.classList.toggle('sl-button-group__button--inner', index > 0 && index < slottedElements.length - 1);
|
||||
button.classList.toggle('sl-button-group__button--last', index === slottedElements.length - 1);
|
||||
button.classList.toggle('sl-button-group__button--radio', button.tagName.toLowerCase() === 'sl-radio-button');
|
||||
button.toggleAttribute('data-sl-button-group__button', true);
|
||||
button.toggleAttribute('data-sl-button-group__button--first', index === 0);
|
||||
button.toggleAttribute('data-sl-button-group__button--inner', index > 0 && index < slottedElements.length - 1);
|
||||
button.toggleAttribute('data-sl-button-group__button--last', index === slottedElements.length - 1);
|
||||
button.toggleAttribute(
|
||||
'data-sl-button-group__button--radio',
|
||||
button.tagName.toLowerCase() === 'sl-radio-button'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -546,30 +546,30 @@ export default css`
|
|||
* buttons and we style them here instead.
|
||||
*/
|
||||
|
||||
:host(.sl-button-group__button--first:not(.sl-button-group__button--last)) .button {
|
||||
:host([data-sl-button-group__button--first]:not([data-sl-button-group__button--last])) .button {
|
||||
border-start-end-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
|
||||
:host(.sl-button-group__button--inner) .button {
|
||||
:host([data-sl-button-group__button--inner]) .button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
:host(.sl-button-group__button--last:not(.sl-button-group__button--first)) .button {
|
||||
:host([data-sl-button-group__button--last]:not([data-sl-button-group__button--first])) .button {
|
||||
border-start-start-radius: 0;
|
||||
border-end-start-radius: 0;
|
||||
}
|
||||
|
||||
/* All except the first */
|
||||
:host(.sl-button-group__button:not(.sl-button-group__button--first)) {
|
||||
:host([data-sl-button-group__button]:not([data-sl-button-group__button--first])) {
|
||||
margin-inline-start: calc(-1 * var(--sl-input-border-width));
|
||||
}
|
||||
|
||||
/* Add a visual separator between solid buttons */
|
||||
:host(
|
||||
.sl-button-group__button:not(
|
||||
.sl-button-group__button--first,
|
||||
.sl-button-group__button--radio,
|
||||
[data-sl-button-group__button]:not(
|
||||
[data-sl-button-group__button--first],
|
||||
[data-sl-button-group__button--radio],
|
||||
[variant='default']
|
||||
):not(:hover)
|
||||
)
|
||||
|
@ -584,13 +584,13 @@ export default css`
|
|||
}
|
||||
|
||||
/* Bump hovered, focused, and checked buttons up so their focus ring isn't clipped */
|
||||
:host(.sl-button-group__button--hover) {
|
||||
:host([data-sl-button-group__button--hover]) {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
/* Focus and checked are always on top */
|
||||
:host(.sl-button-group__button--focus),
|
||||
:host(.sl-button-group__button[checked]) {
|
||||
:host([data-sl-button-group__button--focus]),
|
||||
:host([data-sl-button-group__button[checked]]) {
|
||||
z-index: 2;
|
||||
}
|
||||
`;
|
||||
|
|
|
@ -92,9 +92,6 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
@state() dragging = false;
|
||||
|
||||
private autoplayController = new AutoplayController(this, () => this.next());
|
||||
private intersectionObserver: IntersectionObserver; // determines which slide is displayed
|
||||
// A map containing the state of all the slides
|
||||
private readonly intersectionObserverEntries = new Map<Element, IntersectionObserverEntry>();
|
||||
private readonly localize = new LocalizeController(this);
|
||||
private mutationObserver: MutationObserver;
|
||||
|
||||
|
@ -102,35 +99,10 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
super.connectedCallback();
|
||||
this.setAttribute('role', 'region');
|
||||
this.setAttribute('aria-label', this.localize.term('carousel'));
|
||||
|
||||
const intersectionObserver = new IntersectionObserver(
|
||||
(entries: IntersectionObserverEntry[]) => {
|
||||
entries.forEach(entry => {
|
||||
// Store all the entries in a map to be processed when scrolling ends
|
||||
this.intersectionObserverEntries.set(entry.target, entry);
|
||||
|
||||
const slide = entry.target;
|
||||
slide.toggleAttribute('inert', !entry.isIntersecting);
|
||||
slide.classList.toggle('--in-view', entry.isIntersecting);
|
||||
slide.setAttribute('aria-hidden', entry.isIntersecting ? 'false' : 'true');
|
||||
});
|
||||
},
|
||||
{
|
||||
root: this,
|
||||
threshold: 0.6
|
||||
}
|
||||
);
|
||||
this.intersectionObserver = intersectionObserver;
|
||||
|
||||
// Store the initial state of each slide
|
||||
intersectionObserver.takeRecords().forEach(entry => {
|
||||
this.intersectionObserverEntries.set(entry.target, entry);
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback(): void {
|
||||
super.disconnectedCallback();
|
||||
this.intersectionObserver.disconnect();
|
||||
this.mutationObserver.disconnect();
|
||||
}
|
||||
|
||||
|
@ -291,26 +263,52 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
this.scrolling = true;
|
||||
}
|
||||
|
||||
/** @internal Synchronizes the slides with the IntersectionObserver API. */
|
||||
private synchronizeSlides() {
|
||||
const io = new IntersectionObserver(
|
||||
entries => {
|
||||
io.disconnect();
|
||||
|
||||
for (const entry of entries) {
|
||||
const slide = entry.target;
|
||||
slide.toggleAttribute('inert', !entry.isIntersecting);
|
||||
slide.classList.toggle('--in-view', entry.isIntersecting);
|
||||
slide.setAttribute('aria-hidden', entry.isIntersecting ? 'false' : 'true');
|
||||
}
|
||||
|
||||
const firstIntersecting = entries.find(entry => entry.isIntersecting);
|
||||
|
||||
if (firstIntersecting) {
|
||||
if (this.loop && firstIntersecting.target.hasAttribute('data-clone')) {
|
||||
const clonePosition = Number(firstIntersecting.target.getAttribute('data-clone'));
|
||||
|
||||
// Scrolls to the original slide without animating, so the user won't notice that the position has changed
|
||||
this.goToSlide(clonePosition, 'instant');
|
||||
} else {
|
||||
const slides = this.getSlides();
|
||||
|
||||
// Update the current index based on the first visible slide
|
||||
const slideIndex = slides.indexOf(firstIntersecting.target as SlCarouselItem);
|
||||
// Set the index to the first "snappable" slide
|
||||
this.activeSlide = Math.ceil(slideIndex / this.slidesPerMove) * this.slidesPerMove;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
root: this.scrollContainer,
|
||||
threshold: 0.6
|
||||
}
|
||||
);
|
||||
|
||||
this.getSlides({ excludeClones: false }).forEach(slide => {
|
||||
io.observe(slide);
|
||||
});
|
||||
}
|
||||
|
||||
private handleScrollEnd() {
|
||||
if (!this.scrolling || this.dragging) return;
|
||||
|
||||
const entries = [...this.intersectionObserverEntries.values()];
|
||||
|
||||
const firstIntersecting: IntersectionObserverEntry | undefined = entries.find(entry => entry.isIntersecting);
|
||||
|
||||
if (this.loop && firstIntersecting?.target.hasAttribute('data-clone')) {
|
||||
const clonePosition = Number(firstIntersecting.target.getAttribute('data-clone'));
|
||||
|
||||
// Scrolls to the original slide without animating, so the user won't notice that the position has changed
|
||||
this.goToSlide(clonePosition, 'instant');
|
||||
} else if (firstIntersecting) {
|
||||
const slides = this.getSlides();
|
||||
|
||||
// Update the current index based on the first visible slide
|
||||
const slideIndex = slides.indexOf(firstIntersecting.target as SlCarouselItem);
|
||||
// Set the index to the first "snappable" slide
|
||||
this.activeSlide = Math.ceil(slideIndex / this.slidesPerMove) * this.slidesPerMove;
|
||||
}
|
||||
this.synchronizeSlides();
|
||||
|
||||
this.scrolling = false;
|
||||
}
|
||||
|
@ -337,14 +335,8 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
@watch('loop', { waitUntilFirstUpdate: true })
|
||||
@watch('slidesPerPage', { waitUntilFirstUpdate: true })
|
||||
initializeSlides() {
|
||||
const intersectionObserver = this.intersectionObserver;
|
||||
|
||||
this.intersectionObserverEntries.clear();
|
||||
|
||||
// Removes all the cloned elements from the carousel
|
||||
this.getSlides({ excludeClones: false }).forEach((slide, index) => {
|
||||
intersectionObserver.unobserve(slide);
|
||||
|
||||
slide.classList.remove('--in-view');
|
||||
slide.classList.remove('--is-active');
|
||||
slide.setAttribute('aria-label', this.localize.term('slideNum', index + 1));
|
||||
|
@ -361,9 +353,7 @@ export default class SlCarousel extends ShoelaceElement {
|
|||
this.createClones();
|
||||
}
|
||||
|
||||
this.getSlides({ excludeClones: false }).forEach(slide => {
|
||||
intersectionObserver.observe(slide);
|
||||
});
|
||||
this.synchronizeSlides();
|
||||
|
||||
// Because the DOM may be changed, restore the scroll position to the active slide
|
||||
this.goToSlide(this.activeSlide, 'auto');
|
||||
|
|
|
@ -1,21 +1,39 @@
|
|||
import '../../../dist/shoelace.js';
|
||||
import { aTimeout, expect, fixture, html, nextFrame, oneEvent, waitUntil } from '@open-wc/testing';
|
||||
import { clickOnElement, dragElement, moveMouseOnElement } from '../../internal/test.js';
|
||||
import { expect, fixture, html, nextFrame, oneEvent } from '@open-wc/testing';
|
||||
import { map } from 'lit/directives/map.js';
|
||||
import { range } from 'lit/directives/range.js';
|
||||
import { resetMouse } from '@web/test-runner-commands';
|
||||
import sinon from 'sinon';
|
||||
import type { SinonStub } from 'sinon';
|
||||
import type SlCarousel from './carousel.js';
|
||||
|
||||
describe('<sl-carousel>', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
const ioCallbacks = new Map<IntersectionObserver, SinonStub>();
|
||||
const intersectionObserverCallbacks = () => {
|
||||
const callbacks = [...ioCallbacks.values()];
|
||||
return waitUntil(() => callbacks.every(callback => callback.called));
|
||||
};
|
||||
const OriginalIntersectionObserver = globalThis.IntersectionObserver;
|
||||
|
||||
beforeEach(() => {
|
||||
globalThis.IntersectionObserver = class IntersectionObserverMock extends OriginalIntersectionObserver {
|
||||
constructor(callback: IntersectionObserverCallback, options?: IntersectionObserverInit) {
|
||||
const stubCallback = sandbox.stub().callsFake(callback);
|
||||
|
||||
super(stubCallback, options);
|
||||
|
||||
ioCallbacks.set(this, stubCallback);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await resetMouse();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
globalThis.IntersectionObserver = OriginalIntersectionObserver;
|
||||
ioCallbacks.clear();
|
||||
});
|
||||
|
||||
it('should render a carousel with default configuration', async () => {
|
||||
|
@ -311,6 +329,7 @@ describe('<sl-carousel>', () => {
|
|||
await clickOnElement(nextButton);
|
||||
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await intersectionObserverCallbacks();
|
||||
await el.updateComplete;
|
||||
|
||||
// Assert
|
||||
|
@ -337,13 +356,19 @@ describe('<sl-carousel>', () => {
|
|||
|
||||
// Act
|
||||
await clickOnElement(nextButton);
|
||||
await aTimeout(50);
|
||||
await clickOnElement(nextButton);
|
||||
await aTimeout(50);
|
||||
await clickOnElement(nextButton);
|
||||
await aTimeout(50);
|
||||
await clickOnElement(nextButton);
|
||||
await aTimeout(50);
|
||||
await clickOnElement(nextButton);
|
||||
await aTimeout(50);
|
||||
await clickOnElement(nextButton);
|
||||
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await intersectionObserverCallbacks();
|
||||
await el.updateComplete;
|
||||
|
||||
// Assert
|
||||
|
@ -502,6 +527,7 @@ describe('<sl-carousel>', () => {
|
|||
|
||||
el.goToSlide(2, 'auto');
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await intersectionObserverCallbacks();
|
||||
await el.updateComplete;
|
||||
|
||||
// Act
|
||||
|
@ -537,6 +563,9 @@ describe('<sl-carousel>', () => {
|
|||
// wait scroll to actual item
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
|
||||
await intersectionObserverCallbacks();
|
||||
await el.updateComplete;
|
||||
|
||||
// Assert
|
||||
expect(nextButton).to.have.attribute('aria-disabled', 'false');
|
||||
expect(el.activeSlide).to.be.equal(0);
|
||||
|
@ -620,6 +649,8 @@ describe('<sl-carousel>', () => {
|
|||
// wait scroll to actual item
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
|
||||
await intersectionObserverCallbacks();
|
||||
|
||||
// Assert
|
||||
expect(previousButton).to.have.attribute('aria-disabled', 'false');
|
||||
expect(el.activeSlide).to.be.equal(2);
|
||||
|
@ -673,6 +704,7 @@ describe('<sl-carousel>', () => {
|
|||
el.goToSlide(1);
|
||||
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await intersectionObserverCallbacks();
|
||||
await nextFrame();
|
||||
|
||||
sandbox.spy(el, 'goToSlide');
|
||||
|
@ -680,6 +712,7 @@ describe('<sl-carousel>', () => {
|
|||
// Act
|
||||
el.previous();
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await intersectionObserverCallbacks();
|
||||
|
||||
const containerRect = el.scrollContainer.getBoundingClientRect();
|
||||
const itemRect = expectedCarouselItem.getBoundingClientRect();
|
||||
|
@ -706,6 +739,7 @@ describe('<sl-carousel>', () => {
|
|||
// Act
|
||||
el.goToSlide(2);
|
||||
await oneEvent(el.scrollContainer, 'scrollend');
|
||||
await intersectionObserverCallbacks();
|
||||
await el.updateComplete;
|
||||
|
||||
// Assert
|
||||
|
|
|
@ -3,6 +3,7 @@ import { classMap } from 'lit/directives/class-map.js';
|
|||
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry.js';
|
||||
import { getTabbableBoundary } from '../../internal/tabbable.js';
|
||||
import { html } from 'lit';
|
||||
import { ifDefined } from 'lit/directives/if-defined.js';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
import { property, query } from 'lit/decorators.js';
|
||||
import { waitForEvent } from '../../internal/event.js';
|
||||
|
@ -102,6 +103,11 @@ export default class SlDropdown extends ShoelaceElement {
|
|||
*/
|
||||
@property({ type: Boolean }) hoist = false;
|
||||
|
||||
/**
|
||||
* Syncs the popup width or height to that of the trigger element.
|
||||
*/
|
||||
@property({ reflect: true }) sync: 'width' | 'height' | 'both' | undefined = undefined;
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
|
||||
|
@ -409,6 +415,7 @@ export default class SlDropdown extends ShoelaceElement {
|
|||
shift
|
||||
auto-size="vertical"
|
||||
auto-size-padding="10"
|
||||
sync=${ifDefined(this.sync ? this.sync : undefined)}
|
||||
class=${classMap({
|
||||
dropdown: true,
|
||||
'dropdown--open': this.open
|
||||
|
|
|
@ -42,9 +42,21 @@ export default class SlIcon extends ShoelaceElement {
|
|||
let fileData: Response;
|
||||
|
||||
if (library?.spriteSheet) {
|
||||
return html`<svg part="svg">
|
||||
this.svg = html`<svg part="svg">
|
||||
<use part="use" href="${url}"></use>
|
||||
</svg>`;
|
||||
|
||||
// Using a templateResult requires the SVG to be written to the DOM first before we can grab the SVGElement
|
||||
// to be passed to the library's mutator function.
|
||||
await this.updateComplete;
|
||||
|
||||
const svg = this.shadowRoot!.querySelector("[part='svg']")!;
|
||||
|
||||
if (typeof library.mutator === 'function') {
|
||||
library.mutator(svg as SVGElement);
|
||||
}
|
||||
|
||||
return this.svg;
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -204,6 +204,10 @@ describe('<sl-icon>', () => {
|
|||
const rect = use?.getBoundingClientRect();
|
||||
expect(rect?.width).to.equal(0);
|
||||
expect(rect?.width).to.equal(0);
|
||||
|
||||
// Make sure the mutator is applied.
|
||||
// https://github.com/shoelace-style/shoelace/issues/1925
|
||||
expect(svg?.getAttribute('fill')).to.equal('currentColor');
|
||||
});
|
||||
|
||||
// TODO: <use> svg icons don't emit a "load" or "error" event...if we can figure out how to get the event to emit errors.
|
||||
|
|
|
@ -251,13 +251,16 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont
|
|||
}
|
||||
|
||||
private handleClearClick(event: MouseEvent) {
|
||||
this.value = '';
|
||||
this.emit('sl-clear');
|
||||
this.emit('sl-input');
|
||||
this.emit('sl-change');
|
||||
this.input.focus();
|
||||
event.preventDefault();
|
||||
|
||||
event.stopPropagation();
|
||||
if (this.value !== '') {
|
||||
this.value = '';
|
||||
this.emit('sl-clear');
|
||||
this.emit('sl-input');
|
||||
this.emit('sl-change');
|
||||
}
|
||||
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
private handleFocus() {
|
||||
|
@ -493,14 +496,11 @@ export default class SlInput extends ShoelaceElement implements ShoelaceFormCont
|
|||
@blur=${this.handleBlur}
|
||||
/>
|
||||
|
||||
${hasClearIcon
|
||||
${isClearIconVisible
|
||||
? html`
|
||||
<button
|
||||
part="clear-button"
|
||||
class=${classMap({
|
||||
input__clear: true,
|
||||
'input__clear--visible': isClearIconVisible
|
||||
})}
|
||||
class="input__clear"
|
||||
type="button"
|
||||
aria-label=${this.localize.term('clearEntry')}
|
||||
@click=${this.handleClearClick}
|
||||
|
|
|
@ -247,10 +247,6 @@ export default css`
|
|||
* Clearable + Password Toggle
|
||||
*/
|
||||
|
||||
.input__clear:not(.input__clear--visible) {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.input__clear,
|
||||
.input__password-toggle {
|
||||
display: inline-flex;
|
||||
|
@ -275,10 +271,6 @@ export default css`
|
|||
outline: none;
|
||||
}
|
||||
|
||||
.input--empty .input__clear {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
/* Don't show the browser's password toggle in Edge */
|
||||
::-ms-reveal {
|
||||
display: none;
|
||||
|
|
|
@ -46,6 +46,8 @@ import type { CSSResultGroup, PropertyValueMap } from 'lit';
|
|||
* @csspart item--selected - Applied when the tree item is selected.
|
||||
* @csspart indentation - The tree item's indentation container.
|
||||
* @csspart expand-button - The container that wraps the tree item's expand button and spinner.
|
||||
* @csspart spinner - The spinner that shows when a lazy tree item is in the loading state.
|
||||
* @csspart spinner__base - The spinner's base part.
|
||||
* @csspart label - The tree item's label.
|
||||
* @csspart children - The container that wraps the tree item's nested children.
|
||||
* @csspart checkbox - The checkbox that shows when using multiselect.
|
||||
|
@ -257,7 +259,10 @@ export default class SlTreeItem extends ShoelaceElement {
|
|||
})}
|
||||
aria-hidden="true"
|
||||
>
|
||||
${when(this.loading, () => html` <sl-spinner></sl-spinner> `)}
|
||||
${when(
|
||||
this.loading,
|
||||
() => html` <sl-spinner part="spinner" exportparts="base:spinner__base"></sl-spinner> `
|
||||
)}
|
||||
<slot class="tree-item__expand-icon-slot" name="expand-icon">
|
||||
<sl-icon library="system" name=${isRtl ? 'chevron-left' : 'chevron-right'}></sl-icon>
|
||||
</slot>
|
||||
|
|
|
@ -144,12 +144,16 @@ export default class SlTree extends ShoelaceElement {
|
|||
.forEach((status: 'expand' | 'collapse') => {
|
||||
const existingIcon = item.querySelector(`[slot="${status}-icon"]`);
|
||||
|
||||
const expandButtonIcon = this.getExpandButtonIcon(status);
|
||||
|
||||
if (!expandButtonIcon) return;
|
||||
|
||||
if (existingIcon === null) {
|
||||
// No separator exists, add one
|
||||
item.append(this.getExpandButtonIcon(status)!);
|
||||
item.append(expandButtonIcon);
|
||||
} else if (existingIcon.hasAttribute('data-default')) {
|
||||
// A default separator exists, replace it
|
||||
existingIcon.replaceWith(this.getExpandButtonIcon(status)!);
|
||||
existingIcon.replaceWith(expandButtonIcon);
|
||||
} else {
|
||||
// The user provided a custom icon, leave it alone
|
||||
}
|
||||
|
|
|
@ -752,4 +752,29 @@ describe('<sl-tree>', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
// https://github.com/shoelace-style/shoelace/issues/1916
|
||||
it("Should not render 'null' if it can't find a custom icon", async () => {
|
||||
const tree = await fixture<SlTree>(html`
|
||||
<sl-tree>
|
||||
<sl-tree-item>
|
||||
Item 1
|
||||
<sl-icon name="1-circle" slot="expand-icon"></sl-icon>
|
||||
<sl-tree-item> Item A </sl-tree-item>
|
||||
</sl-tree-item>
|
||||
<sl-tree-item>
|
||||
Item 2
|
||||
<sl-tree-item>Item A</sl-tree-item>
|
||||
<sl-tree-item>Item B</sl-tree-item>
|
||||
</sl-tree-item>
|
||||
<sl-tree-item>
|
||||
Item 3
|
||||
<sl-tree-item>Item A</sl-tree-item>
|
||||
<sl-tree-item>Item B</sl-tree-item>
|
||||
</sl-tree-item>
|
||||
</sl-tree>
|
||||
`);
|
||||
|
||||
expect(tree.textContent).to.not.includes('null');
|
||||
});
|
||||
});
|
||||
|
|
Ładowanie…
Reference in New Issue