Merge branch 'next' into autoload

autoload
Cory LaViska 2023-03-02 10:49:30 -05:00
commit a27fd4d2e9
13 zmienionych plików z 145 dodań i 46 usunięć

Wyświetl plik

@ -582,12 +582,19 @@ The content of the carousel can be changed by appending or removing carousel ite
slide.innerText = `Slide ${dynamicCarousel.children.length + 1}`;
slide.style.setProperty('background', `var(--sl-color-${color}-200)`);
dynamicCarousel.appendChild(slide);
dynamicRemove.disabled = false;
};
const removeSlide = () => {
const slide = dynamicCarousel.children[dynamicCarousel.children.length - 1];
slide.remove();
colorIndex--;
const numSlides = dynamicCarousel.querySelectorAll('sl-carousel-item').length;
if (numSlides > 1) {
slide.remove();
colorIndex--;
}
dynamicRemove.disabled = numSlides - 1 <= 1;
};
dynamicAdd.addEventListener('click', addSlide);

Wyświetl plik

@ -10,9 +10,14 @@ New versions of Shoelace are released as-needed and generally occur when a criti
- Added an experimental autoloader
- Added the `subpath` argument to `getBasePath()` to make it easier to generate full paths to any file
- Added `tag__base`, `tag__content`, `tag__remove-button`, `tag__remove-button__base` parts to `<sl-select>`
## 2.2.0
- Added TypeScript types to all custom events [#1183](https://github.com/shoelace-style/shoelace/pull/1183)
- Added the `svg` part to `<sl-icon>`
- Added the `getForm()` method to all form controls [#1180](https://github.com/shoelace-style/shoelace/issues/1180)
- Added the experimental carousel component [#851](https://github.com/shoelace-style/shoelace/pull/851)
- Fixed a bug in `<sl-select>` that caused the display label to render incorrectly in Chrome after form validation [#1197](https://github.com/shoelace-style/shoelace/discussions/1197)
- Fixed a bug in `<sl-input>` that prevented users from applying their own value for `autocapitalize`, `autocomplete`, and `autocorrect` when using `type="password` [#1205](https://github.com/shoelace-style/shoelace/issues/1205)
- Fixed a bug in `<sl-tab-group>` that prevented scroll controls from showing when dynamically adding tabs [#1208](https://github.com/shoelace-style/shoelace/issues/1208)
@ -22,6 +27,7 @@ New versions of Shoelace are released as-needed and generally occur when a criti
- Fixed a bug in `<sl-menu-item>` that caused the focus color to show when selecting menu items with a mouse or touch device
- Fixed a bug in `<sl-select>` that caused `sl-change` and `sl-input` to be emitted too early [#1201](https://github.com/shoelace-style/shoelace/issues/1201)
- Fixed a positioning edge case that caused `<sl-popup>` to positioned nested popups incorrectly [#1135](https://github.com/shoelace-style/shoelace/issues/1135)
- Fixed a bug in `<sl-tree>` that caused the tree item to collapse when clicking a child item, dragging the mouse, and releasing it on the parent node [#1082](https://github.com/shoelace-style/shoelace/issues/1082)
- Updated `@shoelace-style/localize` to 3.1.0
- Updated `@floating-ui/dom` to 1.2.1

4
package-lock.json wygenerowano
Wyświetl plik

@ -1,12 +1,12 @@
{
"name": "@shoelace-style/shoelace",
"version": "2.1.0",
"version": "2.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@shoelace-style/shoelace",
"version": "2.1.0",
"version": "2.2.0",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.5.0",

Wyświetl plik

@ -1,7 +1,7 @@
{
"name": "@shoelace-style/shoelace",
"description": "A forward-thinking library of web components.",
"version": "2.1.0",
"version": "2.2.0",
"homepage": "https://github.com/shoelace-style/shoelace",
"author": "Cory LaViska",
"license": "MIT",

Wyświetl plik

@ -8,6 +8,7 @@ export class AutoplayController implements ReactiveController {
private host: ReactiveElement;
private timerId = 0;
private tickCallback: () => void;
private activeInteractions = 0;
paused = false;
stopped = true;
@ -57,12 +58,16 @@ export class AutoplayController implements ReactiveController {
}
pause = () => {
this.paused = true;
this.host.requestUpdate();
if (!this.activeInteractions++) {
this.paused = true;
this.host.requestUpdate();
}
};
resume = () => {
this.paused = false;
this.host.requestUpdate();
if (!--this.activeInteractions) {
this.paused = false;
this.host.requestUpdate();
}
};
}

Wyświetl plik

@ -28,8 +28,8 @@ export default css`
.carousel__pagination {
grid-area: pagination;
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: var(--sl-spacing-small);
}
@ -64,6 +64,7 @@ export default css`
scroll-snap-type: x mandatory;
scroll-padding-inline: var(--scroll-hint);
padding-inline: var(--scroll-hint);
overflow-y: hidden;
}
.carousel__slides--vertical {
@ -74,6 +75,7 @@ export default css`
scroll-snap-type: y mandatory;
scroll-padding-block: var(--scroll-hint);
padding-block: var(--scroll-hint);
overflow-x: hidden;
}
.carousel__slides--dragging,
@ -140,6 +142,8 @@ export default css`
background-color: var(--sl-color-neutral-300);
will-change: transform;
transition: var(--sl-transition-fast) ease-in;
padding: 0;
margin: 0;
}
.carousel__pagination-item--active {

Wyświetl plik

@ -78,6 +78,34 @@ describe('<sl-carousel>', () => {
// Assert
expect(el.next).not.to.have.been.called;
});
it('should not resume if the user is still interacting', async () => {
// Arrange
const el = await fixture<SlCarousel>(html`
<sl-carousel autoplay autoplay-interval="10">
<sl-carousel-item>Node 1</sl-carousel-item>
<sl-carousel-item>Node 2</sl-carousel-item>
<sl-carousel-item>Node 3</sl-carousel-item>
</sl-carousel>
`);
sinon.stub(el, 'next');
await el.updateComplete;
// Act
el.dispatchEvent(new Event('mouseenter'));
el.dispatchEvent(new Event('focusin'));
await el.updateComplete;
el.dispatchEvent(new Event('mouseleave'));
await el.updateComplete;
clock.next();
clock.next();
// Assert
expect(el.next).not.to.have.been.called;
});
});
describe('when `loop` attribute is provided', () => {

Wyświetl plik

@ -168,13 +168,21 @@ export default class SlCarousel extends ShoelaceElement {
goToSlide(index: number, behavior: ScrollBehavior = 'smooth') {
const { slidesPerPage, loop } = this;
const slides = this.getSlides();
const slidesWithClones = this.getSlides({ excludeClones: false });
const normalizedIndex = clamp(index + (loop ? slidesPerPage : 0), 0, slidesWithClones.length - 1);
const slide = slidesWithClones[normalizedIndex];
// Sets the next index without taking into account clones, if any.
const newActiveSlide = (index + slides.length) % slides.length;
this.activeSlide = newActiveSlide;
// Get the index of the next slide. For looping carousel it adds `slidesPerPage`
// to normalize the starting index in order to ignore the first nth clones.
const nextSlideIndex = clamp(index + (loop ? slidesPerPage : 0), 0, slidesWithClones.length - 1);
const nextSlide = slidesWithClones[nextSlideIndex];
this.scrollContainer.scrollTo({
left: slide.offsetLeft,
top: slide.offsetTop,
left: nextSlide.offsetLeft,
top: nextSlide.offsetTop,
behavior: prefersReducedMotion() ? 'auto' : behavior
});
}
@ -307,13 +315,18 @@ export default class SlCarousel extends ShoelaceElement {
this.scrollController.mouseDragging = this.mouseDragging;
}
private renderPagination = () => {
const slides = this.getSlides();
const slidesCount = slides.length;
private getPageCount() {
return Math.ceil(this.getSlides().length / this.slidesPerPage);
}
const { activeSlide, slidesPerPage } = this;
const pagesCount = Math.ceil(slidesCount / slidesPerPage);
const currentPage = Math.floor(activeSlide / slidesPerPage);
private getCurrentPage() {
return Math.floor(this.activeSlide / this.slidesPerPage);
}
private renderPagination = () => {
const { slidesPerPage } = this;
const pagesCount = this.getPageCount();
const currentPage = this.getCurrentPage();
return html`
<nav part="pagination" role="tablist" class="carousel__pagination" aria-controls="scroll-container">
@ -338,11 +351,11 @@ export default class SlCarousel extends ShoelaceElement {
};
private renderNavigation = () => {
const { loop, activeSlide } = this;
const slides = this.getSlides();
const slidesCount = slides.length;
const prevEnabled = loop || activeSlide > 0;
const nextEnabled = loop || activeSlide < slidesCount - 1;
const { loop } = this;
const pagesCount = this.getPageCount();
const currentPage = this.getCurrentPage();
const prevEnabled = loop || currentPage > 0;
const nextEnabled = loop || currentPage < pagesCount - 1;
const isLtr = this.localize.dir() === 'ltr';
return html`

Wyświetl plik

@ -77,6 +77,8 @@ export class ScrollController<T extends ScrollHost> implements ReactiveControlle
})
);
this.host.requestUpdate();
} else {
this.handleScrollEnd();
}
}

Wyświetl plik

@ -59,6 +59,10 @@ import type SlRemoveEvent from '../../events/sl-remove';
* @csspart listbox - The listbox container where options are slotted.
* @csspart tags - The container that houses option tags when `multiselect` is used.
* @csspart tag - The individual tags that represent each multiselect option.
* @csspart tag__base - The tag's base part.
* @csspart tag__content - The tag's content part.
* @csspart tag__remove-button - The tag's remove button.
* @csspart tag__remove-button__base - The tag's remove button base part.
* @csspart clear-button - The clear button.
* @csspart expand-icon - The container that wraps the expand icon.
*/
@ -766,6 +770,12 @@ export default class SlSelect extends ShoelaceElement implements ShoelaceFormCon
return html`
<sl-tag
part="tag"
exportparts="
base:tag__base,
content:tag__content,
remove-button:tag__remove-button,
remove-button__base:tag__remove-button__base
"
?pill=${this.pill}
size=${this.size}
removable

Wyświetl plik

@ -1,4 +1,5 @@
import { expect, fixture, html, triggerBlurFor, triggerFocusFor } from '@open-wc/testing';
import { aTimeout, expect, fixture, html, triggerBlurFor, triggerFocusFor } from '@open-wc/testing';
import { clickOnElement } from '../../internal/test';
import { sendKeys } from '@web/test-runner-commands';
import sinon from 'sinon';
import type SlTree from './tree';
@ -433,7 +434,7 @@ describe('<sl-tree>', () => {
const expandButton: HTMLElement = node.shadowRoot!.querySelector('.tree-item__expand-button')!;
// Act
expandButton.click();
await clickOnElement(expandButton);
await el.updateComplete;
// Assert
@ -453,10 +454,10 @@ describe('<sl-tree>', () => {
await el.updateComplete;
// Act
node0.click();
await clickOnElement(node0);
await el.updateComplete;
node1.click();
await clickOnElement(node1);
await el.updateComplete;
// Assert
@ -474,10 +475,10 @@ describe('<sl-tree>', () => {
await el.updateComplete;
// Act
node0.click();
await clickOnElement(node0);
await el.updateComplete;
node1.click();
await clickOnElement(node1);
await el.updateComplete;
// Assert
@ -492,7 +493,7 @@ describe('<sl-tree>', () => {
await el.updateComplete;
// Act
parentNode.click();
await clickOnElement(parentNode);
await parentNode.updateComplete;
// Assert
@ -511,10 +512,10 @@ describe('<sl-tree>', () => {
await el.updateComplete;
// Act
node0.click();
await clickOnElement(node0);
await el.updateComplete;
node1.click();
await clickOnElement(node1);
await el.updateComplete;
// Assert
@ -529,7 +530,7 @@ describe('<sl-tree>', () => {
const parentNode = el.children[2] as SlTreeItem;
// Act
parentNode.click();
await clickOnElement(parentNode);
await el.updateComplete;
// Assert
@ -549,7 +550,10 @@ describe('<sl-tree>', () => {
const childNode = parentNode.children[0] as SlTreeItem;
// Act
childNode.click();
parentNode.expanded = true;
await parentNode.updateComplete;
await aTimeout(300);
await clickOnElement(childNode);
await el.updateComplete;
// Assert
@ -572,9 +576,9 @@ describe('<sl-tree>', () => {
const node = el.children[0] as SlTreeItem;
// Act
node.click();
await clickOnElement(node);
await el.updateComplete;
node.click();
await clickOnElement(node);
await Promise.all([node.updateComplete, el.updateComplete]);
// Assert
@ -598,9 +602,9 @@ describe('<sl-tree>', () => {
const node = el.children[0] as SlTreeItem;
// Act
node.click();
await clickOnElement(node);
await el.updateComplete;
node.click();
await clickOnElement(node);
await Promise.all([node.updateComplete, el.updateComplete]);
// Assert
@ -621,7 +625,7 @@ describe('<sl-tree>', () => {
const node = el.querySelector<SlTreeItem>('#expandable')!;
// Act
node.click();
await clickOnElement(node);
await Promise.all([node.updateComplete, el.updateComplete]);
// Assert
@ -643,9 +647,9 @@ describe('<sl-tree>', () => {
const node = el.children[0] as SlTreeItem;
// Act
node.click();
await clickOnElement(node);
await Promise.all([node.updateComplete, el.updateComplete]);
node.click();
await clickOnElement(node);
await Promise.all([node.updateComplete, el.updateComplete]);
// Assert

Wyświetl plik

@ -90,6 +90,7 @@ export default class SlTree extends ShoelaceElement {
private lastFocusedItem: SlTreeItem;
private readonly localize = new LocalizeController(this);
private mutationObserver: MutationObserver;
private clickTarget: SlTreeItem | null = null;
async connectedCallback() {
super.connectedCallback();
@ -292,13 +293,20 @@ export default class SlTree extends ShoelaceElement {
}
private handleClick(event: Event) {
const target = event.target as HTMLElement;
const target = event.target as SlTreeItem;
const treeItem = target.closest('sl-tree-item')!;
const isExpandButton = event
.composedPath()
.some((el: HTMLElement) => el?.classList?.contains('tree-item__expand-button'));
if (!treeItem || treeItem.disabled) {
//
// Don't Do anything if there's no tree item, if it's disabled, or if the click doesn't match the initial target
// from mousedown. The latter case prevents the user from starting a click on one item and ending it on another,
// causing the parent node to collapse.
//
// See https://github.com/shoelace-style/shoelace/issues/1082
//
if (!treeItem || treeItem.disabled || target !== this.clickTarget) {
return;
}
@ -309,6 +317,11 @@ export default class SlTree extends ShoelaceElement {
}
}
handleMouseDown(event: MouseEvent) {
// Record the click target so we know which item the click initially targeted
this.clickTarget = event.target as SlTreeItem;
}
private handleFocusOut(event: FocusEvent) {
const relatedTarget = event.relatedTarget as HTMLElement;
@ -392,7 +405,13 @@ export default class SlTree extends ShoelaceElement {
render() {
return html`
<div part="base" class="tree" @click=${this.handleClick} @keydown=${this.handleKeyDown}>
<div
part="base"
class="tree"
@click=${this.handleClick}
@keydown=${this.handleKeyDown}
@mousedown=${this.handleMouseDown}
>
<slot @slotchange=${this.handleSlotChange}></slot>
<slot name="expand-icon" hidden aria-hidden="true"> </slot>
<slot name="collapse-icon" hidden aria-hidden="true"> </slot>

Wyświetl plik

@ -50,6 +50,7 @@ export async function clickOnElement(
await sendMouse({ type: 'click', position: [clickX, clickY] });
}
/** A testing utility that moves the mouse onto an element. */
export async function moveMouseOnElement(
/** The element to click */
el: Element,