kopia lustrzana https://github.com/shoelace-style/shoelace
Merge branch 'next' into current
commit
0ac75df8fe
.github/workflows
docs/pages
src
components
internal
translations
|
@ -11,15 +11,14 @@ on:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [18.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Update system packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade -y
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
|
|
|
@ -155,4 +155,20 @@ const App = () => (
|
|||
);
|
||||
```
|
||||
|
||||
### Tailwind users
|
||||
|
||||
Using TailwindCSS with Shoelace [may override divider styles](https://github.com/shoelace-style/shoelace/issues/2088), making them invisible. As a workaround, add this to your Tailwind config file.
|
||||
|
||||
```css
|
||||
@layer base {
|
||||
sl-divider:not([vertical]) {
|
||||
border-top: solid var(--width) var(--color);
|
||||
}
|
||||
|
||||
sl-divider[vertical] {
|
||||
border-left: solid var(--width) var(--color);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
{% endraw %}
|
||||
|
|
|
@ -200,20 +200,22 @@ const App = () => (
|
|||
|
||||
### Snapping
|
||||
|
||||
To snap panels at specific positions while dragging, add the `snap` attribute with one or more space-separated values. Values must be in pixels or percentages. For example, to snap the panel at `100px` and `50%`, use `snap="100px 50%"`. You can also customize how close the divider must be before snapping with the `snap-threshold` attribute.
|
||||
To snap panels at specific positions while dragging, you can use the `snap` attribute. You can provide one or more space-separated pixel or percentage values, either as single values or within a `repeat()` expression, which will be repeated along the length of the panel. You can also customize how close the divider must be before snapping with the `snap-threshold` attribute.
|
||||
|
||||
For example, to snap the panel at `100px` and `50%`, use `snap="100px 50%"`.
|
||||
|
||||
```html:preview
|
||||
<div class="split-panel-snapping">
|
||||
<sl-split-panel snap="100px 50%">
|
||||
<div
|
||||
slot="start"
|
||||
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
>
|
||||
Start
|
||||
</div>
|
||||
<div
|
||||
slot="end"
|
||||
style="height: 200px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
>
|
||||
End
|
||||
</div>
|
||||
|
@ -239,16 +241,106 @@ To snap panels at specific positions while dragging, add the `snap` attribute wi
|
|||
transform: translateX(-3px);
|
||||
}
|
||||
|
||||
.split-panel-snapping-dots::before {
|
||||
.split-panel-snapping .split-panel-snapping-dots::before {
|
||||
left: 100px;
|
||||
}
|
||||
|
||||
.split-panel-snapping-dots::after {
|
||||
.split-panel-snapping .split-panel-snapping-dots::after {
|
||||
left: 50%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
Or, if you want to snap the panel to every `100px` interval, as well as at 50% of the panel's size, you can use `snap="repeat(100px) 50%"`.
|
||||
|
||||
```html:preview
|
||||
<div class="split-panel-snapping-repeat">
|
||||
<sl-split-panel snap="repeat(100px) 50%">
|
||||
<div
|
||||
slot="start"
|
||||
style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
>
|
||||
Start
|
||||
</div>
|
||||
<div
|
||||
slot="end"
|
||||
style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
>
|
||||
End
|
||||
</div>
|
||||
</sl-split-panel>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.split-panel-snapping-repeat {
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### Using a Custom Snap Function
|
||||
|
||||
You can also implement a custom snap function which controls the snapping manually. To do this, you need to acquire a reference to the element in Javascript and set the `snap` property. For example, if you want to snap the divider to either `100px` from the left or `100px` from the right, you can set the `snap` property to a function encoding that logic.
|
||||
|
||||
```js
|
||||
panel.snap = ({ pos, size }) => (pos < size / 2 ? 100 : size - 100);
|
||||
```
|
||||
|
||||
Note that the `snap-threshold` property will not automatically be applied if `snap` is set to a function. Instead, the function itself must handle applying the threshold if desired, and is passed a `snapThreshold` member with its parameters.
|
||||
|
||||
```html:preview
|
||||
<div class="split-panel-snapping-fn">
|
||||
<sl-split-panel>
|
||||
<div
|
||||
slot="start"
|
||||
style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
>
|
||||
Start
|
||||
</div>
|
||||
<div
|
||||
slot="end"
|
||||
style="height: 150px; background: var(--sl-color-neutral-50); display: flex; align-items: center; justify-content: center; overflow: hidden;"
|
||||
>
|
||||
End
|
||||
</div>
|
||||
</sl-split-panel>
|
||||
|
||||
<div class="split-panel-snapping-dots"></div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.split-panel-snapping-fn {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.split-panel-snapping-fn .split-panel-snapping-dots::before,
|
||||
.split-panel-snapping-fn .split-panel-snapping-dots::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--sl-color-neutral-400);
|
||||
transform: translateX(-3px);
|
||||
}
|
||||
|
||||
.split-panel-snapping-fn .split-panel-snapping-dots::before {
|
||||
left: 100px;
|
||||
}
|
||||
|
||||
.split-panel-snapping-fn .split-panel-snapping-dots::after {
|
||||
left: calc(100% - 100px);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const container = document.querySelector('.split-panel-snapping-fn');
|
||||
const splitPanel = container.querySelector('sl-split-panel');
|
||||
splitPanel.snap = ({ pos, size }) => (pos < size / 2) ? 100 : (size - 100);
|
||||
</script>
|
||||
```
|
||||
|
||||
{% raw %}
|
||||
|
||||
```jsx:react
|
||||
|
|
|
@ -57,10 +57,21 @@ If you'd rather not use the CDN for assets, you can create a build task that cop
|
|||
One caveat is there's currently Svelte only supports `bind:value` directive in `<input>`, `<textarea>` and `<select>`, but you can still achieve two-way binding manually.
|
||||
|
||||
```jsx
|
||||
// This doesn't work
|
||||
// ❌ These do not work
|
||||
<sl-input bind:value="name"></sl-input>
|
||||
// This works, but it's a bit longer
|
||||
<sl-input value={name} oninput={event => message = event.target.value}></sl-input>
|
||||
|
||||
<sl-select bind:value="job">
|
||||
<sl-option value="designer">Designer</sl-option>
|
||||
<sl-option value="developer">Developer</sl-option>
|
||||
</sl-select>
|
||||
|
||||
// ✅ These are a bit longer, but work
|
||||
<sl-input value={name} oninput={event => name = event.target.value}></sl-input>
|
||||
|
||||
<sl-select value={job} onsl-input={event => job = event.target.value}>
|
||||
<sl-option value="designer">Designer</sl-option>
|
||||
<sl-option value="developer">Developer</sl-option>
|
||||
</sl-select>
|
||||
```
|
||||
|
||||
:::tip
|
||||
|
|
|
@ -67,9 +67,9 @@ When binding complex data such as objects and arrays, use the `.prop` modifier t
|
|||
One caveat is there's currently [no support for v-model on custom elements](https://github.com/vuejs/vue/issues/7830), but you can still achieve two-way binding manually.
|
||||
|
||||
```html
|
||||
<!-- This doesn't work -->
|
||||
<!-- ❌ This doesn't work -->
|
||||
<sl-input v-model="name"></sl-input>
|
||||
<!-- This works, but it's a bit longer -->
|
||||
<!-- ✅ This works, but it's a bit longer -->
|
||||
<sl-input :value="name" @input="name = $event.target.value"></sl-input>
|
||||
```
|
||||
|
||||
|
|
|
@ -100,9 +100,9 @@ When binding complex data such as objects and arrays, use the `.prop` modifier t
|
|||
One caveat is there's currently [no support for v-model on custom elements](https://github.com/vuejs/vue/issues/7830), but you can still achieve two-way binding manually.
|
||||
|
||||
```html
|
||||
<!-- This doesn't work -->
|
||||
<!-- ❌ This doesn't work -->
|
||||
<sl-input v-model="name"></sl-input>
|
||||
<!-- This works, but it's a bit longer -->
|
||||
<!-- ✅ This works, but it's a bit longer -->
|
||||
<sl-input :value="name" @input="name = $event.target.value"></sl-input>
|
||||
```
|
||||
|
||||
|
|
|
@ -12,6 +12,15 @@ Components with the <sl-badge variant="warning" pill>Experimental</sl-badge> bad
|
|||
|
||||
New versions of Shoelace are released as-needed and generally occur when a critical mass of changes have accumulated. At any time, you can see what's coming in the next release by visiting [next.shoelace.style](https://next.shoelace.style).
|
||||
|
||||
## 2.20.0
|
||||
|
||||
- Added the ability to set a custom snap function and use `repeat(n)` to `<sl-split-panel>` [#2340]
|
||||
- Fixed a bug with radios in `<sl-dialog>` focus trapping.
|
||||
- Improved performance of `<sl-select>` when using a large number of options [#2318]
|
||||
- Improved `<sl-alert>` to create the toast stack when used only, making it usable in SSR environments [#2359]
|
||||
- Improved `scrollend-polyfill` so it only runs on the client to make it usable in SSR environments [#2359]
|
||||
- Updated the Japanese translation [#2329]
|
||||
|
||||
## 2.19.1
|
||||
|
||||
- Fixed a bug in `<sl-tab-group>` that prevented changing tabs by setting `active` on `<sl-tab>` elements [#2298]
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "@shoelace-style/shoelace",
|
||||
"version": "2.19.1",
|
||||
"version": "2.20.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@shoelace-style/shoelace",
|
||||
"version": "2.19.1",
|
||||
"version": "2.20.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@shoelace-style/shoelace",
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "2.19.1",
|
||||
"version": "2.20.0",
|
||||
"homepage": "https://github.com/shoelace-style/shoelace",
|
||||
"author": "Cory LaViska",
|
||||
"license": "MIT",
|
||||
|
|
|
@ -13,8 +13,6 @@ import SlIconButton from '../icon-button/icon-button.component.js';
|
|||
import styles from './alert.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
|
||||
|
||||
/**
|
||||
* @summary Alerts are used to display important messages inline or as toast notifications.
|
||||
* @documentation https://shoelace.style/components/alert
|
||||
|
@ -50,6 +48,17 @@ export default class SlAlert extends ShoelaceElement {
|
|||
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
private static currentToastStack: HTMLDivElement;
|
||||
|
||||
private static get toastStack() {
|
||||
if (!this.currentToastStack) {
|
||||
this.currentToastStack = Object.assign(document.createElement('div'), {
|
||||
className: 'sl-toast-stack'
|
||||
});
|
||||
}
|
||||
return this.currentToastStack;
|
||||
}
|
||||
|
||||
@query('[part~="base"]') base: HTMLElement;
|
||||
|
||||
@query('.alert__countdown-elapsed') countdownElement: HTMLElement;
|
||||
|
@ -195,11 +204,11 @@ export default class SlAlert extends ShoelaceElement {
|
|||
async toast() {
|
||||
return new Promise<void>(resolve => {
|
||||
this.handleCountdownChange();
|
||||
if (toastStack.parentElement === null) {
|
||||
document.body.append(toastStack);
|
||||
if (SlAlert.toastStack.parentElement === null) {
|
||||
document.body.append(SlAlert.toastStack);
|
||||
}
|
||||
|
||||
toastStack.appendChild(this);
|
||||
SlAlert.toastStack.appendChild(this);
|
||||
|
||||
// Wait for the toast stack to render
|
||||
requestAnimationFrame(() => {
|
||||
|
@ -211,12 +220,12 @@ export default class SlAlert extends ShoelaceElement {
|
|||
this.addEventListener(
|
||||
'sl-after-hide',
|
||||
() => {
|
||||
toastStack.removeChild(this);
|
||||
SlAlert.toastStack.removeChild(this);
|
||||
resolve();
|
||||
|
||||
// Remove the toast stack from the DOM when there are no more alerts
|
||||
if (toastStack.querySelector('sl-alert') === null) {
|
||||
toastStack.remove();
|
||||
if (SlAlert.toastStack.querySelector('sl-alert') === null) {
|
||||
SlAlert.toastStack.remove();
|
||||
}
|
||||
},
|
||||
{ once: true }
|
||||
|
|
|
@ -34,6 +34,8 @@ export default class SlOption extends ShoelaceElement {
|
|||
// @ts-expect-error - Controller is currently unused
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
private isInitialized = false;
|
||||
|
||||
@query('.option__label') defaultSlot: HTMLSlotElement;
|
||||
|
||||
@state() current = false; // the user has keyed into the option, but hasn't selected it yet (shows a highlight)
|
||||
|
@ -57,13 +59,17 @@ export default class SlOption extends ShoelaceElement {
|
|||
}
|
||||
|
||||
private handleDefaultSlotChange() {
|
||||
// When the label changes, tell the controller to update
|
||||
customElements.whenDefined('sl-select').then(() => {
|
||||
const controller = this.closest('sl-select');
|
||||
if (controller) {
|
||||
controller.handleDefaultSlotChange();
|
||||
}
|
||||
});
|
||||
if (this.isInitialized) {
|
||||
// When the label changes, tell the controller to update
|
||||
customElements.whenDefined('sl-select').then(() => {
|
||||
const controller = this.closest('sl-select');
|
||||
if (controller) {
|
||||
controller.handleDefaultSlotChange();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
private handleMouseEnter() {
|
||||
|
|
|
@ -10,6 +10,25 @@ import ShoelaceElement from '../../internal/shoelace-element.js';
|
|||
import styles from './split-panel.styles.js';
|
||||
import type { CSSResultGroup } from 'lit';
|
||||
|
||||
export interface SnapFunctionParams {
|
||||
/** The position the divider has been dragged to, in pixels. */
|
||||
pos: number;
|
||||
/** The size of the split-panel across its primary axis, in pixels. */
|
||||
size: number;
|
||||
/** The snap-threshold passed to the split-panel, in pixels. May be infinity. */
|
||||
snapThreshold: number;
|
||||
/** Whether or not the user-agent is RTL. */
|
||||
isRtl: boolean;
|
||||
/** Whether or not the split panel is vertical. */
|
||||
vertical: boolean;
|
||||
}
|
||||
|
||||
/** Used by sl-split-panel to convert an input position into a snapped position. */
|
||||
export type SnapFunction = (opt: SnapFunctionParams) => number | null;
|
||||
|
||||
/** A SnapFunction which performs no snapping. */
|
||||
export const SNAP_NONE = () => null;
|
||||
|
||||
/**
|
||||
* @summary Split panels display two adjacent panels, allowing the user to reposition them.
|
||||
* @documentation https://shoelace.style/components/split-panel
|
||||
|
@ -67,11 +86,72 @@ export default class SlSplitPanel extends ShoelaceElement {
|
|||
*/
|
||||
@property() primary?: 'start' | 'end';
|
||||
|
||||
// Returned when the property is queried, so that string 'snap's are preserved.
|
||||
private snapValue: string | SnapFunction = '';
|
||||
// Actually used for computing snap points. All string snaps are converted via `toSnapFunction`.
|
||||
private snapFunction: SnapFunction = SNAP_NONE;
|
||||
|
||||
/**
|
||||
* One or more space-separated values at which the divider should snap. Values can be in pixels or percentages, e.g.
|
||||
* `"100px 50%"`.
|
||||
* Converts a string containing either a series of fixed/repeated snap points (e.g. "repeat(20%)", "100px 200px 800px", or "10% 50% repeat(10px)") into a SnapFunction. `SnapFunction`s take in a `SnapFunctionOpts` and return the position that the split panel should snap to.
|
||||
*
|
||||
* @param snap - The snap string.
|
||||
* @returns a `SnapFunction` representing the snap string's logic.
|
||||
*/
|
||||
@property() snap?: string;
|
||||
private toSnapFunction(snap: string): SnapFunction {
|
||||
const snapPoints = snap.split(' ');
|
||||
|
||||
return ({ pos, size, snapThreshold, isRtl, vertical }) => {
|
||||
let newPos = pos;
|
||||
let minDistance = Number.POSITIVE_INFINITY;
|
||||
|
||||
snapPoints.forEach(value => {
|
||||
let snapPoint: number;
|
||||
|
||||
if (value.startsWith('repeat(')) {
|
||||
const repeatVal = snap.substring('repeat('.length, snap.length - 1);
|
||||
const isPercent = repeatVal.endsWith('%');
|
||||
const repeatNum = Number.parseFloat(repeatVal);
|
||||
const snapIntervalPx = isPercent ? size * (repeatNum / 100) : repeatNum;
|
||||
snapPoint = Math.round((isRtl && !vertical ? size - pos : pos) / snapIntervalPx) * snapIntervalPx;
|
||||
} else if (value.endsWith('%')) {
|
||||
snapPoint = size * (Number.parseFloat(value) / 100);
|
||||
} else {
|
||||
snapPoint = Number.parseFloat(value);
|
||||
}
|
||||
|
||||
if (isRtl && !vertical) {
|
||||
snapPoint = size - snapPoint;
|
||||
}
|
||||
|
||||
const distance = Math.abs(pos - snapPoint);
|
||||
|
||||
if (distance <= snapThreshold && distance < minDistance) {
|
||||
newPos = snapPoint;
|
||||
minDistance = distance;
|
||||
}
|
||||
});
|
||||
|
||||
return newPos;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Either one or more space-separated values at which the divider should snap, in pixels, percentages, or repeat expressions e.g. `'100px 50% 500px' or `repeat(50%) 10px`,
|
||||
* or a function which takes in a `SnapFunctionParams`, and returns a position to snap to, e.g. `({ pos }) => Math.round(pos / 8) * 8`.
|
||||
*/
|
||||
@property({ reflect: true })
|
||||
set snap(snap: string | SnapFunction | null | undefined) {
|
||||
this.snapValue = snap ?? '';
|
||||
if (snap) {
|
||||
this.snapFunction = typeof snap === 'string' ? this.toSnapFunction(snap) : snap;
|
||||
} else {
|
||||
this.snapFunction = SNAP_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
get snap(): string | SnapFunction {
|
||||
return this.snapValue;
|
||||
}
|
||||
|
||||
/** How close the divider must be to a snap point until snapping occurs. */
|
||||
@property({ type: Number, attribute: 'snap-threshold' }) snapThreshold = 12;
|
||||
|
@ -125,30 +205,14 @@ export default class SlSplitPanel extends ShoelaceElement {
|
|||
}
|
||||
|
||||
// Check snap points
|
||||
if (this.snap) {
|
||||
const snaps = this.snap.split(' ');
|
||||
|
||||
snaps.forEach(value => {
|
||||
let snapPoint: number;
|
||||
|
||||
if (value.endsWith('%')) {
|
||||
snapPoint = this.size * (parseFloat(value) / 100);
|
||||
} else {
|
||||
snapPoint = parseFloat(value);
|
||||
}
|
||||
|
||||
if (isRtl && !this.vertical) {
|
||||
snapPoint = this.size - snapPoint;
|
||||
}
|
||||
|
||||
if (
|
||||
newPositionInPixels >= snapPoint - this.snapThreshold &&
|
||||
newPositionInPixels <= snapPoint + this.snapThreshold
|
||||
) {
|
||||
newPositionInPixels = snapPoint;
|
||||
}
|
||||
});
|
||||
}
|
||||
newPositionInPixels =
|
||||
this.snapFunction({
|
||||
pos: newPositionInPixels,
|
||||
size: this.size,
|
||||
snapThreshold: this.snapThreshold,
|
||||
isRtl: isRtl,
|
||||
vertical: this.vertical
|
||||
}) ?? newPositionInPixels;
|
||||
|
||||
this.position = clamp(this.pixelsToPercentage(newPositionInPixels), 0, 100);
|
||||
},
|
||||
|
|
|
@ -26,54 +26,61 @@ const decorate = <T, M extends keyof T>(
|
|||
} as MethodOf<T, M>;
|
||||
};
|
||||
|
||||
const isSupported = 'onscrollend' in window;
|
||||
(() => {
|
||||
// SSR environments should not apply the polyfill
|
||||
if (typeof window === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSupported) {
|
||||
const pointers = new Set();
|
||||
const scrollHandlers = new WeakMap<EventTarget, EventListenerOrEventListenerObject>();
|
||||
const isSupported = 'onscrollend' in window;
|
||||
|
||||
const handlePointerDown = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.add(touch.identifier);
|
||||
}
|
||||
};
|
||||
if (!isSupported) {
|
||||
const pointers = new Set();
|
||||
const scrollHandlers = new WeakMap<EventTarget, EventListenerOrEventListenerObject>();
|
||||
|
||||
const handlePointerUp = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.delete(touch.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('touchstart', handlePointerDown, true);
|
||||
document.addEventListener('touchend', handlePointerUp, true);
|
||||
document.addEventListener('touchcancel', handlePointerUp, true);
|
||||
|
||||
decorate(EventTarget.prototype, 'addEventListener', function (this: EventTarget, addEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
|
||||
const handleScrollEnd = debounce(() => {
|
||||
if (!pointers.size) {
|
||||
// If no pointer is active in the scroll area then the scroll has ended
|
||||
this.dispatchEvent(new Event('scrollend'));
|
||||
} else {
|
||||
// otherwise let's wait a bit more
|
||||
handleScrollEnd();
|
||||
const handlePointerDown = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.add(touch.identifier);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
addEventListener.call(this, 'scroll', handleScrollEnd, { passive: true });
|
||||
scrollHandlers.set(this, handleScrollEnd);
|
||||
});
|
||||
const handlePointerUp = (event: TouchEvent) => {
|
||||
for (const touch of event.changedTouches) {
|
||||
pointers.delete(touch.identifier);
|
||||
}
|
||||
};
|
||||
|
||||
decorate(EventTarget.prototype, 'removeEventListener', function (this: EventTarget, removeEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
document.addEventListener('touchstart', handlePointerDown, true);
|
||||
document.addEventListener('touchend', handlePointerUp, true);
|
||||
document.addEventListener('touchcancel', handlePointerUp, true);
|
||||
|
||||
const scrollHandler = scrollHandlers.get(this);
|
||||
if (scrollHandler) {
|
||||
removeEventListener.call(this, 'scroll', scrollHandler, { passive: true } as unknown as EventListenerOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
decorate(EventTarget.prototype, 'addEventListener', function (this: EventTarget, addEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
|
||||
const handleScrollEnd = debounce(() => {
|
||||
if (!pointers.size) {
|
||||
// If no pointer is active in the scroll area then the scroll has ended
|
||||
this.dispatchEvent(new Event('scrollend'));
|
||||
} else {
|
||||
// otherwise let's wait a bit more
|
||||
handleScrollEnd();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
addEventListener.call(this, 'scroll', handleScrollEnd, { passive: true });
|
||||
scrollHandlers.set(this, handleScrollEnd);
|
||||
});
|
||||
|
||||
decorate(EventTarget.prototype, 'removeEventListener', function (this: EventTarget, removeEventListener, type) {
|
||||
if (type !== 'scrollend') return;
|
||||
|
||||
const scrollHandler = scrollHandlers.get(this);
|
||||
if (scrollHandler) {
|
||||
removeEventListener.call(this, 'scroll', scrollHandler, { passive: true } as unknown as EventListenerOptions);
|
||||
}
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
// Without an import or export, TypeScript sees vars in this file as global
|
||||
export {};
|
||||
|
|
|
@ -80,9 +80,19 @@ function isTabbable(el: HTMLElement) {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Radios without a checked attribute are not tabbable
|
||||
if (tag === 'input' && el.getAttribute('type') === 'radio' && !el.hasAttribute('checked')) {
|
||||
return false;
|
||||
if (tag === 'input' && el.getAttribute('type') === 'radio') {
|
||||
const rootNode = el.getRootNode() as HTMLElement;
|
||||
|
||||
const findRadios = `input[type='radio'][name="${el.getAttribute('name')}"]`;
|
||||
const firstChecked = rootNode.querySelector(`${findRadios}:checked`);
|
||||
|
||||
if (firstChecked) {
|
||||
return firstChecked === el;
|
||||
}
|
||||
|
||||
const firstRadio = rootNode.querySelector(findRadios);
|
||||
|
||||
return firstRadio === el;
|
||||
}
|
||||
|
||||
if (!isVisible(el)) {
|
||||
|
|
|
@ -7,20 +7,19 @@ const translation: Translation = {
|
|||
$dir: 'ltr',
|
||||
|
||||
carousel: 'カルーセル',
|
||||
clearEntry: 'クリアエントリ',
|
||||
clearEntry: 'クリア',
|
||||
close: '閉じる',
|
||||
copied: 'コピーされました',
|
||||
copied: 'コピーしました',
|
||||
copy: 'コピー',
|
||||
currentValue: '現在の価値',
|
||||
currentValue: '現在の値',
|
||||
error: 'エラー',
|
||||
goToSlide: (slide, count) => `${count} 枚中 ${slide} 枚のスライドに移動`,
|
||||
hidePassword: 'パスワードを隠す',
|
||||
loading: '読み込み中',
|
||||
nextSlide: '次のスライド',
|
||||
numOptionsSelected: num => {
|
||||
if (num === 0) return 'オプションが選択されていません';
|
||||
if (num === 1) return '1 つのオプションが選択されました';
|
||||
return `${num} つのオプションが選択されました`;
|
||||
if (num === 0) return '項目が選択されていません';
|
||||
return `${num} 個の項目が選択されました`;
|
||||
},
|
||||
previousSlide: '前のスライド',
|
||||
progress: '進行',
|
||||
|
|
Ładowanie…
Reference in New Issue