ng-blatui 1.23.0 → 1.25.0

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.
@@ -479,6 +479,15 @@ class BuiProgress {
479
479
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
480
480
  indeterminate = input(false, /* @ts-ignore */
481
481
  ...(ngDevMode ? [{ debugName: "indeterminate" }] : /* istanbul ignore next */ []));
482
+ /** Render a circular ring instead of a linear bar. */
483
+ circular = input(false, /* @ts-ignore */
484
+ ...(ngDevMode ? [{ debugName: "circular" }] : /* istanbul ignore next */ []));
485
+ /** Diameter of the circular ring, in pixels. */
486
+ size = input(40, /* @ts-ignore */
487
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
488
+ /** Stroke width of the circular ring, in pixels. */
489
+ thickness = input(4, /* @ts-ignore */
490
+ ...(ngDevMode ? [{ debugName: "thickness" }] : /* istanbul ignore next */ []));
482
491
  ariaLabel = input('Progress', /* @ts-ignore */
483
492
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
484
493
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
@@ -490,11 +499,48 @@ class BuiProgress {
490
499
  ...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
491
500
  indicatorTransform = computed(() => `translateX(-${100 - this.pct()}%)`, /* @ts-ignore */
492
501
  ...(ngDevMode ? [{ debugName: "indicatorTransform" }] : /* istanbul ignore next */ []));
493
- computedClass = computed(() => cn('relative block h-2 w-full overflow-hidden rounded-full bg-primary/20', this.userClass()), /* @ts-ignore */
502
+ radius = computed(() => (this.size() - this.thickness()) / 2, /* @ts-ignore */
503
+ ...(ngDevMode ? [{ debugName: "radius" }] : /* istanbul ignore next */ []));
504
+ circumference = computed(() => 2 * Math.PI * this.radius(), /* @ts-ignore */
505
+ ...(ngDevMode ? [{ debugName: "circumference" }] : /* istanbul ignore next */ []));
506
+ dashOffset = computed(() => this.circumference() * (1 - this.pct() / 100), /* @ts-ignore */
507
+ ...(ngDevMode ? [{ debugName: "dashOffset" }] : /* istanbul ignore next */ []));
508
+ computedClass = computed(() => cn(this.circular()
509
+ ? 'inline-block'
510
+ : 'relative block h-2 w-full overflow-hidden rounded-full bg-primary/20', this.userClass()), /* @ts-ignore */
494
511
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
495
512
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiProgress, deps: [], target: i0.ɵɵFactoryTarget.Component });
496
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiProgress, isStandalone: true, selector: "bui-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "progress", "role": "progressbar", "aria-valuemin": "0", "aria-valuemax": "100" }, properties: { "attr.aria-label": "ariaLabel()", "attr.aria-valuenow": "indeterminate() ? null : rounded()", "attr.aria-valuetext": "indeterminate() ? null : valueText()", "class": "computedClass()" } }, ngImport: i0, template: `
497
- @if (indeterminate()) {
513
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiProgress, isStandalone: true, selector: "bui-progress", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, indeterminate: { classPropertyName: "indeterminate", publicName: "indeterminate", isSignal: true, isRequired: false, transformFunction: null }, circular: { classPropertyName: "circular", publicName: "circular", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, thickness: { classPropertyName: "thickness", publicName: "thickness", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "progress", "role": "progressbar", "aria-valuemin": "0", "aria-valuemax": "100" }, properties: { "attr.aria-label": "ariaLabel()", "attr.aria-valuenow": "indeterminate() ? null : rounded()", "attr.aria-valuetext": "indeterminate() ? null : valueText()", "class": "computedClass()" } }, ngImport: i0, template: `
514
+ @if (circular()) {
515
+ <svg
516
+ [attr.width]="size()"
517
+ [attr.height]="size()"
518
+ [attr.viewBox]="'0 0 ' + size() + ' ' + size()"
519
+ class="-rotate-90"
520
+ aria-hidden="true"
521
+ >
522
+ <circle
523
+ [attr.cx]="size() / 2"
524
+ [attr.cy]="size() / 2"
525
+ [attr.r]="radius()"
526
+ [attr.stroke-width]="thickness()"
527
+ fill="none"
528
+ class="stroke-primary/20"
529
+ />
530
+ <circle
531
+ data-slot="progress-indicator"
532
+ [attr.cx]="size() / 2"
533
+ [attr.cy]="size() / 2"
534
+ [attr.r]="radius()"
535
+ [attr.stroke-width]="thickness()"
536
+ [attr.stroke-dasharray]="circumference()"
537
+ [attr.stroke-dashoffset]="dashOffset()"
538
+ fill="none"
539
+ stroke-linecap="round"
540
+ class="stroke-primary transition-all"
541
+ />
542
+ </svg>
543
+ } @else if (indeterminate()) {
498
544
  <span
499
545
  data-slot="progress-indicator"
500
546
  class="absolute inset-y-0 w-2/5 animate-progress-indeterminate rounded-full bg-primary"
@@ -523,7 +569,36 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
523
569
  '[class]': 'computedClass()',
524
570
  },
525
571
  template: `
526
- @if (indeterminate()) {
572
+ @if (circular()) {
573
+ <svg
574
+ [attr.width]="size()"
575
+ [attr.height]="size()"
576
+ [attr.viewBox]="'0 0 ' + size() + ' ' + size()"
577
+ class="-rotate-90"
578
+ aria-hidden="true"
579
+ >
580
+ <circle
581
+ [attr.cx]="size() / 2"
582
+ [attr.cy]="size() / 2"
583
+ [attr.r]="radius()"
584
+ [attr.stroke-width]="thickness()"
585
+ fill="none"
586
+ class="stroke-primary/20"
587
+ />
588
+ <circle
589
+ data-slot="progress-indicator"
590
+ [attr.cx]="size() / 2"
591
+ [attr.cy]="size() / 2"
592
+ [attr.r]="radius()"
593
+ [attr.stroke-width]="thickness()"
594
+ [attr.stroke-dasharray]="circumference()"
595
+ [attr.stroke-dashoffset]="dashOffset()"
596
+ fill="none"
597
+ stroke-linecap="round"
598
+ class="stroke-primary transition-all"
599
+ />
600
+ </svg>
601
+ } @else if (indeterminate()) {
527
602
  <span
528
603
  data-slot="progress-indicator"
529
604
  class="absolute inset-y-0 w-2/5 animate-progress-indeterminate rounded-full bg-primary"
@@ -537,7 +612,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
537
612
  }
538
613
  `,
539
614
  }]
540
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
615
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], indeterminate: [{ type: i0.Input, args: [{ isSignal: true, alias: "indeterminate", required: false }] }], circular: [{ type: i0.Input, args: [{ isSignal: true, alias: "circular", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], thickness: [{ type: i0.Input, args: [{ isSignal: true, alias: "thickness", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
541
616
 
542
617
  /**
543
618
  * BlatUI avatar. Renders the image when `src` is set and loads; otherwise shows
@@ -750,12 +825,20 @@ class BuiSwitch {
750
825
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
751
826
  size = input('default', /* @ts-ignore */
752
827
  ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
828
+ /** Optional SVG path `d` shown inside the thumb when checked. */
829
+ iconOn = input('', /* @ts-ignore */
830
+ ...(ngDevMode ? [{ debugName: "iconOn" }] : /* istanbul ignore next */ []));
831
+ /** Optional SVG path `d` shown inside the thumb when unchecked. */
832
+ iconOff = input('', /* @ts-ignore */
833
+ ...(ngDevMode ? [{ debugName: "iconOff" }] : /* istanbul ignore next */ []));
753
834
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
754
835
  state = computed(() => (this.checked() ? 'checked' : 'unchecked'), /* @ts-ignore */
755
836
  ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
837
+ activeIcon = computed(() => (this.checked() ? this.iconOn() : this.iconOff()), /* @ts-ignore */
838
+ ...(ngDevMode ? [{ debugName: "activeIcon" }] : /* istanbul ignore next */ []));
756
839
  computedClass = computed(() => cn(SWITCH_BASE, SWITCH_TRACK[this.size()], this.userClass()), /* @ts-ignore */
757
840
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
758
- thumbClass = computed(() => cn(THUMB_BASE, SWITCH_THUMB[this.size()]), /* @ts-ignore */
841
+ thumbClass = computed(() => cn(THUMB_BASE, 'grid place-items-center', SWITCH_THUMB[this.size()]), /* @ts-ignore */
759
842
  ...(ngDevMode ? [{ debugName: "thumbClass" }] : /* istanbul ignore next */ []));
760
843
  toggle() {
761
844
  if (this.disabled()) {
@@ -764,8 +847,23 @@ class BuiSwitch {
764
847
  this.checked.update((value) => !value);
765
848
  }
766
849
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiSwitch, deps: [], target: i0.ɵɵFactoryTarget.Component });
767
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: BuiSwitch, isStandalone: true, selector: "button[buiSwitch]", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, host: { attributes: { "type": "button", "role": "switch", "data-slot": "switch" }, listeners: { "click": "toggle()" }, properties: { "attr.aria-checked": "checked()", "attr.data-state": "state()", "disabled": "disabled()", "class": "computedClass()" } }, ngImport: i0, template: `
768
- <span data-slot="switch-thumb" [attr.data-state]="state()" [class]="thumbClass()"></span>
850
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiSwitch, isStandalone: true, selector: "button[buiSwitch]", inputs: { checked: { classPropertyName: "checked", publicName: "checked", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, iconOn: { classPropertyName: "iconOn", publicName: "iconOn", isSignal: true, isRequired: false, transformFunction: null }, iconOff: { classPropertyName: "iconOff", publicName: "iconOff", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { checked: "checkedChange" }, host: { attributes: { "type": "button", "role": "switch", "data-slot": "switch" }, listeners: { "click": "toggle()" }, properties: { "attr.aria-checked": "checked()", "attr.data-state": "state()", "disabled": "disabled()", "class": "computedClass()" } }, ngImport: i0, template: `
851
+ <span data-slot="switch-thumb" [attr.data-state]="state()" [class]="thumbClass()">
852
+ @if (activeIcon(); as icon) {
853
+ <svg
854
+ viewBox="0 0 24 24"
855
+ fill="none"
856
+ stroke="currentColor"
857
+ stroke-width="2.5"
858
+ stroke-linecap="round"
859
+ stroke-linejoin="round"
860
+ aria-hidden="true"
861
+ class="size-full p-[3px] text-muted-foreground"
862
+ >
863
+ <path [attr.d]="icon" />
864
+ </svg>
865
+ }
866
+ </span>
769
867
  `, isInline: true });
770
868
  }
771
869
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiSwitch, decorators: [{
@@ -783,10 +881,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
783
881
  '(click)': 'toggle()',
784
882
  },
785
883
  template: `
786
- <span data-slot="switch-thumb" [attr.data-state]="state()" [class]="thumbClass()"></span>
884
+ <span data-slot="switch-thumb" [attr.data-state]="state()" [class]="thumbClass()">
885
+ @if (activeIcon(); as icon) {
886
+ <svg
887
+ viewBox="0 0 24 24"
888
+ fill="none"
889
+ stroke="currentColor"
890
+ stroke-width="2.5"
891
+ stroke-linecap="round"
892
+ stroke-linejoin="round"
893
+ aria-hidden="true"
894
+ class="size-full p-[3px] text-muted-foreground"
895
+ >
896
+ <path [attr.d]="icon" />
897
+ </svg>
898
+ }
899
+ </span>
787
900
  `,
788
901
  }]
789
- }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
902
+ }], propDecorators: { checked: [{ type: i0.Input, args: [{ isSignal: true, alias: "checked", required: false }] }, { type: i0.Output, args: ["checkedChange"] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], iconOn: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOn", required: false }] }], iconOff: [{ type: i0.Input, args: [{ isSignal: true, alias: "iconOff", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
790
903
 
791
904
  const TRIGGER = 'focus-visible:border-ring focus-visible:ring-ring/50 flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&>svg]:text-muted-foreground [&>svg]:pointer-events-none [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:translate-y-0.5 [&>svg]:transition-transform [&>svg]:duration-200 [&[aria-expanded=true]>svg]:rotate-180';
792
905
  class BuiAccordion {
@@ -1930,62 +2043,104 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
1930
2043
  }]
1931
2044
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: true }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
1932
2045
 
2046
+ const CONTENT_BASE = 'z-50 w-fit max-w-xs rounded-md px-3 py-1.5 text-xs shadow-md bg-primary text-primary-foreground';
1933
2047
  /** Floating tooltip bubble rendered into a CDK overlay. */
1934
2048
  class BuiTooltipContent {
1935
2049
  text = input('', /* @ts-ignore */
1936
2050
  ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
2051
+ /** Extra classes (e.g. a colour override) merged over the base bubble styles. */
2052
+ extraClass = input('', /* @ts-ignore */
2053
+ ...(ngDevMode ? [{ debugName: "extraClass" }] : /* istanbul ignore next */ []));
2054
+ computedClass = computed(() => cn(CONTENT_BASE, this.extraClass()), /* @ts-ignore */
2055
+ ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
1937
2056
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiTooltipContent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1938
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: BuiTooltipContent, isStandalone: true, selector: "bui-tooltip-content", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "tooltip" }, classAttribute: "bg-primary text-primary-foreground z-50 w-fit max-w-xs rounded-md px-3 py-1.5 text-xs shadow-md" }, ngImport: i0, template: `{{ text() }}`, isInline: true });
2057
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: BuiTooltipContent, isStandalone: true, selector: "bui-tooltip-content", inputs: { text: { classPropertyName: "text", publicName: "text", isSignal: true, isRequired: false, transformFunction: null }, extraClass: { classPropertyName: "extraClass", publicName: "extraClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "tooltip" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `{{ text() }}`, isInline: true });
1939
2058
  }
1940
2059
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiTooltipContent, decorators: [{
1941
2060
  type: Component,
1942
2061
  args: [{
1943
2062
  selector: 'bui-tooltip-content',
1944
- host: {
1945
- role: 'tooltip',
1946
- class: 'bg-primary text-primary-foreground z-50 w-fit max-w-xs rounded-md px-3 py-1.5 text-xs shadow-md',
1947
- },
2063
+ host: { role: 'tooltip', '[class]': 'computedClass()' },
1948
2064
  template: `{{ text() }}`,
1949
2065
  }]
1950
- }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }] } });
2066
+ }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "text", required: false }] }], extraClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "extraClass", required: false }] }] } });
2067
+ const POSITIONS = {
2068
+ top: { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -6 },
2069
+ bottom: { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 6 },
2070
+ left: { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -6 },
2071
+ right: { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 6 },
2072
+ };
2073
+ const FALLBACK = {
2074
+ top: 'bottom',
2075
+ bottom: 'top',
2076
+ left: 'right',
2077
+ right: 'left',
2078
+ };
1951
2079
  /**
1952
2080
  * BlatUI tooltip. Shows an accessible `role="tooltip"` bubble on hover/focus via the
1953
2081
  * Angular CDK overlay and wires `aria-describedby` on the host. SSR-safe (the overlay
1954
- * is only created in the browser, on interaction).
2082
+ * is only created in the browser, on interaction). Configure the placement with `side`,
2083
+ * a hover delay with `delay`, and the bubble colour with `tooltipClass`.
1955
2084
  */
1956
2085
  class BuiTooltip {
1957
2086
  text = input.required({ ...(ngDevMode ? { debugName: "text" } : /* istanbul ignore next */ {}), alias: 'buiTooltip' });
2087
+ side = input('top', /* @ts-ignore */
2088
+ ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
2089
+ /** Delay in milliseconds before the tooltip appears on hover/focus. */
2090
+ delay = input(0, /* @ts-ignore */
2091
+ ...(ngDevMode ? [{ debugName: "delay" }] : /* istanbul ignore next */ []));
2092
+ /** Extra classes applied to the tooltip bubble (e.g. a semantic colour). */
2093
+ tooltipClass = input('', /* @ts-ignore */
2094
+ ...(ngDevMode ? [{ debugName: "tooltipClass" }] : /* istanbul ignore next */ []));
1958
2095
  overlay = inject(Overlay);
1959
2096
  idGenerator = inject(_IdGenerator);
1960
2097
  host = inject(ElementRef);
1961
2098
  overlayRef = null;
2099
+ timer = null;
1962
2100
  describedBy = signal(null, /* @ts-ignore */
1963
2101
  ...(ngDevMode ? [{ debugName: "describedBy" }] : /* istanbul ignore next */ []));
1964
2102
  show() {
1965
2103
  if (this.overlayRef || !this.text()) {
1966
2104
  return;
1967
2105
  }
2106
+ const open = () => {
2107
+ this.open();
2108
+ };
2109
+ if (this.delay() > 0) {
2110
+ this.timer = setTimeout(open, this.delay());
2111
+ }
2112
+ else {
2113
+ open();
2114
+ }
2115
+ }
2116
+ open() {
2117
+ if (this.overlayRef) {
2118
+ return;
2119
+ }
2120
+ const side = this.side();
1968
2121
  const positionStrategy = this.overlay
1969
2122
  .position()
1970
2123
  .flexibleConnectedTo(this.host)
1971
- .withPositions([
1972
- { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -6 },
1973
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 6 },
1974
- ]);
2124
+ .withPositions([POSITIONS[side], POSITIONS[FALLBACK[side]]]);
1975
2125
  this.overlayRef = this.overlay.create({ positionStrategy });
1976
2126
  const reference = this.overlayRef.attach(new ComponentPortal(BuiTooltipContent));
1977
2127
  reference.setInput('text', this.text());
2128
+ reference.setInput('extraClass', this.tooltipClass());
1978
2129
  const id = this.idGenerator.getId('bui-tooltip-');
1979
2130
  reference.location.nativeElement.id = id;
1980
2131
  this.describedBy.set(id);
1981
2132
  }
1982
2133
  hide() {
2134
+ if (this.timer) {
2135
+ clearTimeout(this.timer);
2136
+ this.timer = null;
2137
+ }
1983
2138
  this.overlayRef?.dispose();
1984
2139
  this.overlayRef = null;
1985
2140
  this.describedBy.set(null);
1986
2141
  }
1987
2142
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiTooltip, deps: [], target: i0.ɵɵFactoryTarget.Directive });
1988
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: BuiTooltip, isStandalone: true, selector: "[buiTooltip]", inputs: { text: { classPropertyName: "text", publicName: "buiTooltip", isSignal: true, isRequired: true, transformFunction: null } }, host: { listeners: { "mouseenter": "show()", "mouseleave": "hide()", "focusin": "show()", "focusout": "hide()" }, properties: { "attr.aria-describedby": "describedBy()" } }, ngImport: i0 });
2143
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: BuiTooltip, isStandalone: true, selector: "[buiTooltip]", inputs: { text: { classPropertyName: "text", publicName: "buiTooltip", isSignal: true, isRequired: true, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, delay: { classPropertyName: "delay", publicName: "delay", isSignal: true, isRequired: false, transformFunction: null }, tooltipClass: { classPropertyName: "tooltipClass", publicName: "tooltipClass", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "mouseenter": "show()", "mouseleave": "hide()", "focusin": "show()", "focusout": "hide()" }, properties: { "attr.aria-describedby": "describedBy()" } }, ngImport: i0 });
1989
2144
  }
1990
2145
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiTooltip, decorators: [{
1991
2146
  type: Directive,
@@ -1999,7 +2154,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
1999
2154
  '(focusout)': 'hide()',
2000
2155
  },
2001
2156
  }]
2002
- }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "buiTooltip", required: true }] }] } });
2157
+ }], propDecorators: { text: [{ type: i0.Input, args: [{ isSignal: true, alias: "buiTooltip", required: true }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], delay: [{ type: i0.Input, args: [{ isSignal: true, alias: "delay", required: false }] }], tooltipClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "tooltipClass", required: false }] }] } });
2003
2158
 
2004
2159
  /** Breadcrumb navigation landmark (`<nav aria-label="breadcrumb">`). */
2005
2160
  class BuiBreadcrumb {
@@ -3262,6 +3417,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3262
3417
  }]
3263
3418
  }], propDecorators: { userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3264
3419
 
3420
+ const POPOVER_POSITIONS = {
3421
+ bottom: { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 4 },
3422
+ top: { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -4 },
3423
+ right: { originX: 'end', originY: 'center', overlayX: 'start', overlayY: 'center', offsetX: 4 },
3424
+ left: { originX: 'start', originY: 'center', overlayX: 'end', overlayY: 'center', offsetX: -4 },
3425
+ };
3426
+ const POPOVER_FALLBACK = {
3427
+ bottom: 'top',
3428
+ top: 'bottom',
3429
+ right: 'left',
3430
+ left: 'right',
3431
+ };
3265
3432
  /**
3266
3433
  * Click-triggered popover on the Angular CDK overlay. Bind the content template:
3267
3434
  * `<button [buiPopover]="tpl">…</button>` with `<ng-template #tpl><div buiPopoverContent>…`.
@@ -3269,6 +3436,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3269
3436
  */
3270
3437
  class BuiPopover {
3271
3438
  content = input.required({ ...(ngDevMode ? { debugName: "content" } : /* istanbul ignore next */ {}), alias: 'buiPopover' });
3439
+ side = input('bottom', /* @ts-ignore */
3440
+ ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
3272
3441
  overlay = inject(Overlay);
3273
3442
  host = inject(ElementRef);
3274
3443
  viewContainerRef = inject(ViewContainerRef);
@@ -3284,13 +3453,11 @@ class BuiPopover {
3284
3453
  }
3285
3454
  }
3286
3455
  open() {
3456
+ const side = this.side();
3287
3457
  const positionStrategy = this.overlay
3288
3458
  .position()
3289
3459
  .flexibleConnectedTo(this.host)
3290
- .withPositions([
3291
- { originX: 'center', originY: 'bottom', overlayX: 'center', overlayY: 'top', offsetY: 4 },
3292
- { originX: 'center', originY: 'top', overlayX: 'center', overlayY: 'bottom', offsetY: -4 },
3293
- ]);
3460
+ .withPositions([POPOVER_POSITIONS[side], POPOVER_POSITIONS[POPOVER_FALLBACK[side]]]);
3294
3461
  const overlayReference = this.overlay.create({
3295
3462
  positionStrategy,
3296
3463
  scrollStrategy: this.overlay.scrollStrategies.reposition(),
@@ -3318,7 +3485,7 @@ class BuiPopover {
3318
3485
  this.close();
3319
3486
  }
3320
3487
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiPopover, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3321
- static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: BuiPopover, isStandalone: true, selector: "[buiPopover]", inputs: { content: { classPropertyName: "content", publicName: "buiPopover", isSignal: true, isRequired: true, transformFunction: null } }, host: { attributes: { "aria-haspopup": "dialog" }, listeners: { "click": "toggle()" }, properties: { "attr.aria-expanded": "isOpen()" } }, ngImport: i0 });
3488
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: BuiPopover, isStandalone: true, selector: "[buiPopover]", inputs: { content: { classPropertyName: "content", publicName: "buiPopover", isSignal: true, isRequired: true, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "aria-haspopup": "dialog" }, listeners: { "click": "toggle()" }, properties: { "attr.aria-expanded": "isOpen()" } }, ngImport: i0 });
3322
3489
  }
3323
3490
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiPopover, decorators: [{
3324
3491
  type: Directive,
@@ -3330,11 +3497,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3330
3497
  '(click)': 'toggle()',
3331
3498
  },
3332
3499
  }]
3333
- }], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "buiPopover", required: true }] }] } });
3500
+ }], propDecorators: { content: [{ type: i0.Input, args: [{ isSignal: true, alias: "buiPopover", required: true }] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }] } });
3334
3501
  /** Styling + role for the popover content root (inside the bound template). */
3335
3502
  class BuiPopoverContent {
3336
3503
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
3337
- computedClass = computed(() => cn('z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none', this.userClass()), /* @ts-ignore */
3504
+ computedClass = computed(() => cn('z-50 w-72 max-w-[calc(100vw-2rem)] rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none', this.userClass()), /* @ts-ignore */
3338
3505
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3339
3506
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiPopoverContent, deps: [], target: i0.ɵɵFactoryTarget.Directive });
3340
3507
  static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "22.0.2", type: BuiPopoverContent, isStandalone: true, selector: "[buiPopoverContent]", inputs: { userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "popover-content", "role": "dialog", "tabindex": "-1" }, properties: { "class": "computedClass()" } }, ngImport: i0 });
