@radix-ng/primitives 1.0.0-beta.4 → 1.0.1

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.
Files changed (83) hide show
  1. package/composite/README.md +3 -0
  2. package/fesm2022/radix-ng-primitives-accordion.mjs +12 -36
  3. package/fesm2022/radix-ng-primitives-accordion.mjs.map +1 -1
  4. package/fesm2022/radix-ng-primitives-checkbox.mjs +33 -18
  5. package/fesm2022/radix-ng-primitives-checkbox.mjs.map +1 -1
  6. package/fesm2022/radix-ng-primitives-composite.mjs +515 -0
  7. package/fesm2022/radix-ng-primitives-composite.mjs.map +1 -0
  8. package/fesm2022/radix-ng-primitives-core.mjs +7 -0
  9. package/fesm2022/radix-ng-primitives-core.mjs.map +1 -1
  10. package/fesm2022/radix-ng-primitives-dialog.mjs +54 -12
  11. package/fesm2022/radix-ng-primitives-dialog.mjs.map +1 -1
  12. package/fesm2022/radix-ng-primitives-drawer.mjs +442 -2
  13. package/fesm2022/radix-ng-primitives-drawer.mjs.map +1 -1
  14. package/fesm2022/radix-ng-primitives-editable.mjs +12 -7
  15. package/fesm2022/radix-ng-primitives-editable.mjs.map +1 -1
  16. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs +294 -8
  17. package/fesm2022/radix-ng-primitives-floating-focus-manager.mjs.map +1 -1
  18. package/fesm2022/radix-ng-primitives-focus-scope.mjs +9 -0
  19. package/fesm2022/radix-ng-primitives-focus-scope.mjs.map +1 -1
  20. package/fesm2022/radix-ng-primitives-menu.mjs +71 -20
  21. package/fesm2022/radix-ng-primitives-menu.mjs.map +1 -1
  22. package/fesm2022/radix-ng-primitives-menubar.mjs +68 -36
  23. package/fesm2022/radix-ng-primitives-menubar.mjs.map +1 -1
  24. package/fesm2022/radix-ng-primitives-navigation-menu.mjs +281 -88
  25. package/fesm2022/radix-ng-primitives-navigation-menu.mjs.map +1 -1
  26. package/fesm2022/radix-ng-primitives-number-field.mjs +7 -2
  27. package/fesm2022/radix-ng-primitives-number-field.mjs.map +1 -1
  28. package/fesm2022/radix-ng-primitives-popover.mjs +117 -35
  29. package/fesm2022/radix-ng-primitives-popover.mjs.map +1 -1
  30. package/fesm2022/radix-ng-primitives-popper.mjs +73 -65
  31. package/fesm2022/radix-ng-primitives-popper.mjs.map +1 -1
  32. package/fesm2022/radix-ng-primitives-radio.mjs +77 -36
  33. package/fesm2022/radix-ng-primitives-radio.mjs.map +1 -1
  34. package/fesm2022/radix-ng-primitives-roving-focus.mjs +40 -8
  35. package/fesm2022/radix-ng-primitives-roving-focus.mjs.map +1 -1
  36. package/fesm2022/radix-ng-primitives-scroll-area.mjs +56 -25
  37. package/fesm2022/radix-ng-primitives-scroll-area.mjs.map +1 -1
  38. package/fesm2022/radix-ng-primitives-select.mjs +62 -37
  39. package/fesm2022/radix-ng-primitives-select.mjs.map +1 -1
  40. package/fesm2022/radix-ng-primitives-slider.mjs +259 -28
  41. package/fesm2022/radix-ng-primitives-slider.mjs.map +1 -1
  42. package/fesm2022/radix-ng-primitives-stepper.mjs +11 -7
  43. package/fesm2022/radix-ng-primitives-stepper.mjs.map +1 -1
  44. package/fesm2022/radix-ng-primitives-switch.mjs +10 -5
  45. package/fesm2022/radix-ng-primitives-switch.mjs.map +1 -1
  46. package/fesm2022/radix-ng-primitives-tabs.mjs +64 -30
  47. package/fesm2022/radix-ng-primitives-tabs.mjs.map +1 -1
  48. package/fesm2022/radix-ng-primitives-toggle-group.mjs +69 -19
  49. package/fesm2022/radix-ng-primitives-toggle-group.mjs.map +1 -1
  50. package/fesm2022/radix-ng-primitives-toggle.mjs +37 -13
  51. package/fesm2022/radix-ng-primitives-toggle.mjs.map +1 -1
  52. package/fesm2022/radix-ng-primitives-toolbar.mjs +50 -24
  53. package/fesm2022/radix-ng-primitives-toolbar.mjs.map +1 -1
  54. package/fesm2022/radix-ng-primitives-tooltip.mjs +180 -35
  55. package/fesm2022/radix-ng-primitives-tooltip.mjs.map +1 -1
  56. package/navigation-menu/README.md +5 -2
  57. package/package.json +5 -1
  58. package/types/radix-ng-primitives-accordion.d.ts +9 -13
  59. package/types/radix-ng-primitives-checkbox.d.ts +27 -15
  60. package/types/radix-ng-primitives-composite.d.ts +152 -0
  61. package/types/radix-ng-primitives-core.d.ts +2 -0
  62. package/types/radix-ng-primitives-dialog.d.ts +13 -2
  63. package/types/radix-ng-primitives-drawer.d.ts +40 -2
  64. package/types/radix-ng-primitives-editable.d.ts +11 -5
  65. package/types/radix-ng-primitives-floating-focus-manager.d.ts +113 -16
  66. package/types/radix-ng-primitives-menu.d.ts +13 -5
  67. package/types/radix-ng-primitives-menubar.d.ts +10 -5
  68. package/types/radix-ng-primitives-navigation-menu.d.ts +65 -33
  69. package/types/radix-ng-primitives-number-field.d.ts +8 -3
  70. package/types/radix-ng-primitives-popover.d.ts +26 -10
  71. package/types/radix-ng-primitives-popper.d.ts +1 -0
  72. package/types/radix-ng-primitives-radio.d.ts +22 -13
  73. package/types/radix-ng-primitives-roving-focus.d.ts +15 -1
  74. package/types/radix-ng-primitives-scroll-area.d.ts +4 -1
  75. package/types/radix-ng-primitives-select.d.ts +16 -20
  76. package/types/radix-ng-primitives-slider.d.ts +60 -9
  77. package/types/radix-ng-primitives-stepper.d.ts +11 -4
  78. package/types/radix-ng-primitives-switch.d.ts +10 -4
  79. package/types/radix-ng-primitives-tabs.d.ts +20 -11
  80. package/types/radix-ng-primitives-toggle-group.d.ts +34 -17
  81. package/types/radix-ng-primitives-toggle.d.ts +14 -7
  82. package/types/radix-ng-primitives-toolbar.d.ts +22 -14
  83. package/types/radix-ng-primitives-tooltip.d.ts +38 -14
