@antadesign/anta 0.3.32 → 0.3.33

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.
@@ -4,6 +4,16 @@ const precisionOf = (value) => {
4
4
  const exponent = value.toString().match(/(?:\.(\d+))?(?:e([+-]?\d+))?$/i);
5
5
  return Math.max(0, (exponent?.[1]?.length ?? 0) - Number(exponent?.[2] ?? 0));
6
6
  };
7
+ const parseSliderValue = (source, fallback) => {
8
+ if (source === null || !source.trim()) return fallback;
9
+ const parts = source.trim().split(/\s+/);
10
+ if (parts.length !== 1 && parts.length !== 2) return fallback;
11
+ const numbers = parts.map(Number);
12
+ if (!numbers.every(Number.isFinite)) return fallback;
13
+ return numbers.length === 2 ? [numbers[0], numbers[1]] : numbers[0];
14
+ };
15
+ const serializeSliderValue = (value) => Array.isArray(value) ? value.join(" ") : String(value);
16
+ const sameSliderValue = (left, right) => Array.isArray(left) && Array.isArray(right) ? left[0] === right[0] && left[1] === right[1] : !Array.isArray(left) && !Array.isArray(right) && left === right;
7
17
  class ASliderElement extends HTMLElementBase {
8
18
  static formAssociated = true;
9
19
  static observedAttributes = [
@@ -16,13 +26,19 @@ class ASliderElement extends HTMLElementBase {
16
26
  "track-click",
17
27
  "value-display",
18
28
  "value-prefix",
19
- "value-suffix"
29
+ "value-suffix",
30
+ "inverted",
31
+ "name",
32
+ "aria-label",
33
+ "aria-labelledby"
20
34
  ];
21
35
  internals;
22
36
  #control;
23
37
  #railArea;
24
38
  #fill;
39
+ #fillEnd;
25
40
  #thumb;
41
+ #thumbEnd;
26
42
  #header;
27
43
  #label;
28
44
  #inlineValue;
@@ -32,25 +48,31 @@ class ASliderElement extends HTMLElementBase {
32
48
  #endValuePrefix;
33
49
  #endValueSuffix;
34
50
  #thumbValue;
51
+ #thumbValueEnd;
35
52
  #thumbValuePrefix;
36
53
  #thumbValueSuffix;
54
+ #thumbValueEndPrefix;
55
+ #thumbValueEndSuffix;
37
56
  #markers;
38
57
  #extras;
39
58
  #value = 0;
59
+ #physicalValues = [0, 0];
60
+ #orderReversed = false;
61
+ #lastActiveThumb = 0;
40
62
  #seeded = false;
41
63
  #dirty = false;
42
64
  #drag;
43
65
  get value() {
44
- return this.#value;
66
+ return Array.isArray(this.#value) ? [...this.#value] : this.#value;
45
67
  }
46
68
  set value(value) {
47
- this.setAttribute("value", String(value));
69
+ this.setAttribute("value", Array.isArray(value) ? value.join(" ") : String(value));
48
70
  }
49
71
  get defaultValue() {
50
- return finiteNumber(this.getAttribute("defaultvalue"), this.#min);
72
+ return parseSliderValue(this.getAttribute("defaultvalue"), this.#fallbackValue());
51
73
  }
52
74
  set defaultValue(value) {
53
- this.setAttribute("defaultvalue", String(value));
75
+ this.setAttribute("defaultvalue", Array.isArray(value) ? value.join(" ") : String(value));
54
76
  }
55
77
  constructor() {
56
78
  super();
@@ -113,7 +135,7 @@ class ASliderElement extends HTMLElementBase {
113
135
  }
114
136
  :host([track-click="jump"]) .control { cursor: pointer; }
115
137
  .control[data-dragging] { cursor: grabbing; }
116
- .control[data-dragging] .thumb { background: var(--slider-thumb-active); }
138
+ .control[data-dragging] .thumb[data-active] { background: var(--slider-thumb-active); }
117
139
  :host(:focus-visible) .control:not([data-pointer-focus]) {
118
140
  outline: 1px solid var(--focus-ring);
119
141
  outline-offset: 0;
@@ -137,9 +159,9 @@ class ASliderElement extends HTMLElementBase {
137
159
  background: var(--slider-track);
138
160
  }
139
161
  .fill {
140
- inset-inline-start: calc(var(--slider-track-inset) - var(--anta-slider-thumb-size) / 2);
162
+ inset-inline-start: calc(var(--_fill-start, 0%) + var(--_fill-start-edge, 0px));
141
163
  inset-inline-end: auto;
142
- inline-size: calc(var(--_percent, 0%) + var(--anta-slider-thumb-size) / 2 - var(--slider-track-inset));
164
+ inline-size: calc(var(--_fill-end, 0%) - var(--_fill-start, 0%) + var(--_fill-end-edge, 0px) - var(--_fill-start-edge, 0px));
143
165
  background: var(--slider-fill);
144
166
  }
145
167
  .thumb {
@@ -155,6 +177,12 @@ class ASliderElement extends HTMLElementBase {
155
177
  transition: background-color 75ms ease-out, border-color 75ms ease-out;
156
178
  transform: translate(-50%, -50%);
157
179
  }
180
+ .thumb[data-last-active] { z-index: 1; }
181
+ .thumb:focus-visible {
182
+ outline: 1px solid var(--focus-ring);
183
+ outline-offset: 2px;
184
+ z-index: 2;
185
+ }
158
186
  .thumb-value {
159
187
  display: none;
160
188
  position: absolute;
@@ -176,6 +204,7 @@ class ASliderElement extends HTMLElementBase {
176
204
  ));
177
205
  }
178
206
  :host([value-display="thumb"]) .thumb-value { display: block; }
207
+ :host([value-display="thumb"]) .thumb-value[hidden] { display: none !important; }
179
208
  slot[name="markers"] {
180
209
  display: block;
181
210
  position: relative;
@@ -204,6 +233,7 @@ class ASliderElement extends HTMLElementBase {
204
233
  .fill { background: Highlight; }
205
234
  .thumb { background: Canvas; border-color: CanvasText; }
206
235
  :host(:focus-visible) .control { outline: 2px solid Highlight; }
236
+ .thumb:focus-visible { outline: 2px solid Highlight; }
207
237
  }
208
238
  @media (prefers-reduced-motion: reduce) {
209
239
  .rail,
@@ -248,9 +278,15 @@ class ASliderElement extends HTMLElementBase {
248
278
  this.#fill = document.createElement("div");
249
279
  this.#fill.className = "fill";
250
280
  this.#fill.part.add("fill");
281
+ this.#fillEnd = document.createElement("div");
282
+ this.#fillEnd.className = "fill";
283
+ this.#fillEnd.part.add("fill", "fill-end");
251
284
  this.#thumb = document.createElement("div");
252
285
  this.#thumb.className = "thumb";
253
- this.#thumb.part.add("thumb");
286
+ this.#thumb.part.add("thumb", "thumb-1");
287
+ this.#thumbEnd = document.createElement("div");
288
+ this.#thumbEnd.className = "thumb";
289
+ this.#thumbEnd.part.add("thumb", "thumb-2");
254
290
  this.#thumbValue = document.createElement("span");
255
291
  this.#thumbValue.className = "thumb-value";
256
292
  this.#thumbValue.part.add("thumb-value");
@@ -259,7 +295,15 @@ class ASliderElement extends HTMLElementBase {
259
295
  this.#thumbValueSuffix = document.createElement("span");
260
296
  this.#thumbValueSuffix.className = "value-affix";
261
297
  this.#thumbValue.append(this.#thumbValuePrefix, this.#thumbValueSuffix);
262
- this.#railArea.append(rail, this.#fill, this.#thumb, this.#thumbValue);
298
+ this.#thumbValueEnd = document.createElement("span");
299
+ this.#thumbValueEnd.className = "thumb-value";
300
+ this.#thumbValueEnd.part.add("thumb-value", "thumb-value-2");
301
+ this.#thumbValueEndPrefix = document.createElement("span");
302
+ this.#thumbValueEndPrefix.className = "value-affix";
303
+ this.#thumbValueEndSuffix = document.createElement("span");
304
+ this.#thumbValueEndSuffix.className = "value-affix";
305
+ this.#thumbValueEnd.append(this.#thumbValueEndPrefix, this.#thumbValueEndSuffix);
306
+ this.#railArea.append(rail, this.#fill, this.#fillEnd, this.#thumb, this.#thumbEnd, this.#thumbValue, this.#thumbValueEnd);
263
307
  this.#control.append(this.#railArea);
264
308
  this.#markers = document.createElement("slot");
265
309
  this.#markers.name = "markers";
@@ -267,7 +311,17 @@ class ASliderElement extends HTMLElementBase {
267
311
  this.#extras = document.createElement("slot");
268
312
  this.#extras.part.add("extras");
269
313
  for (const slot of [this.#label, this.#markers, this.#extras]) {
270
- slot.addEventListener("slotchange", () => this.#syncSlotVisibility());
314
+ slot.addEventListener("slotchange", () => {
315
+ this.#syncSlotVisibility();
316
+ if (slot === this.#label) this.#paintAccessibility();
317
+ });
318
+ }
319
+ for (const [index, thumb] of [this.#thumb, this.#thumbEnd].entries()) {
320
+ thumb.addEventListener("focus", () => {
321
+ if (!Array.isArray(this.#value)) return;
322
+ this.#lastActiveThumb = index;
323
+ this.#paint();
324
+ });
271
325
  }
272
326
  this.#control.addEventListener("pointerdown", (event) => this.#beginDrag(event));
273
327
  this.#control.addEventListener("pointermove", (event) => this.#moveDrag(event));
@@ -280,7 +334,7 @@ class ASliderElement extends HTMLElementBase {
280
334
  }
281
335
  connectedCallback() {
282
336
  if (!this.#seeded) {
283
- this.#value = this.#initialValue();
337
+ this.#applyValue(this.#initialValue());
284
338
  this.#seeded = true;
285
339
  }
286
340
  this.#paint();
@@ -290,29 +344,29 @@ class ASliderElement extends HTMLElementBase {
290
344
  if (!this.#seeded) return;
291
345
  if (name === "value") {
292
346
  if (this.hasAttribute("value")) {
293
- this.#value = this.#normalize(finiteNumber(this.getAttribute("value"), this.#min));
347
+ this.#applyValue(parseSliderValue(this.getAttribute("value"), this.#fallbackValue()));
294
348
  this.#dirty = false;
295
349
  } else if (!this.#dirty) {
296
- this.#value = this.#initialValue();
350
+ this.#applyValue(this.#initialValue());
297
351
  }
298
352
  } else if (name === "defaultvalue" && !this.hasAttribute("value") && !this.#dirty) {
299
- this.#value = this.#initialValue();
353
+ this.#applyValue(this.#initialValue());
300
354
  } else if (name === "min" || name === "max" || name === "step") {
301
- this.#value = this.#normalize(this.#value);
355
+ this.#applyValue(this.#value);
302
356
  }
303
357
  this.#paint();
304
358
  if (name === "value-display") this.#syncSlotVisibility();
305
359
  }
306
360
  formResetCallback() {
307
361
  const previous = this.#value;
308
- this.#value = this.#normalize(finiteNumber(this.getAttribute("defaultvalue"), this.#min));
362
+ this.#applyValue(this.defaultValue);
309
363
  this.#dirty = false;
310
364
  this.#paint();
311
- if (this.#value !== previous) this.#emitInputAndChange();
365
+ if (!sameSliderValue(this.#value, previous)) this.#emitInputAndChange();
312
366
  }
313
367
  formStateRestoreCallback(state) {
314
368
  if (typeof state !== "string") return;
315
- this.#value = this.#normalize(finiteNumber(state, this.#min));
369
+ this.#applyValue(parseSliderValue(state, this.#fallbackValue()));
316
370
  this.#dirty = true;
317
371
  this.#paint();
318
372
  }
@@ -332,9 +386,13 @@ class ASliderElement extends HTMLElementBase {
332
386
  get #isDisabled() {
333
387
  return this.hasAttribute("disabled") || this.matches(":disabled");
334
388
  }
389
+ #fallbackValue() {
390
+ const range = Array.isArray(this.#value) || Array.isArray(parseSliderValue(this.getAttribute("value"), this.#min));
391
+ return range ? [this.#min, this.#max] : this.#min;
392
+ }
335
393
  #initialValue() {
336
394
  const source = this.getAttribute("value") ?? this.getAttribute("defaultvalue");
337
- return this.#normalize(finiteNumber(source, this.#min));
395
+ return parseSliderValue(source, this.#fallbackValue());
338
396
  }
339
397
  #normalize(value) {
340
398
  const min = this.#min;
@@ -344,38 +402,145 @@ class ASliderElement extends HTMLElementBase {
344
402
  const precision = Math.min(12, Math.max(precisionOf(min), precisionOf(max), precisionOf(step)));
345
403
  return Number(Math.min(max, Math.max(min, stepped)).toFixed(precision));
346
404
  }
347
- #percent(value = this.#value) {
405
+ #normalizeValue(value) {
406
+ if (!Array.isArray(value)) return this.#normalize(value);
407
+ const low = this.#normalize(value[0]);
408
+ const high = this.#normalize(value[1]);
409
+ return low <= high ? [low, high] : [high, low];
410
+ }
411
+ #applyValue(value) {
412
+ const next = this.#normalizeValue(value);
413
+ const wasRange = Array.isArray(this.#value);
414
+ this.#value = next;
415
+ if (Array.isArray(next)) {
416
+ if (!wasRange) this.#orderReversed = false;
417
+ this.#physicalValues = this.#orderReversed ? [next[1], next[0]] : [next[0], next[1]];
418
+ } else {
419
+ this.#physicalValues = [next, next];
420
+ this.#orderReversed = false;
421
+ }
422
+ }
423
+ #percent(value) {
348
424
  const range = this.#max - this.#min;
349
425
  return range > 0 ? (value - this.#min) / range * 100 : 0;
350
426
  }
351
427
  #paint() {
352
- const percent = `${this.#percent()}%`;
353
- this.#railArea.style.setProperty("--_percent", percent);
354
- const value = this.#displayValue();
355
- const displayedValue = String(this.#value);
428
+ const currentValue = this.#value;
429
+ const range = Array.isArray(currentValue);
430
+ const low = range ? currentValue[0] : this.#min;
431
+ const high = range ? currentValue[1] : currentValue;
432
+ const lowPercent = `${this.#percent(low)}%`;
433
+ const highPercent = `${this.#percent(high)}%`;
434
+ const [first, second] = range ? this.#physicalValues : [high, high];
435
+ this.#thumb.style.setProperty("--_percent", `${this.#percent(first)}%`);
436
+ this.#thumbEnd.style.setProperty("--_percent", `${this.#percent(second)}%`);
437
+ this.#thumb.toggleAttribute("data-last-active", this.#lastActiveThumb === 0);
438
+ this.#thumbEnd.toggleAttribute("data-last-active", this.#lastActiveThumb === 1);
439
+ this.#thumbValue.style.setProperty("--_percent", `${this.#percent(first)}%`);
440
+ this.#thumbValueEnd.style.setProperty("--_percent", `${this.#percent(second)}%`);
441
+ this.#thumbEnd.hidden = !range;
442
+ const coincident = range && first === second;
443
+ this.#thumbValue.hidden = coincident && this.#lastActiveThumb !== 0;
444
+ this.#thumbValueEnd.hidden = !range || coincident && this.#lastActiveThumb !== 1;
445
+ const edgeStart = "calc(var(--slider-track-inset) - var(--anta-slider-thumb-size) / 2)";
446
+ const edgeEnd = "calc(var(--anta-slider-thumb-size) / 2 - var(--slider-track-inset))";
447
+ const setFill = (fill, start, end, startEdge = "0px", endEdge = "0px") => {
448
+ fill.style.setProperty("--_fill-start", start);
449
+ fill.style.setProperty("--_fill-end", end);
450
+ fill.style.setProperty("--_fill-start-edge", startEdge);
451
+ fill.style.setProperty("--_fill-end-edge", endEdge);
452
+ };
453
+ if (this.hasAttribute("inverted")) {
454
+ this.#fill.hidden = range ? low === this.#min : high === this.#max;
455
+ setFill(this.#fill, range ? "0%" : highPercent, range ? lowPercent : "100%", range ? edgeStart : high === this.#min ? edgeStart : "0px", range ? "0px" : edgeEnd);
456
+ this.#fillEnd.hidden = !range || high === this.#max;
457
+ if (range) setFill(this.#fillEnd, highPercent, "100%", "0px", edgeEnd);
458
+ } else {
459
+ this.#fill.hidden = range ? low === high : high === this.#min;
460
+ setFill(this.#fill, range ? lowPercent : "0%", highPercent, range && low !== this.#min ? "0px" : edgeStart, range && high === this.#max ? edgeEnd : "0px");
461
+ this.#fillEnd.hidden = true;
462
+ }
463
+ this.#renderValue(this.#inlineValue, this.#inlineValuePrefix, this.#inlineValueSuffix, range ? low : high, range ? high : void 0);
464
+ this.#renderValue(this.#endValue, this.#endValuePrefix, this.#endValueSuffix, range ? low : high, range ? high : void 0);
465
+ this.#renderValue(this.#thumbValue, this.#thumbValuePrefix, this.#thumbValueSuffix, first);
466
+ if (range) this.#renderValue(this.#thumbValueEnd, this.#thumbValueEndPrefix, this.#thumbValueEndSuffix, second);
467
+ this.#paintAccessibility();
468
+ this.#paintFormValue();
469
+ }
470
+ #renderValue(element, prefixElement, suffixElement, first, second) {
471
+ const prefix = this.getAttribute("value-prefix") ?? "";
472
+ const suffix = this.getAttribute("value-suffix") ?? "";
473
+ element.textContent = String(first);
474
+ prefixElement.textContent = prefix;
475
+ suffixElement.textContent = suffix;
476
+ element.prepend(prefixElement);
477
+ element.append(suffixElement);
478
+ if (second === void 0) return;
479
+ const secondPrefix = prefixElement.cloneNode(true);
480
+ const secondSuffix = suffixElement.cloneNode(true);
481
+ element.append("\u2013", secondPrefix, String(second), secondSuffix);
482
+ }
483
+ #formatValue(value) {
356
484
  const prefix = this.getAttribute("value-prefix") ?? "";
357
485
  const suffix = this.getAttribute("value-suffix") ?? "";
358
- for (const [element, prefixElement, suffixElement] of [
359
- [this.#inlineValue, this.#inlineValuePrefix, this.#inlineValueSuffix],
360
- [this.#endValue, this.#endValuePrefix, this.#endValueSuffix],
361
- [this.#thumbValue, this.#thumbValuePrefix, this.#thumbValueSuffix]
362
- ]) {
363
- element.textContent = displayedValue;
364
- prefixElement.textContent = prefix;
365
- suffixElement.textContent = suffix;
366
- element.prepend(prefixElement);
367
- element.append(suffixElement);
486
+ return `${prefix}${value}${suffix}`;
487
+ }
488
+ #paintAccessibility() {
489
+ const range = Array.isArray(this.#value);
490
+ const slottedLabel = this.#label.assignedNodes({ flatten: true }).map((node) => node.textContent ?? "").join("").trim();
491
+ const root = this.getRootNode();
492
+ const referencedLabel = (this.getAttribute("aria-labelledby") ?? "").split(/\s+/).map((id) => root.getElementById(id)?.textContent?.trim() ?? "").filter(Boolean).join(" ");
493
+ const base = (this.getAttribute("aria-label") ?? referencedLabel ?? slottedLabel).trim() || slottedLabel;
494
+ const internals = this.internals;
495
+ if (internals) {
496
+ internals.role = range ? "group" : "slider";
497
+ internals.ariaLabel = !this.hasAttribute("aria-label") && !this.hasAttribute("aria-labelledby") ? slottedLabel || null : null;
498
+ internals.ariaValueMin = range ? null : String(this.#min);
499
+ internals.ariaValueMax = range ? null : String(this.#max);
500
+ internals.ariaValueNow = range ? null : String(this.#value);
501
+ internals.ariaValueText = range ? null : this.#formatValue(this.#value);
502
+ }
503
+ for (const [index, thumb] of [this.#thumb, this.#thumbEnd].entries()) {
504
+ if (!range) {
505
+ thumb.removeAttribute("role");
506
+ thumb.tabIndex = -1;
507
+ thumb.removeAttribute("aria-label");
508
+ thumb.removeAttribute("aria-valuemin");
509
+ thumb.removeAttribute("aria-valuemax");
510
+ thumb.removeAttribute("aria-valuenow");
511
+ thumb.removeAttribute("aria-valuetext");
512
+ thumb.removeAttribute("aria-disabled");
513
+ continue;
514
+ }
515
+ const endpoint = index === 0 !== this.#orderReversed ? "minimum" : "maximum";
516
+ const value = this.#physicalValues[index];
517
+ thumb.role = "slider";
518
+ thumb.tabIndex = this.#isDisabled ? -1 : 0;
519
+ thumb.ariaLabel = base ? `${base} ${endpoint}` : `${endpoint[0].toUpperCase()}${endpoint.slice(1)} value`;
520
+ thumb.ariaValueMin = String(this.#min);
521
+ thumb.ariaValueMax = String(this.#max);
522
+ thumb.ariaValueNow = String(value);
523
+ thumb.ariaValueText = this.#formatValue(value);
524
+ thumb.ariaDisabled = this.#isDisabled ? "true" : null;
368
525
  }
526
+ }
527
+ #paintFormValue() {
369
528
  const internals = this.internals;
370
529
  if (!internals) return;
371
- internals.ariaValueMin = String(this.#min);
372
- internals.ariaValueMax = String(this.#max);
373
- internals.ariaValueNow = String(this.#value);
374
- internals.ariaValueText = value;
375
- internals.setFormValue(this.#isDisabled ? null : String(this.#value), String(this.#value));
376
- }
377
- #displayValue() {
378
- return `${this.getAttribute("value-prefix") ?? ""}${this.#value}${this.getAttribute("value-suffix") ?? ""}`;
530
+ const state = serializeSliderValue(this.#value);
531
+ if (this.#isDisabled) {
532
+ internals.setFormValue(null, state);
533
+ } else if (Array.isArray(this.#value)) {
534
+ const formValue = new FormData();
535
+ const name = this.getAttribute("name");
536
+ if (name) {
537
+ formValue.append(name, String(this.#value[0]));
538
+ formValue.append(name, String(this.#value[1]));
539
+ }
540
+ internals.setFormValue(formValue, state);
541
+ } else {
542
+ internals.setFormValue(state, state);
543
+ }
379
544
  }
380
545
  #syncSlotVisibility() {
381
546
  const assigned = (slot) => slot.assignedNodes({ flatten: true }).length > 0;
@@ -383,12 +548,25 @@ class ASliderElement extends HTMLElementBase {
383
548
  this.#markers.hidden = !assigned(this.#markers);
384
549
  this.#extras.hidden = !assigned(this.#extras);
385
550
  }
386
- #setValue(value) {
551
+ #setValue(value, thumb = 0) {
387
552
  const next = this.#normalize(value);
388
- if (next === this.#value) return false;
389
- this.#value = next;
553
+ if (Array.isArray(this.#value)) {
554
+ const previous = this.#value;
555
+ this.#physicalValues[thumb] = next;
556
+ if (this.#physicalValues[0] !== this.#physicalValues[1]) {
557
+ this.#orderReversed = this.#physicalValues[0] > this.#physicalValues[1];
558
+ }
559
+ this.#value = this.#orderReversed ? [this.#physicalValues[1], this.#physicalValues[0]] : [this.#physicalValues[0], this.#physicalValues[1]];
560
+ this.#lastActiveThumb = thumb;
561
+ this.#paint();
562
+ if (sameSliderValue(previous, this.#value)) return false;
563
+ } else {
564
+ if (next === this.#value) return false;
565
+ this.#value = next;
566
+ this.#physicalValues = [next, next];
567
+ this.#paint();
568
+ }
390
569
  this.#dirty = true;
391
- this.#paint();
392
570
  this.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
393
571
  return true;
394
572
  }
@@ -398,19 +576,33 @@ class ASliderElement extends HTMLElementBase {
398
576
  }
399
577
  #valueAt(clientX) {
400
578
  const rect = this.#railArea.getBoundingClientRect();
401
- if (rect.width <= 0) return this.#value;
579
+ if (rect.width <= 0) return Array.isArray(this.#value) ? this.#physicalValues[this.#lastActiveThumb] : this.#value;
402
580
  return this.#min + (clientX - rect.left) / rect.width * (this.#max - this.#min);
403
581
  }
582
+ #thumbAt(clientX) {
583
+ if (!Array.isArray(this.#value)) return 0;
584
+ const candidate = this.#valueAt(clientX);
585
+ const firstDistance = Math.abs(candidate - this.#physicalValues[0]);
586
+ const secondDistance = Math.abs(candidate - this.#physicalValues[1]);
587
+ return firstDistance === secondDistance ? this.#lastActiveThumb : firstDistance < secondDistance ? 0 : 1;
588
+ }
404
589
  #beginDrag(event) {
405
590
  if (this.#isDisabled || event.button !== 0) return;
406
591
  const rect = this.#railArea.getBoundingClientRect();
407
592
  if (rect.width <= 0) return;
408
- this.#drag = { pointerId: event.pointerId, startX: event.clientX, startValue: this.#value };
593
+ const thumb = event.composedPath()[0] === this.#thumb ? 0 : event.composedPath()[0] === this.#thumbEnd ? 1 : this.#thumbAt(event.clientX);
594
+ const startValue = Array.isArray(this.#value) ? this.#physicalValues[thumb] : this.#value;
595
+ this.#drag = { pointerId: event.pointerId, startX: event.clientX, startValue, startPublicValue: this.value, thumb };
409
596
  this.#control.setPointerCapture(event.pointerId);
410
597
  this.#control.dataset.dragging = "";
411
598
  this.#control.dataset.pointerFocus = "";
412
- this.focus({ preventScroll: true });
413
- if (this.getAttribute("track-click") === "jump") this.#setValue(this.#valueAt(event.clientX));
599
+ this.#lastActiveThumb = thumb;
600
+ this.#thumb.toggleAttribute("data-last-active", thumb === 0);
601
+ this.#thumbEnd.toggleAttribute("data-last-active", thumb === 1);
602
+ const focusTarget = Array.isArray(this.#value) ? [this.#thumb, this.#thumbEnd][thumb] : this;
603
+ focusTarget.focus({ preventScroll: true });
604
+ [this.#thumb, this.#thumbEnd][thumb].dataset.active = "";
605
+ if (this.getAttribute("track-click") === "jump") this.#setValue(this.#valueAt(event.clientX), thumb);
414
606
  event.preventDefault();
415
607
  }
416
608
  #moveDrag(event) {
@@ -421,7 +613,7 @@ class ASliderElement extends HTMLElementBase {
421
613
  return;
422
614
  }
423
615
  const next = this.getAttribute("track-click") === "jump" ? this.#valueAt(event.clientX) : drag.startValue + (event.clientX - drag.startX) / this.#railArea.getBoundingClientRect().width * (this.#max - this.#min);
424
- this.#setValue(next);
616
+ this.#setValue(next, drag.thumb);
425
617
  event.preventDefault();
426
618
  }
427
619
  #finishDrag(event) {
@@ -429,28 +621,33 @@ class ASliderElement extends HTMLElementBase {
429
621
  if (!drag || drag.pointerId !== event.pointerId) return;
430
622
  this.#drag = void 0;
431
623
  delete this.#control.dataset.dragging;
624
+ delete [this.#thumb, this.#thumbEnd][drag.thumb].dataset.active;
432
625
  if (this.#control.hasPointerCapture(event.pointerId)) this.#control.releasePointerCapture(event.pointerId);
433
- if (this.#value !== drag.startValue) this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
626
+ if (!sameSliderValue(this.#value, drag.startPublicValue)) this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
434
627
  }
435
628
  #handleKeydown(event) {
436
- if (event.target !== this || this.#isDisabled || isModifiedNavigationKey(event)) return;
629
+ const source = event.composedPath()[0];
630
+ const range = Array.isArray(this.#value);
631
+ const thumb = source === this.#thumb ? 0 : source === this.#thumbEnd ? 1 : void 0;
632
+ if ((range ? thumb === void 0 : source !== this) || this.#isDisabled || isModifiedNavigationKey(event)) return;
437
633
  delete this.#control.dataset.pointerFocus;
438
634
  const page = Math.max(this.#step, (this.#max - this.#min) / 10);
635
+ const current = range ? this.#physicalValues[thumb ?? 0] : this.#value;
439
636
  let next;
440
637
  switch (event.key) {
441
638
  case "ArrowRight":
442
639
  case "ArrowUp":
443
- next = this.#value + this.#step;
640
+ next = current + this.#step;
444
641
  break;
445
642
  case "ArrowLeft":
446
643
  case "ArrowDown":
447
- next = this.#value - this.#step;
644
+ next = current - this.#step;
448
645
  break;
449
646
  case "PageUp":
450
- next = this.#value + page;
647
+ next = current + page;
451
648
  break;
452
649
  case "PageDown":
453
- next = this.#value - page;
650
+ next = current - page;
454
651
  break;
455
652
  case "Home":
456
653
  next = this.#min;
@@ -462,7 +659,7 @@ class ASliderElement extends HTMLElementBase {
462
659
  return;
463
660
  }
464
661
  event.preventDefault();
465
- if (this.#setValue(next)) this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
662
+ if (this.#setValue(next, range ? thumb ?? 0 : 0)) this.dispatchEvent(new Event("change", { bubbles: true, composed: true }));
466
663
  }
467
664
  }
468
665
  function register_a_slider() {
@@ -705,9 +705,12 @@ export interface AInputAttributes extends BaseAttributes {
705
705
  * wrapper) the glyph. Only `critical` carries validity weight (`aria-invalid`
706
706
  * + `:state(invalid)`); the others are advisory. Omit for the neutral field. */
707
707
  status?: 'neutral' | 'brand' | 'info' | 'success' | 'warning' | 'critical';
708
- /** Custom accent color (any literal CSS color) — tints the resting + hover
709
- * border via an oklch derivation. `status` overrides it for validation. */
708
+ /** Custom accent color (any literal CSS color) — tints the border when shown
709
+ * via an oklch derivation. `status` overrides it for validation. */
710
710
  tone?: string;
711
+ /** Field emphasis. Secondary removes the fill; tertiary also makes the
712
+ * resting border transparent. Status borders remain visible. */
713
+ priority?: 'primary' | 'secondary' | 'tertiary';
711
714
  /** Disabled state. Presence-based. */
712
715
  disabled?: boolean | '';
713
716
  /** Read-only state. Presence-based. */
@@ -762,9 +765,9 @@ export interface AInputAttributes extends BaseAttributes {
762
765
  /** Attributes for the `<a-slider>` custom element. For the typed JSX wrapper,
763
766
  * display formatting, and marker layout use `Slider` from `@antadesign/anta`. */
764
767
  export interface ASliderAttributes extends BaseAttributes {
765
- /** Controlled numeric value. */
768
+ /** Controlled value. Range attributes use two space-separated numbers. */
766
769
  value?: number | string;
767
- /** Initial uncontrolled value and form-reset baseline. */
770
+ /** Initial uncontrolled value and form-reset baseline. Range attributes use two space-separated numbers. */
768
771
  defaultvalue?: number | string;
769
772
  /** Lowest permitted value. Defaults to 0. */
770
773
  min?: number | string;
@@ -792,6 +795,8 @@ export interface ASliderAttributes extends BaseAttributes {
792
795
  round?: boolean | number | string;
793
796
  /** `jump` moves to a pressed rail position. Omit for relative dragging. */
794
797
  'track-click'?: 'jump';
798
+ /** Fill the rail outside the selected value or range. Presence-based. */
799
+ inverted?: boolean | '';
795
800
  /** `inline`, `thumb`, or `none`; omit for a value at the label row's end. */
796
801
  'value-display'?: 'inline' | 'thumb' | 'none';
797
802
  /** Text inserted before the live numeric value. */
package/dist/index.d.ts CHANGED
@@ -70,7 +70,7 @@ export type { ExpanderProps } from './components/Expander';
70
70
  export { Input } from './components/Input';
71
71
  export type { InputProps, InputChangeAttrs } from './components/Input';
72
72
  export { Slider } from './components/Slider';
73
- export type { SliderProps, SliderMarker, SliderChangeAttrs } from './components/Slider';
73
+ export type { SliderProps, SliderMarker, SliderChangeAttrs, SliderRangeValue, SliderValue } from './components/Slider';
74
74
  export { Calendar } from './components/Calendar';
75
75
  export type { CalendarProps, CalendarChangeAttrs } from './components/Calendar';
76
76
  export { InputDate } from './components/InputDate';