@@ -3654,6 +3821,9 @@ class BuiMeter {
3654
3821
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
3655
3822
  tone = input('default', /* @ts-ignore */
3656
3823
  ...(ngDevMode ? [{ debugName: "tone" }] : /* istanbul ignore next */ []));
3824
+ /** Auto-pick the fill tone by value: the highest threshold whose `at` ≤ value wins. */
3825
+ thresholds = input([], /* @ts-ignore */
3826
+ ...(ngDevMode ? [{ debugName: "thresholds" }] : /* istanbul ignore next */ []));
3657
3827
  showValue = input(true, /* @ts-ignore */
3658
3828
  ...(ngDevMode ? [{ debugName: "showValue" }] : /* istanbul ignore next */ []));
3659
3829
  unit = input('%', /* @ts-ignore */
@@ -3668,12 +3838,24 @@ class BuiMeter {
3668
3838
  ...(ngDevMode ? [{ debugName: "pct" }] : /* istanbul ignore next */ []));
3669
3839
  valueText = computed(() => `${this.value()}${this.unit()}`, /* @ts-ignore */
3670
3840
  ...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
3671
- fillClass = computed(() => TONES[this.tone()], /* @ts-ignore */
3841
+ fillClass = computed(() => {
3842
+ const value = this.clamped();
3843
+ let tone = this.tone();
3844
+ let best = -Infinity;
3845
+ for (const threshold of this.thresholds()) {
3846
+ if (value < threshold.at || threshold.at < best) {
3847
+ continue;
3848
+ }
3849
+ best = threshold.at;
3850
+ tone = threshold.tone;
3851
+ }
3852
+ return TONES[tone];
3853
+ }, /* @ts-ignore */
3672
3854
  ...(ngDevMode ? [{ debugName: "fillClass" }] : /* istanbul ignore next */ []));
3673
3855
  computedClass = computed(() => cn('grid w-full gap-1.5', this.userClass()), /* @ts-ignore */
3674
3856
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3675
3857
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiMeter, deps: [], target: i0.ɵɵFactoryTarget.Component });
3676
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiMeter, isStandalone: true, selector: "bui-meter", inputs: { value: { classPropertyName: "value", publicName: "value", 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 }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "meter" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
3858
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiMeter, isStandalone: true, selector: "bui-meter", inputs: { value: { classPropertyName: "value", publicName: "value", 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 }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, tone: { classPropertyName: "tone", publicName: "tone", isSignal: true, isRequired: false, transformFunction: null }, thresholds: { classPropertyName: "thresholds", publicName: "thresholds", isSignal: true, isRequired: false, transformFunction: null }, showValue: { classPropertyName: "showValue", publicName: "showValue", isSignal: true, isRequired: false, transformFunction: null }, unit: { classPropertyName: "unit", publicName: "unit", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "meter" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
3677
3859
  @if (label() || showValue()) {
3678
3860
  <div class="flex items-center justify-between gap-2 text-sm">
3679
3861
  @if (label()) {
@@ -3738,7 +3920,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3738
3920
  </div>
3739
3921
  `,
3740
3922
  }]
3741
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3923
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], tone: [{ type: i0.Input, args: [{ isSignal: true, alias: "tone", required: false }] }], thresholds: [{ type: i0.Input, args: [{ isSignal: true, alias: "thresholds", required: false }] }], showValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "showValue", required: false }] }], unit: [{ type: i0.Input, args: [{ isSignal: true, alias: "unit", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3742
3924
 
3743
3925
  const TREND_CLASS = {
3744
3926
  up: 'text-emerald-600 font-medium',
@@ -3757,7 +3939,24 @@ class BuiStat {
3757
3939
  ...(ngDevMode ? [{ debugName: "trend" }] : /* istanbul ignore next */ []));
3758
3940
  caption = input(null, /* @ts-ignore */
3759
3941
  ...(ngDevMode ? [{ debugName: "caption" }] : /* istanbul ignore next */ []));
3942
+ /** Optional inline sparkline drawn from a series of numbers. */
3943
+ sparkline = input([], /* @ts-ignore */
3944
+ ...(ngDevMode ? [{ debugName: "sparkline" }] : /* istanbul ignore next */ []));
3760
3945
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
3946
+ sparkPoints = computed(() => {
3947
+ const values = this.sparkline();
3948
+ if (values.length < 2) {
3949
+ return '';
3950
+ }
3951
+ const min = Math.min(...values);
3952
+ const max = Math.max(...values);
3953
+ const span = max - min || 1;
3954
+ const step = 100 / (values.length - 1);
3955
+ return values
3956
+ .map((value, index) => `${index * step},${30 - ((value - min) / span) * 28}`)
3957
+ .join(' ');
3958
+ }, /* @ts-ignore */
3959
+ ...(ngDevMode ? [{ debugName: "sparkPoints" }] : /* istanbul ignore next */ []));
3761
3960
  resolvedTrend = computed(() => {
3762
3961
  const explicit = this.trend();
3763
3962
  if (explicit) {
@@ -3778,7 +3977,7 @@ class BuiStat {
3778
3977
  computedClass = computed(() => cn('flex flex-col gap-1 rounded-lg border bg-card p-4', this.userClass()), /* @ts-ignore */
3779
3978
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
3780
3979
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiStat, deps: [], target: i0.ɵɵFactoryTarget.Component });
3781
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiStat, isStandalone: true, selector: "bui-stat", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, change: { classPropertyName: "change", publicName: "change", isSignal: true, isRequired: false, transformFunction: null }, trend: { classPropertyName: "trend", publicName: "trend", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "stat" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
3980
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiStat, isStandalone: true, selector: "bui-stat", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, change: { classPropertyName: "change", publicName: "change", isSignal: true, isRequired: false, transformFunction: null }, trend: { classPropertyName: "trend", publicName: "trend", isSignal: true, isRequired: false, transformFunction: null }, caption: { classPropertyName: "caption", publicName: "caption", isSignal: true, isRequired: false, transformFunction: null }, sparkline: { classPropertyName: "sparkline", publicName: "sparkline", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "stat" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
3782
3981
  @if (label()) {
3783
3982
  <span class="text-sm font-medium text-muted-foreground">{{ label() }}</span>
3784
3983
  }
@@ -3791,6 +3990,24 @@ class BuiStat {
3791
3990
  }
3792
3991
  </span>
3793
3992
  }
3993
+ @if (sparkPoints()) {
3994
+ <svg
3995
+ viewBox="0 0 100 32"
3996
+ preserveAspectRatio="none"
3997
+ aria-hidden="true"
3998
+ [class]="'mt-2 h-8 w-full ' + trendClass()"
3999
+ >
4000
+ <polyline
4001
+ [attr.points]="sparkPoints()"
4002
+ fill="none"
4003
+ stroke="currentColor"
4004
+ stroke-width="2"
4005
+ stroke-linejoin="round"
4006
+ stroke-linecap="round"
4007
+ vector-effect="non-scaling-stroke"
4008
+ />
4009
+ </svg>
4010
+ }
3794
4011
  `, isInline: true });
3795
4012
  }
3796
4013
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiStat, decorators: [{
@@ -3811,9 +4028,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
3811
4028
  }
3812
4029
  </span>
3813
4030
  }
4031
+ @if (sparkPoints()) {
4032
+ <svg
4033
+ viewBox="0 0 100 32"
4034
+ preserveAspectRatio="none"
4035
+ aria-hidden="true"
4036
+ [class]="'mt-2 h-8 w-full ' + trendClass()"
4037
+ >
4038
+ <polyline
4039
+ [attr.points]="sparkPoints()"
4040
+ fill="none"
4041
+ stroke="currentColor"
4042
+ stroke-width="2"
4043
+ stroke-linejoin="round"
4044
+ stroke-linecap="round"
4045
+ vector-effect="non-scaling-stroke"
4046
+ />
4047
+ </svg>
4048
+ }
3814
4049
  `,
3815
4050
  }]
3816
- }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], change: [{ type: i0.Input, args: [{ isSignal: true, alias: "change", required: false }] }], trend: [{ type: i0.Input, args: [{ isSignal: true, alias: "trend", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
4051
+ }], propDecorators: { label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }], change: [{ type: i0.Input, args: [{ isSignal: true, alias: "change", required: false }] }], trend: [{ type: i0.Input, args: [{ isSignal: true, alias: "trend", required: false }] }], caption: [{ type: i0.Input, args: [{ isSignal: true, alias: "caption", required: false }] }], sparkline: [{ type: i0.Input, args: [{ isSignal: true, alias: "sparkline", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
3817
4052
 
3818
4053
  /**
3819
4054
  * Hover-triggered card on the Angular CDK overlay. Opens on hover/focus after a delay
@@ -4000,13 +4235,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
4000
4235
  }], propDecorators: { code: [{ type: i0.Input, args: [{ isSignal: true, alias: "code", required: false }] }], filename: [{ type: i0.Input, args: [{ isSignal: true, alias: "filename", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
4001
4236
 
4002
4237
  /**
4003
- * A single-value slider (`role="slider"`) with pointer drag and full keyboard support
4004
- * (arrows, Home/End, PageUp/PageDown). Horizontal or vertical. SSR-safe — geometry is
4238
+ * A slider (`role="slider"`) with pointer drag and full keyboard support
4239
+ * (arrows, Home/End, PageUp/PageDown). Single value, or a two-thumb `range`
4240
+ * (binds `value` + `endValue`). Horizontal or vertical. SSR-safe — geometry is
4005
4241
  * only read inside browser event handlers.
4006
4242
  */
4007
4243
  class BuiSlider {
4008
4244
  value = model(0, /* @ts-ignore */
4009
4245
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
4246
+ /** Enable a second thumb; binds `endValue` as the upper handle. */
4247
+ range = input(false, /* @ts-ignore */
4248
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
4249
+ endValue = model(100, /* @ts-ignore */
4250
+ ...(ngDevMode ? [{ debugName: "endValue" }] : /* istanbul ignore next */ []));
4010
4251
  min = input(0, /* @ts-ignore */
4011
4252
  ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
4012
4253
  max = input(100, /* @ts-ignore */
@@ -4021,12 +4262,21 @@ class BuiSlider {
4021
4262
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
4022
4263
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
4023
4264
  track = viewChild.required('track');
4265
+ active = signal('start', /* @ts-ignore */
4266
+ ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
4024
4267
  dragging = false;
4025
- pct = computed(() => {
4026
- const range = this.max() - this.min() || 1;
4027
- return ((this.value() - this.min()) / range) * 100;
4028
- }, /* @ts-ignore */
4268
+ pct = computed(() => this.toPct(this.value()), /* @ts-ignore */
4029
4269
  ...(ngDevMode ? [{ debugName: "pct" }] : /* istanbul ignore next */ []));
4270
+ endPct = computed(() => this.toPct(this.endValue()), /* @ts-ignore */
4271
+ ...(ngDevMode ? [{ debugName: "endPct" }] : /* istanbul ignore next */ []));
4272
+ fillStart = computed(() => this.range() ? Math.min(this.pct(), this.endPct()) : 0, /* @ts-ignore */
4273
+ ...(ngDevMode ? [{ debugName: "fillStart" }] : /* istanbul ignore next */ []));
4274
+ fillSize = computed(() => this.range() ? Math.abs(this.endPct() - this.pct()) : this.pct(), /* @ts-ignore */
4275
+ ...(ngDevMode ? [{ debugName: "fillSize" }] : /* istanbul ignore next */ []));
4276
+ thumbClass = computed(() => cn('absolute block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] outline-none hover:ring-4 focus-visible:ring-4', this.orientation() === 'vertical'
4277
+ ? 'left-1/2 -translate-x-1/2 translate-y-1/2'
4278
+ : 'top-1/2 -translate-x-1/2 -translate-y-1/2'), /* @ts-ignore */
4279
+ ...(ngDevMode ? [{ debugName: "thumbClass" }] : /* istanbul ignore next */ []));
4030
4280
  computedClass = computed(() => cn('relative flex touch-none items-center select-none data-disabled:pointer-events-none data-disabled:opacity-50', this.orientation() === 'vertical' ? 'h-40 w-fit flex-col' : 'w-full', this.userClass()), /* @ts-ignore */
4031
4281
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
4032
4282
  onPointerDown(event) {
@@ -4034,31 +4284,38 @@ class BuiSlider {
4034
4284
  return;
4035
4285
  }
4036
4286
  this.dragging = true;
4037
- this.value.set(this.valueFromPointer(event));
4287
+ const next = this.valueFromPointer(event);
4288
+ if (this.range()) {
4289
+ const toStart = Math.abs(next - this.value());
4290
+ const toEnd = Math.abs(next - this.endValue());
4291
+ this.active.set(toEnd < toStart ? 'end' : 'start');
4292
+ }
4293
+ this.setThumb(next);
4038
4294
  }
4039
4295
  onMove(event) {
4040
4296
  if (this.dragging) {
4041
- this.value.set(this.valueFromPointer(event));
4297
+ this.setThumb(this.valueFromPointer(event));
4042
4298
  }
4043
4299
  }
4044
4300
  onUp() {
4045
4301
  this.dragging = false;
4046
4302
  }
4047
- onKeydown(event) {
4303
+ onKeydown(event, thumb) {
4048
4304
  if (this.disabled()) {
4049
4305
  return;
4050
4306
  }
4307
+ const current = thumb === 'end' ? this.endValue() : this.value();
4051
4308
  const big = Math.max(this.step(), (this.max() - this.min()) / 10);
4052
4309
  let next;
4053
4310
  switch (event.key) {
4054
4311
  case 'ArrowRight':
4055
4312
  case 'ArrowUp': {
4056
- next = this.value() + this.step();
4313
+ next = current + this.step();
4057
4314
  break;
4058
4315
  }
4059
4316
  case 'ArrowLeft':
4060
4317
  case 'ArrowDown': {
4061
- next = this.value() - this.step();
4318
+ next = current - this.step();
4062
4319
  break;
4063
4320
  }
4064
4321
  case 'Home': {
@@ -4070,11 +4327,11 @@ class BuiSlider {
4070
4327
  break;
4071
4328
  }
4072
4329
  case 'PageUp': {
4073
- next = this.value() + big;
4330
+ next = current + big;
4074
4331
  break;
4075
4332
  }
4076
4333
  case 'PageDown': {
4077
- next = this.value() - big;
4334
+ next = current - big;
4078
4335
  break;
4079
4336
  }
4080
4337
  default: {
@@ -4082,7 +4339,22 @@ class BuiSlider {
4082
4339
  }
4083
4340
  }
4084
4341
  event.preventDefault();
4085
- this.value.set(this.clamp(next));
4342
+ this.writeThumb(thumb, this.clamp(next));
4343
+ }
4344
+ setThumb(next) {
4345
+ this.writeThumb(this.range() ? this.active() : 'start', next);
4346
+ }
4347
+ writeThumb(thumb, next) {
4348
+ if (thumb === 'end') {
4349
+ this.endValue.set(next);
4350
+ }
4351
+ else {
4352
+ this.value.set(next);
4353
+ }
4354
+ }
4355
+ toPct(value) {
4356
+ const range = this.max() - this.min() || 1;
4357
+ return ((value - this.min()) / range) * 100;
4086
4358
  }
4087
4359
  valueFromPointer(event) {
4088
4360
  const rect = this.track().nativeElement.getBoundingClientRect();
@@ -4099,7 +4371,7 @@ class BuiSlider {
4099
4371
  return Math.max(this.min(), Math.min(this.max(), value));
4100
4372
  }
4101
4373
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiSlider, deps: [], target: i0.ɵɵFactoryTarget.Component });
4102
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "22.0.2", type: BuiSlider, isStandalone: true, selector: "bui-slider", inputs: { value: { classPropertyName: "value", publicName: "value", 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 }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "slider" }, listeners: { "document:pointermove": "onMove($event)", "document:pointerup": "onUp()" }, properties: { "attr.data-orientation": "orientation()", "attr.data-disabled": "disabled() ? '' : null", "class": "computedClass()" } }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: `
4374
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiSlider, isStandalone: true, selector: "bui-slider", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, endValue: { classPropertyName: "endValue", publicName: "endValue", 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 }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", endValue: "endValueChange" }, host: { attributes: { "data-slot": "slider" }, listeners: { "document:pointermove": "onMove($event)", "document:pointerup": "onUp()" }, properties: { "attr.data-orientation": "orientation()", "attr.data-disabled": "disabled() ? '' : null", "class": "computedClass()" } }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: `
4103
4375
  <span
4104
4376
  #track
4105
4377
  data-slot="slider-track"
@@ -4110,31 +4382,45 @@ class BuiSlider {
4110
4382
  <span
4111
4383
  data-slot="slider-range"
4112
4384
  class="absolute bg-primary"
4113
- [class]="orientation() === 'vertical' ? 'bottom-0 w-full' : 'left-0 h-full'"
4114
- [style.height.%]="orientation() === 'vertical' ? pct() : null"
4115
- [style.width.%]="orientation() === 'vertical' ? null : pct()"
4385
+ [class]="orientation() === 'vertical' ? 'w-full' : 'h-full'"
4386
+ [style.bottom.%]="orientation() === 'vertical' ? fillStart() : null"
4387
+ [style.height.%]="orientation() === 'vertical' ? fillSize() : null"
4388
+ [style.left.%]="orientation() === 'vertical' ? null : fillStart()"
4389
+ [style.width.%]="orientation() === 'vertical' ? null : fillSize()"
4116
4390
  ></span>
4117
4391
  </span>
4118
4392
  <span
4119
4393
  data-slot="slider-thumb"
4120
4394
  role="slider"
4121
4395
  [attr.aria-orientation]="orientation()"
4122
- [attr.aria-label]="ariaLabel()"
4396
+ [attr.aria-label]="range() ? ariaLabel() + ' minimum' : ariaLabel()"
4123
4397
  [attr.aria-valuemin]="min()"
4124
- [attr.aria-valuemax]="max()"
4398
+ [attr.aria-valuemax]="range() ? endValue() : max()"
4125
4399
  [attr.aria-valuenow]="value()"
4126
4400
  [attr.aria-disabled]="disabled()"
4127
4401
  [attr.tabindex]="disabled() ? -1 : 0"
4128
- class="absolute block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] outline-none hover:ring-4 focus-visible:ring-4"
4129
- [class]="
4130
- orientation() === 'vertical'
4131
- ? 'left-1/2 -translate-x-1/2 translate-y-1/2'
4132
- : 'top-1/2 -translate-x-1/2 -translate-y-1/2'
4133
- "
4402
+ [class]="thumbClass()"
4134
4403
  [style.bottom.%]="orientation() === 'vertical' ? pct() : null"
4135
4404
  [style.left.%]="orientation() === 'vertical' ? null : pct()"
4136
- (keydown)="onKeydown($event)"
4405
+ (keydown)="onKeydown($event, 'start')"
4137
4406
  ></span>
4407
+ @if (range()) {
4408
+ <span
4409
+ data-slot="slider-thumb"
4410
+ role="slider"
4411
+ [attr.aria-orientation]="orientation()"
4412
+ [attr.aria-label]="ariaLabel() + ' maximum'"
4413
+ [attr.aria-valuemin]="value()"
4414
+ [attr.aria-valuemax]="max()"
4415
+ [attr.aria-valuenow]="endValue()"
4416
+ [attr.aria-disabled]="disabled()"
4417
+ [attr.tabindex]="disabled() ? -1 : 0"
4418
+ [class]="thumbClass()"
4419
+ [style.bottom.%]="orientation() === 'vertical' ? endPct() : null"
4420
+ [style.left.%]="orientation() === 'vertical' ? null : endPct()"
4421
+ (keydown)="onKeydown($event, 'end')"
4422
+ ></span>
4423
+ }
4138
4424
  `, isInline: true });
4139
4425
  }
4140
4426
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiSlider, decorators: [{
@@ -4160,34 +4446,48 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
4160
4446
  <span
4161
4447
  data-slot="slider-range"
4162
4448
  class="absolute bg-primary"
4163
- [class]="orientation() === 'vertical' ? 'bottom-0 w-full' : 'left-0 h-full'"
4164
- [style.height.%]="orientation() === 'vertical' ? pct() : null"
4165
- [style.width.%]="orientation() === 'vertical' ? null : pct()"
4449
+ [class]="orientation() === 'vertical' ? 'w-full' : 'h-full'"
4450
+ [style.bottom.%]="orientation() === 'vertical' ? fillStart() : null"
4451
+ [style.height.%]="orientation() === 'vertical' ? fillSize() : null"
4452
+ [style.left.%]="orientation() === 'vertical' ? null : fillStart()"
4453
+ [style.width.%]="orientation() === 'vertical' ? null : fillSize()"
4166
4454
  ></span>
4167
4455
  </span>
4168
4456
  <span
4169
4457
  data-slot="slider-thumb"
4170
4458
  role="slider"
4171
4459
  [attr.aria-orientation]="orientation()"
4172
- [attr.aria-label]="ariaLabel()"
4460
+ [attr.aria-label]="range() ? ariaLabel() + ' minimum' : ariaLabel()"
4173
4461
  [attr.aria-valuemin]="min()"
4174
- [attr.aria-valuemax]="max()"
4462
+ [attr.aria-valuemax]="range() ? endValue() : max()"
4175
4463
  [attr.aria-valuenow]="value()"
4176
4464
  [attr.aria-disabled]="disabled()"
4177
4465
  [attr.tabindex]="disabled() ? -1 : 0"
4178
- class="absolute block size-4 shrink-0 rounded-full border border-primary bg-background shadow-sm ring-ring/50 transition-[color,box-shadow] outline-none hover:ring-4 focus-visible:ring-4"
4179
- [class]="
4180
- orientation() === 'vertical'
4181
- ? 'left-1/2 -translate-x-1/2 translate-y-1/2'
4182
- : 'top-1/2 -translate-x-1/2 -translate-y-1/2'
4183
- "
4466
+ [class]="thumbClass()"
4184
4467
  [style.bottom.%]="orientation() === 'vertical' ? pct() : null"
4185
4468
  [style.left.%]="orientation() === 'vertical' ? null : pct()"
4186
- (keydown)="onKeydown($event)"
4469
+ (keydown)="onKeydown($event, 'start')"
4187
4470
  ></span>
4471
+ @if (range()) {
4472
+ <span
4473
+ data-slot="slider-thumb"
4474
+ role="slider"
4475
+ [attr.aria-orientation]="orientation()"
4476
+ [attr.aria-label]="ariaLabel() + ' maximum'"
4477
+ [attr.aria-valuemin]="value()"
4478
+ [attr.aria-valuemax]="max()"
4479
+ [attr.aria-valuenow]="endValue()"
4480
+ [attr.aria-disabled]="disabled()"
4481
+ [attr.tabindex]="disabled() ? -1 : 0"
4482
+ [class]="thumbClass()"
4483
+ [style.bottom.%]="orientation() === 'vertical' ? endPct() : null"
4484
+ [style.left.%]="orientation() === 'vertical' ? null : endPct()"
4485
+ (keydown)="onKeydown($event, 'end')"
4486
+ ></span>
4487
+ }
4188
4488
  `,
4189
4489
  }]
4190
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], 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 }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
4490
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }], endValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "endValue", required: false }] }, { type: i0.Output, args: ["endValueChange"] }], 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 }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
4191
4491
 
4192
4492
  const STAR_SIZE = { sm: 'size-4', default: 'size-5', lg: 'size-6' };
4193
4493
  /** A star rating (`role="radiogroup"`) with hover preview and keyboard support. */
@@ -4326,7 +4626,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
4326
4626
  }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
4327
4627
 
4328
4628
  const FIELD_HEIGHT = { sm: 'h-7 text-xs', default: 'h-8 text-sm', lg: 'h-9 text-base' };