@@ -1,8 +1,8 @@
1
1
  import * as i0 from '@angular/core';
2
- import { inject, ElementRef, DestroyRef, Directive, computed, input, numberAttribute, booleanAttribute, model, output, signal, NgModule } from '@angular/core';
2
+ import { inject, ElementRef, DestroyRef, Directive, computed, input, numberAttribute, booleanAttribute, model, output, signal, afterNextRender, afterRenderEffect, untracked, NgModule } from '@angular/core';
3
3
  import { DOCUMENT } from '@angular/common';
4
4
  import * as i1 from '@radix-ng/primitives/core';
5
- import { createContext, clamp, injectControlValueAccessor, injectId, RdxControlValueAccessor } from '@radix-ng/primitives/core';
5
+ import { createContext, clamp, injectControlValueAccessor, injectDocument, injectId, createCancelableChangeEventDetails, RdxControlValueAccessor } from '@radix-ng/primitives/core';
6
6
  export { clamp } from '@radix-ng/primitives/core';
7
7
  import { injectDirection } from '@radix-ng/primitives/direction-provider';
8
8
 
@@ -34,6 +34,20 @@ function getMidpoint(element) {
34
34
  const rect = element.getBoundingClientRect();
35
35
  return { x: (rect.left + rect.right) / 2, y: (rect.top + rect.bottom) / 2 };
36
36
  }
37
+ /** Calculates the control-relative percent for an edge-aligned thumb. */
38
+ function getInsetThumbPositionPercent(control, thumb, thumbValuePercent, vertical) {
39
+ const thumbRect = thumb.getBoundingClientRect();
40
+ const controlRect = control.getBoundingClientRect();
41
+ const side = vertical ? 'height' : 'width';
42
+ const controlSide = controlRect[side];
43
+ if (!(controlSide > 0)) {
44
+ return undefined;
45
+ }
46
+ const controlSize = controlSide - thumbRect[side];
47
+ const thumbOffsetFromControlEdge = thumbRect[side] / 2 + (controlSize * thumbValuePercent) / 100;
48
+ const nextPosition = (thumbOffsetFromControlEdge / controlSide) * 100;
49
+ return Number.isFinite(nextPosition) ? nextPosition : undefined;
50
+ }
37
51
  /** Converts an array of values into clamped 0–100 percentages. */
