@m3e/slider 1.0.0-rc.1 → 1.0.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,723 +0,0 @@
1
- import { css, CSSResultGroup, html, LitElement, PropertyValues, unsafeCSS } from "lit";
2
- import { customElement, property, query, state } from "lit/decorators.js";
3
- import { ifDefined } from "lit/directives/if-defined.js";
4
-
5
- import { DesignToken, prefersReducedMotion, ResizeController, Role, safeStyleMap } from "@m3e/core";
6
-
7
- import { M3eSliderThumbElement } from "./SliderThumbElement";
8
- import { SliderSize } from "./SliderSize";
9
-
10
- /**
11
- * @summary
12
- * Allows for the selection of numeric values from a range.
13
- *
14
- * @description
15
- * The `m3e-slider` component enables users to select a numeric value from a continuous or discrete range.
16
- * Designed according to Material 3 principles, it supports labeled value indicators, tick marks, and
17
- * snapping behavior.
18
- *
19
- * @example
20
- * The following example illustrates a labelled slider with thumb used to select a single numeric value.
21
- * ```html
22
- * <m3e-slider labelled>
23
- * <m3e-slider-thumb value="50"></m3e-slider-thumb>
24
- * </m3e-slider>
25
- * ```
26
- *
27
- * @example
28
- * The next example illustrates a labelled range slider with two thumbs used to select a minimum and maximum numeric value.
29
- * ```html
30
- * <m3e-slider labelled>
31
- * <m3e-slider-thumb value="25"></m3e-slider-thumb>
32
- * <m3e-slider-thumb value="75"></m3e-slider-thumb>
33
- * </m3e-slider>
34
- * ```
35
- *
36
- * @tag m3e-slider
37
- *
38
- * @attr disabled - Whether the element is disabled.
39
- * @attr discrete - Whether to show tick marks.
40
- * @attr labelled - Whether to show value labels when activated.
41
- * @attr max - The maximum allowable value.
42
- * @attr min - The minimum allowable value.
43
- * @attr step - The value at which the thumb will snap.
44
- * @attr size - The size of the slider.
45
- *
46
- * @cssprop --m3e-slider-min-width - Minimum inline size of the slider host.
47
- * @cssprop --m3e-slider-small-height - Height of the slider when size is small or extra-small.
48
- * @cssprop --m3e-slider-medium-height - Height of the slider when size is medium.
49
- * @cssprop --m3e-slider-large-height - Height of the slider when size is large.
50
- * @cssprop --m3e-slider-extra-large-height - Height of the slider when size is extra-large.
51
- * @cssprop --m3e-slider-small-active-track-shape - Corner shape of the active track for small sliders.
52
- * @cssprop --m3e-slider-small-inactive-active-track-start-shape - Corner shape of the inactive track start for small sliders.
53
- * @cssprop --m3e-slider-small-inactive-track-end-shape - Corner shape of the inactive track end for small sliders.
54
- * @cssprop --m3e-slider-medium-active-track-shape - Corner shape of the active track for medium sliders.
55
- * @cssprop --m3e-slider-medium-inactive-active-track-start-shape - Corner shape of the inactive track start for medium sliders.
56
- * @cssprop --m3e-slider-medium-inactive-track-end-shape - Corner shape of the inactive track end for medium sliders.
57
- * @cssprop --m3e-slider-large-active-track-shape - Corner shape of the active track for large sliders.
58
- * @cssprop --m3e-slider-large-inactive-active-track-start-shape - Corner shape of the inactive track start for large sliders.
59
- * @cssprop --m3e-slider-large-inactive-track-end-shape - Corner shape of the inactive track end for large sliders.
60
- * @cssprop --m3e-slider-extra-large-active-track-shape - Corner shape of the active track for extra-large sliders.
61
- * @cssprop --m3e-slider-extra-large-inactive-active-track-start-shape - Corner shape of the inactive track start for extra-large sliders.
62
- * @cssprop --m3e-slider-extra-large-inactive-track-end-shape - Corner shape of the inactive track end for extra-large sliders.
63
- * @cssprop --m3e-slider-extra-small-track-height - Height of the track for extra-small sliders.
64
- * @cssprop --m3e-slider-small-track-height - Height of the track for small sliders.
65
- * @cssprop --m3e-slider-medium-track-height - Height of the track for medium sliders.
66
- * @cssprop --m3e-slider-large-track-height - Height of the track for large sliders.
67
- * @cssprop --m3e-slider-extra-large-track-height - Height of the track for extra-large sliders.
68
- * @cssprop --m3e-slider-tick-size - Size of each tick mark.
69
- * @cssprop --m3e-slider-tick-shape - Corner shape of each tick mark.
70
- * @cssprop --m3e-slider-inactive-track-color - Background color of the inactive track when enabled.
71
- * @cssprop --m3e-slider-disabled-inactive-track-color - Base color of the inactive track when disabled.
72
- * @cssprop --m3e-slider-disabled-inactive-track-opacity - Opacity of the inactive track when disabled.
73
- * @cssprop --m3e-slider-active-track-color - Background color of the active track when enabled.
74
- * @cssprop --m3e-slider-disabled-active-track-color - Base color of the active track when disabled.
75
- * @cssprop --m3e-slider-disabled-active-track-opacity - Opacity of the active track when disabled.
76
- * @cssprop --m3e-slider-tick-active-color - Color of active ticks when enabled.
77
- * @cssprop --m3e-slider-disabled-tick-active-color - Color of active ticks when disabled.
78
- * @cssprop --m3e-slider-tick-inactive-color - Color of inactive ticks when enabled.
79
- * @cssprop --m3e-slider-disabled-tick-inactive-color - Color of inactive ticks when disabled.
80
- */
81
- @customElement("m3e-slider")
82
- export class M3eSliderElement extends Role(LitElement, "none") {
83
- /** The styles of the element. */
84
- static override styles: CSSResultGroup = css`
85
- :host {
86
- display: inline-block;
87
- vertical-align: middle;
88
- min-inline-size: var(--m3e-slider-min-width, 12.5rem);
89
- }
90
- :host(:not([disabled])) {
91
- cursor: pointer;
92
- }
93
- :host([size="extra-small"]),
94
- :host([size="small"]) {
95
- height: var(--m3e-slider-small-height, 2.75rem);
96
- }
97
- :host([size="extra-small"]) .base,
98
- :host([size="small"]) .base {
99
- --_slider-active-track-shape: var(--m3e-slider-small-active-track-shape, ${DesignToken.shape.corner.smallStart});
100
- --_slider-inactive-track-start-shape: var(
101
- --m3e-slider-small-inactive-active-track-start-shape,
102
- ${DesignToken.shape.corner.smallStart}
103
- );
104
- --_slider-inactive-track-end-shape: var(
105
- --m3e-slider-small-inactive-track-end-shape,
106
- ${DesignToken.shape.corner.smallEnd}
107
- );
108
- }
109
- :host([size="extra-small"]) .track {
110
- height: calc(var(--m3e-slider-extra-small-track-height, 1rem));
111
- }
112
- :host([size="small"]) .track {
113
- height: calc(var(--m3e-slider-small-track-height, 1.5rem));
114
- }
115
- :host([size="medium"]) {
116
- height: var(--m3e-slider-medium-height, 3.25rem);
117
- }
118
- :host([size="medium"]) .base {
119
- --_slider-active-track-shape: var(
120
- --m3e-slider-medium-active-track-shape,
121
- ${DesignToken.shape.corner.mediumStart}
122
- );
123
- --_slider-inactive-track-start-shape: var(
124
- --m3e-slider-medium-inactive-active-track-start-shape,
125
- ${DesignToken.shape.corner.mediumStart}
126
- );
127
- --_slider-inactive-track-end-shape: var(
128
- --m3e-slider-medium-inactive-track-end-shape,
129
- ${DesignToken.shape.corner.mediumEnd}
130
- );
131
- }
132
- :host([size="medium"]) .track {
133
- height: var(--m3e-slider-medium-track-height, 2.5rem);
134
- }
135
- :host([size="large"]) {
136
- height: var(--m3e-slider-large-height, 4.25rem);
137
- }
138
- :host([size="large"]) .base {
139
- --_slider-active-track-shape: var(--m3e-slider-large-active-track-shape, ${DesignToken.shape.corner.largeStart});
140
- --_slider-inactive-track-start-shape: var(
141
- --m3e-slider-large-inactive-active-track-start-shape,
142
- ${DesignToken.shape.corner.largeStart}
143
- );
144
- --_slider-inactive-track-end-shape: var(
145
- --m3e-slider-large-inactive-track-end-shape,
146
- ${DesignToken.shape.corner.largeEnd}
147
- );
148
- }
149
- :host([size="large"]) .track {
150
- height: var(--m3e-slider-large-track-height, 3.5rem);
151
- }
152
- :host([size="extra-large"]) {
153
- height: var(--m3e-slider-extra-large-height, 6.75rem);
154
- }
155
- :host([size="extra-large"]) .base {
156
- --_slider-active-track-shape: var(
157
- --m3e-slider-extra-large-active-track-shape,
158
- ${DesignToken.shape.corner.extraLargeStart}
159
- );
160
- --_slider-inactive-track-start-shape: var(
161
- --m3e-slider-extra-large-inactive-active-track-start-shape,
162
- ${DesignToken.shape.corner.extraLargeStart}
163
- );
164
- --_slider-inactive-track-end-shape: var(
165
- --m3e-slider-extra-large-inactive-track-end-shape,
166
- ${DesignToken.shape.corner.extraLargeEnd}
167
- );
168
- }
169
- :host([size="extra-large"]) .track {
170
- height: var(--m3e-slider-extra-large-track-height, 6rem);
171
- }
172
- :host(.-animating) .track-active,
173
- :host(.-animating) .track-inactive.start,
174
- :host(.-animating) .track-inactive.end {
175
- transition: ${unsafeCSS(`margin-left ${DesignToken.motion.spring.fastEffects},
176
- width ${DesignToken.motion.spring.fastEffects}`)};
177
- }
178
- .base {
179
- display: inline-flex;
180
- align-items: center;
181
- position: relative;
182
- width: 100%;
183
- height: 100%;
184
- border-radius: inherit;
185
- outline: none;
186
- }
187
- .track {
188
- position: relative;
189
- flex: 1 1 auto;
190
- }
191
- .track-inactive,
192
- .track-active {
193
- position: absolute;
194
- height: 100%;
195
- }
196
- .track-active {
197
- margin-left: var(--_slider-active-track-offset, 0px);
198
- width: var(--_slider-active-track-size, 0px);
199
- border-radius: var(--_slider-active-track-middle-shape, var(--_slider-active-track-shape));
200
- }
201
- .track-inactive.start {
202
- width: var(--_slider-inactive-track-before-size, 0px);
203
- border-radius: var(--_slider-inactive-track-start-shape);
204
- }
205
- .track-inactive.end {
206
- margin-left: var(--_slider-inactive-track-after-offset, 0px);
207
- width: var(--_slider-inactive-track-after-size, 0px);
208
- border-radius: var(--_slider-inactive-track-end-shape);
209
- }
210
- .ticks {
211
- position: absolute;
212
- width: 100%;
213
- height: var(--m3e-slider-tick-size, 0.25rem);
214
- overflow: visible;
215
- }
216
- .tick {
217
- position: absolute;
218
- top: 0;
219
- left: calc(var(--m3e-slider-tick-size, 0.25rem) + calc(var(--m3e-slider-tick-size, 0.25rem) / 2));
220
- width: var(--m3e-slider-tick-size, 0.25rem);
221
- height: var(--m3e-slider-tick-size, 0.25rem);
222
- border-radius: var(--m3e-slider-tick-shape, ${DesignToken.shape.corner.full});
223
- }
224
- :host(:not([disabled])) .track-inactive {
225
- background-color: var(--m3e-slider-inactive-track-color, ${DesignToken.color.secondaryContainer});
226
- }
227
- :host([disabled]) .track-inactive {
228
- background-color: color-mix(
229
- in srgb,
230
- var(--m3e-slider-disabled-inactive-track-color, ${DesignToken.color.onSurface})
231
- var(--m3e-slider-disabled-inactive-track-opacity, 12%),
232
- transparent
233
- );
234
- }
235
- :host(:not([disabled])) .track-active {
236
- background-color: var(--m3e-slider-active-track-color, ${DesignToken.color.primary});
237
- }
238
- :host([disabled]) .track-active {
239
- background-color: color-mix(
240
- in srgb,
241
- var(--m3e-slider-disabled-active-track-color, ${DesignToken.color.onSurface})
242
- var(--m3e-slider-disabled-active-track-opacity, 38%),
243
- transparent
244
- );
245
- }
246
- :host(:not([disabled])) .tick.active {
247
- background-color: var(--m3e-slider-tick-active-color, ${DesignToken.color.onPrimary});
248
- }
249
- :host([disabled]) .tick.active {
250
- background-color: var(--m3e-slider-disabled-tick-active-color, ${DesignToken.color.inverseOnSurface});
251
- }
252
- :host(:not([disabled])) .tick.inactive {
253
- background-color: var(--m3e-slider-tick-inactive-color, ${DesignToken.color.onSecondaryContainer});
254
- }
255
- :host([disabled]) .tick.inactive {
256
- background-color: var(--m3e-slider-disabled-tick-inactive-color, ${DesignToken.color.onSurface});
257
- }
258
- :host(:not([discrete])) .tick.active {
259
- display: none;
260
- }
261
- :host(:hover[labelled]) .base,
262
- :host(:focus-within[labelled]) .base {
263
- --_slider-label-visibility: visible;
264
- --_slider-label-opacity: 1;
265
- --_slider-label-transform: scale(1);
266
- }
267
- @media (forced-colors: active) {
268
- :host(:not([disabled])) .track-inactive {
269
- background-color: unset;
270
- }
271
- :host(:not([disabled])) .base.range .track-inactive.start,
272
- :host(:not([disabled])) .track-inactive.end {
273
- border: 1px solid CanvasText;
274
- box-sizing: border-box;
275
- }
276
- :host(:not([disabled])) .tick.inactive {
277
- background-color: CanvasText;
278
- }
279
- :host(:not([disabled])) .tick.active {
280
- background-color: Canvas;
281
- }
282
- :host(:not([disabled])) .track-active {
283
- background-color: CanvasText;
284
- }
285
- :host([disabled]) .base.range .track-inactive.start,
286
- :host([disabled]) .track-inactive.end {
287
- border: 1px solid GrayText;
288
- box-sizing: border-box;
289
- }
290
- :host([disabled]) .track-active {
291
- background-color: GrayText;
292
- }
293
- :host([disabled]) .tick.inactive {
294
- background-color: GrayText;
295
- }
296
- :host([disabled]) .tick.active {
297
- background-color: Canvas;
298
- }
299
- }
300
- `;
301
-
302
- /** @private */
303
- @query(".base") private readonly _base?: HTMLElement;
304
-
305
- /** @private */
306
- @state() private _ticks = new Array<{ value: number; active: boolean }>();
307
-
308
- /** @private */ #thumbs = new Array<M3eSliderThumbElement>();
309
- /** @private */ #activeThumb?: M3eSliderThumbElement;
310
- /** @private */ #cachedWidth = 0;
311
- /** @private */ #cachedThumbWidth = 0;
312
- /** @private */ #cachedLeft = 0;
313
-
314
- constructor() {
315
- super();
316
- new ResizeController(this, { callback: () => this.#updateDimensions(true) });
317
- }
318
-
319
- /**
320
- * The size of the slider.
321
- * @default "extra-small"
322
- */
323
- @property({ reflect: true }) size: SliderSize = "extra-small";
324
-
325
- /**
326
- * Whether the element is disabled.
327
- * @default false
328
- */
329
- @property({ type: Boolean, reflect: true }) disabled = false;
330
-
331
- /**
332
- * Whether to show tick marks.
333
- * @default false
334
- */
335
- @property({ type: Boolean, reflect: true }) discrete = false;
336
-
337
- /**
338
- * The minimum allowable value.
339
- * @default 0
340
- */
341
- @property({ type: Number }) min = 0;
342
-
343
- /**
344
- * The maximum allowable value.
345
- * @default 100
346
- */
347
- @property({ type: Number }) max = 100;
348
-
349
- /**
350
- * The value at which the thumb will snap.
351
- * @default 1
352
- */
353
- @property({ type: Number }) step = 1;
354
-
355
- /**
356
- * Whether to show value labels when activated.
357
- * @default false
358
- */
359
- @property({ type: Boolean }) labelled = false;
360
-
361
- /** The function used to format display values. */
362
- @property({ attribute: false }) displayWith: ((value: number | null) => string) | null = null;
363
-
364
- /** The thumbs used to select values. */
365
- get thumbs(): readonly M3eSliderThumbElement[] {
366
- return this.#thumbs;
367
- }
368
-
369
- /** Whether the slider is a range slider. */
370
- get isRange(): boolean {
371
- return this.#thumbs.length > 1;
372
- }
373
-
374
- /** The thumb used to select a value. */
375
- get thumb(): M3eSliderThumbElement | null {
376
- return this.#thumbs[0] ?? null;
377
- }
378
-
379
- /** The thumb used to select the lower value of a range slider. */
380
- get lowerThumb(): M3eSliderThumbElement | null {
381
- return this.thumb;
382
- }
383
-
384
- /** The thumb used to select the upper value of a range slider. */
385
- get upperThumb(): M3eSliderThumbElement | null {
386
- return this.#thumbs[1] ?? null;
387
- }
388
-
389
- /** @inheritdoc */
390
- protected override updated(_changedProperties: PropertyValues<this>): void {
391
- super.updated(_changedProperties);
392
-
393
- if (_changedProperties.has("disabled")) {
394
- this.#thumbs.forEach((x) => (x.disabled = this.disabled));
395
- }
396
- }
397
-
398
- /** @inheritdoc */
399
- protected override render(): unknown {
400
- return html`<div
401
- class="base"
402
- tabindex="${ifDefined(!this.disabled ? "-1" : undefined)}"
403
- @pointerdown="${this.#handlePointerDown}"
404
- @pointermove="${this.#handlePointerMove}"
405
- @pointerup="${this.#handlePointerUp}"
406
- @keydown="${this.#handleKeyDown}"
407
- @value-change="${this.#handleThumbChange}"
408
- >
409
- <div class="track" aria-hidden="true">
410
- <div class="track-inactive start"></div>
411
- <div class="track-active"></div>
412
- <div class="track-inactive end"></div>
413
- </div>
414
- <div class="ticks" aria-hidden="true">${this._ticks.map((x) => this.#renderTick(x))}</div>
415
- <slot @slotchange="${this.#handleSlotChange}"></slot>
416
- </div>`;
417
- }
418
-
419
- /** @private */
420
- #renderTick(tick: { value: number; active: boolean }) {
421
- return html`<div
422
- class="tick ${tick.active ? "active" : "inactive"}"
423
- style="${safeStyleMap({
424
- transform: `translate(${this.#pointFromValue(tick.value)}px, 0)`,
425
- })}"
426
- ></div>`;
427
- }
428
-
429
- /** @private */
430
- #handleSlotChange(e: Event): void {
431
- this.#thumbs = (<HTMLSlotElement>e.target)
432
- .assignedElements({ flatten: true })
433
- .filter((x) => x instanceof M3eSliderThumbElement);
434
-
435
- if (this.#thumbs.length > 2) {
436
- this.#thumbs.length = 2;
437
- }
438
- if (this.isRange) {
439
- this._base?.style.setProperty("--_slider-active-track-middle-shape", `0`);
440
- } else {
441
- this._base?.style.removeProperty("--_slider-active-track-middle-shape");
442
- }
443
-
444
- this.#updateThumbs();
445
- }
446
-
447
- /** @private */
448
- #updateThumbs(): void {
449
- this.#thumbs.forEach((thumb, i) => {
450
- if (this.disabled) {
451
- thumb.disabled = true;
452
- }
453
- thumb.ariaValueMin = `${this.#thumbs[i - 1]?.value ?? this.min}`;
454
- thumb.ariaValueMax = `${this.#thumbs[i + 1]?.value ?? this.max}`;
455
- thumb.ariaValueNow = `${thumb.value ?? this.#thumbs[i - 1]?.value ?? this.min}`;
456
- });
457
- }
458
-
459
- /** @private */
460
- #pointFromValue(value: number): number {
461
- return (this.#cachedWidth - this.#cachedThumbWidth) * ((value - this.min) / (this.max - this.min));
462
- }
463
-
464
- /** @private */
465
- #valueFromPoint(e: PointerEvent): number {
466
- const pos = e.clientX - this.#cachedLeft;
467
- const step = this.step === 0 ? 1 : this.step;
468
- const numSteps = Math.floor((this.max - this.min) / step);
469
- const percentage = pos / this.#cachedWidth;
470
- const fixedPercentage = Math.round(percentage * numSteps) / numSteps;
471
- const impreciseValue = fixedPercentage * (this.max - this.min) + this.min;
472
- return Math.round(impreciseValue / step) * step;
473
- }
474
-
475
- /** @private */
476
- #updateCachedDimensions(force = false): void {
477
- if (!this.lowerThumb) return;
478
- this.#cachedWidth = !force && this.#cachedWidth > 0 ? this.#cachedWidth : this.clientWidth;
479
- this.#cachedThumbWidth =
480
- !force && this.#cachedThumbWidth > 0 ? this.#cachedThumbWidth : this.lowerThumb.clientWidth;
481
- this.#cachedLeft = !force && this.#cachedLeft > 0 ? this.#cachedLeft : this.getBoundingClientRect().left;
482
- }
483
-
484
- /** @private */
485
- #updateDimensions(force = false): void {
486
- this.#updateCachedDimensions(force);
487
- if (!this.lowerThumb) return;
488
-
489
- const lowerValue = this.lowerThumb.value ?? this.min;
490
- const lowerPos = this.#pointFromValue(lowerValue);
491
- this.lowerThumb.style.transform = `translate(${lowerPos}px, 0)`;
492
-
493
- if (!this.upperThumb) {
494
- this._base?.classList.toggle("range", false);
495
- this._base?.style.setProperty("--_slider-active-track-size", `${lowerPos}px`);
496
- this._base?.style.setProperty("--_slider-inactive-track-after-offset", `${lowerPos + this.#cachedThumbWidth}px`);
497
- this._base?.style.setProperty(
498
- "--_slider-inactive-track-after-size",
499
- `${this.#cachedWidth - lowerPos - this.#cachedThumbWidth}px`
500
- );
501
-
502
- this.#updateTicks((i) => i < lowerValue);
503
- } else {
504
- const upperValue = this.upperThumb.value ?? lowerValue;
505
- const upperPos = this.#pointFromValue(upperValue);
506
- this.upperThumb.style.transform = `translate(${upperPos}px, 0)`;
507
-
508
- this._base?.classList.toggle("range", true);
509
- this._base?.style.setProperty("--_slider-inactive-track-before-size", `${lowerPos}px`);
510
- this._base?.style.setProperty("--_slider-active-track-offset", `${lowerPos + this.#cachedThumbWidth}px`);
511
- this._base?.style.setProperty("--_slider-active-track-size", `${upperPos - lowerPos - this.#cachedThumbWidth}px`);
512
- this._base?.style.setProperty("--_slider-inactive-track-after-offset", `${upperPos + this.#cachedThumbWidth}px`);
513
- this._base?.style.setProperty(
514
- "--_slider-inactive-track-after-size",
515
- `${this.#cachedWidth - this.#cachedThumbWidth - upperPos}px`
516
- );
517
-
518
- this.#updateTicks((i) => i > lowerValue && i < upperValue);
519
- }
520
- }
521
-
522
- /** @private */
523
- #updateTicks(active: (value: number) => boolean): void {
524
- this._ticks = [];
525
- if (this.discrete && this.step > 1) {
526
- for (let i = this.min; i <= this.max; i += this.step) {
527
- this._ticks.push({ value: i, active: active(i) });
528
- }
529
- } else {
530
- this._ticks.push({ value: this.min, active: active(this.min) });
531
- if (this.min < 0 && this.max > 0) {
532
- this._ticks.push({ value: 0, active: active(0) });
533
- }
534
- this._ticks.push({ value: this.max, active: active(this.max) });
535
- }
536
- }
537
-
538
- /** @private */
539
- #handlePointerDown(e: PointerEvent): void {
540
- if (e.pointerType === "mouse" && e.button > 1) return;
541
- if (!this.lowerThumb || this.disabled) return;
542
-
543
- if (e.target instanceof HTMLElement) {
544
- e.target.setPointerCapture(e.pointerId);
545
- }
546
-
547
- this.#activeThumb = e.composedPath().find((x) => x instanceof M3eSliderThumbElement) as
548
- | M3eSliderThumbElement
549
- | undefined;
550
-
551
- if (this.#activeThumb) {
552
- return;
553
- }
554
-
555
- const value = this.#valueFromPoint(e);
556
- if (!this.upperThumb) {
557
- if (!this.lowerThumb.disabled) {
558
- this.#changeThumb(this.lowerThumb, value, true);
559
- this.#activeThumb = this.lowerThumb;
560
- }
561
- } else {
562
- const lowerValue = this.lowerThumb.value ?? this.min;
563
- const upperValue = this.upperThumb.value ?? lowerValue;
564
-
565
- if (value < lowerValue) {
566
- if (!this.lowerThumb.disabled) {
567
- this.#changeThumb(this.lowerThumb, value, true);
568
- this.#activeThumb = this.lowerThumb;
569
- }
570
- } else if (value > upperValue) {
571
- if (!this.upperThumb.disabled) {
572
- this.#changeThumb(this.upperThumb, value, true);
573
- this.#activeThumb = this.upperThumb;
574
- }
575
- } else {
576
- const mid = (lowerValue + upperValue) / 2;
577
- if (value < mid && !this.lowerThumb.disabled) {
578
- this.#changeThumb(this.lowerThumb, value, true);
579
- this.#activeThumb = this.lowerThumb;
580
- } else if (!this.upperThumb.disabled) {
581
- this.#changeThumb(this.upperThumb, value, true);
582
- this.#activeThumb = this.upperThumb;
583
- }
584
- }
585
- }
586
- }
587
-
588
- #handlePointerMove(e: PointerEvent): void {
589
- if (!(e.target instanceof HTMLElement) || !e.target.hasPointerCapture(e.pointerId) || !this.#activeThumb) return;
590
-
591
- const value = this.#valueFromPoint(e);
592
- let min = this.min;
593
- let max = this.max;
594
-
595
- if (this.#activeThumb === this.upperThumb) {
596
- min = Math.max(min, this.lowerThumb?.value ?? 0);
597
- } else if (this.upperThumb) {
598
- max = Math.min(max, this.upperThumb.value ?? this.max);
599
- }
600
-
601
- if (this.classList.contains("-animating")) {
602
- this.classList.toggle("-animating", false);
603
- this.#activeThumb.style.transition = "";
604
- }
605
-
606
- this.#changeThumb(this.#activeThumb, Math.min(max, Math.max(min, value)));
607
- }
608
-
609
- /** @private */
610
- #handlePointerUp(e: PointerEvent): void {
611
- if (e.pointerType === "mouse" && e.button > 1) return;
612
- if (!this.lowerThumb || this.disabled) return;
613
-
614
- if (e.target instanceof HTMLElement) {
615
- e.target.releasePointerCapture(e.pointerId);
616
- }
617
-
618
- if (this.#activeThumb) {
619
- this.#activeThumb.focus();
620
- }
621
- }
622
-
623
- /** @private */
624
- #handleKeyDown(e: KeyboardEvent): void {
625
- this.#activeThumb = e.composedPath().find((x) => x instanceof M3eSliderThumbElement) as
626
- | M3eSliderThumbElement
627
- | undefined;
628
-
629
- if (!this.#activeThumb) return;
630
-
631
- const value = this.#activeThumb.value ?? 0;
632
-
633
- let min = this.min;
634
- let max = this.max;
635
-
636
- if (this.#activeThumb === this.upperThumb) {
637
- min = Math.max(min, this.lowerThumb?.value ?? 0);
638
- } else if (this.upperThumb) {
639
- max = Math.max(max, this.upperThumb.value ?? this.max);
640
- }
641
-
642
- switch (e.key) {
643
- case "Home":
644
- this.#changeThumb(this.#activeThumb, min);
645
- e.preventDefault();
646
- break;
647
-
648
- case "End":
649
- this.#changeThumb(this.#activeThumb, max);
650
- e.preventDefault();
651
- break;
652
-
653
- case "PageUp":
654
- this.#changeThumb(this.#activeThumb, Math.min(max, value + (this.step > 1 ? this.step : 10)));
655
- e.preventDefault();
656
- break;
657
-
658
- case "PageDown":
659
- this.#changeThumb(this.#activeThumb, Math.max(min, value - (this.step > 1 ? this.step : 10)));
660
- e.preventDefault();
661
- break;
662
-
663
- case "Down":
664
- case "ArrowDown":
665
- case "Left":
666
- case "ArrowLeft":
667
- this.#changeThumb(this.#activeThumb, Math.max(min, value - this.step));
668
- e.preventDefault();
669
- break;
670
-
671
- case "Up":
672
- case "ArrowUp":
673
- case "Right":
674
- case "ArrowRight":
675
- this.#changeThumb(this.#activeThumb, Math.min(max, value + this.step));
676
- e.preventDefault();
677
- break;
678
-
679
- case " ":
680
- e.preventDefault();
681
- break;
682
- }
683
- }
684
-
685
- /** @private */
686
- #handleThumbChange(e: Event): void {
687
- e.stopPropagation();
688
- this.#updateThumbs();
689
- this.#updateDimensions();
690
- }
691
-
692
- /** @private */
693
- #changeThumb(thumb: M3eSliderThumbElement, value: number, animate = false): void {
694
- if (thumb.value === value) return;
695
- const prev = thumb.value;
696
- if (animate && !prefersReducedMotion()) {
697
- this.classList.toggle("-animating", true);
698
- thumb.addEventListener(
699
- "transitionend",
700
- () => {
701
- thumb.style.transition = "";
702
- this.classList.toggle("-animating", false);
703
- },
704
- { once: true }
705
- );
706
- thumb.style.transition = `transform ${DesignToken.motion.spring.fastEffects}`;
707
- }
708
- thumb.value = value;
709
- thumb.markAsDirty();
710
- thumb.markAsTouched();
711
- if (thumb.dispatchEvent(new Event("input", { bubbles: true, composed: true, cancelable: true }))) {
712
- thumb.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
713
- } else {
714
- thumb.value = prev;
715
- }
716
- }
717
- }
718
-
719
- declare global {
720
- interface HTMLElementTagNameMap {
721
- "m3e-slider": M3eSliderElement;
722
- }
723
- }