4329
- const BTN_SIZE = {
4629
+ const BTN_SIZE$1 = {
4330
4630
  sm: 'w-7 [&_svg]:size-3',
4331
4631
  default: 'w-8 [&_svg]:size-3.5',
4332
4632
  lg: 'w-9 [&_svg]:size-4',
@@ -4359,7 +4659,7 @@ class BuiQuantitySelector {
4359
4659
  computedClass = computed(() => cn('inline-flex items-stretch overflow-hidden rounded-md border border-input bg-transparent shadow-xs transition-[color,box-shadow] focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50 has-[input:disabled]:pointer-events-none has-[input:disabled]:opacity-50 dark:bg-input/30', this.userClass()), /* @ts-ignore */
4360
4660
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
4361
4661
  btnClass(side) {
4362
- return cn('flex shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none not-disabled:cursor-pointer hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50', side === 'e' ? 'border-e' : 'border-s', 'border-input', BTN_SIZE[this.size()], FIELD_HEIGHT[this.size()]);
4662
+ return cn('flex shrink-0 items-center justify-center text-muted-foreground transition-colors outline-none not-disabled:cursor-pointer hover:bg-accent hover:text-accent-foreground disabled:pointer-events-none disabled:opacity-50', side === 'e' ? 'border-e' : 'border-s', 'border-input', BTN_SIZE$1[this.size()], FIELD_HEIGHT[this.size()]);
4363
4663
  }
4364
4664
  fieldClass() {
4365
4665
  return cn('min-w-0 border-0 bg-transparent text-center font-medium tabular-nums outline-none', FIELD_HEIGHT[this.size()], FIELD_WIDTH[this.size()]);
@@ -5435,6 +5735,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
5435
5735
  class BuiSelect {
5436
5736
  value = model('', /* @ts-ignore */
5437
5737
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
5738
+ /** Select several options; binds `values` instead of `value`. */
5739
+ multiple = input(false, /* @ts-ignore */
5740
+ ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
5741
+ values = model([], /* @ts-ignore */
5742
+ ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
5438
5743
  options = input([], /* @ts-ignore */
5439
5744
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
5440
5745
  placeholder = input('Select…', /* @ts-ignore */
@@ -5448,10 +5753,24 @@ class BuiSelect {
5448
5753
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
5449
5754
  active = signal(0, /* @ts-ignore */
5450
5755
  ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
5451
- selectedLabel = computed(() => this.options().find((option) => option.value === this.value())?.label ?? '', /* @ts-ignore */
5756
+ selectedOption = computed(() => this.options().find((option) => option.value === this.value()), /* @ts-ignore */
5757
+ ...(ngDevMode ? [{ debugName: "selectedOption" }] : /* istanbul ignore next */ []));
5758
+ selectedLabel = computed(() => {
5759
+ if (this.multiple()) {
5760
+ const chosen = this.values();
5761
+ return this.options()
5762
+ .filter((option) => chosen.includes(option.value))
5763
+ .map((option) => option.label)
5764
+ .join(', ');
5765
+ }
5766
+ return this.selectedOption()?.label ?? '';
5767
+ }, /* @ts-ignore */
5452
5768
  ...(ngDevMode ? [{ debugName: "selectedLabel" }] : /* istanbul ignore next */ []));
5453
5769
  computedClass = computed(() => cn('relative block', this.userClass()), /* @ts-ignore */
5454
5770
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
5771
+ isSelected(value) {
5772
+ return this.multiple() ? this.values().includes(value) : value === this.value();
5773
+ }
5455
5774
  toggle() {
5456
5775
  if (this.open()) {
5457
5776
  this.close();
@@ -5464,6 +5783,17 @@ class BuiSelect {
5464
5783
  if (option.disabled) {
5465
5784
  return;
5466
5785
  }
5786
+ if (this.multiple()) {
5787
+ const next = new Set(this.values());
5788
+ if (next.has(option.value)) {
5789
+ next.delete(option.value);
5790
+ }
5791
+ else {
5792
+ next.add(option.value);
5793
+ }
5794
+ this.values.set([...next]);
5795
+ return;
5796
+ }
5467
5797
  this.value.set(option.value);
5468
5798
  this.close();
5469
5799
  }
@@ -5532,7 +5862,7 @@ class BuiSelect {
5532
5862
  this.open.set(false);
5533
5863
  }
5534
5864
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiSelect, deps: [], target: i0.ɵɵFactoryTarget.Component });
5535
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiSelect, isStandalone: true, selector: "bui-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "select" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
5865
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiSelect, isStandalone: true, selector: "bui-select", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, host: { attributes: { "data-slot": "select" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
5536
5866
  <button
5537
5867
  type="button"
5538
5868
  role="combobox"
@@ -5546,7 +5876,23 @@ class BuiSelect {
5546
5876
  (click)="toggle()"
5547
5877
  (keydown)="onKeydown($event)"
5548
5878
  >
5549
- <span class="truncate">{{ selectedLabel() || placeholder() }}</span>
5879
+ <span class="flex min-w-0 items-center gap-2 truncate">
5880
+ @if (selectedOption()?.icon; as icon) {
5881
+ <svg
5882
+ viewBox="0 0 24 24"
5883
+ fill="none"
5884
+ stroke="currentColor"
5885
+ stroke-width="2"
5886
+ stroke-linecap="round"
5887
+ stroke-linejoin="round"
5888
+ aria-hidden="true"
5889
+ class="size-4 shrink-0"
5890
+ >
5891
+ <path [attr.d]="icon" />
5892
+ </svg>
5893
+ }
5894
+ {{ selectedLabel() || placeholder() }}
5895
+ </span>
5550
5896
  <svg
5551
5897
  viewBox="0 0 24 24"
5552
5898
  fill="none"
@@ -5564,21 +5910,43 @@ class BuiSelect {
5564
5910
  <ul
5565
5911
  [id]="listId"
5566
5912
  role="listbox"
5913
+ [attr.aria-multiselectable]="multiple() ? true : null"
5567
5914
  class="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
5568
5915
  >
5569
5916
  @for (option of options(); track option.value; let i = $index) {
5917
+ @if (option.group && option.group !== options()[i - 1]?.group) {
5918
+ <li role="presentation" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
5919
+ {{ option.group }}
5920
+ </li>
5921
+ }
5570
5922
  <li
5571
5923
  [id]="listId + '-' + i"
5572
5924
  role="option"
5573
- [attr.aria-selected]="option.value === value()"
5925
+ [attr.aria-selected]="isSelected(option.value)"
5574
5926
  [attr.aria-disabled]="option.disabled ? true : null"
5575
5927
  class="relative flex cursor-default items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none"
5576
5928
  [class]="i === active() ? 'bg-accent text-accent-foreground' : ''"
5577
5929
  (click)="select(option)"
5578
5930
  (mouseenter)="active.set(i)"
5579
5931
  >
5580
- <span class="truncate">{{ option.label }}</span>
5581
- @if (option.value === value()) {
5932
+ <span class="flex min-w-0 items-center gap-2 truncate">
5933
+ @if (option.icon; as icon) {
5934
+ <svg
5935
+ viewBox="0 0 24 24"
5936
+ fill="none"
5937
+ stroke="currentColor"
5938
+ stroke-width="2"
5939
+ stroke-linecap="round"
5940
+ stroke-linejoin="round"
5941
+ aria-hidden="true"
5942
+ class="size-4 shrink-0"
5943
+ >
5944
+ <path [attr.d]="icon" />
5945
+ </svg>
5946
+ }
5947
+ {{ option.label }}
5948
+ </span>
5949
+ @if (isSelected(option.value)) {
5582
5950
  <svg
5583
5951
  viewBox="0 0 24 24"
5584
5952
  fill="none"
@@ -5621,7 +5989,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
5621
5989
  (click)="toggle()"
5622
5990
  (keydown)="onKeydown($event)"
5623
5991
  >
5624
- <span class="truncate">{{ selectedLabel() || placeholder() }}</span>
5992
+ <span class="flex min-w-0 items-center gap-2 truncate">
5993
+ @if (selectedOption()?.icon; as icon) {
5994
+ <svg
5995
+ viewBox="0 0 24 24"
5996
+ fill="none"
5997
+ stroke="currentColor"
5998
+ stroke-width="2"
5999
+ stroke-linecap="round"
6000
+ stroke-linejoin="round"
6001
+ aria-hidden="true"
6002
+ class="size-4 shrink-0"
6003
+ >
6004
+ <path [attr.d]="icon" />
6005
+ </svg>
6006
+ }
6007
+ {{ selectedLabel() || placeholder() }}
6008
+ </span>
5625
6009
  <svg
5626
6010
  viewBox="0 0 24 24"
5627
6011
  fill="none"
@@ -5639,21 +6023,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
5639
6023
  <ul
5640
6024
  [id]="listId"
5641
6025
  role="listbox"
6026
+ [attr.aria-multiselectable]="multiple() ? true : null"
5642
6027
  class="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
5643
6028
  >
5644
6029
  @for (option of options(); track option.value; let i = $index) {
6030
+ @if (option.group && option.group !== options()[i - 1]?.group) {
6031
+ <li role="presentation" class="px-2 py-1.5 text-xs font-medium text-muted-foreground">
6032
+ {{ option.group }}
6033
+ </li>
6034
+ }
5645
6035
  <li
5646
6036
  [id]="listId + '-' + i"
5647
6037
  role="option"
5648
- [attr.aria-selected]="option.value === value()"
6038
+ [attr.aria-selected]="isSelected(option.value)"
5649
6039
  [attr.aria-disabled]="option.disabled ? true : null"
5650
6040
  class="relative flex cursor-default items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none"
5651
6041
  [class]="i === active() ? 'bg-accent text-accent-foreground' : ''"
5652
6042
  (click)="select(option)"
5653
6043
  (mouseenter)="active.set(i)"
5654
6044
  >
5655
- <span class="truncate">{{ option.label }}</span>
5656
- @if (option.value === value()) {
6045
+ <span class="flex min-w-0 items-center gap-2 truncate">
6046
+ @if (option.icon; as icon) {
6047
+ <svg
6048
+ viewBox="0 0 24 24"
6049
+ fill="none"
6050
+ stroke="currentColor"
6051
+ stroke-width="2"
6052
+ stroke-linecap="round"
6053
+ stroke-linejoin="round"
6054
+ aria-hidden="true"
6055
+ class="size-4 shrink-0"
6056
+ >
6057
+ <path [attr.d]="icon" />
6058
+ </svg>
6059
+ }
6060
+ {{ option.label }}
6061
+ </span>
6062
+ @if (isSelected(option.value)) {
5657
6063
  <svg
5658
6064
  viewBox="0 0 24 24"
5659
6065
  fill="none"
@@ -5673,7 +6079,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
5673
6079
  }
5674
6080
  `,
5675
6081
  }]
5676
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
6082
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
5677
6083
 
5678
6084
  const VARIANTS$1 = {
5679
6085
  primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
@@ -5774,12 +6180,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
5774
6180
  class BuiCombobox {
5775
6181
  value = model('', /* @ts-ignore */
5776
6182
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6183
+ /** Select several options; binds `values` instead of `value`. */
6184
+ multiple = input(false, /* @ts-ignore */
6185
+ ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
6186
+ values = model([], /* @ts-ignore */
6187
+ ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
5777
6188
  options = input([], /* @ts-ignore */
5778
6189
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
5779
6190
  placeholder = input('Search…', /* @ts-ignore */
5780
6191
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
5781
6192
  disabled = input(false, /* @ts-ignore */
5782
6193
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
6194
+ /** When false, the field can't be typed in — it just opens the full list (select-like). */
6195
+ searchable = input(true, /* @ts-ignore */
6196
+ ...(ngDevMode ? [{ debugName: "searchable" }] : /* istanbul ignore next */ []));
5783
6197
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
5784
6198
  listId = inject(_IdGenerator).getId('bui-combobox-');
5785
6199
  host = inject(ElementRef);
@@ -5789,11 +6203,23 @@ class BuiCombobox {
5789
6203
  ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
5790
6204
  query = signal('', /* @ts-ignore */
5791
6205
  ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
5792
- selectedLabel = computed(() => this.options().find((option) => option.value === this.value())?.label ?? '', /* @ts-ignore */
6206
+ selectedLabel = computed(() => {
6207
+ if (this.multiple()) {
6208
+ const chosen = this.values();
6209
+ return this.options()
6210
+ .filter((option) => chosen.includes(option.value))
6211
+ .map((option) => option.label)
6212
+ .join(', ');
6213
+ }
6214
+ return this.options().find((option) => option.value === this.value())?.label ?? '';
6215
+ }, /* @ts-ignore */
5793
6216
  ...(ngDevMode ? [{ debugName: "selectedLabel" }] : /* istanbul ignore next */ []));
5794
6217
  display = computed(() => (this.open() ? this.query() : this.selectedLabel()), /* @ts-ignore */
5795
6218
  ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
5796
6219
  filtered = computed(() => {
6220
+ if (!this.searchable()) {
6221
+ return this.options();
6222
+ }
5797
6223
  const query = this.query().trim().toLowerCase();
5798
6224
  return query === ''
5799
6225
  ? this.options()
@@ -5803,6 +6229,9 @@ class BuiCombobox {
5803
6229
  computedClass = computed(() => cn('relative block', this.userClass()), /* @ts-ignore */
5804
6230
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
5805
6231
  onInput(event) {
6232
+ if (!this.searchable()) {
6233
+ return;
6234
+ }
5806
6235
  this.query.set(event.target.value);
5807
6236
  this.active.set(0);
5808
6237
  this.open.set(true);
@@ -5812,7 +6241,22 @@ class BuiCombobox {
5812
6241
  this.active.set(0);
5813
6242
  this.open.set(true);
5814
6243
  }
6244
+ isSelected(value) {
6245
+ return this.multiple() ? this.values().includes(value) : value === this.value();
6246
+ }
5815
6247
  select(option) {
6248
+ if (this.multiple()) {
6249
+ const next = new Set(this.values());
6250
+ if (next.has(option.value)) {
6251
+ next.delete(option.value);
6252
+ }
6253
+ else {
6254
+ next.add(option.value);
6255
+ }
6256
+ this.values.set([...next]);
6257
+ this.query.set('');
6258
+ return;
6259
+ }
5816
6260
  this.value.set(option.value);
5817
6261
  this.query.set('');
5818
6262
  this.open.set(false);
@@ -5862,7 +6306,7 @@ class BuiCombobox {
5862
6306
  this.query.set('');
5863
6307
  }
5864
6308
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiCombobox, deps: [], target: i0.ɵɵFactoryTarget.Component });
5865
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCombobox, isStandalone: true, selector: "bui-combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "combobox" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
6309
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCombobox, isStandalone: true, selector: "bui-combobox", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, searchable: { classPropertyName: "searchable", publicName: "searchable", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, host: { attributes: { "data-slot": "combobox" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
5866
6310
  <input
5867
6311
  type="text"
5868
6312
  role="combobox"
@@ -5873,6 +6317,8 @@ class BuiCombobox {
5873
6317
  [value]="display()"
5874
6318
  [placeholder]="placeholder()"
5875
6319
  [disabled]="disabled()"
6320
+ [readonly]="!searchable()"
6321
+ [class.cursor-pointer]="!searchable()"
5876
6322
  class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50"
5877
6323
  (input)="onInput($event)"
5878
6324
  (focus)="openList()"
@@ -5882,19 +6328,50 @@ class BuiCombobox {
5882
6328
  <ul
5883
6329
  [id]="listId"
5884
6330
  role="listbox"
6331
+ [attr.aria-multiselectable]="multiple() ? true : null"
5885
6332
  class="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
5886
6333
  >
5887
6334
  @for (option of filtered(); track option.value; let i = $index) {
5888
6335
  <li
5889
6336
  [id]="listId + '-' + i"
5890
6337
  role="option"
5891
- [attr.aria-selected]="option.value === value()"
5892
- class="relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none"
6338
+ [attr.aria-selected]="isSelected(option.value)"
6339
+ class="relative flex cursor-default items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none"
5893
6340
  [class]="i === active() ? 'bg-accent text-accent-foreground' : ''"
5894
6341
  (click)="select(option)"
5895
6342
  (mouseenter)="active.set(i)"
5896
6343
  >
5897
- {{ option.label }}
6344
+ <span class="flex min-w-0 items-center truncate">
6345
+ @if (option.icon; as icon) {
6346
+ <svg
6347
+ viewBox="0 0 24 24"
6348
+ fill="none"
6349
+ stroke="currentColor"
6350
+ stroke-width="2"
6351
+ stroke-linecap="round"
6352
+ stroke-linejoin="round"
6353
+ aria-hidden="true"
6354
+ class="mr-2 size-4 shrink-0"
6355
+ >
6356
+ <path [attr.d]="icon" />
6357
+ </svg>
6358
+ }
6359
+ {{ option.label }}
6360
+ </span>
6361
+ @if (isSelected(option.value)) {
6362
+ <svg
6363
+ viewBox="0 0 24 24"
6364
+ fill="none"
6365
+ stroke="currentColor"
6366
+ stroke-width="2"
6367
+ stroke-linecap="round"
6368
+ stroke-linejoin="round"
6369
+ aria-hidden="true"
6370
+ class="size-4 shrink-0"
6371
+ >
6372
+ <path d="M20 6 9 17l-5-5" />
6373
+ </svg>
6374
+ }
5898
6375
  </li>
5899
6376
  }
5900
6377
  </ul>
@@ -5921,6 +6398,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
5921
6398
  [value]="display()"
5922
6399
  [placeholder]="placeholder()"
5923
6400
  [disabled]="disabled()"
6401
+ [readonly]="!searchable()"
6402
+ [class.cursor-pointer]="!searchable()"
5924
6403
  class="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50"
5925
6404
  (input)="onInput($event)"
5926
6405
  (focus)="openList()"
@@ -5930,26 +6409,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
5930
6409
  <ul
5931
6410
  [id]="listId"
5932
6411
  role="listbox"
6412
+ [attr.aria-multiselectable]="multiple() ? true : null"
5933
6413
  class="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md"
5934
6414
  >
5935
6415
  @for (option of filtered(); track option.value; let i = $index) {
5936
6416
  <li
5937
6417
  [id]="listId + '-' + i"
5938
6418
  role="option"
5939
- [attr.aria-selected]="option.value === value()"
5940
- class="relative flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-none select-none"
6419
+ [attr.aria-selected]="isSelected(option.value)"
6420
+ class="relative flex cursor-default items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none"
5941
6421
  [class]="i === active() ? 'bg-accent text-accent-foreground' : ''"
5942
6422
  (click)="select(option)"
5943
6423
  (mouseenter)="active.set(i)"
5944
6424
  >
5945
- {{ option.label }}
6425
+ <span class="flex min-w-0 items-center truncate">
6426
+ @if (option.icon; as icon) {
6427
+ <svg
6428
+ viewBox="0 0 24 24"
6429
+ fill="none"
6430
+ stroke="currentColor"
6431
+ stroke-width="2"
6432
+ stroke-linecap="round"
6433
+ stroke-linejoin="round"
6434
+ aria-hidden="true"
6435
+ class="mr-2 size-4 shrink-0"
6436
+ >
6437
+ <path [attr.d]="icon" />
6438
+ </svg>
6439
+ }
6440
+ {{ option.label }}
6441
+ </span>
6442
+ @if (isSelected(option.value)) {
6443
+ <svg
6444
+ viewBox="0 0 24 24"
6445
+ fill="none"
6446
+ stroke="currentColor"
6447
+ stroke-width="2"
6448
+ stroke-linecap="round"
6449
+ stroke-linejoin="round"
6450
+ aria-hidden="true"
6451
+ class="size-4 shrink-0"
6452
+ >
6453
+ <path d="M20 6 9 17l-5-5" />
6454
+ </svg>
6455
+ }
5946
6456
  </li>
5947
6457
  }
5948
6458
  </ul>
5949
6459
  }
5950
6460
  `,
5951
6461
  }]
5952
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
6462
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], searchable: [{ type: i0.Input, args: [{ isSignal: true, alias: "searchable", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
5953
6463
 
5954
6464
  /** A live countdown to a target time (`role="timer"`). SSR-safe — ticks only in the browser. */
5955
6465
  class BuiCountdown {
@@ -6981,7 +7491,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
6981
7491
  }]
6982
7492
  }], propDecorators: { show: [{ type: i0.Input, args: [{ isSignal: true, alias: "show", required: false }] }], message: [{ type: i0.Input, args: [{ isSignal: true, alias: "message", required: false }] }], blur: [{ type: i0.Input, args: [{ isSignal: true, alias: "blur", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
6983
7493
 
6984
- const BTN = 'border-input text-foreground hover:bg-accent inline-flex h-9 w-9 items-center justify-center border bg-transparent outline-none transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4';
7494
+ const BTN = 'border-input text-foreground hover:bg-accent inline-flex items-center justify-center border bg-transparent outline-none transition-colors disabled:pointer-events-none disabled:opacity-50 [&_svg]:size-4';
7495
+ const INPUT = 'w-16 [appearance:textfield] border-y border-input bg-transparent text-center tabular-nums outline-none focus-visible:relative focus-visible:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none';
7496
+ const BTN_SIZE = {
7497
+ sm: 'size-8',
7498
+ default: 'size-9',
7499
+ lg: 'size-10',
7500
+ };
7501
+ const INPUT_SIZE = {
7502
+ sm: 'h-8 text-xs',
7503
+ default: 'h-9 text-sm',
7504
+ lg: 'h-10 text-base',
7505
+ };
6985
7506
  /** A numeric stepper: − / + buttons around a number field, with min/max/step clamping. */
6986
7507
  class BuiNumberInput {
6987
7508
  value = model(0, /* @ts-ignore */
@@ -6993,10 +7514,15 @@ class BuiNumberInput {
6993
7514
  stepBy = input(1, { ...(ngDevMode ? { debugName: "stepBy" } : /* istanbul ignore next */ {}), alias: 'step' });
6994
7515
  disabled = input(false, /* @ts-ignore */
6995
7516
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7517
+ size = input('default', /* @ts-ignore */
7518
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
6996
7519
  ariaLabel = input('', /* @ts-ignore */
6997
7520
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
6998
7521
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
6999
- BTN = BTN;
7522
+ btnClass = computed(() => cn(BTN, BTN_SIZE[this.size()]), /* @ts-ignore */
7523
+ ...(ngDevMode ? [{ debugName: "btnClass" }] : /* istanbul ignore next */ []));
7524
+ inputClass = computed(() => cn(INPUT, INPUT_SIZE[this.size()]), /* @ts-ignore */
7525
+ ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
7000
7526
  atMin = computed(() => {
7001
7527
  const min = this.min();
7002
7528
  return min !== null && this.value() <= min;
@@ -7029,10 +7555,10 @@ class BuiNumberInput {
7029
7555
  this.value.set(value);
7030
7556
  }
7031
7557
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiNumberInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
7032
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: BuiNumberInput, isStandalone: true, selector: "bui-number-input", inputs: { value: { classPropertyName: "value", publicName: "value", 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 }, stepBy: { classPropertyName: "stepBy", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "number-input" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
7558
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: BuiNumberInput, isStandalone: true, selector: "bui-number-input", inputs: { value: { classPropertyName: "value", publicName: "value", 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 }, stepBy: { classPropertyName: "stepBy", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "number-input" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
7033
7559
  <button
7034
7560
  type="button"
7035
- [class]="BTN + ' rounded-l-md'"
7561
+ [class]="btnClass() + ' rounded-l-md'"
7036
7562
  (click)="step(-1)"
7037
7563
  [disabled]="disabled() || atMin()"
7038
7564
  aria-label="Decrease"
@@ -7058,13 +7584,13 @@ class BuiNumberInput {
7058
7584
  [step]="stepBy()"
7059
7585
  [disabled]="disabled()"
7060
7586
  [attr.aria-label]="ariaLabel() || null"
7061
- class="h-9 w-16 [appearance:textfield] border-y border-input bg-transparent text-center text-sm tabular-nums outline-none focus-visible:relative focus-visible:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
7587
+ [class]="inputClass()"
7062
7588
  (input)="onInput($event)"
7063
7589
  (change)="onInput($event)"
7064
7590
  />
7065
7591
  <button
7066
7592
  type="button"
7067
- [class]="BTN + ' rounded-r-md'"
7593
+ [class]="btnClass() + ' rounded-r-md'"
7068
7594
  (click)="step(1)"
7069
7595
  [disabled]="disabled() || atMax()"
7070
7596
  aria-label="Increase"
@@ -7092,7 +7618,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
7092
7618
  template: `
7093
7619
  <button
7094
7620
  type="button"
7095
- [class]="BTN + ' rounded-l-md'"
7621
+ [class]="btnClass() + ' rounded-l-md'"
7096
7622
  (click)="step(-1)"
7097
7623
  [disabled]="disabled() || atMin()"
7098
7624
  aria-label="Decrease"
@@ -7118,13 +7644,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
7118
7644
  [step]="stepBy()"
7119
7645
  [disabled]="disabled()"
7120
7646
  [attr.aria-label]="ariaLabel() || null"
7121
- class="h-9 w-16 [appearance:textfield] border-y border-input bg-transparent text-center text-sm tabular-nums outline-none focus-visible:relative focus-visible:z-10 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
7647
+ [class]="inputClass()"
7122
7648
  (input)="onInput($event)"
7123
7649
  (change)="onInput($event)"
7124
7650
  />
7125
7651
  <button
7126
7652
  type="button"
7127
- [class]="BTN + ' rounded-r-md'"
7653
+ [class]="btnClass() + ' rounded-r-md'"
7128
7654
  (click)="step(1)"
7129
7655
  [disabled]="disabled() || atMax()"
7130
7656
  aria-label="Increase"
@@ -7144,7 +7670,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
7144
7670
  </button>
7145
7671
  `,
7146
7672
  }]
7147
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], stepBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
7673
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], stepBy: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
7148
7674
 
7149
7675
  /** A radio-group of product variants, rendered as pills or colour swatches. */
7150
7676
  class BuiVariantSelector {
@@ -8641,7 +9167,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
8641
9167
  }]
8642
9168
  }], ctorParameters: () => [], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
8643
9169
 
8644
- /** A styled native time input (`<input type="time">`). */
9170
+ const SELECT_CLASS = 'h-9 rounded-md border border-input bg-background px-2 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50';
9171
+ /** A styled time field: a native `<input type="time">`, or hour/minute selects in `select` mode. */
8645
9172
  class BuiTimeField {
8646
9173
  value = model('', /* @ts-ignore */
8647
9174
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
@@ -8657,53 +9184,133 @@ class BuiTimeField {
8657
9184
  ...(ngDevMode ? [{ debugName: "seconds" }] : /* istanbul ignore next */ []));
8658
9185
  disabled = input(false, /* @ts-ignore */
8659
9186
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
9187
+ /** `select` renders hour + minute dropdowns instead of a native time input. */
9188
+ mode = input('input', /* @ts-ignore */
9189
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
9190
+ /** Minute step used in `select` mode. */
9191
+ minuteStep = input(5, /* @ts-ignore */
9192
+ ...(ngDevMode ? [{ debugName: "minuteStep" }] : /* istanbul ignore next */ []));
8660
9193
  ariaLabel = input('', /* @ts-ignore */
8661
9194
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
8662
9195
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
9196
+ selectClass = SELECT_CLASS;
9197
+ hourPart = computed(() => (this.value().split(':', 1)[0] || '12').padStart(2, '0'), /* @ts-ignore */
9198
+ ...(ngDevMode ? [{ debugName: "hourPart" }] : /* istanbul ignore next */ []));
9199
+ minutePart = computed(() => (this.value().split(':', 2)[1] || '00').padStart(2, '0'), /* @ts-ignore */
9200
+ ...(ngDevMode ? [{ debugName: "minutePart" }] : /* istanbul ignore next */ []));
9201
+ hours = computed(() => Array.from({ length: 24 }, (_, hour) => String(hour).padStart(2, '0')), /* @ts-ignore */
9202
+ ...(ngDevMode ? [{ debugName: "hours" }] : /* istanbul ignore next */ []));
9203
+ minutes = computed(() => {
9204
+ const step = Math.max(1, this.minuteStep());
9205
+ return Array.from({ length: Math.ceil(60 / step) }, (_, index) => String(index * step).padStart(2, '0'));
9206
+ }, /* @ts-ignore */
9207
+ ...(ngDevMode ? [{ debugName: "minutes" }] : /* istanbul ignore next */ []));
8663
9208
  computedClass = computed(() => cn('inline-block', this.userClass()), /* @ts-ignore */
8664
9209
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
8665
9210
  onInput(event) {
8666
9211
  this.value.set(event.target.value);
8667
9212
  }
9213
+ setHour(event) {
9214
+ this.value.set(`${event.target.value}:${this.minutePart()}`);
9215
+ }
9216
+ setMinute(event) {
9217
+ this.value.set(`${this.hourPart()}:${event.target.value}`);
9218
+ }
8668
9219
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiTimeField, deps: [], target: i0.ɵɵFactoryTarget.Component });
8669
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "22.0.2", type: BuiTimeField, isStandalone: true, selector: "bui-time-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, 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 }, seconds: { classPropertyName: "seconds", publicName: "seconds", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "time-field" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
8670
- <input
8671
- type="time"
8672
- [value]="value()"
8673
- [attr.name]="name() || null"
8674
- [attr.id]="id() || null"
8675
- [attr.min]="min() || null"
8676
- [attr.max]="max() || null"
8677
- [attr.step]="seconds() ? 1 : null"
8678
- [disabled]="disabled()"
8679
- [attr.aria-label]="ariaLabel() || null"
8680
- class="h-9 rounded-md border border-input bg-background px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50"
8681
- (input)="onInput($event)"
8682
- />
8683
- `, isInline: true });
8684
- }
8685
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiTimeField, decorators: [{
8686
- type: Component,
8687
- args: [{
8688
- selector: 'bui-time-field',
8689
- host: { 'data-slot': 'time-field', '[class]': 'computedClass()' },
8690
- template: `
8691
- <input
8692
- type="time"
8693
- [value]="value()"
8694
- [attr.name]="name() || null"
8695
- [attr.id]="id() || null"
8696
- [attr.min]="min() || null"
8697
- [attr.max]="max() || null"
8698
- [attr.step]="seconds() ? 1 : null"
8699
- [disabled]="disabled()"
8700
- [attr.aria-label]="ariaLabel() || null"
8701
- class="h-9 rounded-md border border-input bg-background px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50"
8702
- (input)="onInput($event)"
8703
- />
9220
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiTimeField, isStandalone: true, selector: "bui-time-field", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, 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 }, seconds: { classPropertyName: "seconds", publicName: "seconds", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, minuteStep: { classPropertyName: "minuteStep", publicName: "minuteStep", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "time-field" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
9221
+ @if (mode() === 'select') {
9222
+ <div class="inline-flex items-center gap-1">
9223
+ <select
9224
+ [class]="selectClass"
9225
+ [value]="hourPart()"
9226
+ [disabled]="disabled()"
9227
+ [attr.aria-label]="(ariaLabel() || 'Time') + ' hours'"
9228
+ (change)="setHour($event)"
9229
+ >
9230
+ @for (hour of hours(); track hour) {
9231
+ <option [value]="hour">{{ hour }}</option>
9232
+ }
9233
+ </select>
9234
+ <span aria-hidden="true" class="text-muted-foreground">:</span>
9235
+ <select
9236
+ [class]="selectClass"
9237
+ [value]="minutePart()"
9238
+ [disabled]="disabled()"
9239
+ [attr.aria-label]="(ariaLabel() || 'Time') + ' minutes'"
9240
+ (change)="setMinute($event)"
9241
+ >
9242
+ @for (minute of minutes(); track minute) {
9243
+ <option [value]="minute">{{ minute }}</option>
9244
+ }
9245
+ </select>
9246
+ </div>
9247
+ } @else {
9248
+ <input
9249
+ type="time"
9250
+ [value]="value()"
9251
+ [attr.name]="name() || null"
9252
+ [attr.id]="id() || null"
9253
+ [attr.min]="min() || null"
9254
+ [attr.max]="max() || null"
9255
+ [attr.step]="seconds() ? 1 : null"
9256
+ [disabled]="disabled()"
9257
+ [attr.aria-label]="ariaLabel() || null"
9258
+ class="h-9 rounded-md border border-input bg-background px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50"
9259
+ (input)="onInput($event)"
9260
+ />
9261
+ }
9262
+ `, isInline: true });
9263
+ }
9264
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiTimeField, decorators: [{
9265
+ type: Component,
9266
+ args: [{
9267
+ selector: 'bui-time-field',
9268
+ host: { 'data-slot': 'time-field', '[class]': 'computedClass()' },
9269
+ template: `
9270
+ @if (mode() === 'select') {
9271
+ <div class="inline-flex items-center gap-1">
9272
+ <select
9273
+ [class]="selectClass"
9274
+ [value]="hourPart()"
9275
+ [disabled]="disabled()"
9276
+ [attr.aria-label]="(ariaLabel() || 'Time') + ' hours'"
9277
+ (change)="setHour($event)"
9278
+ >
9279
+ @for (hour of hours(); track hour) {
9280
+ <option [value]="hour">{{ hour }}</option>
9281
+ }
9282
+ </select>
9283
+ <span aria-hidden="true" class="text-muted-foreground">:</span>
9284
+ <select
9285
+ [class]="selectClass"
9286
+ [value]="minutePart()"
9287
+ [disabled]="disabled()"
9288
+ [attr.aria-label]="(ariaLabel() || 'Time') + ' minutes'"
9289
+ (change)="setMinute($event)"
9290
+ >
9291
+ @for (minute of minutes(); track minute) {
9292
+ <option [value]="minute">{{ minute }}</option>
9293
+ }
9294
+ </select>
9295
+ </div>
9296
+ } @else {
9297
+ <input
9298
+ type="time"
9299
+ [value]="value()"
9300
+ [attr.name]="name() || null"
9301
+ [attr.id]="id() || null"
9302
+ [attr.min]="min() || null"
9303
+ [attr.max]="max() || null"
9304
+ [attr.step]="seconds() ? 1 : null"
9305
+ [disabled]="disabled()"
9306
+ [attr.aria-label]="ariaLabel() || null"
9307
+ class="h-9 rounded-md border border-input bg-background px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50"
9308
+ (input)="onInput($event)"
9309
+ />
9310
+ }
8704
9311
  `,
8705
9312
  }]
8706
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], 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 }] }], seconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "seconds", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
9313
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], 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 }] }], seconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "seconds", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], minuteStep: [{ type: i0.Input, args: [{ isSignal: true, alias: "minuteStep", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
8707
9314
 
8708
9315
  /** A group of toggle buttons (single or multiple selection). Use `button[buiToggleGroupItem]`. */
8709
9316
  class BuiToggleGroup {
@@ -8902,6 +9509,9 @@ class BuiEditable {
8902
9509
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
8903
9510
  label = input('value', /* @ts-ignore */
8904
9511
  ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
9512
+ /** Edit in a multi-line textarea instead of a single-line input. */
9513
+ multiline = input(false, /* @ts-ignore */
9514
+ ...(ngDevMode ? [{ debugName: "multiline" }] : /* istanbul ignore next */ []));
8905
9515
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
8906
9516
  editing = signal(false, /* @ts-ignore */
8907
9517
  ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
@@ -8936,19 +9546,33 @@ class BuiEditable {
8936
9546
  this.draft.set(event.target.value);
8937
9547
  }
8938
9548
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiEditable, deps: [], target: i0.ɵɵFactoryTarget.Component });
8939
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiEditable, isStandalone: true, selector: "bui-editable", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "editable" }, properties: { "class": "computedClass()" } }, viewQueries: [{ propertyName: "field", first: true, predicate: ["field"], descendants: true, isSignal: true }], ngImport: i0, template: `
9549
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiEditable, isStandalone: true, selector: "bui-editable", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, multiline: { classPropertyName: "multiline", publicName: "multiline", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "editable" }, properties: { "class": "computedClass()" } }, viewQueries: [{ propertyName: "field", first: true, predicate: ["field"], descendants: true, isSignal: true }], ngImport: i0, template: `
8940
9550
  @if (editing()) {
8941
- <input
8942
- #field
8943
- [value]="draft()"
8944
- [placeholder]="placeholder()"
8945
- [attr.aria-label]="'Edit ' + label()"
8946
- class="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
8947
- (input)="onInput($event)"
8948
- (blur)="commit()"
8949
- (keydown.enter)="commit()"
8950
- (keydown.escape)="cancel()"
8951
- />
9551
+ @if (multiline()) {
9552
+ <textarea
9553
+ #field
9554
+ [value]="draft()"
9555
+ [placeholder]="placeholder()"
9556
+ [attr.aria-label]="'Edit ' + label()"
9557
+ rows="3"
9558
+ class="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
9559
+ (input)="onInput($event)"
9560
+ (blur)="commit()"
9561
+ (keydown.escape)="cancel()"
9562
+ ></textarea>
9563
+ } @else {
9564
+ <input
9565
+ #field
9566
+ [value]="draft()"
9567
+ [placeholder]="placeholder()"
9568
+ [attr.aria-label]="'Edit ' + label()"
9569
+ class="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
9570
+ (input)="onInput($event)"
9571
+ (blur)="commit()"
9572
+ (keydown.enter)="commit()"
9573
+ (keydown.escape)="cancel()"
9574
+ />
9575
+ }
8952
9576
  } @else {
8953
9577
  <button
8954
9578
  type="button"
@@ -8968,17 +9592,31 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
8968
9592
  host: { 'data-slot': 'editable', '[class]': 'computedClass()' },
8969
9593
  template: `
8970
9594
  @if (editing()) {
8971
- <input
8972
- #field
8973
- [value]="draft()"
8974
- [placeholder]="placeholder()"
8975
- [attr.aria-label]="'Edit ' + label()"
8976
- class="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
8977
- (input)="onInput($event)"
8978
- (blur)="commit()"
8979
- (keydown.enter)="commit()"
8980
- (keydown.escape)="cancel()"
8981
- />
9595
+ @if (multiline()) {
9596
+ <textarea
9597
+ #field
9598
+ [value]="draft()"
9599
+ [placeholder]="placeholder()"
9600
+ [attr.aria-label]="'Edit ' + label()"
9601
+ rows="3"
9602
+ class="w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
9603
+ (input)="onInput($event)"
9604
+ (blur)="commit()"
9605
+ (keydown.escape)="cancel()"
9606
+ ></textarea>
9607
+ } @else {
9608
+ <input
9609
+ #field
9610
+ [value]="draft()"
9611
+ [placeholder]="placeholder()"
9612
+ [attr.aria-label]="'Edit ' + label()"
9613
+ class="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
9614
+ (input)="onInput($event)"
9615
+ (blur)="commit()"
9616
+ (keydown.enter)="commit()"
9617
+ (keydown.escape)="cancel()"
9618
+ />
9619
+ }
8982
9620
  } @else {
8983
9621
  <button
8984
9622
  type="button"
@@ -8991,7 +9629,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
8991
9629
  }
8992
9630
  `,
8993
9631
  }]
8994
- }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], field: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
9632
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], multiline: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiline", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], field: [{ type: i0.ViewChild, args: ['field', { isSignal: true }] }] } });
8995
9633
 
8996
9634
  /** A floating action button that expands to a stack of actions. Closes on Escape / outside click. */
8997
9635
  class BuiSpeedDial {
@@ -9266,7 +9904,7 @@ class BuiKnob {
9266
9904
  (wheel)="onWheel($event)"
9267
9905
  (keydown)="onKeydown($event)"
9268
9906
  >
9269
- <svg viewBox="0 0 100 100" class="size-full">
9907
+ <svg viewBox="0 0 100 100" class="size-full" aria-hidden="true">
9270
9908
  <circle
9271
9909
  cx="50"
9272
9910
  cy="50"
@@ -9325,7 +9963,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
9325
9963
  (wheel)="onWheel($event)"
9326
9964
  (keydown)="onKeydown($event)"
9327
9965
  >
9328
- <svg viewBox="0 0 100 100" class="size-full">
9966
+ <svg viewBox="0 0 100 100" class="size-full" aria-hidden="true">
9329
9967
  <circle
9330
9968
  cx="50"
9331
9969
  cy="50"
@@ -9865,7 +10503,7 @@ class BuiConfetti {
9865
10503
  }
9866
10504
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiConfetti, deps: [], target: i0.ɵɵFactoryTarget.Component });
9867
10505
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiConfetti, isStandalone: true, selector: "bui-confetti", inputs: { count: { classPropertyName: "count", publicName: "count", isSignal: true, isRequired: false, transformFunction: null }, spread: { classPropertyName: "spread", publicName: "spread", isSignal: true, isRequired: false, transformFunction: null }, colors: { classPropertyName: "colors", publicName: "colors", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "confetti" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
9868
- <span (click)="fire()">
10506
+ <span (click)="fire()" (keydown.enter)="fire()" (keydown.space)="fire()">
9869
10507
  <ng-content>
9870
10508
  <button
9871
10509
  type="button"
@@ -9888,7 +10526,7 @@ class BuiConfetti {
9888
10526
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiConfetti, decorators: [{
9889
10527
  type: Component,
9890
10528
  args: [{ selector: 'bui-confetti', host: { 'data-slot': 'confetti', '[class]': 'computedClass()' }, template: `
9891
- <span (click)="fire()">
10529
+ <span (click)="fire()" (keydown.enter)="fire()" (keydown.space)="fire()">
9892
10530
  <ng-content>
9893
10531
  <button
9894
10532
  type="button"
@@ -10058,6 +10696,9 @@ class BuiInputOtp {
10058
10696
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
10059
10697
  alphanumeric = input(false, /* @ts-ignore */
10060
10698
  ...(ngDevMode ? [{ debugName: "alphanumeric" }] : /* istanbul ignore next */ []));
10699
+ /** Insert a visual separator after every N boxes (0 = none). */
10700
+ groupSize = input(0, /* @ts-ignore */
10701
+ ...(ngDevMode ? [{ debugName: "groupSize" }] : /* istanbul ignore next */ []));
10061
10702
  ariaLabel = input('One-time password', /* @ts-ignore */
10062
10703
  ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
10063
10704
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
@@ -10069,6 +10710,10 @@ class BuiInputOtp {
10069
10710
  charAt(index) {
10070
10711
  return this.value().charAt(index);
10071
10712
  }
10713
+ isSeparator(index) {
10714
+ const size = this.groupSize();
10715
+ return size > 0 && (index + 1) % size === 0 && index < this.maxlength() - 1;
10716
+ }
10072
10717
  onInput(index, event) {
10073
10718
  const element = event.target;
10074
10719
  const char = this.sanitize(element.value).slice(-1);
@@ -10109,7 +10754,7 @@ class BuiInputOtp {
10109
10754
  boxes.item(index).focus();
10110
10755
  }
10111
10756
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiInputOtp, deps: [], target: i0.ɵɵFactoryTarget.Component });
10112
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiInputOtp, isStandalone: true, selector: "bui-input-otp", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, maxlength: { classPropertyName: "maxlength", publicName: "maxlength", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, alphanumeric: { classPropertyName: "alphanumeric", publicName: "alphanumeric", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "input-otp" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
10757
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiInputOtp, isStandalone: true, selector: "bui-input-otp", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, maxlength: { classPropertyName: "maxlength", publicName: "maxlength", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, alphanumeric: { classPropertyName: "alphanumeric", publicName: "alphanumeric", isSignal: true, isRequired: false, transformFunction: null }, groupSize: { classPropertyName: "groupSize", publicName: "groupSize", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "input-otp" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
10113
10758
  @for (slot of slots(); track slot) {
10114
10759
  <input
10115
10760
  type="text"
@@ -10125,6 +10770,9 @@ class BuiInputOtp {
10125
10770
  (paste)="onPaste($event)"
10126
10771
  (focus)="onFocus($event)"
10127
10772
  />
10773
+ @if (isSeparator(slot)) {
10774
+ <span aria-hidden="true" class="text-muted-foreground select-none">-</span>
10775
+ }
10128
10776
  }
10129
10777
  `, isInline: true });
10130
10778
  }
@@ -10149,10 +10797,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
10149
10797
  (paste)="onPaste($event)"
10150
10798
  (focus)="onFocus($event)"
10151
10799
  />
10800
+ @if (isSeparator(slot)) {
10801
+ <span aria-hidden="true" class="text-muted-foreground select-none">-</span>
10802
+ }
10152
10803
  }
10153
10804
  `,
10154
10805
  }]
10155
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], maxlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxlength", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], alphanumeric: [{ type: i0.Input, args: [{ isSignal: true, alias: "alphanumeric", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
10806
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], maxlength: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxlength", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], alphanumeric: [{ type: i0.Input, args: [{ isSignal: true, alias: "alphanumeric", required: false }] }], groupSize: [{ type: i0.Input, args: [{ isSignal: true, alias: "groupSize", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
10156
10807
 
10157
10808
  const COUNTRIES = [
10158
10809
  { code: 'US', name: 'United States', dial: '+1', flag: '🇺🇸' },
@@ -10423,7 +11074,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
10423
11074
  class BuiHeatmap {
10424
11075
  data = input([], /* @ts-ignore */
10425
11076
  ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
11077
+ /** Smaller cells + tighter gaps. */
11078
+ compact = input(false, /* @ts-ignore */
11079
+ ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
10426
11080
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
11081
+ gridClass = computed(() => cn('grid grid-flow-col grid-rows-7', this.compact() ? 'gap-0.5' : 'gap-1'), /* @ts-ignore */
11082
+ ...(ngDevMode ? [{ debugName: "gridClass" }] : /* istanbul ignore next */ []));
10427
11083
  cells = computed(() => {
10428
11084
  const data = this.data();
10429
11085
  if (data.length > 0) {
@@ -10439,6 +11095,9 @@ class BuiHeatmap {
10439
11095
  ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
10440
11096
  computedClass = computed(() => cn('inline-block', this.userClass()), /* @ts-ignore */
10441
11097
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
11098
+ cellClass(count) {
11099
+ return cn(this.compact() ? 'size-2' : 'size-3', this.levelClass(count));
11100
+ }
10442
11101
  levelClass(count) {
10443
11102
  if (count <= 0) {
10444
11103
  return 'bg-muted';
@@ -10456,12 +11115,12 @@ class BuiHeatmap {
10456
11115
  return 'bg-emerald-500 dark:bg-emerald-400';
10457
11116
  }
10458
11117
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiHeatmap, deps: [], target: i0.ɵɵFactoryTarget.Component });
10459
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiHeatmap, isStandalone: true, selector: "bui-heatmap", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "heatmap" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
10460
- <div class="grid grid-flow-col grid-rows-7 gap-1">
11118
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiHeatmap, isStandalone: true, selector: "bui-heatmap", inputs: { data: { classPropertyName: "data", publicName: "data", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "heatmap" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
11119
+ <div [class]="gridClass()">
10461
11120
  @for (cell of cells(); track $index) {
10462
11121
  <span
10463
- class="size-3 rounded-[2px]"
10464
- [class]="levelClass(cell)"
11122
+ class="rounded-[2px]"
11123
+ [class]="cellClass(cell)"
10465
11124
  [attr.title]="cell + ' contributions'"
10466
11125
  ></span>
10467
11126
  }
@@ -10474,18 +11133,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
10474
11133
  selector: 'bui-heatmap',
10475
11134
  host: { 'data-slot': 'heatmap', '[class]': 'computedClass()' },
10476
11135
  template: `
10477
- <div class="grid grid-flow-col grid-rows-7 gap-1">
11136
+ <div [class]="gridClass()">
10478
11137
  @for (cell of cells(); track $index) {
10479
11138
  <span
10480
- class="size-3 rounded-[2px]"
10481
- [class]="levelClass(cell)"
11139
+ class="rounded-[2px]"
11140
+ [class]="cellClass(cell)"
10482
11141
  [attr.title]="cell + ' contributions'"
10483
11142
  ></span>
10484
11143
  }
10485
11144
  </div>
10486
11145
  `,
10487
11146
  }]
10488
- }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
11147
+ }], propDecorators: { data: [{ type: i0.Input, args: [{ isSignal: true, alias: "data", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
10489
11148
 
10490
11149
  /** An inline citation marker that reveals a source popover on hover / focus / click. */
10491
11150
  class BuiCitation {
@@ -11217,6 +11876,9 @@ class BuiAudioPlayer {
11217
11876
  ...(ngDevMode ? [{ debugName: "artist" }] : /* istanbul ignore next */ []));
11218
11877
  autoplay = input(false, /* @ts-ignore */
11219
11878
  ...(ngDevMode ? [{ debugName: "autoplay" }] : /* istanbul ignore next */ []));
11879
+ /** Condensed layout: hides the title block and mute button. */
11880
+ compact = input(false, /* @ts-ignore */
11881
+ ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
11220
11882
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
11221
11883
  a = viewChild('a', /* @ts-ignore */
11222
11884
  ...(ngDevMode ? [{ debugName: "a" }] : /* istanbul ignore next */ []));
@@ -11277,7 +11939,7 @@ class BuiAudioPlayer {
11277
11939
  return `${minutes}:${rest.toString().padStart(2, '0')}`;
11278
11940
  }
11279
11941
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiAudioPlayer, deps: [], target: i0.ɵɵFactoryTarget.Component });
11280
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiAudioPlayer, isStandalone: true, selector: "bui-audio-player", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, artist: { classPropertyName: "artist", publicName: "artist", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "audio-player" }, properties: { "class": "computedClass()" } }, viewQueries: [{ propertyName: "a", first: true, predicate: ["a"], descendants: true, isSignal: true }], ngImport: i0, template: `
11942
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiAudioPlayer, isStandalone: true, selector: "bui-audio-player", inputs: { src: { classPropertyName: "src", publicName: "src", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, artist: { classPropertyName: "artist", publicName: "artist", isSignal: true, isRequired: false, transformFunction: null }, autoplay: { classPropertyName: "autoplay", publicName: "autoplay", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "audio-player" }, properties: { "class": "computedClass()" } }, viewQueries: [{ propertyName: "a", first: true, predicate: ["a"], descendants: true, isSignal: true }], ngImport: i0, template: `
11281
11943
  <audio
11282
11944
  #a
11283
11945
  [src]="src() || null"
@@ -11304,7 +11966,7 @@ class BuiAudioPlayer {
11304
11966
  }
11305
11967
  </svg>
11306
11968
  </button>
11307
- @if (title()) {
11969
+ @if (title() && !compact()) {
11308
11970
  <div class="min-w-0">
11309
11971
  <div class="truncate text-sm font-medium">{{ title() }}</div>
11310
11972
  @if (artist()) {
@@ -11324,22 +11986,24 @@ class BuiAudioPlayer {
11324
11986
  (input)="seek($event)"
11325
11987
  />
11326
11988
  <span class="text-xs text-muted-foreground tabular-nums">{{ format(duration()) }}</span>
11327
- <button
11328
- type="button"
11329
- class="shrink-0 text-muted-foreground hover:text-foreground"
11330
- [attr.aria-label]="muted() ? 'Unmute' : 'Mute'"
11331
- (click)="toggleMute()"
11332
- >
11333
- <svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
11334
- @if (muted()) {
11335
- <path
11336
- d="M3 9v6h4l5 5V4L7 9H3zm13.5 3a4.5 4.5 0 0 0-2.5-4v2.2l2.45 2.45c.03-.21.05-.43.05-.65z"
11337
- />
11338
- } @else {
11339
- <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 8v8a4.5 4.5 0 0 0 2.5-4z" />
11340
- }
11341
- </svg>
11342
- </button>
11989
+ @if (!compact()) {
11990
+ <button
11991
+ type="button"
11992
+ class="shrink-0 text-muted-foreground hover:text-foreground"
11993
+ [attr.aria-label]="muted() ? 'Unmute' : 'Mute'"
11994
+ (click)="toggleMute()"
11995
+ >
11996
+ <svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
11997
+ @if (muted()) {
11998
+ <path
11999
+ d="M3 9v6h4l5 5V4L7 9H3zm13.5 3a4.5 4.5 0 0 0-2.5-4v2.2l2.45 2.45c.03-.21.05-.43.05-.65z"
12000
+ />
12001
+ } @else {
12002
+ <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 8v8a4.5 4.5 0 0 0 2.5-4z" />
12003
+ }
12004
+ </svg>
12005
+ </button>
12006
+ }
11343
12007
  `, isInline: true });
11344
12008
  }
11345
12009
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiAudioPlayer, decorators: [{
@@ -11374,7 +12038,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
11374
12038
  }
11375
12039
  </svg>
11376
12040
  </button>
11377
- @if (title()) {
12041
+ @if (title() && !compact()) {
11378
12042
  <div class="min-w-0">
11379
12043
  <div class="truncate text-sm font-medium">{{ title() }}</div>
11380
12044
  @if (artist()) {
@@ -11394,25 +12058,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
11394
12058
  (input)="seek($event)"
11395
12059
  />
11396
12060
  <span class="text-xs text-muted-foreground tabular-nums">{{ format(duration()) }}</span>
11397
- <button
11398
- type="button"
11399
- class="shrink-0 text-muted-foreground hover:text-foreground"
11400
- [attr.aria-label]="muted() ? 'Unmute' : 'Mute'"
11401
- (click)="toggleMute()"
11402
- >
11403
- <svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
11404
- @if (muted()) {
11405
- <path
11406
- d="M3 9v6h4l5 5V4L7 9H3zm13.5 3a4.5 4.5 0 0 0-2.5-4v2.2l2.45 2.45c.03-.21.05-.43.05-.65z"
11407
- />
11408
- } @else {
11409
- <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 8v8a4.5 4.5 0 0 0 2.5-4z" />
11410
- }
11411
- </svg>
11412
- </button>
12061
+ @if (!compact()) {
12062
+ <button
12063
+ type="button"
12064
+ class="shrink-0 text-muted-foreground hover:text-foreground"
12065
+ [attr.aria-label]="muted() ? 'Unmute' : 'Mute'"
12066
+ (click)="toggleMute()"
12067
+ >
12068
+ <svg class="size-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
12069
+ @if (muted()) {
12070
+ <path
12071
+ d="M3 9v6h4l5 5V4L7 9H3zm13.5 3a4.5 4.5 0 0 0-2.5-4v2.2l2.45 2.45c.03-.21.05-.43.05-.65z"
12072
+ />
12073
+ } @else {
12074
+ <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3A4.5 4.5 0 0 0 14 8v8a4.5 4.5 0 0 0 2.5-4z" />
12075
+ }
12076
+ </svg>
12077
+ </button>
12078
+ }
11413
12079
  `,
11414
12080
  }]
11415
- }], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], artist: [{ type: i0.Input, args: [{ isSignal: true, alias: "artist", required: false }] }], autoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoplay", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], a: [{ type: i0.ViewChild, args: ['a', { isSignal: true }] }] } });
12081
+ }], propDecorators: { src: [{ type: i0.Input, args: [{ isSignal: true, alias: "src", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], artist: [{ type: i0.Input, args: [{ isSignal: true, alias: "artist", required: false }] }], autoplay: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoplay", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], a: [{ type: i0.ViewChild, args: ['a', { isSignal: true }] }] } });
11416
12082
 
11417
12083
  /** A canvas signature pad with draw, undo and clear. SSR-safe (canvas set up in the browser). */
11418
12084
  class BuiSignaturePad {
@@ -13180,16 +13846,46 @@ function pad(value) {
13180
13846
  function isoOf(date) {
13181
13847
  return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
13182
13848
  }
13183
- /** A single-month date calendar. Pick a day; navigate months. SSR-safe. */
13849
+ /**
13850
+ * A date calendar. Modes: `single` (one day), `range` (start–end), `multiple` (a set of days).
13851
+ * Render several months side by side with `months`. SSR-safe.
13852
+ */
13184
13853
  class BuiCalendar {
13185
13854
  value = model('', /* @ts-ignore */
13186
13855
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
13856
+ /** Selection mode: `single` | `range` | `multiple`. */
13857
+ mode = input('single', /* @ts-ignore */
13858
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
13859
+ /** Selected range (mode="range"). */
13860
+ range = model({ start: '', end: '' }, /* @ts-ignore */
13861
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
13862
+ /** Selected days (mode="multiple"). */
13863
+ values = model([], /* @ts-ignore */
13864
+ ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
13865
+ /** Number of month grids to render side by side. */
13866
+ months = input(1, /* @ts-ignore */
13867
+ ...(ngDevMode ? [{ debugName: "months" }] : /* istanbul ignore next */ []));
13187
13868
  weekStart = input(0, /* @ts-ignore */
13188
13869
  ...(ngDevMode ? [{ debugName: "weekStart" }] : /* istanbul ignore next */ []));
13189
13870
  minDate = input('', /* @ts-ignore */
13190
13871
  ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
13191
13872
  maxDate = input('', /* @ts-ignore */
13192
13873
  ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
13874
+ /** Specific ISO dates (yyyy-mm-dd) to disable. */
13875
+ disabledDates = input([], /* @ts-ignore */
13876
+ ...(ngDevMode ? [{ debugName: "disabledDates" }] : /* istanbul ignore next */ []));
13877
+ /** Disable Saturdays and Sundays. */
13878
+ disableWeekends = input(false, /* @ts-ignore */
13879
+ ...(ngDevMode ? [{ debugName: "disableWeekends" }] : /* istanbul ignore next */ []));
13880
+ /** Show an ISO week-number column on the left. */
13881
+ showWeekNumbers = input(false, /* @ts-ignore */
13882
+ ...(ngDevMode ? [{ debugName: "showWeekNumbers" }] : /* istanbul ignore next */ []));
13883
+ /** `dropdown` swaps the month label for month + year selects (single month only). */
13884
+ captionLayout = input('label', /* @ts-ignore */
13885
+ ...(ngDevMode ? [{ debugName: "captionLayout" }] : /* istanbul ignore next */ []));
13886
+ /** Hide days that fall outside the current month. */
13887
+ hideOutsideDays = input(false, /* @ts-ignore */
13888
+ ...(ngDevMode ? [{ debugName: "hideOutsideDays" }] : /* istanbul ignore next */ []));
13193
13889
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
13194
13890
  view = signal(new Date(new Date().getFullYear(), new Date().getMonth(), 1), /* @ts-ignore */
13195
13891
  ...(ngDevMode ? [{ debugName: "view" }] : /* istanbul ignore next */ []));
@@ -13200,47 +13896,149 @@ class BuiCalendar {
13200
13896
  ...(ngDevMode ? [{ debugName: "weekdays" }] : /* istanbul ignore next */ []));
13201
13897
  monthLabel = computed(() => this.view().toLocaleDateString('en-US', { month: 'long', year: 'numeric' }), /* @ts-ignore */
13202
13898
  ...(ngDevMode ? [{ debugName: "monthLabel" }] : /* istanbul ignore next */ []));
13203
- weeks = computed(() => {
13899
+ viewMonth = computed(() => this.view().getMonth(), /* @ts-ignore */
13900
+ ...(ngDevMode ? [{ debugName: "viewMonth" }] : /* istanbul ignore next */ []));
13901
+ viewYear = computed(() => this.view().getFullYear(), /* @ts-ignore */
13902
+ ...(ngDevMode ? [{ debugName: "viewYear" }] : /* istanbul ignore next */ []));
13903
+ monthOptions = computed(() => Array.from({ length: 12 }, (_, index) => ({
13904
+ value: index,
13905
+ label: new Date(2000, index, 1).toLocaleDateString('en-US', { month: 'long' }),
13906
+ })), /* @ts-ignore */
13907
+ ...(ngDevMode ? [{ debugName: "monthOptions" }] : /* istanbul ignore next */ []));
13908
+ years = computed(() => {
13909
+ const current = this.viewYear();
13910
+ return Array.from({ length: 21 }, (_, index) => current - 10 + index);
13911
+ }, /* @ts-ignore */
13912
+ ...(ngDevMode ? [{ debugName: "years" }] : /* istanbul ignore next */ []));
13913
+ monthGrids = computed(() => {
13204
13914
  const view = this.view();
13205
- const year = view.getFullYear();
13206
- const month = view.getMonth();
13207
- const offset = (((new Date(year, month, 1).getDay() - this.weekStart()) % 7) + 7) % 7;
13208
- const today = isoOf(new Date());
13209
- return Array.from({ length: 6 }, (_, w) => Array.from({ length: 7 }, (_, d) => {
13210
- const date = new Date(year, month, 1 - offset + w * 7 + d);
13211
- const iso = isoOf(date);
13915
+ return Array.from({ length: Math.max(1, this.months()) }, (_, index) => {
13916
+ const base = new Date(view.getFullYear(), view.getMonth() + index, 1);
13212
13917
  return {
13213
- iso,
13214
- num: date.getDate(),
13215
- inMonth: date.getMonth() === month,
13216
- isToday: iso === today,
13217
- disabled: this.isDisabled(iso),
13918
+ key: `${base.getFullYear()}-${base.getMonth()}`,
13919
+ label: base.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }),
13920
+ weeks: this.weeksFor(base),
13218
13921
  };
13219
- }));
13922
+ });
13220
13923
  }, /* @ts-ignore */
13221
- ...(ngDevMode ? [{ debugName: "weeks" }] : /* istanbul ignore next */ []));
13924
+ ...(ngDevMode ? [{ debugName: "monthGrids" }] : /* istanbul ignore next */ []));
13222
13925
  computedClass = computed(() => cn('inline-block rounded-lg bg-card p-3', this.userClass()), /* @ts-ignore */
13223
13926
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
13927
+ weekNum(iso) {
13928
+ const [year, month, day] = iso.split('-').map(Number);
13929
+ const date = new Date(Date.UTC(year, month - 1, day));
13930
+ const dayNumber = (date.getUTCDay() + 6) % 7;
13931
+ date.setUTCDate(date.getUTCDate() - dayNumber + 3);
13932
+ const firstThursday = new Date(Date.UTC(date.getUTCFullYear(), 0, 4));
13933
+ return 1 + Math.round((date.getTime() - firstThursday.getTime()) / (7 * 24 * 3600 * 1000));
13934
+ }
13224
13935
  changeMonth(delta) {
13225
13936
  const view = this.view();
13226
13937
  this.view.set(new Date(view.getFullYear(), view.getMonth() + delta, 1));
13227
13938
  }
13939
+ setMonth(event) {
13940
+ const month = Number(event.target.value);
13941
+ this.view.set(new Date(this.viewYear(), month, 1));
13942
+ }
13943
+ setYear(event) {
13944
+ const year = Number(event.target.value);
13945
+ this.view.set(new Date(year, this.viewMonth(), 1));
13946
+ }
13228
13947
  select(iso) {
13948
+ const mode = this.mode();
13949
+ if (mode === 'range') {
13950
+ const { start, end } = this.range();
13951
+ if (start === '' || end !== '') {
13952
+ this.range.set({ start: iso, end: '' });
13953
+ }
13954
+ else if (iso < start) {
13955
+ this.range.set({ start: iso, end: start });
13956
+ }
13957
+ else {
13958
+ this.range.set({ start, end: iso });
13959
+ }
13960
+ return;
13961
+ }
13962
+ if (mode === 'multiple') {
13963
+ const next = new Set(this.values());
13964
+ if (next.has(iso)) {
13965
+ next.delete(iso);
13966
+ }
13967
+ else {
13968
+ next.add(iso);
13969
+ }
13970
+ this.values.set([...next]);
13971
+ return;
13972
+ }
13229
13973
  this.value.set(iso);
13230
13974
  }
13975
+ isSelected(iso) {
13976
+ if (iso === '') {
13977
+ return false;
13978
+ }
13979
+ switch (this.mode()) {
13980
+ case 'range': {
13981
+ const { start, end } = this.range();
13982
+ return iso === start || iso === end;
13983
+ }
13984
+ case 'multiple': {
13985
+ return this.values().includes(iso);
13986
+ }
13987
+ default: {
13988
+ return iso === this.value();
13989
+ }
13990
+ }
13991
+ }
13992
+ inRangeMiddle(iso) {
13993
+ if (this.mode() !== 'range') {
13994
+ return false;
13995
+ }
13996
+ const { start, end } = this.range();
13997
+ return start !== '' && end !== '' && iso > start && iso < end;
13998
+ }
13231
13999
  dayClass(day) {
13232
- if (day.iso === this.value()) {
13233
- return 'bg-primary text-primary-foreground font-medium';
14000
+ if (this.isSelected(day.iso)) {
14001
+ return 'rounded-md bg-primary text-primary-foreground font-medium';
13234
14002
  }
13235
- return cn('hover:bg-accent disabled:pointer-events-none disabled:opacity-40', day.inMonth ? '' : 'text-muted-foreground/50', day.isToday ? 'border border-primary' : '');
14003
+ if (this.inRangeMiddle(day.iso)) {
14004
+ return 'bg-accent text-accent-foreground';
14005
+ }
14006
+ return cn('rounded-md hover:bg-accent disabled:pointer-events-none disabled:opacity-40', day.inMonth ? '' : 'text-muted-foreground/50', day.isToday ? 'border border-primary' : '');
14007
+ }
14008
+ weeksFor(base) {
14009
+ const year = base.getFullYear();
14010
+ const month = base.getMonth();
14011
+ const offset = (((new Date(year, month, 1).getDay() - this.weekStart()) % 7) + 7) % 7;
14012
+ const today = isoOf(new Date());
14013
+ return Array.from({ length: 6 }, (_, w) => Array.from({ length: 7 }, (_, d) => {
14014
+ const date = new Date(year, month, 1 - offset + w * 7 + d);
14015
+ const iso = isoOf(date);
14016
+ return {
14017
+ iso,
14018
+ num: date.getDate(),
14019
+ inMonth: date.getMonth() === month,
14020
+ isToday: iso === today,
14021
+ disabled: this.isDisabled(iso, date),
14022
+ };
14023
+ }));
13236
14024
  }
13237
- isDisabled(iso) {
14025
+ isDisabled(iso, date) {
13238
14026
  const min = this.minDate();
13239
14027
  const max = this.maxDate();
13240
- return (min !== '' && iso < min) || (max !== '' && iso > max);
14028
+ if ((min !== '' && iso < min) || (max !== '' && iso > max)) {
14029
+ return true;
14030
+ }
14031
+ if (this.disabledDates().includes(iso)) {
14032
+ return true;
14033
+ }
14034
+ if (this.disableWeekends()) {
14035
+ const day = date.getDay();
14036
+ return day === 0 || day === 6;
14037
+ }
14038
+ return false;
13241
14039
  }
13242
14040
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiCalendar, deps: [], target: i0.ɵɵFactoryTarget.Component });
13243
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCalendar, isStandalone: true, selector: "bui-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "calendar" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
14041
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCalendar, isStandalone: true, selector: "bui-calendar", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, months: { classPropertyName: "months", publicName: "months", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, disableWeekends: { classPropertyName: "disableWeekends", publicName: "disableWeekends", isSignal: true, isRequired: false, transformFunction: null }, showWeekNumbers: { classPropertyName: "showWeekNumbers", publicName: "showWeekNumbers", isSignal: true, isRequired: false, transformFunction: null }, captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, hideOutsideDays: { classPropertyName: "hideOutsideDays", publicName: "hideOutsideDays", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", range: "rangeChange", values: "valuesChange" }, host: { attributes: { "data-slot": "calendar" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
13244
14042
  <div class="flex items-center justify-between pb-2">
13245
14043
  <button
13246
14044
  type="button"
@@ -13261,7 +14059,34 @@ class BuiCalendar {
13261
14059
  <path d="m15 18-6-6 6-6" />
13262
14060
  </svg>
13263
14061
  </button>
13264
- <div class="text-sm font-medium" aria-live="polite">{{ monthLabel() }}</div>
14062
+ @if (months() === 1 && captionLayout() === 'dropdown') {
14063
+ <div class="flex items-center gap-1">
14064
+ <select
14065
+ aria-label="Month"
14066
+ class="rounded-md border-0 bg-transparent py-1 text-sm font-medium outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
14067
+ [value]="viewMonth()"
14068
+ (change)="setMonth($event)"
14069
+ >
14070
+ @for (month of monthOptions(); track month.value) {
14071
+ <option [value]="month.value">{{ month.label }}</option>
14072
+ }
14073
+ </select>
14074
+ <select
14075
+ aria-label="Year"
14076
+ class="rounded-md border-0 bg-transparent py-1 text-sm font-medium outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
14077
+ [value]="viewYear()"
14078
+ (change)="setYear($event)"
14079
+ >
14080
+ @for (year of years(); track year) {
14081
+ <option [value]="year">{{ year }}</option>
14082
+ }
14083
+ </select>
14084
+ </div>
14085
+ } @else if (months() === 1) {
14086
+ <div class="text-sm font-medium" aria-live="polite">{{ monthLabel() }}</div>
14087
+ } @else {
14088
+ <span aria-hidden="true"></span>
14089
+ }
13265
14090
  <button
13266
14091
  type="button"
13267
14092
  class="inline-flex size-7 items-center justify-center rounded-md hover:bg-accent"
@@ -13282,38 +14107,61 @@ class BuiCalendar {
13282
14107
  </svg>
13283
14108
  </button>
13284
14109
  </div>
13285
- <table role="grid" class="w-full border-collapse">
13286
- <thead>
13287
- <tr>
13288
- @for (weekday of weekdays(); track weekday) {
13289
- <th scope="col" class="size-9 text-xs font-normal text-muted-foreground">
13290
- {{ weekday }}
13291
- </th>
14110
+ <div class="flex flex-col gap-4 sm:flex-row">
14111
+ @for (grid of monthGrids(); track grid.key) {
14112
+ <div>
14113
+ @if (months() > 1) {
14114
+ <div class="pb-2 text-center text-sm font-medium" aria-live="polite">
14115
+ {{ grid.label }}
14116
+ </div>
13292
14117
  }
13293
- </tr>
13294
- </thead>
13295
- <tbody>
13296
- @for (week of weeks(); track $index) {
13297
- <tr>
13298
- @for (day of week; track day.iso) {
13299
- <td class="p-0 text-center">
13300
- <button
13301
- type="button"
13302
- class="size-9 rounded-md text-sm"
13303
- [class]="dayClass(day)"
13304
- [attr.aria-pressed]="day.iso === value()"
13305
- [attr.aria-current]="day.isToday ? 'date' : null"
13306
- [disabled]="day.disabled"
13307
- (click)="select(day.iso)"
13308
- >
13309
- {{ day.num }}
13310
- </button>
13311
- </td>
13312
- }
13313
- </tr>
13314
- }
13315
- </tbody>
13316
- </table>
14118
+ <table role="grid" class="w-full border-collapse">
14119
+ <thead>
14120
+ <tr>
14121
+ @if (showWeekNumbers()) {
14122
+ <th scope="col" class="size-9 text-xs font-normal text-muted-foreground">#</th>
14123
+ }
14124
+ @for (weekday of weekdays(); track weekday) {
14125
+ <th scope="col" class="size-9 text-xs font-normal text-muted-foreground">
14126
+ {{ weekday }}
14127
+ </th>
14128
+ }
14129
+ </tr>
14130
+ </thead>
14131
+ <tbody>
14132
+ @for (week of grid.weeks; track $index) {
14133
+ <tr>
14134
+ @if (showWeekNumbers()) {
14135
+ <td class="size-9 text-center text-xs text-muted-foreground/70 tabular-nums">
14136
+ {{ weekNum(week[0].iso) }}
14137
+ </td>
14138
+ }
14139
+ @for (day of week; track day.iso) {
14140
+ <td class="p-0 text-center">
14141
+ @if (day.inMonth || !hideOutsideDays()) {
14142
+ <button
14143
+ type="button"
14144
+ class="size-9 text-sm"
14145
+ [class]="dayClass(day)"
14146
+ [attr.aria-pressed]="isSelected(day.iso)"
14147
+ [attr.aria-current]="day.isToday ? 'date' : null"
14148
+ [disabled]="day.disabled"
14149
+ (click)="select(day.iso)"
14150
+ >
14151
+ {{ day.num }}
14152
+ </button>
14153
+ } @else {
14154
+ <span class="block size-9"></span>
14155
+ }
14156
+ </td>
14157
+ }
14158
+ </tr>
14159
+ }
14160
+ </tbody>
14161
+ </table>
14162
+ </div>
14163
+ }
14164
+ </div>
13317
14165
  `, isInline: true });
13318
14166
  }
13319
14167
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiCalendar, decorators: [{
@@ -13342,7 +14190,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13342
14190
  <path d="m15 18-6-6 6-6" />
13343
14191
  </svg>
13344
14192
  </button>
13345
- <div class="text-sm font-medium" aria-live="polite">{{ monthLabel() }}</div>
14193
+ @if (months() === 1 && captionLayout() === 'dropdown') {
14194
+ <div class="flex items-center gap-1">
14195
+ <select
14196
+ aria-label="Month"
14197
+ class="rounded-md border-0 bg-transparent py-1 text-sm font-medium outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
14198
+ [value]="viewMonth()"
14199
+ (change)="setMonth($event)"
14200
+ >
14201
+ @for (month of monthOptions(); track month.value) {
14202
+ <option [value]="month.value">{{ month.label }}</option>
14203
+ }
14204
+ </select>
14205
+ <select
14206
+ aria-label="Year"
14207
+ class="rounded-md border-0 bg-transparent py-1 text-sm font-medium outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
14208
+ [value]="viewYear()"
14209
+ (change)="setYear($event)"
14210
+ >
14211
+ @for (year of years(); track year) {
14212
+ <option [value]="year">{{ year }}</option>
14213
+ }
14214
+ </select>
14215
+ </div>
14216
+ } @else if (months() === 1) {
14217
+ <div class="text-sm font-medium" aria-live="polite">{{ monthLabel() }}</div>
14218
+ } @else {
14219
+ <span aria-hidden="true"></span>
14220
+ }
13346
14221
  <button
13347
14222
  type="button"
13348
14223
  class="inline-flex size-7 items-center justify-center rounded-md hover:bg-accent"
@@ -13363,66 +14238,112 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13363
14238
  </svg>
13364
14239
  </button>
13365
14240
  </div>
13366
- <table role="grid" class="w-full border-collapse">
13367
- <thead>
13368
- <tr>
13369
- @for (weekday of weekdays(); track weekday) {
13370
- <th scope="col" class="size-9 text-xs font-normal text-muted-foreground">
13371
- {{ weekday }}
13372
- </th>
14241
+ <div class="flex flex-col gap-4 sm:flex-row">
14242
+ @for (grid of monthGrids(); track grid.key) {
14243
+ <div>
14244
+ @if (months() > 1) {
14245
+ <div class="pb-2 text-center text-sm font-medium" aria-live="polite">
14246
+ {{ grid.label }}
14247
+ </div>
13373
14248
  }
13374
- </tr>
13375
- </thead>
13376
- <tbody>
13377
- @for (week of weeks(); track $index) {
13378
- <tr>
13379
- @for (day of week; track day.iso) {
13380
- <td class="p-0 text-center">
13381
- <button
13382
- type="button"
13383
- class="size-9 rounded-md text-sm"
13384
- [class]="dayClass(day)"
13385
- [attr.aria-pressed]="day.iso === value()"
13386
- [attr.aria-current]="day.isToday ? 'date' : null"
13387
- [disabled]="day.disabled"
13388
- (click)="select(day.iso)"
13389
- >
13390
- {{ day.num }}
13391
- </button>
13392
- </td>
13393
- }
13394
- </tr>
13395
- }
13396
- </tbody>
13397
- </table>
14249
+ <table role="grid" class="w-full border-collapse">
14250
+ <thead>
14251
+ <tr>
14252
+ @if (showWeekNumbers()) {
14253
+ <th scope="col" class="size-9 text-xs font-normal text-muted-foreground">#</th>
14254
+ }
14255
+ @for (weekday of weekdays(); track weekday) {
14256
+ <th scope="col" class="size-9 text-xs font-normal text-muted-foreground">
14257
+ {{ weekday }}
14258
+ </th>
14259
+ }
14260
+ </tr>
14261
+ </thead>
14262
+ <tbody>
14263
+ @for (week of grid.weeks; track $index) {
14264
+ <tr>
14265
+ @if (showWeekNumbers()) {
14266
+ <td class="size-9 text-center text-xs text-muted-foreground/70 tabular-nums">
14267
+ {{ weekNum(week[0].iso) }}
14268
+ </td>
14269
+ }
14270
+ @for (day of week; track day.iso) {
14271
+ <td class="p-0 text-center">
14272
+ @if (day.inMonth || !hideOutsideDays()) {
14273
+ <button
14274
+ type="button"
14275
+ class="size-9 text-sm"
14276
+ [class]="dayClass(day)"
14277
+ [attr.aria-pressed]="isSelected(day.iso)"
14278
+ [attr.aria-current]="day.isToday ? 'date' : null"
14279
+ [disabled]="day.disabled"
14280
+ (click)="select(day.iso)"
14281
+ >
14282
+ {{ day.num }}
14283
+ </button>
14284
+ } @else {
14285
+ <span class="block size-9"></span>
14286
+ }
14287
+ </td>
14288
+ }
14289
+ </tr>
14290
+ }
14291
+ </tbody>
14292
+ </table>
14293
+ </div>
14294
+ }
14295
+ </div>
13398
14296
  `,
13399
14297
  }]
13400
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], weekStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStart", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
14298
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }, { type: i0.Output, args: ["rangeChange"] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }], months: [{ type: i0.Input, args: [{ isSignal: true, alias: "months", required: false }] }], weekStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStart", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], disabledDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDates", required: false }] }], disableWeekends: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableWeekends", required: false }] }], showWeekNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWeekNumbers", required: false }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], hideOutsideDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideOutsideDays", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
13401
14299
 
13402
14300
  /** A date input that opens a calendar popover. */
13403
14301
  class BuiDatePicker {
13404
14302
  value = model('', /* @ts-ignore */
13405
14303
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
14304
+ /** `single` (default) or `range`. */
14305
+ mode = input('single', /* @ts-ignore */
14306
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
14307
+ /** Selected range when mode="range". */
14308
+ range = model({ start: '', end: '' }, /* @ts-ignore */
14309
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
14310
+ /** Month grids shown in the popover (handy for range). */
14311
+ months = input(1, /* @ts-ignore */
14312
+ ...(ngDevMode ? [{ debugName: "months" }] : /* istanbul ignore next */ []));
13406
14313
  placeholder = input('Pick a date', /* @ts-ignore */
13407
14314
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
13408
14315
  minDate = input('', /* @ts-ignore */
13409
14316
  ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
13410
14317
  maxDate = input('', /* @ts-ignore */
13411
14318
  ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
14319
+ weekStart = input(0, /* @ts-ignore */
14320
+ ...(ngDevMode ? [{ debugName: "weekStart" }] : /* istanbul ignore next */ []));
14321
+ disabledDates = input([], /* @ts-ignore */
14322
+ ...(ngDevMode ? [{ debugName: "disabledDates" }] : /* istanbul ignore next */ []));
14323
+ disableWeekends = input(false, /* @ts-ignore */
14324
+ ...(ngDevMode ? [{ debugName: "disableWeekends" }] : /* istanbul ignore next */ []));
14325
+ showWeekNumbers = input(false, /* @ts-ignore */
14326
+ ...(ngDevMode ? [{ debugName: "showWeekNumbers" }] : /* istanbul ignore next */ []));
14327
+ captionLayout = input('label', /* @ts-ignore */
14328
+ ...(ngDevMode ? [{ debugName: "captionLayout" }] : /* istanbul ignore next */ []));
14329
+ hideOutsideDays = input(false, /* @ts-ignore */
14330
+ ...(ngDevMode ? [{ debugName: "hideOutsideDays" }] : /* istanbul ignore next */ []));
13412
14331
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
13413
14332
  host = inject(ElementRef);
13414
14333
  open = signal(false, /* @ts-ignore */
13415
14334
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
14335
+ hasValue = computed(() => (this.mode() === 'range' ? this.range().start : this.value()) !== '', /* @ts-ignore */
14336
+ ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
13416
14337
  display = computed(() => {
13417
- const value = this.value();
13418
- if (value === '') {
13419
- return this.placeholder();
14338
+ if (this.mode() === 'range') {
14339
+ const { start, end } = this.range();
14340
+ if (start === '') {
14341
+ return this.placeholder();
14342
+ }
14343
+ return end === '' ? `${this.fmt(start)} – …` : `${this.fmt(start)} – ${this.fmt(end)}`;
13420
14344
  }
13421
- return new Date(`${value}T00:00:00`).toLocaleDateString('en-US', {
13422
- month: 'short',
13423
- day: 'numeric',
13424
- year: 'numeric',
13425
- });
14345
+ const value = this.value();
14346
+ return value === '' ? this.placeholder() : this.fmt(value);
13426
14347
  }, /* @ts-ignore */
13427
14348
  ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
13428
14349
  computedClass = computed(() => cn('relative inline-block', this.userClass()), /* @ts-ignore */
@@ -13431,13 +14352,26 @@ class BuiDatePicker {
13431
14352
  this.value.set(iso);
13432
14353
  this.open.set(false);
13433
14354
  }
14355
+ onRange(range) {
14356
+ this.range.set(range);
14357
+ if (range.start !== '' && range.end !== '') {
14358
+ this.open.set(false);
14359
+ }
14360
+ }
14361
+ fmt(iso) {
14362
+ return new Date(`${iso}T00:00:00`).toLocaleDateString('en-US', {
14363
+ month: 'short',
14364
+ day: 'numeric',
14365
+ year: 'numeric',
14366
+ });
14367
+ }
13434
14368
  onDocumentClick(event) {
13435
14369
  if (this.open() && !this.host.nativeElement.contains(event.target)) {
13436
14370
  this.open.set(false);
13437
14371
  }
13438
14372
  }
13439
14373
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiDatePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
13440
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiDatePicker, isStandalone: true, selector: "bui-date-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "date-picker" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
14374
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiDatePicker, isStandalone: true, selector: "bui-date-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, months: { classPropertyName: "months", publicName: "months", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, weekStart: { classPropertyName: "weekStart", publicName: "weekStart", isSignal: true, isRequired: false, transformFunction: null }, disabledDates: { classPropertyName: "disabledDates", publicName: "disabledDates", isSignal: true, isRequired: false, transformFunction: null }, disableWeekends: { classPropertyName: "disableWeekends", publicName: "disableWeekends", isSignal: true, isRequired: false, transformFunction: null }, showWeekNumbers: { classPropertyName: "showWeekNumbers", publicName: "showWeekNumbers", isSignal: true, isRequired: false, transformFunction: null }, captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, hideOutsideDays: { classPropertyName: "hideOutsideDays", publicName: "hideOutsideDays", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", range: "rangeChange" }, host: { attributes: { "data-slot": "date-picker" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
13441
14375
  <button
13442
14376
  type="button"
13443
14377
  class="flex h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
@@ -13457,19 +14391,29 @@ class BuiDatePicker {
13457
14391
  <path d="M8 2v4M16 2v4M3 10h18" />
13458
14392
  <rect width="18" height="18" x="3" y="4" rx="2" />
13459
14393
  </svg>
13460
- <span [class]="value() ? '' : 'text-muted-foreground'">{{ display() }}</span>
14394
+ <span [class]="hasValue() ? '' : 'text-muted-foreground'">{{ display() }}</span>
13461
14395
  </button>
13462
14396
  @if (open()) {
13463
14397
  <div class="absolute z-50 mt-1 rounded-lg border bg-popover shadow-md">
13464
14398
  <bui-calendar
14399
+ [mode]="mode()"
14400
+ [months]="months()"
13465
14401
  [value]="value()"
14402
+ [range]="range()"
13466
14403
  [minDate]="minDate()"
13467
14404
  [maxDate]="maxDate()"
14405
+ [weekStart]="weekStart()"
14406
+ [disabledDates]="disabledDates()"
14407
+ [disableWeekends]="disableWeekends()"
14408
+ [showWeekNumbers]="showWeekNumbers()"
14409
+ [captionLayout]="captionLayout()"
14410
+ [hideOutsideDays]="hideOutsideDays()"
13468
14411
  (valueChange)="onPick($event)"
14412
+ (rangeChange)="onRange($event)"
13469
14413
  />
13470
14414
  </div>
13471
14415
  }
13472
- `, isInline: true, dependencies: [{ kind: "component", type: BuiCalendar, selector: "bui-calendar", inputs: ["value", "weekStart", "minDate", "maxDate", "class"], outputs: ["valueChange"] }] });
14416
+ `, isInline: true, dependencies: [{ kind: "component", type: BuiCalendar, selector: "bui-calendar", inputs: ["value", "mode", "range", "values", "months", "weekStart", "minDate", "maxDate", "disabledDates", "disableWeekends", "showWeekNumbers", "captionLayout", "hideOutsideDays", "class"], outputs: ["valueChange", "rangeChange", "valuesChange"] }] });
13473
14417
  }
13474
14418
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiDatePicker, decorators: [{
13475
14419
  type: Component,
@@ -13501,35 +14445,69 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13501
14445
  <path d="M8 2v4M16 2v4M3 10h18" />
13502
14446
  <rect width="18" height="18" x="3" y="4" rx="2" />
13503
14447
  </svg>
13504
- <span [class]="value() ? '' : 'text-muted-foreground'">{{ display() }}</span>
14448
+ <span [class]="hasValue() ? '' : 'text-muted-foreground'">{{ display() }}</span>
13505
14449
  </button>
13506
14450
  @if (open()) {
13507
14451
  <div class="absolute z-50 mt-1 rounded-lg border bg-popover shadow-md">
13508
14452
  <bui-calendar
14453
+ [mode]="mode()"
14454
+ [months]="months()"
13509
14455
  [value]="value()"
14456
+ [range]="range()"
13510
14457
  [minDate]="minDate()"
13511
14458
  [maxDate]="maxDate()"
14459
+ [weekStart]="weekStart()"
14460
+ [disabledDates]="disabledDates()"
14461
+ [disableWeekends]="disableWeekends()"
14462
+ [showWeekNumbers]="showWeekNumbers()"
14463
+ [captionLayout]="captionLayout()"
14464
+ [hideOutsideDays]="hideOutsideDays()"
13512
14465
  (valueChange)="onPick($event)"
14466
+ (rangeChange)="onRange($event)"
13513
14467
  />
13514
14468
  </div>
13515
14469
  }
13516
14470
  `,
13517
14471
  }]
13518
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
14472
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }, { type: i0.Output, args: ["rangeChange"] }], months: [{ type: i0.Input, args: [{ isSignal: true, alias: "months", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], weekStart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weekStart", required: false }] }], disabledDates: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabledDates", required: false }] }], disableWeekends: [{ type: i0.Input, args: [{ isSignal: true, alias: "disableWeekends", required: false }] }], showWeekNumbers: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWeekNumbers", required: false }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], hideOutsideDays: [{ type: i0.Input, args: [{ isSignal: true, alias: "hideOutsideDays", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
13519
14473
 
13520
14474
  /** A slide carousel. Project slides as direct children; arrows + dots navigate. SSR-safe. */
13521
14475
  class BuiCarousel {
13522
14476
  index = model(0, /* @ts-ignore */
13523
14477
  ...(ngDevMode ? [{ debugName: "index" }] : /* istanbul ignore next */ []));
14478
+ orientation = input('horizontal', /* @ts-ignore */
14479
+ ...(ngDevMode ? [{ debugName: "orientation" }] : /* istanbul ignore next */ []));
14480
+ /** Slides visible at once (e.g. 2 or 3 for a multi-item carousel). */
14481
+ perView = input(1, /* @ts-ignore */
14482
+ ...(ngDevMode ? [{ debugName: "perView" }] : /* istanbul ignore next */ []));
13524
14483
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
13525
14484
  track = viewChild('track', /* @ts-ignore */
13526
14485
  ...(ngDevMode ? [{ debugName: "track" }] : /* istanbul ignore next */ []));
13527
14486
  count = signal(0, /* @ts-ignore */
13528
14487
  ...(ngDevMode ? [{ debugName: "count" }] : /* istanbul ignore next */ []));
13529
- dots = computed(() => Array.from({ length: this.count() }, (_, index) => index), /* @ts-ignore */
14488
+ vertical = computed(() => this.orientation() === 'vertical', /* @ts-ignore */
14489
+ ...(ngDevMode ? [{ debugName: "vertical" }] : /* istanbul ignore next */ []));
14490
+ maxIndex = computed(() => Math.max(0, this.count() - this.perView()), /* @ts-ignore */
14491
+ ...(ngDevMode ? [{ debugName: "maxIndex" }] : /* istanbul ignore next */ []));
14492
+ dots = computed(() => Array.from({ length: this.maxIndex() + 1 }, (_, index) => index), /* @ts-ignore */
13530
14493
  ...(ngDevMode ? [{ debugName: "dots" }] : /* istanbul ignore next */ []));
13531
14494
  computedClass = computed(() => cn('relative block', this.userClass()), /* @ts-ignore */
13532
14495
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
14496
+ viewportClass = computed(() => cn('overflow-hidden rounded-lg', this.vertical() && 'h-full'), /* @ts-ignore */
14497
+ ...(ngDevMode ? [{ debugName: "viewportClass" }] : /* istanbul ignore next */ []));
14498
+ trackClass = computed(() => cn('flex transition-transform duration-300 [&>*]:shrink-0 [&>*]:grow-0', this.vertical()
14499
+ ? 'h-full flex-col [&>*]:h-[calc(100%/var(--bui-per,1))] [&>*]:w-full'
14500
+ : '[&>*]:w-[calc(100%/var(--bui-per,1))]'), /* @ts-ignore */
14501
+ ...(ngDevMode ? [{ debugName: "trackClass" }] : /* istanbul ignore next */ []));
14502
+ trackTransform = computed(() => {
14503
+ const offset = this.index() * (100 / this.perView());
14504
+ return this.vertical() ? `translateY(-${offset}%)` : `translateX(-${offset}%)`;
14505
+ }, /* @ts-ignore */
14506
+ ...(ngDevMode ? [{ debugName: "trackTransform" }] : /* istanbul ignore next */ []));
14507
+ prevClass = computed(() => cn('absolute inline-flex size-8 items-center justify-center rounded-full border bg-background shadow-sm disabled:opacity-40', this.vertical() ? 'top-2 left-1/2 -translate-x-1/2' : 'start-2 top-1/2 -translate-y-1/2'), /* @ts-ignore */
14508
+ ...(ngDevMode ? [{ debugName: "prevClass" }] : /* istanbul ignore next */ []));
14509
+ nextClass = computed(() => cn('absolute inline-flex size-8 items-center justify-center rounded-full border bg-background shadow-sm disabled:opacity-40', this.vertical() ? 'bottom-2 left-1/2 -translate-x-1/2' : 'end-2 top-1/2 -translate-y-1/2'), /* @ts-ignore */
14510
+ ...(ngDevMode ? [{ debugName: "nextClass" }] : /* istanbul ignore next */ []));
13533
14511
  constructor() {
13534
14512
  afterNextRender(() => {
13535
14513
  this.count.set(this.track()?.nativeElement.children.length ?? 0);
@@ -13539,22 +14517,23 @@ class BuiCarousel {
13539
14517
  this.index.set(Math.max(0, this.index() - 1));
13540
14518
  }
13541
14519
  next() {
13542
- this.index.set(Math.min(this.count() - 1, this.index() + 1));
14520
+ this.index.set(Math.min(this.maxIndex(), this.index() + 1));
13543
14521
  }
13544
14522
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiCarousel, deps: [], target: i0.ɵɵFactoryTarget.Component });
13545
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCarousel, isStandalone: true, selector: "bui-carousel", inputs: { index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { index: "indexChange" }, host: { attributes: { "data-slot": "carousel", "role": "region", "aria-roledescription": "carousel" }, properties: { "class": "computedClass()" } }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: `
13546
- <div class="overflow-hidden rounded-lg">
14523
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCarousel, isStandalone: true, selector: "bui-carousel", inputs: { index: { classPropertyName: "index", publicName: "index", isSignal: true, isRequired: false, transformFunction: null }, orientation: { classPropertyName: "orientation", publicName: "orientation", isSignal: true, isRequired: false, transformFunction: null }, perView: { classPropertyName: "perView", publicName: "perView", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { index: "indexChange" }, host: { attributes: { "data-slot": "carousel", "role": "region", "aria-roledescription": "carousel" }, properties: { "class": "computedClass()" } }, viewQueries: [{ propertyName: "track", first: true, predicate: ["track"], descendants: true, isSignal: true }], ngImport: i0, template: `
14524
+ <div [class]="viewportClass()">
13547
14525
  <div
13548
14526
  #track
13549
- class="flex transition-transform duration-300 [&>*]:w-full [&>*]:shrink-0 [&>*]:grow-0"
13550
- [style.transform]="'translateX(-' + index() * 100 + '%)'"
14527
+ [class]="trackClass()"
14528
+ [style.--bui-per]="perView()"
14529
+ [style.transform]="trackTransform()"
13551
14530
  >
13552
14531
  <ng-content />
13553
14532
  </div>
13554
14533
  </div>
13555
14534
  <button
13556
14535
  type="button"
13557
- class="absolute start-2 top-1/2 inline-flex size-8 -translate-y-1/2 items-center justify-center rounded-full border bg-background shadow-sm disabled:opacity-40"
14536
+ [class]="prevClass()"
13558
14537
  [disabled]="index() === 0"
13559
14538
  aria-label="Previous slide"
13560
14539
  (click)="prev()"
@@ -13569,13 +14548,13 @@ class BuiCarousel {
13569
14548
  stroke-linejoin="round"
13570
14549
  aria-hidden="true"
13571
14550
  >
13572
- <path d="m15 18-6-6 6-6" />
14551
+ <path [attr.d]="vertical() ? 'm18 15-6-6-6 6' : 'm15 18-6-6 6-6'" />
13573
14552
  </svg>
13574
14553
  </button>
13575
14554
  <button
13576
14555
  type="button"
13577
- class="absolute end-2 top-1/2 inline-flex size-8 -translate-y-1/2 items-center justify-center rounded-full border bg-background shadow-sm disabled:opacity-40"
13578
- [disabled]="index() >= count() - 1"
14556
+ [class]="nextClass()"
14557
+ [disabled]="index() >= maxIndex()"
13579
14558
  aria-label="Next slide"
13580
14559
  (click)="next()"
13581
14560
  >
@@ -13589,10 +14568,10 @@ class BuiCarousel {
13589
14568
  stroke-linejoin="round"
13590
14569
  aria-hidden="true"
13591
14570
  >
13592
- <path d="m9 18 6-6-6-6" />
14571
+ <path [attr.d]="vertical() ? 'm6 9 6 6 6-6' : 'm9 18 6-6-6-6'" />
13593
14572
  </svg>
13594
14573
  </button>
13595
- @if (count() > 1) {
14574
+ @if (maxIndex() > 0) {
13596
14575
  <div class="mt-3 flex justify-center gap-1.5">
13597
14576
  @for (dot of dots(); track dot) {
13598
14577
  <button
@@ -13619,18 +14598,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13619
14598
  '[class]': 'computedClass()',
13620
14599
  },
13621
14600
  template: `
13622
- <div class="overflow-hidden rounded-lg">
14601
+ <div [class]="viewportClass()">
13623
14602
  <div
13624
14603
  #track
13625
- class="flex transition-transform duration-300 [&>*]:w-full [&>*]:shrink-0 [&>*]:grow-0"
13626
- [style.transform]="'translateX(-' + index() * 100 + '%)'"
14604
+ [class]="trackClass()"
14605
+ [style.--bui-per]="perView()"
14606
+ [style.transform]="trackTransform()"
13627
14607
  >
13628
14608
  <ng-content />
13629
14609
  </div>
13630
14610
  </div>
13631
14611
  <button
13632
14612
  type="button"
13633
- class="absolute start-2 top-1/2 inline-flex size-8 -translate-y-1/2 items-center justify-center rounded-full border bg-background shadow-sm disabled:opacity-40"
14613
+ [class]="prevClass()"
13634
14614
  [disabled]="index() === 0"
13635
14615
  aria-label="Previous slide"
13636
14616
  (click)="prev()"
@@ -13645,13 +14625,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13645
14625
  stroke-linejoin="round"
13646
14626
  aria-hidden="true"
13647
14627
  >
13648
- <path d="m15 18-6-6 6-6" />
14628
+ <path [attr.d]="vertical() ? 'm18 15-6-6-6 6' : 'm15 18-6-6 6-6'" />
13649
14629
  </svg>
13650
14630
  </button>
13651
14631
  <button
13652
14632
  type="button"
13653
- class="absolute end-2 top-1/2 inline-flex size-8 -translate-y-1/2 items-center justify-center rounded-full border bg-background shadow-sm disabled:opacity-40"
13654
- [disabled]="index() >= count() - 1"
14633
+ [class]="nextClass()"
14634
+ [disabled]="index() >= maxIndex()"
13655
14635
  aria-label="Next slide"
13656
14636
  (click)="next()"
13657
14637
  >
@@ -13665,10 +14645,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13665
14645
  stroke-linejoin="round"
13666
14646
  aria-hidden="true"
13667
14647
  >
13668
- <path d="m9 18 6-6-6-6" />
14648
+ <path [attr.d]="vertical() ? 'm6 9 6 6 6-6' : 'm9 18 6-6-6-6'" />
13669
14649
  </svg>
13670
14650
  </button>
13671
- @if (count() > 1) {
14651
+ @if (maxIndex() > 0) {
13672
14652
  <div class="mt-3 flex justify-center gap-1.5">
13673
14653
  @for (dot of dots(); track dot) {
13674
14654
  <button
@@ -13684,7 +14664,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13684
14664
  }
13685
14665
  `,
13686
14666
  }]
13687
- }], ctorParameters: () => [], propDecorators: { index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }, { type: i0.Output, args: ["indexChange"] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
14667
+ }], ctorParameters: () => [], propDecorators: { index: [{ type: i0.Input, args: [{ isSignal: true, alias: "index", required: false }] }, { type: i0.Output, args: ["indexChange"] }], orientation: [{ type: i0.Input, args: [{ isSignal: true, alias: "orientation", required: false }] }], perView: [{ type: i0.Input, args: [{ isSignal: true, alias: "perView", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], track: [{ type: i0.ViewChild, args: ['track', { isSignal: true }] }] } });
13688
14668
 
13689
14669
  /** A command palette: a filterable, keyboard-navigable list of grouped actions. */
13690
14670
  class BuiCommand {
@@ -13692,8 +14672,16 @@ class BuiCommand {
13692
14672
  ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
13693
14673
  placeholder = input('Type a command or search…', /* @ts-ignore */
13694
14674
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
14675
+ /** Render a checkmark column; activating an item toggles its checked state. */
14676
+ checkable = input(false, /* @ts-ignore */
14677
+ ...(ngDevMode ? [{ debugName: "checkable" }] : /* istanbul ignore next */ []));
13695
14678
  selected = output();
13696
14679
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
14680
+ checkedValues = signal(new Set(), /* @ts-ignore */
14681
+ ...(ngDevMode ? [{ debugName: "checkedValues" }] : /* istanbul ignore next */ []));
14682
+ isChecked(item) {
14683
+ return this.checkedValues().has(item.value ?? item.label);
14684
+ }
13697
14685
  listId = inject(_IdGenerator).getId('bui-command-');
13698
14686
  query = signal('', /* @ts-ignore */
13699
14687
  ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
@@ -13752,10 +14740,21 @@ class BuiCommand {
13752
14740
  }
13753
14741
  }
13754
14742
  choose(item) {
13755
- this.selected.emit(item);
14743
+ if (this.checkable()) {
14744
+ const key = item.value ?? item.label;
14745
+ const next = new Set(this.checkedValues());
14746
+ if (next.has(key)) {
14747
+ next.delete(key);
14748
+ }
14749
+ else {
14750
+ next.add(key);
14751
+ }
14752
+ this.checkedValues.set(next);
14753
+ }
14754
+ this.selected.emit(item);
13756
14755
  }
13757
14756
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiCommand, deps: [], target: i0.ɵɵFactoryTarget.Component });
13758
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCommand, isStandalone: true, selector: "bui-command", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, host: { attributes: { "data-slot": "command" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
14757
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiCommand, isStandalone: true, selector: "bui-command", inputs: { groups: { classPropertyName: "groups", publicName: "groups", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, checkable: { classPropertyName: "checkable", publicName: "checkable", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { selected: "selected" }, host: { attributes: { "data-slot": "command" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
13759
14758
  <div class="flex items-center gap-2 border-b px-3">
13760
14759
  <svg
13761
14760
  class="size-4 shrink-0 text-muted-foreground"
@@ -13799,6 +14798,24 @@ class BuiCommand {
13799
14798
  (click)="choose(row.item!)"
13800
14799
  (mouseenter)="active.set(row.itemIndex)"
13801
14800
  >
14801
+ @if (checkable()) {
14802
+ <span class="flex w-4 shrink-0 justify-center">
14803
+ @if (isChecked(row.item!)) {
14804
+ <svg
14805
+ class="size-4"
14806
+ viewBox="0 0 24 24"
14807
+ fill="none"
14808
+ stroke="currentColor"
14809
+ stroke-width="2.5"
14810
+ stroke-linecap="round"
14811
+ stroke-linejoin="round"
14812
+ aria-hidden="true"
14813
+ >
14814
+ <path d="M20 6 9 17l-5-5" />
14815
+ </svg>
14816
+ }
14817
+ </span>
14818
+ }
13802
14819
  <span>{{ row.label }}</span>
13803
14820
  @if (row.item?.shortcut) {
13804
14821
  <kbd class="ms-auto text-xs text-muted-foreground">{{ row.item?.shortcut }}</kbd>
@@ -13863,6 +14880,24 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13863
14880
  (click)="choose(row.item!)"
13864
14881
  (mouseenter)="active.set(row.itemIndex)"
13865
14882
  >
14883
+ @if (checkable()) {
14884
+ <span class="flex w-4 shrink-0 justify-center">
14885
+ @if (isChecked(row.item!)) {
14886
+ <svg
14887
+ class="size-4"
14888
+ viewBox="0 0 24 24"
14889
+ fill="none"
14890
+ stroke="currentColor"
14891
+ stroke-width="2.5"
14892
+ stroke-linecap="round"
14893
+ stroke-linejoin="round"
14894
+ aria-hidden="true"
14895
+ >
14896
+ <path d="M20 6 9 17l-5-5" />
14897
+ </svg>
14898
+ }
14899
+ </span>
14900
+ }
13866
14901
  <span>{{ row.label }}</span>
13867
14902
  @if (row.item?.shortcut) {
13868
14903
  <kbd class="ms-auto text-xs text-muted-foreground">{{ row.item?.shortcut }}</kbd>
@@ -13878,7 +14913,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13878
14913
  </ul>
13879
14914
  `,
13880
14915
  }]
13881
- }], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
14916
+ }], propDecorators: { groups: [{ type: i0.Input, args: [{ isSignal: true, alias: "groups", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], checkable: [{ type: i0.Input, args: [{ isSignal: true, alias: "checkable", required: false }] }], selected: [{ type: i0.Output, args: ["selected"] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
13882
14917
 
13883
14918
  /** A right-click context menu. Project the target; pass menu `items`. */
13884
14919
  class BuiContextMenu {
@@ -13917,6 +14952,64 @@ class BuiContextMenu {
13917
14952
  @for (item of items(); track $index) {
13918
14953
  @if (item.separator) {
13919
14954
  <div role="separator" class="-mx-1 my-1 h-px bg-border"></div>
14955
+ } @else if (item.children && item.children.length > 0) {
14956
+ <div class="group/sub relative">
14957
+ <button
14958
+ type="button"
14959
+ role="menuitem"
14960
+ aria-haspopup="menu"
14961
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent focus-visible:bg-accent"
14962
+ >
14963
+ @if (item.icon) {
14964
+ <svg
14965
+ viewBox="0 0 24 24"
14966
+ fill="none"
14967
+ stroke="currentColor"
14968
+ stroke-width="2"
14969
+ stroke-linecap="round"
14970
+ stroke-linejoin="round"
14971
+ aria-hidden="true"
14972
+ class="size-4 shrink-0"
14973
+ >
14974
+ <path [attr.d]="item.icon" />
14975
+ </svg>
14976
+ }
14977
+ <span>{{ item.label }}</span>
14978
+ <svg
14979
+ viewBox="0 0 24 24"
14980
+ fill="none"
14981
+ stroke="currentColor"
14982
+ stroke-width="2"
14983
+ stroke-linecap="round"
14984
+ stroke-linejoin="round"
14985
+ aria-hidden="true"
14986
+ class="ms-auto size-4 opacity-60"
14987
+ >
14988
+ <path d="m9 18 6-6-6-6" />
14989
+ </svg>
14990
+ </button>
14991
+ <div
14992
+ role="menu"
14993
+ class="absolute top-0 left-full z-50 ml-1 hidden min-w-44 rounded-md border bg-popover p-1 text-popover-foreground shadow-md group-hover/sub:block"
14994
+ >
14995
+ @for (child of item.children; track $index) {
14996
+ <button
14997
+ type="button"
14998
+ role="menuitem"
14999
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent focus-visible:bg-accent"
15000
+ [class.text-destructive]="child.danger"
15001
+ (click)="select(child)"
15002
+ >
15003
+ <span>{{ child.label }}</span>
15004
+ @if (child.shortcut) {
15005
+ <span class="ms-auto text-xs text-muted-foreground">{{
15006
+ child.shortcut
15007
+ }}</span>
15008
+ }
15009
+ </button>
15010
+ }
15011
+ </div>
15012
+ </div>
13920
15013
  } @else {
13921
15014
  <button
13922
15015
  type="button"
@@ -13925,6 +15018,20 @@ class BuiContextMenu {
13925
15018
  [class.text-destructive]="item.danger"
13926
15019
  (click)="select(item)"
13927
15020
  >
15021
+ @if (item.icon) {
15022
+ <svg
15023
+ viewBox="0 0 24 24"
15024
+ fill="none"
15025
+ stroke="currentColor"
15026
+ stroke-width="2"
15027
+ stroke-linecap="round"
15028
+ stroke-linejoin="round"
15029
+ aria-hidden="true"
15030
+ class="size-4 shrink-0"
15031
+ >
15032
+ <path [attr.d]="item.icon" />
15033
+ </svg>
15034
+ }
13928
15035
  <span>{{ item.label }}</span>
13929
15036
  @if (item.shortcut) {
13930
15037
  <span class="ms-auto text-xs text-muted-foreground">{{ item.shortcut }}</span>
@@ -13959,6 +15066,64 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13959
15066
  @for (item of items(); track $index) {
13960
15067
  @if (item.separator) {
13961
15068
  <div role="separator" class="-mx-1 my-1 h-px bg-border"></div>
15069
+ } @else if (item.children && item.children.length > 0) {
15070
+ <div class="group/sub relative">
15071
+ <button
15072
+ type="button"
15073
+ role="menuitem"
15074
+ aria-haspopup="menu"
15075
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent focus-visible:bg-accent"
15076
+ >
15077
+ @if (item.icon) {
15078
+ <svg
15079
+ viewBox="0 0 24 24"
15080
+ fill="none"
15081
+ stroke="currentColor"
15082
+ stroke-width="2"
15083
+ stroke-linecap="round"
15084
+ stroke-linejoin="round"
15085
+ aria-hidden="true"
15086
+ class="size-4 shrink-0"
15087
+ >
15088
+ <path [attr.d]="item.icon" />
15089
+ </svg>
15090
+ }
15091
+ <span>{{ item.label }}</span>
15092
+ <svg
15093
+ viewBox="0 0 24 24"
15094
+ fill="none"
15095
+ stroke="currentColor"
15096
+ stroke-width="2"
15097
+ stroke-linecap="round"
15098
+ stroke-linejoin="round"
15099
+ aria-hidden="true"
15100
+ class="ms-auto size-4 opacity-60"
15101
+ >
15102
+ <path d="m9 18 6-6-6-6" />
15103
+ </svg>
15104
+ </button>
15105
+ <div
15106
+ role="menu"
15107
+ class="absolute top-0 left-full z-50 ml-1 hidden min-w-44 rounded-md border bg-popover p-1 text-popover-foreground shadow-md group-hover/sub:block"
15108
+ >
15109
+ @for (child of item.children; track $index) {
15110
+ <button
15111
+ type="button"
15112
+ role="menuitem"
15113
+ class="flex w-full items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none hover:bg-accent focus-visible:bg-accent"
15114
+ [class.text-destructive]="child.danger"
15115
+ (click)="select(child)"
15116
+ >
15117
+ <span>{{ child.label }}</span>
15118
+ @if (child.shortcut) {
15119
+ <span class="ms-auto text-xs text-muted-foreground">{{
15120
+ child.shortcut
15121
+ }}</span>
15122
+ }
15123
+ </button>
15124
+ }
15125
+ </div>
15126
+ </div>
13962
15127
  } @else {
13963
15128
  <button
13964
15129
  type="button"
@@ -13967,6 +15132,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
13967
15132
  [class.text-destructive]="item.danger"
13968
15133
  (click)="select(item)"
13969
15134
  >
15135
+ @if (item.icon) {
15136
+ <svg
15137
+ viewBox="0 0 24 24"
15138
+ fill="none"
15139
+ stroke="currentColor"
15140
+ stroke-width="2"
15141
+ stroke-linecap="round"
15142
+ stroke-linejoin="round"
15143
+ aria-hidden="true"
15144
+ class="size-4 shrink-0"
15145
+ >
15146
+ <path [attr.d]="item.icon" />
15147
+ </svg>
15148
+ }
13970
15149
  <span>{{ item.label }}</span>
13971
15150
  @if (item.shortcut) {
13972
15151
  <span class="ms-auto text-xs text-muted-foreground">{{ item.shortcut }}</span>
@@ -14472,28 +15651,53 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
14472
15651
  class BuiDatetimePicker {
14473
15652
  value = model('', /* @ts-ignore */
14474
15653
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
15654
+ /** `single` (default) or `range`. */
15655
+ mode = input('single', /* @ts-ignore */
15656
+ ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
15657
+ /** Selected range when mode="range" — start/end as `YYYY-MM-DDTHH:mm`, sharing one time. */
15658
+ range = model({ start: '', end: '' }, /* @ts-ignore */
15659
+ ...(ngDevMode ? [{ debugName: "range" }] : /* istanbul ignore next */ []));
15660
+ /** Month grids shown in the popover (handy for range). */
15661
+ months = input(1, /* @ts-ignore */
15662
+ ...(ngDevMode ? [{ debugName: "months" }] : /* istanbul ignore next */ []));
14475
15663
  placeholder = input('Pick date & time', /* @ts-ignore */
14476
15664
  ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
15665
+ minDate = input('', /* @ts-ignore */
15666
+ ...(ngDevMode ? [{ debugName: "minDate" }] : /* istanbul ignore next */ []));
15667
+ maxDate = input('', /* @ts-ignore */
15668
+ ...(ngDevMode ? [{ debugName: "maxDate" }] : /* istanbul ignore next */ []));
15669
+ captionLayout = input('label', /* @ts-ignore */
15670
+ ...(ngDevMode ? [{ debugName: "captionLayout" }] : /* istanbul ignore next */ []));
15671
+ seconds = input(false, /* @ts-ignore */
15672
+ ...(ngDevMode ? [{ debugName: "seconds" }] : /* istanbul ignore next */ []));
14477
15673
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
14478
15674
  host = inject(ElementRef);
14479
15675
  open = signal(false, /* @ts-ignore */
14480
15676
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
14481
15677
  datePart = computed(() => this.value().split('T', 1).at(0) ?? '', /* @ts-ignore */
14482
15678
  ...(ngDevMode ? [{ debugName: "datePart" }] : /* istanbul ignore next */ []));
14483
- timePart = computed(() => this.value().split('T', 2).at(1) ?? '12:00', /* @ts-ignore */
15679
+ timePart = computed(() => {
15680
+ const source = this.mode() === 'range' ? this.range().start : this.value();
15681
+ return source.split('T', 2).at(1) ?? '12:00';
15682
+ }, /* @ts-ignore */
14484
15683
  ...(ngDevMode ? [{ debugName: "timePart" }] : /* istanbul ignore next */ []));
15684
+ calRange = computed(() => {
15685
+ const { start, end } = this.range();
15686
+ return { start: start.split('T', 1).at(0) ?? '', end: end.split('T', 1).at(0) ?? '' };
15687
+ }, /* @ts-ignore */
15688
+ ...(ngDevMode ? [{ debugName: "calRange" }] : /* istanbul ignore next */ []));
15689
+ hasValue = computed(() => (this.mode() === 'range' ? this.range().start : this.value()) !== '', /* @ts-ignore */
15690
+ ...(ngDevMode ? [{ debugName: "hasValue" }] : /* istanbul ignore next */ []));
14485
15691
  display = computed(() => {
14486
- const value = this.value();
14487
- if (value === '') {
14488
- return this.placeholder();
15692
+ if (this.mode() === 'range') {
15693
+ const { start, end } = this.range();
15694
+ if (start === '') {
15695
+ return this.placeholder();
15696
+ }
15697
+ return end === '' ? `${this.fmt(start)} – …` : `${this.fmt(start)} – ${this.fmt(end)}`;
14489
15698
  }
14490
- return new Date(value).toLocaleString('en-US', {
14491
- month: 'short',
14492
- day: 'numeric',
14493
- year: 'numeric',
14494
- hour: 'numeric',
14495
- minute: '2-digit',
14496
- });
15699
+ const value = this.value();
15700
+ return value === '' ? this.placeholder() : this.fmt(value);
14497
15701
  }, /* @ts-ignore */
14498
15702
  ...(ngDevMode ? [{ debugName: "display" }] : /* istanbul ignore next */ []));
14499
15703
  computedClass = computed(() => cn('relative inline-block', this.userClass()), /* @ts-ignore */
@@ -14501,10 +15705,33 @@ class BuiDatetimePicker {
14501
15705
  onDate(iso) {
14502
15706
  this.value.set(`${iso}T${this.timePart()}`);
14503
15707
  }
15708
+ onRange(range) {
15709
+ const time = this.timePart();
15710
+ this.range.set({
15711
+ start: range.start === '' ? '' : `${range.start}T${time}`,
15712
+ end: range.end === '' ? '' : `${range.end}T${time}`,
15713
+ });
15714
+ }
14504
15715
  onTime(time) {
15716
+ if (this.mode() === 'range') {
15717
+ const { start, end } = this.calRange();
15718
+ this.range.set({
15719
+ start: start === '' ? '' : `${start}T${time}`,
15720
+ end: end === '' ? '' : `${end}T${time}`,
15721
+ });
15722
+ return;
15723
+ }
14505
15724
  const date = this.datePart();
14506
15725
  this.value.set(`${date === '' ? this.todayIso() : date}T${time}`);
14507
15726
  }
15727
+ fmt(value) {
15728
+ return new Date(value).toLocaleString('en-US', {
15729
+ month: 'short',
15730
+ day: 'numeric',
15731
+ hour: 'numeric',
15732
+ minute: '2-digit',
15733
+ });
15734
+ }
14508
15735
  onDocumentClick(event) {
14509
15736
  if (this.open() && !this.host.nativeElement.contains(event.target)) {
14510
15737
  this.open.set(false);
@@ -14515,7 +15742,7 @@ class BuiDatetimePicker {
14515
15742
  return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')}`;
14516
15743
  }
14517
15744
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiDatetimePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
14518
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiDatetimePicker, isStandalone: true, selector: "bui-datetime-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "datetime-picker" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
15745
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiDatetimePicker, isStandalone: true, selector: "bui-datetime-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, range: { classPropertyName: "range", publicName: "range", isSignal: true, isRequired: false, transformFunction: null }, months: { classPropertyName: "months", publicName: "months", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, minDate: { classPropertyName: "minDate", publicName: "minDate", isSignal: true, isRequired: false, transformFunction: null }, maxDate: { classPropertyName: "maxDate", publicName: "maxDate", isSignal: true, isRequired: false, transformFunction: null }, captionLayout: { classPropertyName: "captionLayout", publicName: "captionLayout", isSignal: true, isRequired: false, transformFunction: null }, seconds: { classPropertyName: "seconds", publicName: "seconds", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", range: "rangeChange" }, host: { attributes: { "data-slot": "datetime-picker" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
14519
15746
  <button
14520
15747
  type="button"
14521
15748
  class="flex h-9 w-full items-center gap-2 rounded-md border border-input bg-transparent px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
@@ -14535,18 +15762,40 @@ class BuiDatetimePicker {
14535
15762
  <path d="M8 2v4M16 2v4M3 10h18" />
14536
15763
  <rect width="18" height="18" x="3" y="4" rx="2" />
14537
15764
  </svg>
14538
- <span [class]="value() ? '' : 'text-muted-foreground'">{{ display() }}</span>
15765
+ <span [class]="hasValue() ? '' : 'text-muted-foreground'">{{ display() }}</span>
14539
15766
  </button>
14540
15767
  @if (open()) {
14541
15768
  <div class="absolute z-50 mt-1 rounded-lg border bg-popover shadow-md">
14542
- <bui-calendar [value]="datePart()" (valueChange)="onDate($event)" />
15769
+ @if (mode() === 'range') {
15770
+ <bui-calendar
15771
+ mode="range"
15772
+ [months]="months()"
15773
+ [range]="calRange()"
15774
+ [minDate]="minDate()"
15775
+ [maxDate]="maxDate()"
15776
+ [captionLayout]="captionLayout()"
15777
+ (rangeChange)="onRange($event)"
15778
+ />
15779
+ } @else {
15780
+ <bui-calendar
15781
+ [value]="datePart()"
15782
+ [minDate]="minDate()"
15783
+ [maxDate]="maxDate()"
15784
+ [captionLayout]="captionLayout()"
15785
+ (valueChange)="onDate($event)"
15786
+ />
15787
+ }
14543
15788
  <div class="flex items-center gap-2 border-t p-2">
14544
15789
  <span class="text-xs text-muted-foreground">Time</span>
14545
- <bui-time-field [value]="timePart()" (valueChange)="onTime($event)" />
15790
+ <bui-time-field
15791
+ [value]="timePart()"
15792
+ [seconds]="seconds()"
15793
+ (valueChange)="onTime($event)"
15794
+ />
14546
15795
  </div>
14547
15796
  </div>
14548
15797
  }
14549
- `, isInline: true, dependencies: [{ kind: "component", type: BuiCalendar, selector: "bui-calendar", inputs: ["value", "weekStart", "minDate", "maxDate", "class"], outputs: ["valueChange"] }, { kind: "component", type: BuiTimeField, selector: "bui-time-field", inputs: ["value", "name", "id", "min", "max", "seconds", "disabled", "ariaLabel", "class"], outputs: ["valueChange"] }] });
15798
+ `, isInline: true, dependencies: [{ kind: "component", type: BuiCalendar, selector: "bui-calendar", inputs: ["value", "mode", "range", "values", "months", "weekStart", "minDate", "maxDate", "disabledDates", "disableWeekends", "showWeekNumbers", "captionLayout", "hideOutsideDays", "class"], outputs: ["valueChange", "rangeChange", "valuesChange"] }, { kind: "component", type: BuiTimeField, selector: "bui-time-field", inputs: ["value", "name", "id", "min", "max", "seconds", "disabled", "mode", "minuteStep", "ariaLabel", "class"], outputs: ["valueChange"] }] });
14550
15799
  }
14551
15800
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiDatetimePicker, decorators: [{
14552
15801
  type: Component,
@@ -14578,25 +15827,57 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
14578
15827
  <path d="M8 2v4M16 2v4M3 10h18" />
14579
15828
  <rect width="18" height="18" x="3" y="4" rx="2" />
14580
15829
  </svg>
14581
- <span [class]="value() ? '' : 'text-muted-foreground'">{{ display() }}</span>
15830
+ <span [class]="hasValue() ? '' : 'text-muted-foreground'">{{ display() }}</span>
14582
15831
  </button>
14583
15832
  @if (open()) {
14584
15833
  <div class="absolute z-50 mt-1 rounded-lg border bg-popover shadow-md">
14585
- <bui-calendar [value]="datePart()" (valueChange)="onDate($event)" />
15834
+ @if (mode() === 'range') {
15835
+ <bui-calendar
15836
+ mode="range"
15837
+ [months]="months()"
15838
+ [range]="calRange()"
15839
+ [minDate]="minDate()"
15840
+ [maxDate]="maxDate()"
15841
+ [captionLayout]="captionLayout()"
15842
+ (rangeChange)="onRange($event)"
15843
+ />
15844
+ } @else {
15845
+ <bui-calendar
15846
+ [value]="datePart()"
15847
+ [minDate]="minDate()"
15848
+ [maxDate]="maxDate()"
15849
+ [captionLayout]="captionLayout()"
15850
+ (valueChange)="onDate($event)"
15851
+ />
15852
+ }
14586
15853
  <div class="flex items-center gap-2 border-t p-2">
14587
15854
  <span class="text-xs text-muted-foreground">Time</span>
14588
- <bui-time-field [value]="timePart()" (valueChange)="onTime($event)" />
15855
+ <bui-time-field
15856
+ [value]="timePart()"
15857
+ [seconds]="seconds()"
15858
+ (valueChange)="onTime($event)"
15859
+ />
14589
15860
  </div>
14590
15861
  </div>
14591
15862
  }
14592
15863
  `,
14593
15864
  }]
14594
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
15865
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], range: [{ type: i0.Input, args: [{ isSignal: true, alias: "range", required: false }] }, { type: i0.Output, args: ["rangeChange"] }], months: [{ type: i0.Input, args: [{ isSignal: true, alias: "months", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], minDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "minDate", required: false }] }], maxDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxDate", required: false }] }], captionLayout: [{ type: i0.Input, args: [{ isSignal: true, alias: "captionLayout", required: false }] }], seconds: [{ type: i0.Input, args: [{ isSignal: true, alias: "seconds", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
14595
15866
 
15867
+ const AC_SIZE = {
15868
+ sm: 'h-8 text-xs',
15869
+ default: 'h-9 text-sm',
15870
+ lg: 'h-11 text-base',
15871
+ };
14596
15872
  /** A free-text input with a filtered suggestion list. */
14597
15873
  class BuiAutocomplete {
14598
15874
  value = model('', /* @ts-ignore */
14599
15875
  ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
15876
+ /** Pick several values, shown as removable chips; binds `values`. */
15877
+ multiple = input(false, /* @ts-ignore */
15878
+ ...(ngDevMode ? [{ debugName: "multiple" }] : /* istanbul ignore next */ []));
15879
+ values = model([], /* @ts-ignore */
15880
+ ...(ngDevMode ? [{ debugName: "values" }] : /* istanbul ignore next */ []));
14600
15881
  options = input([], /* @ts-ignore */
14601
15882
  ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
14602
15883
  placeholder = input('Search...', /* @ts-ignore */
@@ -14607,7 +15888,16 @@ class BuiAutocomplete {
14607
15888
  ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
14608
15889
  name = input('', /* @ts-ignore */
14609
15890
  ...(ngDevMode ? [{ debugName: "name" }] : /* istanbul ignore next */ []));
15891
+ size = input('default', /* @ts-ignore */
15892
+ ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
15893
+ /** Optional leading icon, given as an SVG path `d` string. */
15894
+ icon = input('', /* @ts-ignore */
15895
+ ...(ngDevMode ? [{ debugName: "icon" }] : /* istanbul ignore next */ []));
14610
15896
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
15897
+ inputClass = computed(() => cn('w-full rounded-md border border-input bg-transparent shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50', AC_SIZE[this.size()], this.icon() ? 'pr-3 pl-9' : 'px-3'), /* @ts-ignore */
15898
+ ...(ngDevMode ? [{ debugName: "inputClass" }] : /* istanbul ignore next */ []));
15899
+ boxClass = computed(() => cn('flex min-h-9 w-full flex-wrap items-center gap-1 rounded-md border border-input bg-transparent px-2 py-1 shadow-xs focus-within:border-ring focus-within:ring-[3px] focus-within:ring-ring/50'), /* @ts-ignore */
15900
+ ...(ngDevMode ? [{ debugName: "boxClass" }] : /* istanbul ignore next */ []));
14611
15901
  host = inject(ElementRef);
14612
15902
  listId = inject(_IdGenerator).getId('bui-autocomplete-');
14613
15903
  open = signal(false, /* @ts-ignore */
@@ -14616,10 +15906,14 @@ class BuiAutocomplete {
14616
15906
  ...(ngDevMode ? [{ debugName: "active" }] : /* istanbul ignore next */ []));
14617
15907
  filtered = computed(() => {
14618
15908
  const query = this.value().trim().toLowerCase();
15909
+ const picked = this.values();
15910
+ const pool = this.multiple()
15911
+ ? this.options().filter((option) => !picked.includes(option))
15912
+ : this.options();
14619
15913
  if (query === '') {
14620
- return this.options();
15914
+ return pool;
14621
15915
  }
14622
- return this.options().filter((option) => option.toLowerCase().includes(query));
15916
+ return pool.filter((option) => option.toLowerCase().includes(query));
14623
15917
  }, /* @ts-ignore */
14624
15918
  ...(ngDevMode ? [{ debugName: "filtered" }] : /* istanbul ignore next */ []));
14625
15919
  computedClass = computed(() => cn('relative block', this.userClass()), /* @ts-ignore */
@@ -14647,37 +15941,114 @@ class BuiAutocomplete {
14647
15941
  else if (event.key === 'Escape') {
14648
15942
  this.open.set(false);
14649
15943
  }
15944
+ else if (event.key === 'Backspace' && this.multiple() && this.value() === '') {
15945
+ const picked = this.values();
15946
+ if (picked.length > 0) {
15947
+ this.values.set(picked.slice(0, -1));
15948
+ }
15949
+ }
14650
15950
  }
14651
15951
  choose(option) {
15952
+ if (this.multiple()) {
15953
+ if (!this.values().includes(option)) {
15954
+ this.values.set([...this.values(), option]);
15955
+ }
15956
+ this.value.set('');
15957
+ this.active.set(0);
15958
+ return;
15959
+ }
14652
15960
  this.value.set(option);
14653
15961
  this.open.set(false);
14654
15962
  }
15963
+ removeChip(chip) {
15964
+ this.values.set(this.values().filter((value) => value !== chip));
15965
+ }
14655
15966
  onDocumentClick(event) {
14656
15967
  if (this.open() && !this.host.nativeElement.contains(event.target)) {
14657
15968
  this.open.set(false);
14658
15969
  }
14659
15970
  }
14660
15971
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiAutocomplete, deps: [], target: i0.ɵɵFactoryTarget.Component });
14661
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiAutocomplete, isStandalone: true, selector: "bui-autocomplete", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, empty: { classPropertyName: "empty", publicName: "empty", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { attributes: { "data-slot": "autocomplete" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
14662
- <input
14663
- type="text"
14664
- role="combobox"
14665
- aria-autocomplete="list"
14666
- [attr.aria-expanded]="open()"
14667
- [attr.aria-controls]="listId"
14668
- [value]="value()"
14669
- [placeholder]="placeholder()"
14670
- [disabled]="disabled()"
14671
- [attr.name]="name() || null"
14672
- class="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
14673
- (input)="onInput($event)"
14674
- (focus)="open.set(true)"
14675
- (keydown)="onKeydown($event)"
14676
- />
15972
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiAutocomplete, isStandalone: true, selector: "bui-autocomplete", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, multiple: { classPropertyName: "multiple", publicName: "multiple", isSignal: true, isRequired: false, transformFunction: null }, values: { classPropertyName: "values", publicName: "values", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, empty: { classPropertyName: "empty", publicName: "empty", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, name: { classPropertyName: "name", publicName: "name", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", values: "valuesChange" }, host: { attributes: { "data-slot": "autocomplete" }, listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
15973
+ @if (multiple()) {
15974
+ <div [class]="boxClass()">
15975
+ @for (chip of values(); track chip) {
15976
+ <span
15977
+ class="inline-flex items-center gap-1 rounded bg-secondary px-1.5 py-0.5 text-xs text-secondary-foreground"
15978
+ >
15979
+ {{ chip }}
15980
+ <button
15981
+ type="button"
15982
+ class="text-muted-foreground hover:text-foreground"
15983
+ [attr.aria-label]="'Remove ' + chip"
15984
+ (click)="removeChip(chip); $event.stopPropagation()"
15985
+ >
15986
+ <svg
15987
+ class="size-3"
15988
+ viewBox="0 0 24 24"
15989
+ fill="none"
15990
+ stroke="currentColor"
15991
+ stroke-width="2.5"
15992
+ stroke-linecap="round"
15993
+ aria-hidden="true"
15994
+ >
15995
+ <path d="M18 6 6 18M6 6l12 12" />
15996
+ </svg>
15997
+ </button>
15998
+ </span>
15999
+ }
16000
+ <input
16001
+ type="text"
16002
+ role="combobox"
16003
+ aria-autocomplete="list"
16004
+ [attr.aria-expanded]="open()"
16005
+ [attr.aria-controls]="listId"
16006
+ [value]="value()"
16007
+ [placeholder]="values().length > 0 ? '' : placeholder()"
16008
+ [disabled]="disabled()"
16009
+ [attr.name]="name() || null"
16010
+ class="min-w-24 flex-1 bg-transparent text-sm outline-none disabled:cursor-not-allowed"
16011
+ (input)="onInput($event)"
16012
+ (focus)="open.set(true)"
16013
+ (keydown)="onKeydown($event)"
16014
+ />
16015
+ </div>
16016
+ } @else {
16017
+ @if (icon(); as icon) {
16018
+ <svg
16019
+ viewBox="0 0 24 24"
16020
+ fill="none"
16021
+ stroke="currentColor"
16022
+ stroke-width="2"
16023
+ stroke-linecap="round"
16024
+ stroke-linejoin="round"
16025
+ aria-hidden="true"
16026
+ class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground"
16027
+ >
16028
+ <path [attr.d]="icon" />
16029
+ </svg>
16030
+ }
16031
+ <input
16032
+ type="text"
16033
+ role="combobox"
16034
+ aria-autocomplete="list"
16035
+ [attr.aria-expanded]="open()"
16036
+ [attr.aria-controls]="listId"
16037
+ [value]="value()"
16038
+ [placeholder]="placeholder()"
16039
+ [disabled]="disabled()"
16040
+ [attr.name]="name() || null"
16041
+ [class]="inputClass()"
16042
+ (input)="onInput($event)"
16043
+ (focus)="open.set(true)"
16044
+ (keydown)="onKeydown($event)"
16045
+ />
16046
+ }
14677
16047
  @if (open() && filtered().length > 0) {
14678
16048
  <ul
14679
16049
  [id]="listId"
14680
16050
  role="listbox"
16051
+ [attr.aria-multiselectable]="multiple() ? true : null"
14681
16052
  class="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 shadow-md"
14682
16053
  >
14683
16054
  @for (option of filtered(); track option; let i = $index) {
@@ -14712,25 +16083,85 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
14712
16083
  '(document:click)': 'onDocumentClick($event)',
14713
16084
  },
14714
16085
  template: `
14715
- <input
14716
- type="text"
14717
- role="combobox"
14718
- aria-autocomplete="list"
14719
- [attr.aria-expanded]="open()"
14720
- [attr.aria-controls]="listId"
14721
- [value]="value()"
14722
- [placeholder]="placeholder()"
14723
- [disabled]="disabled()"
14724
- [attr.name]="name() || null"
14725
- class="h-9 w-full rounded-md border border-input bg-transparent px-3 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50"
14726
- (input)="onInput($event)"
14727
- (focus)="open.set(true)"
14728
- (keydown)="onKeydown($event)"
14729
- />
16086
+ @if (multiple()) {
16087
+ <div [class]="boxClass()">
16088
+ @for (chip of values(); track chip) {
16089
+ <span
16090
+ class="inline-flex items-center gap-1 rounded bg-secondary px-1.5 py-0.5 text-xs text-secondary-foreground"
16091
+ >
16092
+ {{ chip }}
16093
+ <button
16094
+ type="button"
16095
+ class="text-muted-foreground hover:text-foreground"
16096
+ [attr.aria-label]="'Remove ' + chip"
16097
+ (click)="removeChip(chip); $event.stopPropagation()"
16098
+ >
16099
+ <svg
16100
+ class="size-3"
16101
+ viewBox="0 0 24 24"
16102
+ fill="none"
16103
+ stroke="currentColor"
16104
+ stroke-width="2.5"
16105
+ stroke-linecap="round"
16106
+ aria-hidden="true"
16107
+ >
16108
+ <path d="M18 6 6 18M6 6l12 12" />
16109
+ </svg>
16110
+ </button>
16111
+ </span>
16112
+ }
16113
+ <input
16114
+ type="text"
16115
+ role="combobox"
16116
+ aria-autocomplete="list"
16117
+ [attr.aria-expanded]="open()"
16118
+ [attr.aria-controls]="listId"
16119
+ [value]="value()"
16120
+ [placeholder]="values().length > 0 ? '' : placeholder()"
16121
+ [disabled]="disabled()"
16122
+ [attr.name]="name() || null"
16123
+ class="min-w-24 flex-1 bg-transparent text-sm outline-none disabled:cursor-not-allowed"
16124
+ (input)="onInput($event)"
16125
+ (focus)="open.set(true)"
16126
+ (keydown)="onKeydown($event)"
16127
+ />
16128
+ </div>
16129
+ } @else {
16130
+ @if (icon(); as icon) {
16131
+ <svg
16132
+ viewBox="0 0 24 24"
16133
+ fill="none"
16134
+ stroke="currentColor"
16135
+ stroke-width="2"
16136
+ stroke-linecap="round"
16137
+ stroke-linejoin="round"
16138
+ aria-hidden="true"
16139
+ class="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground"
16140
+ >
16141
+ <path [attr.d]="icon" />
16142
+ </svg>
16143
+ }
16144
+ <input
16145
+ type="text"
16146
+ role="combobox"
16147
+ aria-autocomplete="list"
16148
+ [attr.aria-expanded]="open()"
16149
+ [attr.aria-controls]="listId"
16150
+ [value]="value()"
16151
+ [placeholder]="placeholder()"
16152
+ [disabled]="disabled()"
16153
+ [attr.name]="name() || null"
16154
+ [class]="inputClass()"
16155
+ (input)="onInput($event)"
16156
+ (focus)="open.set(true)"
16157
+ (keydown)="onKeydown($event)"
16158
+ />
16159
+ }
14730
16160
  @if (open() && filtered().length > 0) {
14731
16161
  <ul
14732
16162
  [id]="listId"
14733
16163
  role="listbox"
16164
+ [attr.aria-multiselectable]="multiple() ? true : null"
14734
16165
  class="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border bg-popover p-1 shadow-md"
14735
16166
  >
14736
16167
  @for (option of filtered(); track option; let i = $index) {
@@ -14755,7 +16186,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
14755
16186
  }
14756
16187
  `,
14757
16188
  }]
14758
- }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], empty: [{ type: i0.Input, args: [{ isSignal: true, alias: "empty", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
16189
+ }], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], multiple: [{ type: i0.Input, args: [{ isSignal: true, alias: "multiple", required: false }] }], values: [{ type: i0.Input, args: [{ isSignal: true, alias: "values", required: false }] }, { type: i0.Output, args: ["valuesChange"] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], empty: [{ type: i0.Input, args: [{ isSignal: true, alias: "empty", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], icon: [{ type: i0.Input, args: [{ isSignal: true, alias: "icon", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
14759
16190
 
14760
16191
  /** A before/after image comparison with a draggable, keyboard-operable divider. */
14761
16192
  class BuiComparisonSlider {
@@ -15191,18 +16622,20 @@ class BuiDrawer {
15191
16622
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
15192
16623
  direction = input('bottom', /* @ts-ignore */
15193
16624
  ...(ngDevMode ? [{ debugName: "direction" }] : /* istanbul ignore next */ []));
16625
+ ariaLabel = input('Drawer', /* @ts-ignore */
16626
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
15194
16627
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
15195
16628
  panelClass = computed(() => cn('fixed z-50 flex flex-col gap-3 border bg-background p-4 shadow-lg', POSITION$1[this.direction()], this.userClass()), /* @ts-ignore */
15196
16629
  ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
15197
16630
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
15198
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiDrawer, isStandalone: true, selector: "bui-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { attributes: { "data-slot": "drawer" }, listeners: { "document:keydown.escape": "open.set(false)" } }, ngImport: i0, template: `
16631
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiDrawer, isStandalone: true, selector: "bui-drawer", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, direction: { classPropertyName: "direction", publicName: "direction", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { attributes: { "data-slot": "drawer" }, listeners: { "document:keydown.escape": "open.set(false)" } }, ngImport: i0, template: `
15199
16632
  @if (open()) {
15200
16633
  <div
15201
16634
  class="fixed inset-0 z-50 bg-black/50"
15202
16635
  (click)="open.set(false)"
15203
16636
  aria-hidden="true"
15204
16637
  ></div>
15205
- <div role="dialog" aria-modal="true" [class]="panelClass()">
16638
+ <div role="dialog" aria-modal="true" [attr.aria-label]="ariaLabel()" [class]="panelClass()">
15206
16639
  <ng-content />
15207
16640
  </div>
15208
16641
  }
@@ -15220,13 +16653,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
15220
16653
  (click)="open.set(false)"
15221
16654
  aria-hidden="true"
15222
16655
  ></div>
15223
- <div role="dialog" aria-modal="true" [class]="panelClass()">
16656
+ <div role="dialog" aria-modal="true" [attr.aria-label]="ariaLabel()" [class]="panelClass()">
15224
16657
  <ng-content />
15225
16658
  </div>
15226
16659
  }
15227
16660
  `,
15228
16661
  }]
15229
- }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
16662
+ }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], direction: [{ type: i0.Input, args: [{ isSignal: true, alias: "direction", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
15230
16663
 
15231
16664
  const PLACEHOLDERS = new Set(['9', 'a', '*']);
15232
16665
  function isTokenChar(char, token) {
@@ -15332,18 +16765,20 @@ class BuiSheet {
15332
16765
  ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
15333
16766
  side = input('right', /* @ts-ignore */
15334
16767
  ...(ngDevMode ? [{ debugName: "side" }] : /* istanbul ignore next */ []));
16768
+ ariaLabel = input('Sheet', /* @ts-ignore */
16769
+ ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
15335
16770
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
15336
16771
  panelClass = computed(() => cn('fixed z-50 flex flex-col gap-4 border bg-background p-6 shadow-lg', SIDE[this.side()], this.userClass()), /* @ts-ignore */
15337
16772
  ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
15338
16773
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiSheet, deps: [], target: i0.ɵɵFactoryTarget.Component });
15339
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiSheet, isStandalone: true, selector: "bui-sheet", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { attributes: { "data-slot": "sheet" }, listeners: { "document:keydown.escape": "open.set(false)" } }, ngImport: i0, template: `
16774
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiSheet, isStandalone: true, selector: "bui-sheet", inputs: { open: { classPropertyName: "open", publicName: "open", isSignal: true, isRequired: false, transformFunction: null }, side: { classPropertyName: "side", publicName: "side", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { open: "openChange" }, host: { attributes: { "data-slot": "sheet" }, listeners: { "document:keydown.escape": "open.set(false)" } }, ngImport: i0, template: `
15340
16775
  @if (open()) {
15341
16776
  <div
15342
16777
  class="fixed inset-0 z-50 bg-black/50"
15343
16778
  (click)="open.set(false)"
15344
16779
  aria-hidden="true"
15345
16780
  ></div>
15346
- <div role="dialog" aria-modal="true" [class]="panelClass()">
16781
+ <div role="dialog" aria-modal="true" [attr.aria-label]="ariaLabel()" [class]="panelClass()">
15347
16782
  <ng-content />
15348
16783
  </div>
15349
16784
  }
@@ -15361,13 +16796,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
15361
16796
  (click)="open.set(false)"
15362
16797
  aria-hidden="true"
15363
16798
  ></div>
15364
- <div role="dialog" aria-modal="true" [class]="panelClass()">
16799
+ <div role="dialog" aria-modal="true" [attr.aria-label]="ariaLabel()" [class]="panelClass()">
15365
16800
  <ng-content />
15366
16801
  </div>
15367
16802
  }
15368
16803
  `,
15369
16804
  }]
15370
- }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
16805
+ }], propDecorators: { open: [{ type: i0.Input, args: [{ isSignal: true, alias: "open", required: false }] }, { type: i0.Output, args: ["openChange"] }], side: [{ type: i0.Input, args: [{ isSignal: true, alias: "side", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
15371
16806
 
15372
16807
  /** Imperative toast store. Inject it and call `show(...)`; render a `<bui-sonner />` once. */
15373
16808
  class BuiToaster {
@@ -15424,6 +16859,10 @@ class BuiSonner {
15424
16859
  toneClass(tone) {
15425
16860
  return TONE[tone ?? 'default'];
15426
16861
  }
16862
+ runAction(toast) {
16863
+ toast.action?.onClick?.();
16864
+ this.toaster.dismiss(toast.id);
16865
+ }
15427
16866
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiSonner, deps: [], target: i0.ɵɵFactoryTarget.Component });
15428
16867
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiSonner, isStandalone: true, selector: "bui-sonner", inputs: { position: { classPropertyName: "position", publicName: "position", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "sonner" } }, ngImport: i0, template: `
15429
16868
  <div role="region" aria-label="Notifications" [class]="regionClass()">
@@ -15439,6 +16878,15 @@ class BuiSonner {
15439
16878
  @if (toast.description) {
15440
16879
  <p class="text-sm opacity-90">{{ toast.description }}</p>
15441
16880
  }
16881
+ @if (toast.action) {
16882
+ <button
16883
+ type="button"
16884
+ class="mt-2 text-sm font-medium underline underline-offset-4 hover:opacity-80"
16885
+ (click)="runAction(toast)"
16886
+ >
16887
+ {{ toast.action.label }}
16888
+ </button>
16889
+ }
15442
16890
  </div>
15443
16891
  <button
15444
16892
  type="button"
@@ -15482,6 +16930,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
15482
16930
  @if (toast.description) {
15483
16931
  <p class="text-sm opacity-90">{{ toast.description }}</p>
15484
16932
  }
16933
+ @if (toast.action) {
16934
+ <button
16935
+ type="button"
16936
+ class="mt-2 text-sm font-medium underline underline-offset-4 hover:opacity-80"
16937
+ (click)="runAction(toast)"
16938
+ >
16939
+ {{ toast.action.label }}
16940
+ </button>
16941
+ }
15485
16942
  </div>
15486
16943
  <button
15487
16944
  type="button"
@@ -15559,6 +17016,19 @@ class BuiNavigationMenu {
15559
17016
  <div
15560
17017
  class="absolute start-0 top-full z-50 mt-1.5 w-64 rounded-lg border bg-popover p-2 shadow-md"
15561
17018
  >
17019
+ @if (item.featured; as featured) {
17020
+ <a
17021
+ [href]="href(featured.href)"
17022
+ class="mb-1 block rounded-md bg-accent/50 p-3 hover:bg-accent"
17023
+ >
17024
+ <span class="text-sm font-semibold">{{ featured.label }}</span>
17025
+ @if (featured.description) {
17026
+ <span class="mt-0.5 block text-xs text-muted-foreground">{{
17027
+ featured.description
17028
+ }}</span>
17029
+ }
17030
+ </a>
17031
+ }
15562
17032
  <ul class="space-y-1">
15563
17033
  @for (link of item.links; track link.label) {
15564
17034
  <li>
@@ -15626,6 +17096,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
15626
17096
  <div
15627
17097
  class="absolute start-0 top-full z-50 mt-1.5 w-64 rounded-lg border bg-popover p-2 shadow-md"
15628
17098
  >
17099
+ @if (item.featured; as featured) {
17100
+ <a
17101
+ [href]="href(featured.href)"
17102
+ class="mb-1 block rounded-md bg-accent/50 p-3 hover:bg-accent"
17103
+ >
17104
+ <span class="text-sm font-semibold">{{ featured.label }}</span>
17105
+ @if (featured.description) {
17106
+ <span class="mt-0.5 block text-xs text-muted-foreground">{{
17107
+ featured.description
17108
+ }}</span>
17109
+ }
17110
+ </a>
17111
+ }
15629
17112
  <ul class="space-y-1">
15630
17113
  @for (link of item.links; track link.label) {
15631
17114
  <li>
@@ -15927,11 +17410,23 @@ function label(iso) {
15927
17410
  class BuiGantt {
15928
17411
  tasks = input([], /* @ts-ignore */
15929
17412
  ...(ngDevMode ? [{ debugName: "tasks" }] : /* istanbul ignore next */ []));
17413
+ milestones = input([], /* @ts-ignore */
17414
+ ...(ngDevMode ? [{ debugName: "milestones" }] : /* istanbul ignore next */ []));
15930
17415
  start = input('', /* @ts-ignore */
15931
17416
  ...(ngDevMode ? [{ debugName: "start" }] : /* istanbul ignore next */ []));
15932
17417
  end = input('', /* @ts-ignore */
15933
17418
  ...(ngDevMode ? [{ debugName: "end" }] : /* istanbul ignore next */ []));
15934
17419
  userClass = input('', { ...(ngDevMode ? { debugName: "userClass" } : /* istanbul ignore next */ {}), alias: 'class' });
17420
+ milestoneRows = computed(() => {
17421
+ const { min, span } = this.bounds();
17422
+ return this.milestones()
17423
+ .filter((milestone) => milestone.date !== '')
17424
+ .map((milestone) => ({
17425
+ name: milestone.name,
17426
+ left: ((toDay(milestone.date) - min) / span) * 100,
17427
+ }));
17428
+ }, /* @ts-ignore */
17429
+ ...(ngDevMode ? [{ debugName: "milestoneRows" }] : /* istanbul ignore next */ []));
15935
17430
  bounds = computed(() => {
15936
17431
  const tasks = this.tasks().filter((task) => task.start !== '' && task.end !== '');
15937
17432
  const starts = tasks.map((task) => toDay(task.start));
@@ -15975,7 +17470,7 @@ class BuiGantt {
15975
17470
  computedClass = computed(() => cn('block', this.userClass()), /* @ts-ignore */
15976
17471
  ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
15977
17472
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.2", ngImport: i0, type: BuiGantt, deps: [], target: i0.ɵɵFactoryTarget.Component });
15978
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiGantt, isStandalone: true, selector: "bui-gantt", inputs: { tasks: { classPropertyName: "tasks", publicName: "tasks", isSignal: true, isRequired: false, transformFunction: null }, start: { classPropertyName: "start", publicName: "start", isSignal: true, isRequired: false, transformFunction: null }, end: { classPropertyName: "end", publicName: "end", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "gantt" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
17473
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.2", type: BuiGantt, isStandalone: true, selector: "bui-gantt", inputs: { tasks: { classPropertyName: "tasks", publicName: "tasks", isSignal: true, isRequired: false, transformFunction: null }, milestones: { classPropertyName: "milestones", publicName: "milestones", isSignal: true, isRequired: false, transformFunction: null }, start: { classPropertyName: "start", publicName: "start", isSignal: true, isRequired: false, transformFunction: null }, end: { classPropertyName: "end", publicName: "end", isSignal: true, isRequired: false, transformFunction: null }, userClass: { classPropertyName: "userClass", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "data-slot": "gantt" }, properties: { "class": "computedClass()" } }, ngImport: i0, template: `
15979
17474
  <div class="space-y-2">
15980
17475
  @for (row of rows(); track row.name) {
15981
17476
  <div class="grid grid-cols-[8rem_1fr] items-center gap-3">
@@ -15996,6 +17491,18 @@ class BuiGantt {
15996
17491
  } @empty {
15997
17492
  <p class="text-sm text-muted-foreground">No tasks.</p>
15998
17493
  }
17494
+ @for (milestone of milestoneRows(); track milestone.name) {
17495
+ <div class="grid grid-cols-[8rem_1fr] items-center gap-3">
17496
+ <span class="truncate text-sm text-muted-foreground">{{ milestone.name }}</span>
17497
+ <div class="relative h-6">
17498
+ <span
17499
+ class="absolute top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rotate-45 rounded-[2px] bg-amber-500"
17500
+ [style.left.%]="milestone.left"
17501
+ [attr.title]="milestone.name"
17502
+ ></span>
17503
+ </div>
17504
+ </div>
17505
+ }
15999
17506
  </div>
16000
17507
  @if (rows().length > 0) {
16001
17508
  <div class="mt-2 grid grid-cols-[8rem_1fr] gap-3 text-xs text-muted-foreground">
@@ -16034,6 +17541,18 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
16034
17541
  } @empty {
16035
17542
  <p class="text-sm text-muted-foreground">No tasks.</p>
16036
17543
  }
17544
+ @for (milestone of milestoneRows(); track milestone.name) {
17545
+ <div class="grid grid-cols-[8rem_1fr] items-center gap-3">
17546
+ <span class="truncate text-sm text-muted-foreground">{{ milestone.name }}</span>
17547
+ <div class="relative h-6">
17548
+ <span
17549
+ class="absolute top-1/2 size-3 -translate-x-1/2 -translate-y-1/2 rotate-45 rounded-[2px] bg-amber-500"
17550
+ [style.left.%]="milestone.left"
17551
+ [attr.title]="milestone.name"
17552
+ ></span>
17553
+ </div>
17554
+ </div>
17555
+ }
16037
17556
  </div>
16038
17557
  @if (rows().length > 0) {
16039
17558
  <div class="mt-2 grid grid-cols-[8rem_1fr] gap-3 text-xs text-muted-foreground">
@@ -16046,7 +17565,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.2", ngImpor
16046
17565
  }
16047
17566
  `,
16048
17567
  }]
16049
- }], propDecorators: { tasks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tasks", required: false }] }], start: [{ type: i0.Input, args: [{ isSignal: true, alias: "start", required: false }] }], end: [{ type: i0.Input, args: [{ isSignal: true, alias: "end", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
17568
+ }], propDecorators: { tasks: [{ type: i0.Input, args: [{ isSignal: true, alias: "tasks", required: false }] }], milestones: [{ type: i0.Input, args: [{ isSignal: true, alias: "milestones", required: false }] }], start: [{ type: i0.Input, args: [{ isSignal: true, alias: "start", required: false }] }], end: [{ type: i0.Input, args: [{ isSignal: true, alias: "end", required: false }] }], userClass: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
16050
17569
 
16051
17570
  const WEEK = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
16052
17571
  function toMinutes(value) {