38
52
  function valueArrayToPercentages(values, min, max) {
39
53
  return values.map((value) => clamp(valueToPercent(value, min, max), 0, 100));
@@ -304,11 +318,22 @@ class RdxSliderControl {
304
318
  this.styles = null;
305
319
  this.moveCount = 0;
306
320
  this.currentInteractionValue = null;
321
+ this.touchId = null;
322
+ this.insetThumbOffset = 0;
323
+ this.onTouchStart = (event) => this.handleTouchStart(event);
307
324
  this.onMove = (event) => this.handleMove(event);
308
325
  this.onUp = (event) => this.handleUp(event);
309
326
  this.onCancel = (event) => this.handleUp(event);
310
- this.root.controlRef.set(this.elementRef.nativeElement);
311
- inject(DestroyRef).onDestroy(() => this.stopListening());
327
+ this.onTouchMove = (event) => this.handleMove(event);
328
+ this.onTouchEnd = (event) => this.handleUp(event);
329
+ const control = this.elementRef.nativeElement;
330
+ this.root.controlRef.set(control);
331
+ control.style.setProperty('touch-action', 'none');
332
+ control.addEventListener('touchstart', this.onTouchStart, { passive: true });
333
+ inject(DestroyRef).onDestroy(() => {
334
+ control.removeEventListener('touchstart', this.onTouchStart);
335
+ this.stopListening();
336
+ });
312
337
  }
313
338
  onPointerDown(event) {
314
339
  const control = this.elementRef.nativeElement;
@@ -319,11 +344,16 @@ class RdxSliderControl {
319
344
  if (!target) {
320
345
  return;
321
346
  }
347
+ if (this.isTargetDisabledThumb(target)) {
348
+ this.root.resetPressedThumb();
349
+ return;
350
+ }
322
351
  // Suppress the nested range input's native click-to-set and drag so the
323
352
  // control fully owns pointer interaction (otherwise releasing on a thumb
324
353
  // fires a native change that snaps the value to the press point inside
325
354
  // the thumb-sized input). Focus is restored manually via focusThumb.
326
355
  event.preventDefault();
356
+ this.touchId = null;
327
357
  this.styles = this.document.defaultView?.getComputedStyle(control) ?? null;
328
358
  this.startPressing({ x: event.clientX, y: event.clientY });
329
359
  const finger = this.getFingerState({ x: event.clientX, y: event.clientY });
@@ -333,7 +363,7 @@ class RdxSliderControl {
333
363
  this.root.setDragging(true);
334
364
  // Pressing directly on a thumb sets a center offset; only a rail press changes value on down.
335
365
  if (this.root.pressedThumbCenterOffset == null) {
336
- this.setValueFromPointer(finger, 'track-press');
366
+ this.setValueFromPointer(finger, 'track-press', event);
337
367
  }
338
368
  this.root.focusThumb(finger.thumbIndex);
339
369
  control.setPointerCapture(event.pointerId);
@@ -342,13 +372,50 @@ class RdxSliderControl {
342
372
  this.document.addEventListener('pointerup', this.onUp, { once: true });
343
373
  this.document.addEventListener('pointercancel', this.onCancel, { once: true });
344
374
  }
375
+ handleTouchStart(event) {
376
+ if (this.root.isDisabled()) {
377
+ return;
378
+ }
379
+ const touch = event.changedTouches[0];
380
+ if (!touch) {
381
+ return;
382
+ }
383
+ if (this.isTargetDisabledThumb(event.target)) {
384
+ this.root.resetPressedThumb();
385
+ return;
386
+ }
387
+ this.touchId = touch.identifier;
388
+ this.styles = this.document.defaultView?.getComputedStyle(this.elementRef.nativeElement) ?? null;
389
+ const fingerCoords = this.getFingerCoords(event);
390
+ if (fingerCoords == null) {
391
+ return;
392
+ }
393
+ this.startPressing(fingerCoords);
394
+ const finger = this.getFingerState(fingerCoords);
395
+ if (finger == null) {
396
+ return;
397
+ }
398
+ this.root.focusThumb(finger.thumbIndex);
399
+ const applied = this.setValueFromPointer(finger, 'track-press', event);
400
+ if (applied && finger.didSwap) {
401
+ this.root.focusThumb(finger.thumbIndex);
402
+ }
403
+ this.moveCount = 0;
404
+ this.document.addEventListener('touchmove', this.onTouchMove, { passive: true });
405
+ this.document.addEventListener('touchend', this.onTouchEnd, { passive: true, once: true });
406
+ this.document.addEventListener('touchcancel', this.onTouchEnd, { passive: true, once: true });
407
+ }
345
408
  handleMove(event) {
409
+ const fingerCoords = this.getFingerCoords(event);
410
+ if (fingerCoords == null) {
411
+ return;
412
+ }
346
413
  this.moveCount += 1;
347
- if (event.buttons === 0) {
414
+ if ('buttons' in event && event.buttons === 0) {
348
415
  this.handleUp(event);
349
416
  return;
350
417
  }
351
- const finger = this.getFingerState({ x: event.clientX, y: event.clientY });
418
+ const finger = this.getFingerState(fingerCoords);
352
419
  if (finger == null) {
353
420
  return;
354
421
  }
@@ -356,7 +423,7 @@ class RdxSliderControl {
356
423
  if (!this.root.dragging() && this.moveCount > INTENTIONAL_DRAG_COUNT_THRESHOLD) {
357
424
  this.root.setDragging(true);
358
425
  }
359
- const applied = this.setValueFromPointer(finger, 'drag');
426
+ const applied = this.setValueFromPointer(finger, 'drag', event);
360
427
  if (applied && finger.didSwap) {
361
428
  this.root.focusThumb(finger.thumbIndex);
362
429
  }
@@ -368,13 +435,14 @@ class RdxSliderControl {
368
435
  this.root.pressedThumbCenterOffset = null;
369
436
  this.root.pressedInput = null;
370
437
  if (this.currentInteractionValue != null) {
371
- this.root.commitValue();
438
+ this.root.commitValue(event);
372
439
  }
373
440
  const control = this.elementRef.nativeElement;
374
- if (control.hasPointerCapture?.(event.pointerId)) {
441
+ if ('pointerId' in event && control.hasPointerCapture?.(event.pointerId)) {
375
442
  control.releasePointerCapture(event.pointerId);
376
443
  }
377
444
  this.root.resetPressedThumb();
445
+ this.touchId = null;
378
446
  this.root.pressedValues = null;
379
447
  this.currentInteractionValue = null;
380
448
  this.stopListening();
@@ -383,6 +451,31 @@ class RdxSliderControl {
383
451
  this.document.removeEventListener('pointermove', this.onMove);
384
452
  this.document.removeEventListener('pointerup', this.onUp);
385
453
  this.document.removeEventListener('pointercancel', this.onCancel);
454
+ this.document.removeEventListener('touchmove', this.onTouchMove);
455
+ this.document.removeEventListener('touchend', this.onTouchEnd);
456
+ this.document.removeEventListener('touchcancel', this.onTouchEnd);
457
+ }
458
+ getFingerCoords(event) {
459
+ if ('changedTouches' in event) {
460
+ if (this.touchId == null) {
461
+ return null;
462
+ }
463
+ for (let i = 0; i < event.changedTouches.length; i += 1) {
464
+ const touch = event.changedTouches[i];
465
+ if (touch.identifier === this.touchId) {
466
+ return { x: touch.clientX, y: touch.clientY };
467
+ }
468
+ }
469
+ return null;
470
+ }
471
+ return { x: event.clientX, y: event.clientY };
472
+ }
473
+ isTargetDisabledThumb(target) {
474
+ const NodeCtor = this.elementRef.nativeElement.ownerDocument.defaultView?.Node;
475
+ if (!NodeCtor || !(target instanceof NodeCtor)) {
476
+ return false;
477
+ }
478
+ return this.root.thumbList().some((thumb) => thumb.disabled() && thumb.element.contains(target));
386
479
  }
387
480
  startPressing(finger) {
388
481
  const values = this.root.values();
@@ -424,10 +517,18 @@ class RdxSliderControl {
424
517
  this.root.pressedThumbIndex = closestThumbIndex;
425
518
  this.root.pressedInput = this.root.thumbList()[closestThumbIndex]?.inputElement ?? null;
426
519
  }
520
+ this.insetThumbOffset = 0;
521
+ if (this.root.inset()) {
522
+ const thumb = this.root.thumbList()[closestThumbIndex]?.element;
523
+ if (thumb) {
524
+ const rect = thumb.getBoundingClientRect();
525
+ this.insetThumbOffset = (this.root.orientation() === 'vertical' ? rect.height : rect.width) / 2;
526
+ }
527
+ }
427
528
  }
428
- setValueFromPointer(finger, reason) {
529
+ setValueFromPointer(finger, reason, event) {
429
530
  const nextValues = Array.isArray(finger.value) ? finger.value : [finger.value];
430
- const applied = this.root.setValue(nextValues, reason);
531
+ const applied = this.root.setValue(nextValues, reason, event, finger.thumbIndex);
431
532
  if (applied) {
432
533
  this.currentInteractionValue = finger.value;
433
534
  if (finger.didSwap) {
@@ -453,7 +554,7 @@ class RdxSliderControl {
453
554
  }
454
555
  const { width, height, bottom, left, right } = control.getBoundingClientRect();
455
556
  const controlOffset = getControlOffset(this.styles, vertical, rtl);
456
- const controlSize = (vertical ? height : width) - controlOffset.start - controlOffset.end;
557
+ const controlSize = (vertical ? height : width) - controlOffset.start - controlOffset.end - this.insetThumbOffset * 2;
457
558
  // A collapsed/unmeasurable track would divide by zero and yield NaN values.
458
559
  if (!(controlSize > 0)) {
459
560
  return null;
@@ -464,7 +565,7 @@ class RdxSliderControl {
464
565
  const valueSize = vertical
465
566
  ? bottom - fingerY - controlOffset.end
466
567
  : (rtl ? right - fingerX : fingerX - left) - controlOffset.start;
467
- const valueRescaled = clamp(valueSize / controlSize, 0, 1);
568
+ const valueRescaled = clamp((valueSize - this.insetThumbOffset) / controlSize, 0, 1);
468
569
  let newValue = (max - min) * valueRescaled + min;
469
570
  newValue = roundValueToStep(newValue, step, min);
470
571
  newValue = clamp(newValue, min, max);
@@ -504,6 +605,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
504
605
  }]
505
606
  }], ctorParameters: () => [] });
506
607
 
608
+ function getInsetStyles(vertical, range, start, end) {
609
+ const startEdge = vertical ? 'bottom' : 'inset-inline-start';
610
+ const mainSide = vertical ? 'height' : 'width';
611
+ const crossSide = vertical ? 'width' : 'height';
612
+ const styles = {
613
+ position: vertical ? 'absolute' : 'relative',
614
+ [crossSide]: 'inherit'
615
+ };
616
+ if (start === undefined || (range && end === undefined)) {
617
+ styles['visibility'] = 'hidden';
618
+ }
619
+ if (!range) {
620
+ styles[startEdge] = 0;
621
+ styles[mainSide] = `${start ?? 0}%`;
622
+ return styles;
623
+ }
624
+ styles[startEdge] = `${start ?? 0}%`;
625
+ styles[mainSide] = `${(end ?? 0) - (start ?? 0)}%`;
626
+ return styles;
627
+ }
507
628
  /**
508
629
  * Visualises the portion of the track between the slider's minimum (or the first
509
630
  * thumb in a range) and the active value.
@@ -519,11 +640,16 @@ class RdxSliderIndicator {
519
640
  const values = this.root.values();
520
641
  const min = this.root.min();
521
642
  const max = this.root.max();
643
+ const inset = this.root.inset();
522
644
  const startEdge = vertical ? 'bottom' : 'inset-inline-start';
523
645
  const mainSide = vertical ? 'height' : 'width';
524
646
  const crossSide = vertical ? 'width' : 'height';
525
647
  const start = valueToPercent(values[0], min, max);
526
648
  const end = valueToPercent(values[values.length - 1], min, max);
649
+ if (inset) {
650
+ const [startPosition, endPosition] = this.root.indicatorPosition();
651
+ return getInsetStyles(vertical, range, startPosition, endPosition);
652
+ }
527
653
  const styles = {
528
654
  position: vertical ? 'absolute' : 'relative',
529
655
  [crossSide]: 'inherit'
@@ -567,6 +693,21 @@ function sortByDomOrder(list) {
567
693
  return 0;
568
694
  });
569
695
  }
696
+ function cloneChangeEventWithTarget(event, value, name) {
697
+ const EventCtor = event.constructor;
698
+ let clonedEvent;
699
+ try {
700
+ clonedEvent = new EventCtor(event.type, event);
701
+ }
702
+ catch {
703
+ clonedEvent = new Event(event.type, event);
704
+ }
705
+ Object.defineProperty(clonedEvent, 'target', {
706
+ writable: true,
707
+ value: { value, name }
708
+ });
709
+ return clonedEvent;
710
+ }
570
711
  /**
571
712
  * Groups all parts of the slider and owns its state, value-change logic and
572
713
  * thumb registration. A single directive drives both orientations — there are no
@@ -578,6 +719,7 @@ class RdxSliderRoot {
578
719
  constructor() {
579
720
  /** @ignore */
580
721
  this.cva = injectControlValueAccessor();
722
+ this.document = injectDocument();
581
723
  this.id = input(injectId('rdx-slider-'), ...(ngDevMode ? [{ debugName: "id" }] : /* istanbul ignore next */ []));
582
724
  /**
583
725
  * The minimum value of the slider.
@@ -620,6 +762,13 @@ class RdxSliderRoot {
620
762
  * @default 'push'
621
763
  */
622
764
  this.thumbCollisionBehavior = input('push', ...(ngDevMode ? [{ debugName: "thumbCollisionBehavior" }] : /* istanbul ignore next */ []));
765
+ /**
766
+ * How the thumbs align with the control when the value is at min/max.
767
+ * `center` aligns the thumb center to the control edge; `edge`/`edge-client-only`
768
+ * inset thumbs so their outer edge aligns to the control edge.
769
+ * @default 'center'
770
+ */
771
+ this.thumbAlignment = input('center', ...(ngDevMode ? [{ debugName: "thumbAlignment" }] : /* istanbul ignore next */ []));
623
772
  /** Options forwarded to `Intl.NumberFormat` when displaying and announcing values. */
624
773
  this.format = input(...(ngDevMode ? [undefined, { debugName: "format" }] : /* istanbul ignore next */ []));
625
774
  /** Locale used for value formatting. */
@@ -650,6 +799,8 @@ class RdxSliderRoot {
650
799
  this.lastUsedThumbIndex = signal(-1, ...(ngDevMode ? [{ debugName: "lastUsedThumbIndex" }] : /* istanbul ignore next */ []));
651
800
  /** @ignore Whether a pointer drag is in progress. */
652
801
  this.dragging = signal(false, ...(ngDevMode ? [{ debugName: "dragging" }] : /* istanbul ignore next */ []));
802
+ /** @ignore Edge-aligned thumb/indicator positions, in control-relative percentages. */
803
+ this.indicatorPosition = signal([undefined, undefined], ...(ngDevMode ? [{ debugName: "indicatorPosition" }] : /* istanbul ignore next */ []));
653
804
  /** @ignore Pointer-drag scratch state (not reactive). */
654
805
  this.pressedThumbIndex = -1;
655
806
  /** @ignore */
@@ -662,6 +813,10 @@ class RdxSliderRoot {
662
813
  this.lastChangeReason = 'none';
663
814
  /** @ignore */
664
815
  this.isDisabled = computed(() => !!this.cva.disabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
816
+ /** @ignore */
817
+ this.inset = computed(() => this.thumbAlignment() !== 'center', ...(ngDevMode ? [{ debugName: "inset" }] : /* istanbul ignore next */ []));
818
+ /** @ignore */
819
+ this.renderBeforeHydration = computed(() => this.thumbAlignment() === 'edge', ...(ngDevMode ? [{ debugName: "renderBeforeHydration" }] : /* istanbul ignore next */ []));
665
820
  /** @ignore The current value source (controlled value, else default, else min). */
666
821
  this.currentRaw = computed(() => this.cva.value() ?? this.defaultValue() ?? this.min(), ...(ngDevMode ? [{ debugName: "currentRaw" }] : /* istanbul ignore next */ []));
667
822
  /** Whether the slider has multiple thumbs (the value is an array). */
@@ -702,6 +857,18 @@ class RdxSliderRoot {
702
857
  this.thumbList()[index]?.inputElement?.focus({ preventScroll: true });
703
858
  }
704
859
  /** @ignore */
860
+ setIndicatorPosition(index, position) {
861
+ this.indicatorPosition.update(([start, end]) => {
862
+ if (index === 0) {
863
+ return [position, end];
864
+ }
865
+ if (index === this.values().length - 1) {
866
+ return [start, position];
867
+ }
868
+ return [start, end];
869
+ });
870
+ }
871
+ /** @ignore */
705
872
  formatValue(value) {
706
873
  return formatNumber(value, this.locale(), this.format());
707
874
  }
@@ -718,21 +885,29 @@ class RdxSliderRoot {
718
885
  * Applies a new full set of values, preserving the single/range value shape.
719
886
  * Returns `false` when the value did not change.
720
887
  */
721
- setValue(nextValues, reason) {
888
+ setValue(nextValues, reason, event, activeThumbIndex = -1) {
722
889
  const next = this.range() ? nextValues : nextValues[0];
723
890
  const current = this.outputValue();
724
891
  const hasNaN = Array.isArray(next) ? next.some((v) => Number.isNaN(v)) : Number.isNaN(next);
725
892
  if (hasNaN || areValuesEqual(next, current)) {
726
893
  return false;
727
894
  }
895
+ const trigger = event?.currentTarget instanceof HTMLElement ? event.currentTarget : undefined;
896
+ const changeEvent = cloneChangeEventWithTarget(event ?? new Event('slider.value-change'), next, this.name());
897
+ const { eventDetails: baseEventDetails } = createCancelableChangeEventDetails(reason, changeEvent, trigger);
898
+ const eventDetails = Object.assign(baseEventDetails, { activeThumbIndex });
899
+ this.onValueChange.emit({ value: next, eventDetails });
900
+ if (eventDetails.isCanceled()) {
901
+ return false;
902
+ }
728
903
  this.lastChangeReason = reason;
904
+ this.lastChangeEvent = eventDetails.event;
729
905
  this.value.set(next);
730
906
  this.cva.setValue(next);
731
- this.onValueChange.emit(next);
732
907
  return true;
733
908
  }
734
909
  /** @ignore Keyboard / native input path: clamps to neighbours, commits immediately. */
735
- handleInputChange(valueInput, index, reason = 'keyboard') {
910
+ handleInputChange(valueInput, index, reason = 'keyboard', event) {
736
911
  if (this.isDisabled()) {
737
912
  return;
738
913
  }
@@ -741,15 +916,24 @@ class RdxSliderRoot {
741
916
  return;
742
917
  }
743
918
  const arr = Array.isArray(result) ? result : [result];
744
- const applied = this.setValue(arr, reason);
919
+ const applied = this.setValue(arr, reason, event, index);
745
920
  this.cva.markAsTouched();
746
921
  if (applied) {
747
- this.onValueCommitted.emit(this.outputValue());
922
+ this.commitValue(event, reason);
748
923
  }
749
924
  }
750
925
  /** @ignore Emits the committed value at the end of a pointer drag. */
751
- commitValue() {
752
- this.onValueCommitted.emit(this.outputValue());
926
+ commitValue(event, reason = this.lastChangeReason) {
927
+ const commitEvent = event ?? this.lastChangeEvent ?? new Event('slider.value-commit');
928
+ const trigger = commitEvent.currentTarget instanceof HTMLElement ? commitEvent.currentTarget : undefined;
929
+ this.onValueCommitted.emit({
930
+ value: this.outputValue(),
931
+ eventDetails: {
932
+ reason,
933
+ event: commitEvent,
934
+ trigger
935
+ }
936
+ });
753
937
  }
754
938
  /** @ignore */
755
939
  markAsTouched() {
@@ -765,8 +949,12 @@ class RdxSliderRoot {
765
949
  this.pressedThumbCenterOffset = null;
766
950
  this.pressedInput = null;
767
951
  }
952
+ /** @ignore */
953
+ getOwnerWindow() {
954
+ return this.document.defaultView ?? undefined;
955
+ }
768
956
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSliderRoot, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
769
- static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSliderRoot, isStandalone: true, selector: "div[rdxSliderRoot]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, minStepsBetweenValues: { classPropertyName: "minStepsBetweenValues", publicName: "minStepsBetweenValues", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, thumbCollisionBehavior: { classPropertyName: "thumbCollisionBehavior", publicName: "thumbCollisionBehavior", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange", onValueCommitted: "onValueCommitted" }, host: { attributes: { "role": "group" }, properties: { "id": "id()", "attr.aria-labelledby": "ariaLabelledBy()", "attr.dir": "dir()", "attr.data-orientation": "orientation()", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-dragging": "dragging() ? \"\" : undefined" } }, providers: [provideSliderRootContext(() => inject(RdxSliderRoot))], exportAs: ["rdxSliderRoot"], hostDirectives: [{ directive: i1.RdxControlValueAccessor, inputs: ["value", "value", "disabled", "disabled"] }], ngImport: i0 }); }
957
+ static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.9", type: RdxSliderRoot, isStandalone: true, selector: "div[rdxSliderRoot]", inputs: { id: { classPropertyName: "id", publicName: "id", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, largeStep: { classPropertyName: "largeStep", publicName: "largeStep", isSignal: true, isRequired: false, transformFunction: null }, minStepsBetweenValues: { classPropertyName: "minStepsBetweenValues", publicName: "minStepsBetweenValues", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, dirInput: { classPropertyName: "dirInput", publicName: "dir", isSignal: true, isRequired: false, transformFunction: null }, thumbCollisionBehavior: { classPropertyName: "thumbCollisionBehavior", publicName: "thumbCollisionBehavior", isSignal: true, isRequired: false, transformFunction: null }, thumbAlignment: { classPropertyName: "thumbAlignment", publicName: "thumbAlignment", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, form: { classPropertyName: "form", publicName: "form", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, defaultValue: { classPropertyName: "defaultValue", publicName: "defaultValue", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, ariaLabelledBy: { classPropertyName: "ariaLabelledBy", publicName: "aria-labelledby", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", onValueChange: "onValueChange", onValueCommitted: "onValueCommitted" }, host: { attributes: { "role": "group" }, properties: { "id": "id()", "attr.aria-labelledby": "ariaLabelledBy()", "attr.dir": "dir()", "attr.data-orientation": "orientation()", "attr.data-disabled": "isDisabled() ? \"\" : undefined", "attr.data-dragging": "dragging() ? \"\" : undefined" } }, providers: [provideSliderRootContext(() => inject(RdxSliderRoot))], exportAs: ["rdxSliderRoot"], hostDirectives: [{ directive: i1.RdxControlValueAccessor, inputs: ["value", "value", "disabled", "disabled"] }], ngImport: i0 }); }
770
958
  }
771
959
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: RdxSliderRoot, decorators: [{
772
960
  type: Directive,
@@ -790,7 +978,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
790
978
  '[attr.data-dragging]': 'dragging() ? "" : undefined'
791
979
  }
792
980
  }]
793
- }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], largeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "largeStep", required: false }] }], minStepsBetweenValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "minStepsBetweenValues", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], thumbCollisionBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbCollisionBehavior", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onValueCommitted: [{ type: i0.Output, args: ["onValueCommitted"] }] } });
981
+ }], propDecorators: { id: [{ type: i0.Input, args: [{ isSignal: true, alias: "id", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], largeStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "largeStep", required: false }] }], minStepsBetweenValues: [{ type: i0.Input, args: [{ isSignal: true, alias: "minStepsBetweenValues", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], dirInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "dir", required: false }] }], thumbCollisionBehavior: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbCollisionBehavior", required: false }] }], thumbAlignment: [{ type: i0.Input, args: [{ isSignal: true, alias: "thumbAlignment", required: false }] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], form: [{ type: i0.Input, args: [{ isSignal: true, alias: "form", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], defaultValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultValue", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], ariaLabelledBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "aria-labelledby", required: false }] }], onValueChange: [{ type: i0.Output, args: ["onValueChange"] }], onValueCommitted: [{ type: i0.Output, args: ["onValueCommitted"] }] } });
794
982
 
795
983
  /**
796
984
  * A draggable handle. Render one per value; place an `input[rdxSliderThumbInput]`
@@ -819,12 +1007,15 @@ class RdxSliderThumb {
819
1007
  const value = this.value();
820
1008
  return value === undefined ? NaN : valueToPercent(value, this.root.min(), this.root.max());
821
1009
  }, ...(ngDevMode ? [{ debugName: "percent" }] : /* istanbul ignore next */ []));
1010
+ this.insetPosition = signal(undefined, ...(ngDevMode ? [{ debugName: "insetPosition" }] : /* istanbul ignore next */ []));
822
1011
  this.thumbStyle = computed(() => {
823
1012
  const vertical = this.root.orientation() === 'vertical';
824
1013
  const rtl = this.root.dir() === 'rtl';
825
1014
  const startEdge = vertical ? 'bottom' : 'inset-inline-start';
826
1015
  const crossOffset = vertical ? 'left' : 'top';
827
1016
  const percent = this.percent();
1017
+ const inset = this.root.inset();
1018
+ const position = this.insetPosition();
828
1019
  if (!Number.isFinite(percent)) {
829
1020
  return { position: 'absolute', visibility: 'hidden' };
830
1021
  }
@@ -843,10 +1034,13 @@ class RdxSliderThumb {
843
1034
  }
844
1035
  const style = {
845
1036
  position: 'absolute',
846
- [startEdge]: `${percent}%`,
1037
+ [startEdge]: inset ? `${position ?? 0}%` : `${percent}%`,
847
1038
  [crossOffset]: '50%',
848
1039
  translate: `${(vertical || !rtl ? -1 : 1) * 50}% ${(vertical ? 1 : -1) * 50}%`
849
1040
  };
1041
+ if (inset && position === undefined) {
1042
+ style['visibility'] = 'hidden';
1043
+ }
850
1044
  if (zIndex !== undefined) {
851
1045
  style['z-index'] = zIndex;
852
1046
  }
@@ -855,7 +1049,44 @@ class RdxSliderThumb {
855
1049
  // Registration is DOM-order sorted on the root and reads no inputs, so the constructor
856
1050
  // (where the host element already exists) is the right place; cleanup goes via DestroyRef.
857
1051
  this.root.registerThumb(this);
858
- inject(DestroyRef).onDestroy(() => this.root.unregisterThumb(this));
1052
+ const destroyRef = inject(DestroyRef);
1053
+ destroyRef.onDestroy(() => this.root.unregisterThumb(this));
1054
+ afterNextRender(() => {
1055
+ const win = this.root.getOwnerWindow();
1056
+ const ResizeObserverCtor = win
1057
+ ?.ResizeObserver;
1058
+ if (!ResizeObserverCtor) {
1059
+ this.updateInsetPosition();
1060
+ return;
1061
+ }
1062
+ const observer = new ResizeObserverCtor(() => this.updateInsetPosition());
1063
+ const control = this.root.controlRef();
1064
+ if (control) {
1065
+ observer.observe(control);
1066
+ }
1067
+ observer.observe(this.element);
1068
+ destroyRef.onDestroy(() => observer.disconnect());
1069
+ });
1070
+ afterRenderEffect(() => {
1071
+ this.root.inset();
1072
+ this.percent();
1073
+ this.index();
1074
+ this.root.values();
1075
+ untracked(() => queueMicrotask(() => this.updateInsetPosition()));
1076
+ });
1077
+ }
1078
+ updateInsetPosition() {
1079
+ if (!this.root.inset()) {
1080
+ this.insetPosition.set(undefined);
1081
+ return;
1082
+ }
1083
+ const control = this.root.controlRef();
1084
+ if (!control) {
1085
+ return;
1086
+ }
1087
+ const position = getInsetThumbPositionPercent(control, this.element, this.percent(), this.root.orientation() === 'vertical');
1088
+ this.insetPosition.set(position);
1089
+ this.root.setIndicatorPosition(this.index(), position);
859
1090
  }
860
1091
  onPointerDown(event) {
861
1092
  if (this.disabled()) {
@@ -920,7 +1151,7 @@ class RdxSliderThumbInput {
920
1151
  onChange(event) {
921
1152
  const value = event.target.valueAsNumber;
922
1153
  if (!Number.isNaN(value)) {
923
- this.root.handleInputChange(value, this.thumb.index(), 'input');
1154
+ this.root.handleInputChange(value, this.thumb.index(), 'input-change', event);
924
1155
  }
925
1156
  }
926
1157
  onFocus() {
@@ -983,7 +1214,7 @@ class RdxSliderThumbInput {
983
1214
  break;
984
1215
  }
985
1216
  if (newValue !== null) {
986
- this.root.handleInputChange(newValue, index, 'keyboard');
1217
+ this.root.handleInputChange(newValue, index, 'keyboard', event);
987
1218
  event.preventDefault();
988
1219
  }
989
1220
  }
@@ -1115,5 +1346,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
1115
1346
  * Generated bundle index. Do not edit.
1116
1347
  */
1117
1348
 
1118
- export { ALL_KEYS, ARROW_KEYS, COMPOSITE_KEYS, RdxSliderControl, RdxSliderIndicator, RdxSliderModule, RdxSliderRoot, RdxSliderThumb, RdxSliderThumbInput, RdxSliderTrack, RdxSliderValue, areValuesEqual, asc, formatNumber, getControlOffset, getDecimalPrecision, getDefaultAriaValueText, getMidpoint, getNewValue, getPushedThumbValues, getSliderValue, injectSliderRootContext, provideSliderRootContext, replaceArrayItemAtIndex, resolveThumbCollision, roundValueToStep, validateMinimumDistance, valueArrayToPercentages, valueToPercent };
1349
+ export { ALL_KEYS, ARROW_KEYS, COMPOSITE_KEYS, RdxSliderControl, RdxSliderIndicator, RdxSliderModule, RdxSliderRoot, RdxSliderThumb, RdxSliderThumbInput, RdxSliderTrack, RdxSliderValue, areValuesEqual, asc, formatNumber, getControlOffset, getDecimalPrecision, getDefaultAriaValueText, getInsetThumbPositionPercent, getMidpoint, getNewValue, getPushedThumbValues, getSliderValue, injectSliderRootContext, provideSliderRootContext, replaceArrayItemAtIndex, resolveThumbCollision, roundValueToStep, validateMinimumDistance, valueArrayToPercentages, valueToPercent };
1119
1350
  //# sourceMappingURL=radix-ng-primitives-slider.mjs.map