@sonny-ui/core 0.1.0-alpha.14 → 0.1.0-alpha.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/fesm2022/sonny-ui-core.mjs +2257 -68
  2. package/fesm2022/sonny-ui-core.mjs.map +1 -1
  3. package/package.json +1 -1
  4. package/src/lib/calendar/calendar.component.spec.ts +87 -0
  5. package/src/lib/calendar/calendar.component.ts +184 -61
  6. package/src/lib/calendar/calendar.types.ts +24 -0
  7. package/src/lib/calendar/index.ts +6 -0
  8. package/src/lib/color-picker/color-picker.component.spec.ts +328 -0
  9. package/src/lib/color-picker/color-picker.component.ts +537 -0
  10. package/src/lib/color-picker/color-picker.types.ts +24 -0
  11. package/src/lib/color-picker/color-picker.utils.ts +183 -0
  12. package/src/lib/color-picker/color-picker.variants.ts +17 -0
  13. package/src/lib/color-picker/index.ts +20 -0
  14. package/src/lib/command-palette/command-palette.component.spec.ts +178 -0
  15. package/src/lib/command-palette/command-palette.component.ts +195 -0
  16. package/src/lib/command-palette/command-palette.service.ts +36 -0
  17. package/src/lib/command-palette/command-palette.types.ts +23 -0
  18. package/src/lib/command-palette/index.ts +7 -0
  19. package/src/lib/date-picker/date-picker.component.spec.ts +131 -0
  20. package/src/lib/date-picker/date-picker.component.ts +220 -0
  21. package/src/lib/date-picker/date-picker.variants.ts +17 -0
  22. package/src/lib/date-picker/index.ts +2 -0
  23. package/src/lib/date-range-picker/date-range-picker.component.spec.ts +151 -0
  24. package/src/lib/date-range-picker/date-range-picker.component.ts +340 -0
  25. package/src/lib/date-range-picker/index.ts +1 -0
  26. package/src/lib/otp-input/index.ts +2 -0
  27. package/src/lib/otp-input/otp-input.component.spec.ts +252 -0
  28. package/src/lib/otp-input/otp-input.component.ts +275 -0
  29. package/src/lib/otp-input/otp-input.variants.ts +18 -0
  30. package/types/sonny-ui-core.d.ts +331 -7
@@ -3,7 +3,7 @@ import { twMerge } from 'tailwind-merge';
3
3
  import { cva } from 'class-variance-authority';
4
4
  export { cva } from 'class-variance-authority';
5
5
  import * as i0 from '@angular/core';
6
- import { inject, PLATFORM_ID, signal, computed, Injectable, InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer, input, Directive, ChangeDetectionStrategy, Component, ElementRef, model, viewChild, forwardRef, HostListener, TemplateRef, output, contentChildren, contentChild, effect, untracked, Injector, afterNextRender, Renderer2 } from '@angular/core';
6
+ import { inject, PLATFORM_ID, signal, computed, Injectable, InjectionToken, makeEnvironmentProviders, provideEnvironmentInitializer, input, Directive, ChangeDetectionStrategy, Component, ElementRef, model, viewChild, forwardRef, HostListener, TemplateRef, output, contentChildren, contentChild, effect, untracked, Injector, afterNextRender, Renderer2, linkedSignal, viewChildren } from '@angular/core';
7
7
  import { DOCUMENT, isPlatformBrowser, NgTemplateOutlet } from '@angular/common';
8
8
  import { Dialog, DialogRef } from '@angular/cdk/dialog';
9
9
  import { NG_VALUE_ACCESSOR } from '@angular/forms';
@@ -5839,20 +5839,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5839
5839
  }], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
5840
5840
 
5841
5841
  class SnyCalendarComponent {
5842
+ // Existing inputs (backwards compatible)
5842
5843
  value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
5843
5844
  min = input(undefined, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
5844
5845
  max = input(undefined, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
5845
5846
  locale = input('en-US', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
5846
5847
  class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
5848
+ // Range mode inputs
5849
+ mode = input('single', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
5850
+ rangeValue = model(null, ...(ngDevMode ? [{ debugName: "rangeValue" }] : /* istanbul ignore next */ []));
5851
+ showNavigation = input(true, ...(ngDevMode ? [{ debugName: "showNavigation" }] : /* istanbul ignore next */ []));
5852
+ initialViewDate = input(undefined, ...(ngDevMode ? [{ debugName: "initialViewDate" }] : /* istanbul ignore next */ []));
5853
+ borderless = input(false, ...(ngDevMode ? [{ debugName: "borderless" }] : /* istanbul ignore next */ []));
5854
+ hostClass = computed(() => this.borderless()
5855
+ ? 'inline-block p-3 bg-background'
5856
+ : 'inline-block p-4 rounded-md border border-border bg-background', ...(ngDevMode ? [{ debugName: "hostClass" }] : /* istanbul ignore next */ []));
5857
+ // Internal state
5847
5858
  _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
5848
- viewDate = signal(new Date(), ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
5859
+ hoveredDate = signal(null, ...(ngDevMode ? [{ debugName: "hoveredDate" }] : /* istanbul ignore next */ []));
5860
+ viewDate = linkedSignal(() => this.initialViewDate() ?? new Date(), ...(ngDevMode ? [{ debugName: "viewDate" }] : /* istanbul ignore next */ []));
5849
5861
  weekDays = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
5862
+ // CVA
5850
5863
  _onChange = () => { };
5851
5864
  onTouched = () => { };
5852
5865
  writeValue(val) {
5853
- this.value.set(val ?? null);
5854
- if (val) {
5855
- this.viewDate.set(new Date(val.getFullYear(), val.getMonth(), 1));
5866
+ if (this.mode() === 'range') {
5867
+ this.rangeValue.set(val ?? null);
5868
+ const range = val;
5869
+ if (range?.start) {
5870
+ this.viewDate.set(new Date(range.start.getFullYear(), range.start.getMonth(), 1));
5871
+ }
5872
+ }
5873
+ else {
5874
+ this.value.set(val ?? null);
5875
+ if (val) {
5876
+ const d = val;
5877
+ this.viewDate.set(new Date(d.getFullYear(), d.getMonth(), 1));
5878
+ }
5856
5879
  }
5857
5880
  }
5858
5881
  registerOnChange(fn) {
@@ -5864,6 +5887,7 @@ class SnyCalendarComponent {
5864
5887
  setDisabledState(isDisabled) {
5865
5888
  this._disabledByCva.set(isDisabled);
5866
5889
  }
5890
+ // Computed
5867
5891
  monthYearLabel = computed(() => {
5868
5892
  const d = this.viewDate();
5869
5893
  return d.toLocaleDateString(this.locale(), { month: 'long', year: 'numeric' });
@@ -5874,6 +5898,8 @@ class SnyCalendarComponent {
5874
5898
  const month = view.getMonth();
5875
5899
  const today = new Date();
5876
5900
  const selected = this.value();
5901
+ const rangeVal = this.mode() === 'range' ? this.rangeValue() : null;
5902
+ const hovered = this.hoveredDate();
5877
5903
  const minDate = this.min();
5878
5904
  const maxDate = this.max();
5879
5905
  const firstDay = new Date(year, month, 1);
@@ -5881,37 +5907,64 @@ class SnyCalendarComponent {
5881
5907
  const daysInMonth = new Date(year, month + 1, 0).getDate();
5882
5908
  const daysInPrevMonth = new Date(year, month, 0).getDate();
5883
5909
  const days = [];
5884
- // Previous month
5885
5910
  for (let i = startDay - 1; i >= 0; i--) {
5886
5911
  const date = new Date(year, month - 1, daysInPrevMonth - i);
5887
- days.push(this.createDay(date, false, today, selected, minDate, maxDate));
5912
+ days.push(this.createDay(date, false, today, selected, rangeVal, hovered, minDate, maxDate));
5888
5913
  }
5889
- // Current month
5890
5914
  for (let d = 1; d <= daysInMonth; d++) {
5891
5915
  const date = new Date(year, month, d);
5892
- days.push(this.createDay(date, true, today, selected, minDate, maxDate));
5916
+ days.push(this.createDay(date, true, today, selected, rangeVal, hovered, minDate, maxDate));
5893
5917
  }
5894
- // Next month fill
5895
5918
  const remaining = 42 - days.length;
5896
5919
  for (let d = 1; d <= remaining; d++) {
5897
5920
  const date = new Date(year, month + 1, d);
5898
- days.push(this.createDay(date, false, today, selected, minDate, maxDate));
5921
+ days.push(this.createDay(date, false, today, selected, rangeVal, hovered, minDate, maxDate));
5899
5922
  }
5900
5923
  return days;
5901
5924
  }, ...(ngDevMode ? [{ debugName: "days" }] : /* istanbul ignore next */ []));
5925
+ // Navigation
5902
5926
  prevMonth() {
5903
- this.viewDate.update((d) => new Date(d.getFullYear(), d.getMonth() - 1, 1));
5927
+ this.viewDate.set(new Date(this.viewDate().getFullYear(), this.viewDate().getMonth() - 1, 1));
5904
5928
  }
5905
5929
  nextMonth() {
5906
- this.viewDate.update((d) => new Date(d.getFullYear(), d.getMonth() + 1, 1));
5930
+ this.viewDate.set(new Date(this.viewDate().getFullYear(), this.viewDate().getMonth() + 1, 1));
5907
5931
  }
5908
- selectDate(date) {
5909
- this.value.set(date);
5910
- this._onChange(date);
5932
+ // Click handler
5933
+ onDayClick(date) {
5934
+ if (this.mode() === 'single') {
5935
+ this.value.set(date);
5936
+ this._onChange(date);
5937
+ this.onTouched();
5938
+ return;
5939
+ }
5940
+ // Range mode
5941
+ const current = this.rangeValue();
5942
+ if (!current?.start || (current.start && current.end)) {
5943
+ this.rangeValue.set({ start: date, end: null });
5944
+ }
5945
+ else {
5946
+ const start = current.start;
5947
+ if (date < start) {
5948
+ this.rangeValue.set({ start: date, end: start });
5949
+ }
5950
+ else if (this.isSameDay(date, start)) {
5951
+ this.rangeValue.set({ start: date, end: date });
5952
+ }
5953
+ else {
5954
+ this.rangeValue.set({ start, end: date });
5955
+ }
5956
+ }
5957
+ this._onChange(this.rangeValue());
5911
5958
  this.onTouched();
5912
5959
  }
5960
+ // Hover handler
5961
+ onDayHover(date) {
5962
+ if (this.mode() === 'range') {
5963
+ this.hoveredDate.set(date);
5964
+ }
5965
+ }
5966
+ // Keyboard
5913
5967
  onKeydown(event) {
5914
- // Simplified keyboard navigation
5915
5968
  switch (event.key) {
5916
5969
  case 'ArrowLeft':
5917
5970
  event.preventDefault();
@@ -5931,9 +5984,29 @@ class SnyCalendarComponent {
5931
5984
  break;
5932
5985
  }
5933
5986
  }
5987
+ // Styling
5934
5988
  dayClass(day) {
5935
- return cn('inline-flex items-center justify-center rounded-md text-sm h-8 w-8 transition-colors', day.isCurrentMonth ? 'text-foreground' : 'text-muted-foreground/50', day.isToday && !day.isSelected && 'bg-accent font-bold', day.isSelected && 'bg-primary text-primary-foreground', day.isDisabled && 'opacity-50 cursor-not-allowed', !day.isDisabled && !day.isSelected && 'hover:bg-accent cursor-pointer');
5936
- }
5989
+ const isEndpoint = day.isRangeStart || day.isRangeEnd;
5990
+ return cn('inline-flex items-center justify-center text-sm h-9 w-9 transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
5991
+ // Shape
5992
+ day.isRangeStart && !day.isRangeEnd ? 'rounded-l-md rounded-r-none' :
5993
+ day.isRangeEnd && !day.isRangeStart ? 'rounded-r-md rounded-l-none' :
5994
+ day.isInRange || day.isRangePreview ? 'rounded-none' :
5995
+ 'rounded-md',
5996
+ // Base text color
5997
+ !day.isCurrentMonth && 'text-muted-foreground/40', day.isCurrentMonth && !day.isSelected && !isEndpoint && 'text-foreground',
5998
+ // Today indicator
5999
+ day.isToday && !day.isSelected && !isEndpoint && 'bg-accent text-accent-foreground font-semibold',
6000
+ // Single selected
6001
+ day.isSelected && this.mode() === 'single' && 'bg-primary text-primary-foreground font-semibold shadow-sm',
6002
+ // Range endpoints
6003
+ isEndpoint && 'bg-primary text-primary-foreground font-semibold shadow-sm',
6004
+ // Range band
6005
+ day.isInRange && 'bg-primary/10 text-foreground', day.isRangePreview && 'bg-primary/5 text-foreground',
6006
+ // States
6007
+ day.isDisabled && 'opacity-40 cursor-not-allowed pointer-events-none', !day.isDisabled && !day.isSelected && !isEndpoint && 'hover:bg-accent hover:text-accent-foreground cursor-pointer');
6008
+ }
6009
+ // Private helpers
5937
6010
  navigateDays(offset) {
5938
6011
  const current = this.value() ?? new Date();
5939
6012
  const next = new Date(current);
@@ -5942,12 +6015,41 @@ class SnyCalendarComponent {
5942
6015
  this._onChange(next);
5943
6016
  this.viewDate.set(new Date(next.getFullYear(), next.getMonth(), 1));
5944
6017
  }
5945
- createDay(date, isCurrentMonth, today, selected, minDate, maxDate) {
6018
+ createDay(date, isCurrentMonth, today, selected, rangeVal, hoveredDate, minDate, maxDate) {
5946
6019
  const isToday = this.isSameDay(date, today);
5947
6020
  const isSelected = selected ? this.isSameDay(date, selected) : false;
5948
6021
  const isDisabled = this._disabledByCva() ||
5949
- (minDate ? date < minDate : false) || (maxDate ? date > maxDate : false);
5950
- return { date, day: date.getDate(), isCurrentMonth, isToday, isSelected, isDisabled };
6022
+ (minDate ? date < minDate : false) ||
6023
+ (maxDate ? date > maxDate : false);
6024
+ let isRangeStart = false;
6025
+ let isRangeEnd = false;
6026
+ let isInRange = false;
6027
+ let isRangePreview = false;
6028
+ if (rangeVal) {
6029
+ const { start, end } = rangeVal;
6030
+ if (start)
6031
+ isRangeStart = this.isSameDay(date, start);
6032
+ if (end)
6033
+ isRangeEnd = this.isSameDay(date, end);
6034
+ if (start && end) {
6035
+ isInRange = date > start && date < end && !isRangeStart && !isRangeEnd;
6036
+ }
6037
+ // Preview: start set, no end yet, user hovering
6038
+ if (start && !end && hoveredDate && !this.isSameDay(hoveredDate, start)) {
6039
+ const previewStart = hoveredDate > start ? start : hoveredDate;
6040
+ const previewEnd = hoveredDate > start ? hoveredDate : start;
6041
+ if (date > previewStart && date < previewEnd) {
6042
+ isRangePreview = true;
6043
+ }
6044
+ if (this.isSameDay(date, hoveredDate) && !isRangeStart) {
6045
+ isRangePreview = true;
6046
+ }
6047
+ }
6048
+ }
6049
+ return {
6050
+ date, day: date.getDate(), isCurrentMonth, isToday, isSelected, isDisabled,
6051
+ isRangeStart, isRangeEnd, isInRange, isRangePreview,
6052
+ };
5951
6053
  }
5952
6054
  isSameDay(a, b) {
5953
6055
  return (a.getFullYear() === b.getFullYear() &&
@@ -5955,39 +6057,48 @@ class SnyCalendarComponent {
5955
6057
  a.getDate() === b.getDate());
5956
6058
  }
5957
6059
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCalendarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5958
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyCalendarComponent, isStandalone: true, selector: "sny-calendar", 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 }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "keydown": "onKeydown($event)" }, properties: { "class": "\"inline-block p-3 rounded-md border bg-background\"" } }, providers: [
6060
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyCalendarComponent, isStandalone: true, selector: "sny-calendar", 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 }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, rangeValue: { classPropertyName: "rangeValue", publicName: "rangeValue", isSignal: true, isRequired: false, transformFunction: null }, showNavigation: { classPropertyName: "showNavigation", publicName: "showNavigation", isSignal: true, isRequired: false, transformFunction: null }, initialViewDate: { classPropertyName: "initialViewDate", publicName: "initialViewDate", isSignal: true, isRequired: false, transformFunction: null }, borderless: { classPropertyName: "borderless", publicName: "borderless", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", rangeValue: "rangeValueChange" }, host: { attributes: { "role": "application", "aria-label": "Calendar" }, listeners: { "keydown": "onKeydown($event)" }, properties: { "class": "hostClass()" } }, providers: [
5959
6061
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyCalendarComponent), multi: true },
5960
6062
  ], ngImport: i0, template: `
5961
- <div class="flex items-center justify-between mb-4">
5962
- <button
5963
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
5964
- (click)="prevMonth()"
5965
- aria-label="Previous month"
5966
- >
5967
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m15 18-6-6 6-6"/></svg>
5968
- </button>
5969
- <span class="text-sm font-medium">{{ monthYearLabel() }}</span>
5970
- <button
5971
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
5972
- (click)="nextMonth()"
5973
- aria-label="Next month"
5974
- >
5975
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>
5976
- </button>
5977
- </div>
6063
+ @if (showNavigation()) {
6064
+ <div class="flex items-center justify-between mb-3">
6065
+ <button
6066
+ type="button"
6067
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6068
+ (click)="prevMonth()"
6069
+ aria-label="Previous month"
6070
+ >
6071
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6072
+ </button>
6073
+ <span class="text-sm font-semibold tracking-tight">{{ monthYearLabel() }}</span>
6074
+ <button
6075
+ type="button"
6076
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6077
+ (click)="nextMonth()"
6078
+ aria-label="Next month"
6079
+ >
6080
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6081
+ </button>
6082
+ </div>
6083
+ }
5978
6084
 
5979
- <div role="grid" aria-label="Calendar" class="grid grid-cols-7 gap-0">
6085
+ <div role="grid" class="grid grid-cols-7 gap-1">
5980
6086
  @for (dayName of weekDays; track dayName) {
5981
- <div class="text-center text-xs text-muted-foreground font-medium py-1">{{ dayName }}</div>
6087
+ <div class="text-center text-xs text-muted-foreground font-medium h-9 flex items-center justify-center" role="columnheader">{{ dayName }}</div>
5982
6088
  }
5983
6089
  @for (day of days(); track day.date.getTime()) {
5984
6090
  <button
6091
+ type="button"
5985
6092
  [class]="dayClass(day)"
5986
6093
  [disabled]="day.isDisabled"
5987
- [attr.aria-selected]="day.isSelected || null"
6094
+ [attr.aria-selected]="day.isSelected || day.isRangeStart || day.isRangeEnd || null"
5988
6095
  [attr.aria-current]="day.isToday ? 'date' : null"
5989
6096
  [attr.aria-disabled]="day.isDisabled || null"
5990
- (click)="selectDate(day.date)"
6097
+ [attr.aria-label]="day.date.toLocaleDateString(locale(), { month: 'long', day: 'numeric', year: 'numeric' })"
6098
+ role="gridcell"
6099
+ (click)="onDayClick(day.date)"
6100
+ (mouseenter)="onDayHover(day.date)"
6101
+ (mouseleave)="onDayHover(null)"
5991
6102
  >
5992
6103
  {{ day.day }}
5993
6104
  </button>
@@ -6002,43 +6113,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
6002
6113
  standalone: true,
6003
6114
  changeDetection: ChangeDetectionStrategy.OnPush,
6004
6115
  host: {
6005
- '[class]': '"inline-block p-3 rounded-md border bg-background"',
6116
+ '[class]': 'hostClass()',
6006
6117
  '(keydown)': 'onKeydown($event)',
6118
+ 'role': 'application',
6119
+ 'aria-label': 'Calendar',
6007
6120
  },
6008
6121
  providers: [
6009
6122
  { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyCalendarComponent), multi: true },
6010
6123
  ],
6011
6124
  template: `
6012
- <div class="flex items-center justify-between mb-4">
6013
- <button
6014
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
6015
- (click)="prevMonth()"
6016
- aria-label="Previous month"
6017
- >
6018
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m15 18-6-6 6-6"/></svg>
6019
- </button>
6020
- <span class="text-sm font-medium">{{ monthYearLabel() }}</span>
6021
- <button
6022
- class="inline-flex items-center justify-center rounded-md text-sm h-7 w-7 hover:bg-accent"
6023
- (click)="nextMonth()"
6024
- aria-label="Next month"
6025
- >
6026
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m9 18 6-6-6-6"/></svg>
6027
- </button>
6028
- </div>
6125
+ @if (showNavigation()) {
6126
+ <div class="flex items-center justify-between mb-3">
6127
+ <button
6128
+ type="button"
6129
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6130
+ (click)="prevMonth()"
6131
+ aria-label="Previous month"
6132
+ >
6133
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6134
+ </button>
6135
+ <span class="text-sm font-semibold tracking-tight">{{ monthYearLabel() }}</span>
6136
+ <button
6137
+ type="button"
6138
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6139
+ (click)="nextMonth()"
6140
+ aria-label="Next month"
6141
+ >
6142
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6143
+ </button>
6144
+ </div>
6145
+ }
6029
6146
 
6030
- <div role="grid" aria-label="Calendar" class="grid grid-cols-7 gap-0">
6147
+ <div role="grid" class="grid grid-cols-7 gap-1">
6031
6148
  @for (dayName of weekDays; track dayName) {
6032
- <div class="text-center text-xs text-muted-foreground font-medium py-1">{{ dayName }}</div>
6149
+ <div class="text-center text-xs text-muted-foreground font-medium h-9 flex items-center justify-center" role="columnheader">{{ dayName }}</div>
6033
6150
  }
6034
6151
  @for (day of days(); track day.date.getTime()) {
6035
6152
  <button
6153
+ type="button"
6036
6154
  [class]="dayClass(day)"
6037
6155
  [disabled]="day.isDisabled"
6038
- [attr.aria-selected]="day.isSelected || null"
6156
+ [attr.aria-selected]="day.isSelected || day.isRangeStart || day.isRangeEnd || null"
6039
6157
  [attr.aria-current]="day.isToday ? 'date' : null"
6040
6158
  [attr.aria-disabled]="day.isDisabled || null"
6041
- (click)="selectDate(day.date)"
6159
+ [attr.aria-label]="day.date.toLocaleDateString(locale(), { month: 'long', day: 'numeric', year: 'numeric' })"
6160
+ role="gridcell"
6161
+ (click)="onDayClick(day.date)"
6162
+ (mouseenter)="onDayHover(day.date)"
6163
+ (mouseleave)="onDayHover(null)"
6042
6164
  >
6043
6165
  {{ day.day }}
6044
6166
  </button>
@@ -6046,7 +6168,2074 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
6046
6168
  </div>
6047
6169
  `,
6048
6170
  }]
6049
- }], 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 }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
6171
+ }], 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 }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], rangeValue: [{ type: i0.Input, args: [{ isSignal: true, alias: "rangeValue", required: false }] }, { type: i0.Output, args: ["rangeValueChange"] }], showNavigation: [{ type: i0.Input, args: [{ isSignal: true, alias: "showNavigation", required: false }] }], initialViewDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialViewDate", required: false }] }], borderless: [{ type: i0.Input, args: [{ isSignal: true, alias: "borderless", required: false }] }] } });
6172
+
6173
+ const datePickerTriggerVariants = cva('inline-flex w-full items-center justify-between whitespace-nowrap rounded-sm border border-border bg-background px-3 py-2 text-sm ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', {
6174
+ variants: {
6175
+ size: {
6176
+ sm: 'h-9 text-xs',
6177
+ md: 'h-10 text-sm',
6178
+ lg: 'h-11 text-base',
6179
+ },
6180
+ },
6181
+ defaultVariants: { size: 'md' },
6182
+ });
6183
+
6184
+ class SnyDatePickerComponent {
6185
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6186
+ placeholder = input('Pick a date...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
6187
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
6188
+ locale = input('en-US', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
6189
+ dateFormat = input({
6190
+ month: 'short',
6191
+ day: 'numeric',
6192
+ year: 'numeric',
6193
+ }, ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
6194
+ min = input(undefined, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
6195
+ max = input(undefined, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
6196
+ clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
6197
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
6198
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
6199
+ open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
6200
+ internalValue = signal(null, ...(ngDevMode ? [{ debugName: "internalValue" }] : /* istanbul ignore next */ []));
6201
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
6202
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
6203
+ triggerRef = viewChild('triggerEl', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
6204
+ dropdownRef = viewChild('dropdownEl', ...(ngDevMode ? [{ debugName: "dropdownRef" }] : /* istanbul ignore next */ []));
6205
+ elRef = inject(ElementRef);
6206
+ scrollHandler = null;
6207
+ resizeHandler = null;
6208
+ _onChange = () => { };
6209
+ onTouched = () => { };
6210
+ // CVA
6211
+ writeValue(val) {
6212
+ this.value.set(val ?? null);
6213
+ this.internalValue.set(val ?? null);
6214
+ }
6215
+ registerOnChange(fn) {
6216
+ this._onChange = fn;
6217
+ }
6218
+ registerOnTouched(fn) {
6219
+ this.onTouched = fn;
6220
+ }
6221
+ setDisabledState(isDisabled) {
6222
+ this._disabledByCva.set(isDisabled);
6223
+ }
6224
+ // Display
6225
+ displayValue = computed(() => {
6226
+ const d = this.value();
6227
+ if (!d)
6228
+ return '';
6229
+ return d.toLocaleDateString(this.locale(), this.dateFormat());
6230
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
6231
+ triggerClass = computed(() => cn(datePickerTriggerVariants({ size: this.size() }), this.class()), ...(ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []));
6232
+ // Actions
6233
+ onDateSelected(date) {
6234
+ this.value.set(date);
6235
+ this._onChange(date);
6236
+ this.close();
6237
+ }
6238
+ clear(event) {
6239
+ event.stopPropagation();
6240
+ this.value.set(null);
6241
+ this.internalValue.set(null);
6242
+ this._onChange(null);
6243
+ }
6244
+ toggle() {
6245
+ if (this.open()) {
6246
+ this.close();
6247
+ }
6248
+ else {
6249
+ this.internalValue.set(this.value());
6250
+ this.updateDropdownPosition();
6251
+ this.open.set(true);
6252
+ this.addGlobalListeners();
6253
+ setTimeout(() => this.updateDropdownPosition());
6254
+ }
6255
+ }
6256
+ close() {
6257
+ this.open.set(false);
6258
+ this.removeGlobalListeners();
6259
+ }
6260
+ // Positioning (combobox pattern)
6261
+ updateDropdownPosition() {
6262
+ const trigger = this.triggerRef()?.nativeElement;
6263
+ if (!trigger)
6264
+ return;
6265
+ const rect = trigger.getBoundingClientRect();
6266
+ const dropdown = this.dropdownRef()?.nativeElement;
6267
+ if (dropdown) {
6268
+ dropdown.style.top = `${rect.bottom + 4}px`;
6269
+ dropdown.style.left = `${rect.left}px`;
6270
+ }
6271
+ }
6272
+ addGlobalListeners() {
6273
+ this.removeGlobalListeners();
6274
+ this.scrollHandler = () => {
6275
+ requestAnimationFrame(() => this.updateDropdownPosition());
6276
+ };
6277
+ this.resizeHandler = () => {
6278
+ requestAnimationFrame(() => this.updateDropdownPosition());
6279
+ };
6280
+ document.addEventListener('scroll', this.scrollHandler, { capture: true, passive: true });
6281
+ window.addEventListener('resize', this.resizeHandler, { passive: true });
6282
+ }
6283
+ removeGlobalListeners() {
6284
+ if (this.scrollHandler) {
6285
+ document.removeEventListener('scroll', this.scrollHandler, { capture: true });
6286
+ this.scrollHandler = null;
6287
+ }
6288
+ if (this.resizeHandler) {
6289
+ window.removeEventListener('resize', this.resizeHandler);
6290
+ this.resizeHandler = null;
6291
+ }
6292
+ }
6293
+ ngOnDestroy() {
6294
+ this.removeGlobalListeners();
6295
+ }
6296
+ onDocumentClick(event) {
6297
+ if (!this.elRef.nativeElement.contains(event.target)) {
6298
+ this.close();
6299
+ }
6300
+ }
6301
+ onEscape() {
6302
+ this.close();
6303
+ }
6304
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDatePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6305
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyDatePickerComponent, isStandalone: true, selector: "sny-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 }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", 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 }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, classAttribute: "relative inline-block w-full" }, providers: [
6306
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDatePickerComponent), multi: true },
6307
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerEl"], descendants: true, isSignal: true }, { propertyName: "dropdownRef", first: true, predicate: ["dropdownEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
6308
+ <button
6309
+ #triggerEl
6310
+ type="button"
6311
+ role="combobox"
6312
+ [attr.aria-expanded]="open()"
6313
+ aria-haspopup="dialog"
6314
+ [disabled]="isDisabled()"
6315
+ [class]="triggerClass()"
6316
+ (click)="toggle()"
6317
+ (blur)="onTouched()"
6318
+ >
6319
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6320
+ {{ displayValue() || placeholder() }}
6321
+ </span>
6322
+ <div class="flex items-center gap-1 shrink-0">
6323
+ @if (clearable() && value()) {
6324
+ <button
6325
+ type="button"
6326
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6327
+ (click)="clear($event)"
6328
+ aria-label="Clear date"
6329
+ >
6330
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6331
+ </button>
6332
+ }
6333
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6334
+ </div>
6335
+ </button>
6336
+
6337
+ @if (open()) {
6338
+ <div
6339
+ #dropdownEl
6340
+ role="dialog"
6341
+ aria-modal="true"
6342
+ aria-label="Choose date"
6343
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
6344
+ >
6345
+ <sny-calendar
6346
+ [(value)]="internalValue"
6347
+ [min]="min()"
6348
+ [max]="max()"
6349
+ [locale]="locale()"
6350
+ (valueChange)="onDateSelected($event)"
6351
+ />
6352
+ </div>
6353
+ }
6354
+ `, isInline: true, dependencies: [{ kind: "component", type: SnyCalendarComponent, selector: "sny-calendar", inputs: ["value", "min", "max", "locale", "class", "mode", "rangeValue", "showNavigation", "initialViewDate", "borderless"], outputs: ["valueChange", "rangeValueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6355
+ }
6356
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDatePickerComponent, decorators: [{
6357
+ type: Component,
6358
+ args: [{
6359
+ selector: 'sny-date-picker',
6360
+ standalone: true,
6361
+ changeDetection: ChangeDetectionStrategy.OnPush,
6362
+ imports: [SnyCalendarComponent],
6363
+ host: { class: 'relative inline-block w-full' },
6364
+ providers: [
6365
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDatePickerComponent), multi: true },
6366
+ ],
6367
+ template: `
6368
+ <button
6369
+ #triggerEl
6370
+ type="button"
6371
+ role="combobox"
6372
+ [attr.aria-expanded]="open()"
6373
+ aria-haspopup="dialog"
6374
+ [disabled]="isDisabled()"
6375
+ [class]="triggerClass()"
6376
+ (click)="toggle()"
6377
+ (blur)="onTouched()"
6378
+ >
6379
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6380
+ {{ displayValue() || placeholder() }}
6381
+ </span>
6382
+ <div class="flex items-center gap-1 shrink-0">
6383
+ @if (clearable() && value()) {
6384
+ <button
6385
+ type="button"
6386
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6387
+ (click)="clear($event)"
6388
+ aria-label="Clear date"
6389
+ >
6390
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6391
+ </button>
6392
+ }
6393
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6394
+ </div>
6395
+ </button>
6396
+
6397
+ @if (open()) {
6398
+ <div
6399
+ #dropdownEl
6400
+ role="dialog"
6401
+ aria-modal="true"
6402
+ aria-label="Choose date"
6403
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
6404
+ >
6405
+ <sny-calendar
6406
+ [(value)]="internalValue"
6407
+ [min]="min()"
6408
+ [max]="max()"
6409
+ [locale]="locale()"
6410
+ (valueChange)="onDateSelected($event)"
6411
+ />
6412
+ </div>
6413
+ }
6414
+ `,
6415
+ }]
6416
+ }], 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 }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerEl', { isSignal: true }] }], dropdownRef: [{ type: i0.ViewChild, args: ['dropdownEl', { isSignal: true }] }], onDocumentClick: [{
6417
+ type: HostListener,
6418
+ args: ['document:click', ['$event']]
6419
+ }], onEscape: [{
6420
+ type: HostListener,
6421
+ args: ['keydown.escape']
6422
+ }] } });
6423
+
6424
+ class SnyDateRangePickerComponent {
6425
+ value = model(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
6426
+ placeholder = input('Pick a date range...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
6427
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
6428
+ locale = input('en-US', ...(ngDevMode ? [{ debugName: "locale" }] : /* istanbul ignore next */ []));
6429
+ dateFormat = input({
6430
+ month: 'short',
6431
+ day: 'numeric',
6432
+ year: 'numeric',
6433
+ }, ...(ngDevMode ? [{ debugName: "dateFormat" }] : /* istanbul ignore next */ []));
6434
+ separator = input(' \u2014 ', ...(ngDevMode ? [{ debugName: "separator" }] : /* istanbul ignore next */ []));
6435
+ dualCalendar = input(false, ...(ngDevMode ? [{ debugName: "dualCalendar" }] : /* istanbul ignore next */ []));
6436
+ presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
6437
+ min = input(undefined, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
6438
+ max = input(undefined, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
6439
+ clearable = input(true, ...(ngDevMode ? [{ debugName: "clearable" }] : /* istanbul ignore next */ []));
6440
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
6441
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
6442
+ open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
6443
+ internalRange = signal(null, ...(ngDevMode ? [{ debugName: "internalRange" }] : /* istanbul ignore next */ []));
6444
+ leftViewDate = signal(new Date(), ...(ngDevMode ? [{ debugName: "leftViewDate" }] : /* istanbul ignore next */ []));
6445
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
6446
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
6447
+ triggerRef = viewChild('triggerEl', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
6448
+ dropdownRef = viewChild('dropdownEl', ...(ngDevMode ? [{ debugName: "dropdownRef" }] : /* istanbul ignore next */ []));
6449
+ elRef = inject(ElementRef);
6450
+ scrollHandler = null;
6451
+ resizeHandler = null;
6452
+ _onChange = () => { };
6453
+ onTouched = () => { };
6454
+ // Computed
6455
+ rightViewDate = computed(() => {
6456
+ const d = this.leftViewDate();
6457
+ return new Date(d.getFullYear(), d.getMonth() + 1, 1);
6458
+ }, ...(ngDevMode ? [{ debugName: "rightViewDate" }] : /* istanbul ignore next */ []));
6459
+ leftMonthLabel = computed(() => this.leftViewDate().toLocaleDateString(this.locale(), { month: 'long', year: 'numeric' }), ...(ngDevMode ? [{ debugName: "leftMonthLabel" }] : /* istanbul ignore next */ []));
6460
+ rightMonthLabel = computed(() => this.rightViewDate().toLocaleDateString(this.locale(), { month: 'long', year: 'numeric' }), ...(ngDevMode ? [{ debugName: "rightMonthLabel" }] : /* istanbul ignore next */ []));
6461
+ displayValue = computed(() => {
6462
+ const r = this.value();
6463
+ if (!r?.start)
6464
+ return '';
6465
+ const fmt = (d) => d.toLocaleDateString(this.locale(), this.dateFormat());
6466
+ if (!r.end)
6467
+ return fmt(r.start) + this.separator() + '...';
6468
+ return fmt(r.start) + this.separator() + fmt(r.end);
6469
+ }, ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
6470
+ triggerClass = computed(() => cn(datePickerTriggerVariants({ size: this.size() }), this.class()), ...(ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []));
6471
+ // CVA
6472
+ writeValue(val) {
6473
+ this.value.set(val ?? null);
6474
+ this.internalRange.set(val ?? null);
6475
+ if (val?.start) {
6476
+ this.leftViewDate.set(new Date(val.start.getFullYear(), val.start.getMonth(), 1));
6477
+ }
6478
+ }
6479
+ registerOnChange(fn) {
6480
+ this._onChange = fn;
6481
+ }
6482
+ registerOnTouched(fn) {
6483
+ this.onTouched = fn;
6484
+ }
6485
+ setDisabledState(isDisabled) {
6486
+ this._disabledByCva.set(isDisabled);
6487
+ }
6488
+ // Actions
6489
+ onRangeChanged(range) {
6490
+ this.internalRange.set(range);
6491
+ if (range?.start && range?.end) {
6492
+ this.value.set(range);
6493
+ this._onChange(range);
6494
+ setTimeout(() => this.close(), 150);
6495
+ }
6496
+ }
6497
+ selectPreset(preset) {
6498
+ this.value.set(preset.range);
6499
+ this.internalRange.set(preset.range);
6500
+ this._onChange(preset.range);
6501
+ this.close();
6502
+ }
6503
+ clear(event) {
6504
+ event.stopPropagation();
6505
+ this.value.set(null);
6506
+ this.internalRange.set(null);
6507
+ this._onChange(null);
6508
+ }
6509
+ prevMonth() {
6510
+ this.leftViewDate.update((d) => new Date(d.getFullYear(), d.getMonth() - 1, 1));
6511
+ }
6512
+ nextMonth() {
6513
+ this.leftViewDate.update((d) => new Date(d.getFullYear(), d.getMonth() + 1, 1));
6514
+ }
6515
+ toggle() {
6516
+ if (this.open()) {
6517
+ this.close();
6518
+ }
6519
+ else {
6520
+ this.internalRange.set(this.value());
6521
+ this.updateDropdownPosition();
6522
+ this.open.set(true);
6523
+ this.addGlobalListeners();
6524
+ setTimeout(() => this.updateDropdownPosition());
6525
+ }
6526
+ }
6527
+ close() {
6528
+ this.open.set(false);
6529
+ this.removeGlobalListeners();
6530
+ }
6531
+ // Positioning
6532
+ updateDropdownPosition() {
6533
+ const trigger = this.triggerRef()?.nativeElement;
6534
+ if (!trigger)
6535
+ return;
6536
+ const rect = trigger.getBoundingClientRect();
6537
+ const dropdown = this.dropdownRef()?.nativeElement;
6538
+ if (dropdown) {
6539
+ dropdown.style.top = `${rect.bottom + 4}px`;
6540
+ dropdown.style.left = `${rect.left}px`;
6541
+ }
6542
+ }
6543
+ addGlobalListeners() {
6544
+ this.removeGlobalListeners();
6545
+ this.scrollHandler = () => {
6546
+ requestAnimationFrame(() => this.updateDropdownPosition());
6547
+ };
6548
+ this.resizeHandler = () => {
6549
+ requestAnimationFrame(() => this.updateDropdownPosition());
6550
+ };
6551
+ document.addEventListener('scroll', this.scrollHandler, { capture: true, passive: true });
6552
+ window.addEventListener('resize', this.resizeHandler, { passive: true });
6553
+ }
6554
+ removeGlobalListeners() {
6555
+ if (this.scrollHandler) {
6556
+ document.removeEventListener('scroll', this.scrollHandler, { capture: true });
6557
+ this.scrollHandler = null;
6558
+ }
6559
+ if (this.resizeHandler) {
6560
+ window.removeEventListener('resize', this.resizeHandler);
6561
+ this.resizeHandler = null;
6562
+ }
6563
+ }
6564
+ ngOnDestroy() {
6565
+ this.removeGlobalListeners();
6566
+ }
6567
+ onDocumentClick(event) {
6568
+ if (!this.elRef.nativeElement.contains(event.target)) {
6569
+ this.close();
6570
+ }
6571
+ }
6572
+ onEscape() {
6573
+ this.close();
6574
+ }
6575
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDateRangePickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
6576
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyDateRangePickerComponent, isStandalone: true, selector: "sny-date-range-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, locale: { classPropertyName: "locale", publicName: "locale", isSignal: true, isRequired: false, transformFunction: null }, dateFormat: { classPropertyName: "dateFormat", publicName: "dateFormat", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, dualCalendar: { classPropertyName: "dualCalendar", publicName: "dualCalendar", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", 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 }, clearable: { classPropertyName: "clearable", publicName: "clearable", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, classAttribute: "relative inline-block w-full" }, providers: [
6577
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDateRangePickerComponent), multi: true },
6578
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerEl"], descendants: true, isSignal: true }, { propertyName: "dropdownRef", first: true, predicate: ["dropdownEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
6579
+ <button
6580
+ #triggerEl
6581
+ type="button"
6582
+ role="combobox"
6583
+ [attr.aria-expanded]="open()"
6584
+ aria-haspopup="dialog"
6585
+ [disabled]="isDisabled()"
6586
+ [class]="triggerClass()"
6587
+ (click)="toggle()"
6588
+ (blur)="onTouched()"
6589
+ >
6590
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6591
+ {{ displayValue() || placeholder() }}
6592
+ </span>
6593
+ <div class="flex items-center gap-1 shrink-0">
6594
+ @if (clearable() && value()?.start) {
6595
+ <button
6596
+ type="button"
6597
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6598
+ (click)="clear($event)"
6599
+ aria-label="Clear date range"
6600
+ >
6601
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6602
+ </button>
6603
+ }
6604
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6605
+ </div>
6606
+ </button>
6607
+
6608
+ @if (open()) {
6609
+ <div
6610
+ #dropdownEl
6611
+ role="dialog"
6612
+ aria-modal="true"
6613
+ aria-label="Choose date range"
6614
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
6615
+ >
6616
+ <div class="flex flex-col sm:flex-row">
6617
+ <!-- Presets sidebar -->
6618
+ @if (presets().length > 0) {
6619
+ <div class="border-b sm:border-b-0 sm:border-r border-border p-3 space-y-0.5 sm:min-w-[150px]">
6620
+ <p class="px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">Presets</p>
6621
+ @for (preset of presets(); track preset.label) {
6622
+ <button
6623
+ type="button"
6624
+ class="w-full text-left px-3 py-2 text-sm rounded-md hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer"
6625
+ (mousedown)="selectPreset(preset); $event.preventDefault()"
6626
+ >
6627
+ {{ preset.label }}
6628
+ </button>
6629
+ }
6630
+ </div>
6631
+ }
6632
+
6633
+ <!-- Calendar(s) -->
6634
+ <div class="flex flex-col sm:flex-row">
6635
+ @if (dualCalendar()) {
6636
+ <!-- Left calendar -->
6637
+ <div class="p-1">
6638
+ <div class="flex items-center justify-between px-3 py-2">
6639
+ <button
6640
+ type="button"
6641
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6642
+ (click)="prevMonth()"
6643
+ aria-label="Previous month"
6644
+ >
6645
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6646
+ </button>
6647
+ <span class="text-sm font-semibold tracking-tight">{{ leftMonthLabel() }}</span>
6648
+ <div class="w-8"></div>
6649
+ </div>
6650
+ <sny-calendar
6651
+ mode="range"
6652
+ [(rangeValue)]="internalRange"
6653
+ [min]="min()"
6654
+ [max]="max()"
6655
+ [locale]="locale()"
6656
+ [showNavigation]="false"
6657
+ [borderless]="true"
6658
+ [initialViewDate]="leftViewDate()"
6659
+ (rangeValueChange)="onRangeChanged($event)"
6660
+ />
6661
+ </div>
6662
+ <div class="border-t sm:border-t-0 sm:border-l border-border"></div>
6663
+ <!-- Right calendar -->
6664
+ <div class="p-1">
6665
+ <div class="flex items-center justify-between px-3 py-2">
6666
+ <div class="w-8"></div>
6667
+ <span class="text-sm font-semibold tracking-tight">{{ rightMonthLabel() }}</span>
6668
+ <button
6669
+ type="button"
6670
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6671
+ (click)="nextMonth()"
6672
+ aria-label="Next month"
6673
+ >
6674
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6675
+ </button>
6676
+ </div>
6677
+ <sny-calendar
6678
+ mode="range"
6679
+ [(rangeValue)]="internalRange"
6680
+ [min]="min()"
6681
+ [max]="max()"
6682
+ [locale]="locale()"
6683
+ [showNavigation]="false"
6684
+ [borderless]="true"
6685
+ [initialViewDate]="rightViewDate()"
6686
+ (rangeValueChange)="onRangeChanged($event)"
6687
+ />
6688
+ </div>
6689
+ } @else {
6690
+ <!-- Single calendar -->
6691
+ <sny-calendar
6692
+ mode="range"
6693
+ [(rangeValue)]="internalRange"
6694
+ [min]="min()"
6695
+ [max]="max()"
6696
+ [locale]="locale()"
6697
+ (rangeValueChange)="onRangeChanged($event)"
6698
+ />
6699
+ }
6700
+ </div>
6701
+ </div>
6702
+ </div>
6703
+ }
6704
+ `, isInline: true, dependencies: [{ kind: "component", type: SnyCalendarComponent, selector: "sny-calendar", inputs: ["value", "min", "max", "locale", "class", "mode", "rangeValue", "showNavigation", "initialViewDate", "borderless"], outputs: ["valueChange", "rangeValueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
6705
+ }
6706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyDateRangePickerComponent, decorators: [{
6707
+ type: Component,
6708
+ args: [{
6709
+ selector: 'sny-date-range-picker',
6710
+ standalone: true,
6711
+ changeDetection: ChangeDetectionStrategy.OnPush,
6712
+ imports: [SnyCalendarComponent],
6713
+ host: { class: 'relative inline-block w-full' },
6714
+ providers: [
6715
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyDateRangePickerComponent), multi: true },
6716
+ ],
6717
+ template: `
6718
+ <button
6719
+ #triggerEl
6720
+ type="button"
6721
+ role="combobox"
6722
+ [attr.aria-expanded]="open()"
6723
+ aria-haspopup="dialog"
6724
+ [disabled]="isDisabled()"
6725
+ [class]="triggerClass()"
6726
+ (click)="toggle()"
6727
+ (blur)="onTouched()"
6728
+ >
6729
+ <span [class]="displayValue() ? 'truncate' : 'text-muted-foreground truncate'">
6730
+ {{ displayValue() || placeholder() }}
6731
+ </span>
6732
+ <div class="flex items-center gap-1 shrink-0">
6733
+ @if (clearable() && value()?.start) {
6734
+ <button
6735
+ type="button"
6736
+ class="rounded-sm p-0.5 hover:bg-muted transition-colors text-muted-foreground hover:text-foreground"
6737
+ (click)="clear($event)"
6738
+ aria-label="Clear date range"
6739
+ >
6740
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
6741
+ </button>
6742
+ }
6743
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="shrink-0 text-muted-foreground"><path d="M8 2v4"/><path d="M16 2v4"/><rect width="18" height="18" x="3" y="4" rx="2"/><path d="M3 10h18"/></svg>
6744
+ </div>
6745
+ </button>
6746
+
6747
+ @if (open()) {
6748
+ <div
6749
+ #dropdownEl
6750
+ role="dialog"
6751
+ aria-modal="true"
6752
+ aria-label="Choose date range"
6753
+ class="fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95"
6754
+ >
6755
+ <div class="flex flex-col sm:flex-row">
6756
+ <!-- Presets sidebar -->
6757
+ @if (presets().length > 0) {
6758
+ <div class="border-b sm:border-b-0 sm:border-r border-border p-3 space-y-0.5 sm:min-w-[150px]">
6759
+ <p class="px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">Presets</p>
6760
+ @for (preset of presets(); track preset.label) {
6761
+ <button
6762
+ type="button"
6763
+ class="w-full text-left px-3 py-2 text-sm rounded-md hover:bg-accent hover:text-accent-foreground transition-colors cursor-pointer"
6764
+ (mousedown)="selectPreset(preset); $event.preventDefault()"
6765
+ >
6766
+ {{ preset.label }}
6767
+ </button>
6768
+ }
6769
+ </div>
6770
+ }
6771
+
6772
+ <!-- Calendar(s) -->
6773
+ <div class="flex flex-col sm:flex-row">
6774
+ @if (dualCalendar()) {
6775
+ <!-- Left calendar -->
6776
+ <div class="p-1">
6777
+ <div class="flex items-center justify-between px-3 py-2">
6778
+ <button
6779
+ type="button"
6780
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6781
+ (click)="prevMonth()"
6782
+ aria-label="Previous month"
6783
+ >
6784
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
6785
+ </button>
6786
+ <span class="text-sm font-semibold tracking-tight">{{ leftMonthLabel() }}</span>
6787
+ <div class="w-8"></div>
6788
+ </div>
6789
+ <sny-calendar
6790
+ mode="range"
6791
+ [(rangeValue)]="internalRange"
6792
+ [min]="min()"
6793
+ [max]="max()"
6794
+ [locale]="locale()"
6795
+ [showNavigation]="false"
6796
+ [borderless]="true"
6797
+ [initialViewDate]="leftViewDate()"
6798
+ (rangeValueChange)="onRangeChanged($event)"
6799
+ />
6800
+ </div>
6801
+ <div class="border-t sm:border-t-0 sm:border-l border-border"></div>
6802
+ <!-- Right calendar -->
6803
+ <div class="p-1">
6804
+ <div class="flex items-center justify-between px-3 py-2">
6805
+ <div class="w-8"></div>
6806
+ <span class="text-sm font-semibold tracking-tight">{{ rightMonthLabel() }}</span>
6807
+ <button
6808
+ type="button"
6809
+ class="inline-flex items-center justify-center rounded-md h-8 w-8 hover:bg-accent hover:text-accent-foreground transition-colors"
6810
+ (click)="nextMonth()"
6811
+ aria-label="Next month"
6812
+ >
6813
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></svg>
6814
+ </button>
6815
+ </div>
6816
+ <sny-calendar
6817
+ mode="range"
6818
+ [(rangeValue)]="internalRange"
6819
+ [min]="min()"
6820
+ [max]="max()"
6821
+ [locale]="locale()"
6822
+ [showNavigation]="false"
6823
+ [borderless]="true"
6824
+ [initialViewDate]="rightViewDate()"
6825
+ (rangeValueChange)="onRangeChanged($event)"
6826
+ />
6827
+ </div>
6828
+ } @else {
6829
+ <!-- Single calendar -->
6830
+ <sny-calendar
6831
+ mode="range"
6832
+ [(rangeValue)]="internalRange"
6833
+ [min]="min()"
6834
+ [max]="max()"
6835
+ [locale]="locale()"
6836
+ (rangeValueChange)="onRangeChanged($event)"
6837
+ />
6838
+ }
6839
+ </div>
6840
+ </div>
6841
+ </div>
6842
+ }
6843
+ `,
6844
+ }]
6845
+ }], 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 }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], locale: [{ type: i0.Input, args: [{ isSignal: true, alias: "locale", required: false }] }], dateFormat: [{ type: i0.Input, args: [{ isSignal: true, alias: "dateFormat", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], dualCalendar: [{ type: i0.Input, args: [{ isSignal: true, alias: "dualCalendar", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], clearable: [{ type: i0.Input, args: [{ isSignal: true, alias: "clearable", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerEl', { isSignal: true }] }], dropdownRef: [{ type: i0.ViewChild, args: ['dropdownEl', { isSignal: true }] }], onDocumentClick: [{
6846
+ type: HostListener,
6847
+ args: ['document:click', ['$event']]
6848
+ }], onEscape: [{
6849
+ type: HostListener,
6850
+ args: ['keydown.escape']
6851
+ }] } });
6852
+
6853
+ function hexToRgb(hex) {
6854
+ const clean = hex.replace(/^#/, '');
6855
+ if (!/^[0-9a-fA-F]+$/.test(clean))
6856
+ return null;
6857
+ if (clean.length === 3) {
6858
+ const r = parseInt(clean[0] + clean[0], 16);
6859
+ const g = parseInt(clean[1] + clean[1], 16);
6860
+ const b = parseInt(clean[2] + clean[2], 16);
6861
+ return { r, g, b };
6862
+ }
6863
+ if (clean.length === 6) {
6864
+ const r = parseInt(clean.slice(0, 2), 16);
6865
+ const g = parseInt(clean.slice(2, 4), 16);
6866
+ const b = parseInt(clean.slice(4, 6), 16);
6867
+ return { r, g, b };
6868
+ }
6869
+ return null;
6870
+ }
6871
+ function rgbToHex(rgb) {
6872
+ const toHex = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, '0');
6873
+ return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
6874
+ }
6875
+ function rgbToHsl(rgb) {
6876
+ const r = rgb.r / 255;
6877
+ const g = rgb.g / 255;
6878
+ const b = rgb.b / 255;
6879
+ const max = Math.max(r, g, b);
6880
+ const min = Math.min(r, g, b);
6881
+ const l = (max + min) / 2;
6882
+ let h = 0;
6883
+ let s = 0;
6884
+ if (max !== min) {
6885
+ const d = max - min;
6886
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
6887
+ switch (max) {
6888
+ case r:
6889
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
6890
+ break;
6891
+ case g:
6892
+ h = ((b - r) / d + 2) / 6;
6893
+ break;
6894
+ case b:
6895
+ h = ((r - g) / d + 4) / 6;
6896
+ break;
6897
+ }
6898
+ }
6899
+ return {
6900
+ h: Math.round(h * 360),
6901
+ s: Math.round(s * 100),
6902
+ l: Math.round(l * 100),
6903
+ };
6904
+ }
6905
+ function hslToRgb(hsl) {
6906
+ const h = hsl.h / 360;
6907
+ const s = hsl.s / 100;
6908
+ const l = hsl.l / 100;
6909
+ if (s === 0) {
6910
+ const v = Math.round(l * 255);
6911
+ return { r: v, g: v, b: v };
6912
+ }
6913
+ const hue2rgb = (p, q, t) => {
6914
+ if (t < 0)
6915
+ t += 1;
6916
+ if (t > 1)
6917
+ t -= 1;
6918
+ if (t < 1 / 6)
6919
+ return p + (q - p) * 6 * t;
6920
+ if (t < 1 / 2)
6921
+ return q;
6922
+ if (t < 2 / 3)
6923
+ return p + (q - p) * (2 / 3 - t) * 6;
6924
+ return p;
6925
+ };
6926
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
6927
+ const p = 2 * l - q;
6928
+ return {
6929
+ r: Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
6930
+ g: Math.round(hue2rgb(p, q, h) * 255),
6931
+ b: Math.round(hue2rgb(p, q, h - 1 / 3) * 255),
6932
+ };
6933
+ }
6934
+ function rgbToHsv(rgb) {
6935
+ const r = rgb.r / 255;
6936
+ const g = rgb.g / 255;
6937
+ const b = rgb.b / 255;
6938
+ const max = Math.max(r, g, b);
6939
+ const min = Math.min(r, g, b);
6940
+ const d = max - min;
6941
+ let h = 0;
6942
+ const s = max === 0 ? 0 : d / max;
6943
+ const v = max;
6944
+ if (max !== min) {
6945
+ switch (max) {
6946
+ case r:
6947
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
6948
+ break;
6949
+ case g:
6950
+ h = ((b - r) / d + 2) / 6;
6951
+ break;
6952
+ case b:
6953
+ h = ((r - g) / d + 4) / 6;
6954
+ break;
6955
+ }
6956
+ }
6957
+ return { h: Math.round(h * 360), s, v };
6958
+ }
6959
+ function hsvToRgb(hsv) {
6960
+ const h = hsv.h / 360;
6961
+ const s = hsv.s;
6962
+ const v = hsv.v;
6963
+ const i = Math.floor(h * 6);
6964
+ const f = h * 6 - i;
6965
+ const p = v * (1 - s);
6966
+ const q = v * (1 - f * s);
6967
+ const t = v * (1 - (1 - f) * s);
6968
+ let r, g, b;
6969
+ switch (i % 6) {
6970
+ case 0:
6971
+ r = v;
6972
+ g = t;
6973
+ b = p;
6974
+ break;
6975
+ case 1:
6976
+ r = q;
6977
+ g = v;
6978
+ b = p;
6979
+ break;
6980
+ case 2:
6981
+ r = p;
6982
+ g = v;
6983
+ b = t;
6984
+ break;
6985
+ case 3:
6986
+ r = p;
6987
+ g = q;
6988
+ b = v;
6989
+ break;
6990
+ case 4:
6991
+ r = t;
6992
+ g = p;
6993
+ b = v;
6994
+ break;
6995
+ default:
6996
+ r = v;
6997
+ g = p;
6998
+ b = q;
6999
+ break;
7000
+ }
7001
+ return {
7002
+ r: Math.round(r * 255),
7003
+ g: Math.round(g * 255),
7004
+ b: Math.round(b * 255),
7005
+ };
7006
+ }
7007
+ function parseColor(input) {
7008
+ const trimmed = input.trim().toLowerCase();
7009
+ // HEX
7010
+ if (trimmed.startsWith('#')) {
7011
+ return hexToRgb(trimmed);
7012
+ }
7013
+ // rgb(r, g, b)
7014
+ const rgbMatch = trimmed.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/);
7015
+ if (rgbMatch) {
7016
+ return {
7017
+ r: Math.min(255, parseInt(rgbMatch[1])),
7018
+ g: Math.min(255, parseInt(rgbMatch[2])),
7019
+ b: Math.min(255, parseInt(rgbMatch[3])),
7020
+ };
7021
+ }
7022
+ // hsl(h, s%, l%)
7023
+ const hslMatch = trimmed.match(/^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/);
7024
+ if (hslMatch) {
7025
+ return hslToRgb({
7026
+ h: Math.min(360, parseInt(hslMatch[1])),
7027
+ s: Math.min(100, parseInt(hslMatch[2])),
7028
+ l: Math.min(100, parseInt(hslMatch[3])),
7029
+ });
7030
+ }
7031
+ return null;
7032
+ }
7033
+ function formatColor(rgb, format, alpha) {
7034
+ switch (format) {
7035
+ case 'hex':
7036
+ return rgbToHex(rgb);
7037
+ case 'rgb':
7038
+ if (alpha !== undefined && alpha < 1) {
7039
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha.toFixed(2)})`;
7040
+ }
7041
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
7042
+ case 'hsl': {
7043
+ const hsl = rgbToHsl(rgb);
7044
+ if (alpha !== undefined && alpha < 1) {
7045
+ return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${alpha.toFixed(2)})`;
7046
+ }
7047
+ return `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
7048
+ }
7049
+ }
7050
+ }
7051
+ function isValidColor(input) {
7052
+ return parseColor(input) !== null;
7053
+ }
7054
+
7055
+ const colorPickerTriggerVariants = cva('inline-flex w-full items-center gap-2 whitespace-nowrap rounded-sm border border-border bg-background px-3 py-2 text-sm ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', {
7056
+ variants: {
7057
+ size: {
7058
+ sm: 'h-9 text-xs',
7059
+ md: 'h-10 text-sm',
7060
+ lg: 'h-11 text-base',
7061
+ },
7062
+ },
7063
+ defaultVariants: { size: 'md' },
7064
+ });
7065
+
7066
+ class SnyColorPickerComponent {
7067
+ // Public API
7068
+ value = model('#000000', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
7069
+ format = input('hex', ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
7070
+ presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
7071
+ showInput = input(true, ...(ngDevMode ? [{ debugName: "showInput" }] : /* istanbul ignore next */ []));
7072
+ showEyeDropper = input(true, ...(ngDevMode ? [{ debugName: "showEyeDropper" }] : /* istanbul ignore next */ []));
7073
+ showFavorites = input(false, ...(ngDevMode ? [{ debugName: "showFavorites" }] : /* istanbul ignore next */ []));
7074
+ inline = input(false, ...(ngDevMode ? [{ debugName: "inline" }] : /* istanbul ignore next */ []));
7075
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7076
+ placeholder = input('Pick a color...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
7077
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
7078
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
7079
+ colorChange = output();
7080
+ formatChange = output();
7081
+ // Internal state
7082
+ hsv = signal({ h: 0, s: 0, v: 0 }, ...(ngDevMode ? [{ debugName: "hsv" }] : /* istanbul ignore next */ []));
7083
+ currentFormat = signal('hex', ...(ngDevMode ? [{ debugName: "currentFormat" }] : /* istanbul ignore next */ []));
7084
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
7085
+ open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
7086
+ favorites = signal([], ...(ngDevMode ? [{ debugName: "favorites" }] : /* istanbul ignore next */ []));
7087
+ copied = signal(false, ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
7088
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
7089
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
7090
+ triggerRef = viewChild('triggerEl', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
7091
+ panelRef = viewChild('panelEl', ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
7092
+ satPanelRef = viewChild('satPanel', ...(ngDevMode ? [{ debugName: "satPanelRef" }] : /* istanbul ignore next */ []));
7093
+ hueTrackRef = viewChild('hueTrack', ...(ngDevMode ? [{ debugName: "hueTrackRef" }] : /* istanbul ignore next */ []));
7094
+ elRef = inject(ElementRef);
7095
+ moveHandler = null;
7096
+ upHandler = null;
7097
+ scrollHandler = null;
7098
+ resizeHandler = null;
7099
+ _onChange = () => { };
7100
+ onTouched = () => { };
7101
+ hasEyeDropper = typeof window !== 'undefined' && 'EyeDropper' in window;
7102
+ // Computed
7103
+ rgb = computed(() => hsvToRgb(this.hsv()), ...(ngDevMode ? [{ debugName: "rgb" }] : /* istanbul ignore next */ []));
7104
+ displayValue = computed(() => formatColor(this.rgb(), this.currentFormat()), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
7105
+ saturationBg = computed(() => `linear-gradient(to right, #fff, hsl(${this.hsv().h}, 100%, 50%))`, ...(ngDevMode ? [{ debugName: "saturationBg" }] : /* istanbul ignore next */ []));
7106
+ triggerClass = computed(() => cn(colorPickerTriggerVariants({ size: this.size() }), this.class()), ...(ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []));
7107
+ panelClass = computed(() => this.inline()
7108
+ ? 'inline-block p-3 rounded-md border border-border bg-popover text-popover-foreground w-60'
7109
+ : 'fixed z-50 p-3 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95 w-60', ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
7110
+ constructor() {
7111
+ // Sync format input
7112
+ effect(() => {
7113
+ const fmt = this.format();
7114
+ untracked(() => this.currentFormat.set(fmt));
7115
+ });
7116
+ // Sync value → HSV when value changes externally
7117
+ effect(() => {
7118
+ const val = this.value();
7119
+ untracked(() => {
7120
+ const rgb = parseColor(val);
7121
+ if (rgb) {
7122
+ this.hsv.set(rgbToHsv(rgb));
7123
+ this.inputValue.set(this.displayValue());
7124
+ }
7125
+ });
7126
+ });
7127
+ }
7128
+ // CVA
7129
+ writeValue(val) {
7130
+ this.value.set(val ?? '#000000');
7131
+ }
7132
+ registerOnChange(fn) {
7133
+ this._onChange = fn;
7134
+ }
7135
+ registerOnTouched(fn) {
7136
+ this.onTouched = fn;
7137
+ }
7138
+ setDisabledState(isDisabled) {
7139
+ this._disabledByCva.set(isDisabled);
7140
+ }
7141
+ // Emit helper
7142
+ emitColor() {
7143
+ const formatted = this.displayValue();
7144
+ this.value.set(formatted);
7145
+ this.inputValue.set(formatted);
7146
+ this._onChange(formatted);
7147
+ this.colorChange.emit(formatted);
7148
+ }
7149
+ // Saturation panel
7150
+ onSatPanelDown(event) {
7151
+ event.preventDefault();
7152
+ this.updateSatFromPosition(event.clientX, event.clientY);
7153
+ this.startDrag((e) => {
7154
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7155
+ const y = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
7156
+ this.updateSatFromPosition(x, y);
7157
+ });
7158
+ }
7159
+ onSatPanelTouch(event) {
7160
+ this.updateSatFromPosition(event.touches[0].clientX, event.touches[0].clientY);
7161
+ this.startDrag((e) => {
7162
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7163
+ const y = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
7164
+ this.updateSatFromPosition(x, y);
7165
+ }, true);
7166
+ }
7167
+ updateSatFromPosition(clientX, clientY) {
7168
+ const panel = this.satPanelRef()?.nativeElement;
7169
+ if (!panel)
7170
+ return;
7171
+ const rect = panel.getBoundingClientRect();
7172
+ const s = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
7173
+ const v = Math.max(0, Math.min(1, 1 - (clientY - rect.top) / rect.height));
7174
+ this.hsv.update((prev) => ({ ...prev, s, v }));
7175
+ this.emitColor();
7176
+ }
7177
+ // Hue slider
7178
+ onHueDown(event) {
7179
+ event.preventDefault();
7180
+ this.updateHueFromPosition(event.clientX);
7181
+ this.startDrag((e) => {
7182
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7183
+ this.updateHueFromPosition(x);
7184
+ });
7185
+ }
7186
+ onHueTouch(event) {
7187
+ this.updateHueFromPosition(event.touches[0].clientX);
7188
+ this.startDrag((e) => {
7189
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7190
+ this.updateHueFromPosition(x);
7191
+ }, true);
7192
+ }
7193
+ updateHueFromPosition(clientX) {
7194
+ const track = this.hueTrackRef()?.nativeElement;
7195
+ if (!track)
7196
+ return;
7197
+ const rect = track.getBoundingClientRect();
7198
+ const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
7199
+ const h = Math.round(percent * 360);
7200
+ this.hsv.update((prev) => ({ ...prev, h }));
7201
+ this.emitColor();
7202
+ }
7203
+ // Drag helpers (same pattern as slider component)
7204
+ startDrag(handler, touch = false) {
7205
+ this.removeDragListeners();
7206
+ this.moveHandler = handler;
7207
+ this.upHandler = () => {
7208
+ this.onTouched();
7209
+ this.removeDragListeners();
7210
+ };
7211
+ if (touch) {
7212
+ document.addEventListener('touchmove', this.moveHandler, { passive: true });
7213
+ document.addEventListener('touchend', this.upHandler);
7214
+ }
7215
+ else {
7216
+ document.addEventListener('mousemove', this.moveHandler);
7217
+ document.addEventListener('mouseup', this.upHandler);
7218
+ }
7219
+ }
7220
+ removeDragListeners() {
7221
+ if (this.moveHandler) {
7222
+ document.removeEventListener('mousemove', this.moveHandler);
7223
+ document.removeEventListener('touchmove', this.moveHandler);
7224
+ this.moveHandler = null;
7225
+ }
7226
+ if (this.upHandler) {
7227
+ document.removeEventListener('mouseup', this.upHandler);
7228
+ document.removeEventListener('touchend', this.upHandler);
7229
+ this.upHandler = null;
7230
+ }
7231
+ }
7232
+ // Input
7233
+ onInputChange(event) {
7234
+ this.inputValue.set(event.target.value);
7235
+ }
7236
+ commitInput() {
7237
+ const val = this.inputValue().trim();
7238
+ if (isValidColor(val)) {
7239
+ const rgb = parseColor(val);
7240
+ this.hsv.set(rgbToHsv(rgb));
7241
+ this.emitColor();
7242
+ }
7243
+ else {
7244
+ this.inputValue.set(this.displayValue());
7245
+ }
7246
+ }
7247
+ // Format
7248
+ cycleFormat() {
7249
+ const formats = ['hex', 'rgb', 'hsl'];
7250
+ const idx = formats.indexOf(this.currentFormat());
7251
+ const next = formats[(idx + 1) % formats.length];
7252
+ this.currentFormat.set(next);
7253
+ this.inputValue.set(this.displayValue());
7254
+ this.formatChange.emit(next);
7255
+ }
7256
+ // Copy
7257
+ copyColor() {
7258
+ navigator.clipboard.writeText(this.displayValue());
7259
+ this.copied.set(true);
7260
+ setTimeout(() => this.copied.set(false), 2000);
7261
+ }
7262
+ // Presets & favorites
7263
+ selectColor(color) {
7264
+ const rgb = parseColor(color);
7265
+ if (rgb) {
7266
+ this.hsv.set(rgbToHsv(rgb));
7267
+ this.emitColor();
7268
+ }
7269
+ }
7270
+ addFavorite() {
7271
+ const hex = rgbToHex(this.rgb());
7272
+ this.favorites.update((favs) => favs.includes(hex) ? favs : [...favs, hex]);
7273
+ }
7274
+ removeFavorite(color) {
7275
+ this.favorites.update((favs) => favs.filter((f) => f !== color));
7276
+ }
7277
+ // EyeDropper
7278
+ async pickFromScreen() {
7279
+ if (!this.hasEyeDropper)
7280
+ return;
7281
+ try {
7282
+ const dropper = new window.EyeDropper();
7283
+ const result = await dropper.open();
7284
+ this.selectColor(result.sRGBHex);
7285
+ }
7286
+ catch {
7287
+ // User cancelled
7288
+ }
7289
+ }
7290
+ // Popover
7291
+ toggle() {
7292
+ if (this.open()) {
7293
+ this.close();
7294
+ }
7295
+ else {
7296
+ this.open.set(true);
7297
+ this.addPositionListeners();
7298
+ setTimeout(() => this.updatePanelPosition());
7299
+ }
7300
+ }
7301
+ close() {
7302
+ this.open.set(false);
7303
+ this.removePositionListeners();
7304
+ }
7305
+ updatePanelPosition() {
7306
+ if (this.inline())
7307
+ return;
7308
+ const trigger = this.triggerRef()?.nativeElement;
7309
+ if (!trigger)
7310
+ return;
7311
+ const rect = trigger.getBoundingClientRect();
7312
+ const panel = this.panelRef()?.nativeElement;
7313
+ if (panel) {
7314
+ panel.style.top = `${rect.bottom + 4}px`;
7315
+ panel.style.left = `${rect.left}px`;
7316
+ }
7317
+ }
7318
+ addPositionListeners() {
7319
+ this.removePositionListeners();
7320
+ this.scrollHandler = () => requestAnimationFrame(() => this.updatePanelPosition());
7321
+ this.resizeHandler = () => requestAnimationFrame(() => this.updatePanelPosition());
7322
+ document.addEventListener('scroll', this.scrollHandler, { capture: true, passive: true });
7323
+ window.addEventListener('resize', this.resizeHandler, { passive: true });
7324
+ }
7325
+ removePositionListeners() {
7326
+ if (this.scrollHandler) {
7327
+ document.removeEventListener('scroll', this.scrollHandler, { capture: true });
7328
+ this.scrollHandler = null;
7329
+ }
7330
+ if (this.resizeHandler) {
7331
+ window.removeEventListener('resize', this.resizeHandler);
7332
+ this.resizeHandler = null;
7333
+ }
7334
+ }
7335
+ onDocumentClick(event) {
7336
+ if (!this.elRef.nativeElement.contains(event.target)) {
7337
+ this.close();
7338
+ }
7339
+ }
7340
+ onEscape() {
7341
+ this.close();
7342
+ }
7343
+ ngOnDestroy() {
7344
+ this.removeDragListeners();
7345
+ this.removePositionListeners();
7346
+ }
7347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyColorPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7348
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyColorPickerComponent, isStandalone: true, selector: "sny-color-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, showInput: { classPropertyName: "showInput", publicName: "showInput", isSignal: true, isRequired: false, transformFunction: null }, showEyeDropper: { classPropertyName: "showEyeDropper", publicName: "showEyeDropper", isSignal: true, isRequired: false, transformFunction: null }, showFavorites: { classPropertyName: "showFavorites", publicName: "showFavorites", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", colorChange: "colorChange", formatChange: "formatChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, classAttribute: "relative inline-block" }, providers: [
7349
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyColorPickerComponent), multi: true },
7350
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerEl"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panelEl"], descendants: true, isSignal: true }, { propertyName: "satPanelRef", first: true, predicate: ["satPanel"], descendants: true, isSignal: true }, { propertyName: "hueTrackRef", first: true, predicate: ["hueTrack"], descendants: true, isSignal: true }], ngImport: i0, template: `
7351
+ <!-- Trigger -->
7352
+ @if (!inline()) {
7353
+ <button
7354
+ #triggerEl
7355
+ type="button"
7356
+ role="combobox"
7357
+ [attr.aria-expanded]="open()"
7358
+ aria-haspopup="dialog"
7359
+ [disabled]="isDisabled()"
7360
+ [class]="triggerClass()"
7361
+ (click)="toggle()"
7362
+ (blur)="onTouched()"
7363
+ >
7364
+ <div
7365
+ class="h-5 w-5 rounded-sm border border-border shrink-0"
7366
+ [style.backgroundColor]="displayValue()"
7367
+ ></div>
7368
+ <span class="truncate">{{ displayValue() || placeholder() }}</span>
7369
+ </button>
7370
+ }
7371
+
7372
+ <!-- Panel -->
7373
+ @if (open() || inline()) {
7374
+ <div
7375
+ #panelEl
7376
+ [class]="panelClass()"
7377
+ role="dialog"
7378
+ aria-modal="true"
7379
+ aria-label="Color picker"
7380
+ >
7381
+ <!-- Saturation/Brightness Panel -->
7382
+ <div
7383
+ #satPanel
7384
+ class="relative h-36 w-full rounded-md cursor-crosshair overflow-hidden"
7385
+ [style.background]="saturationBg()"
7386
+ (mousedown)="onSatPanelDown($event)"
7387
+ (touchstart)="onSatPanelTouch($event)"
7388
+ >
7389
+ <div class="absolute inset-0 bg-gradient-to-t from-black to-transparent"></div>
7390
+ <div
7391
+ class="absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7392
+ [style.left.%]="hsv().s * 100"
7393
+ [style.top.%]="(1 - hsv().v) * 100"
7394
+ ></div>
7395
+ </div>
7396
+
7397
+ <!-- Hue Slider -->
7398
+ <div
7399
+ #hueTrack
7400
+ class="relative h-3 w-full rounded-full cursor-pointer mt-3"
7401
+ style="background: linear-gradient(to right, hsl(0,100%,50%), hsl(60,100%,50%), hsl(120,100%,50%), hsl(180,100%,50%), hsl(240,100%,50%), hsl(300,100%,50%), hsl(360,100%,50%))"
7402
+ (mousedown)="onHueDown($event)"
7403
+ (touchstart)="onHueTouch($event)"
7404
+ >
7405
+ <div
7406
+ class="absolute top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7407
+ [style.left.%]="hsv().h / 360 * 100"
7408
+ [style.backgroundColor]="'hsl(' + hsv().h + ', 100%, 50%)'"
7409
+ ></div>
7410
+ </div>
7411
+
7412
+ <!-- Input + Format + Copy + Actions -->
7413
+ @if (showInput()) {
7414
+ <div class="mt-3 flex items-center gap-1.5">
7415
+ <div
7416
+ class="h-8 w-8 rounded-sm border border-border shrink-0"
7417
+ [style.backgroundColor]="displayValue()"
7418
+ ></div>
7419
+ <input
7420
+ class="flex-1 min-w-0 h-8 rounded-sm border border-border bg-background px-2 text-xs font-mono focus:outline-none focus:ring-1 focus:ring-ring"
7421
+ [value]="inputValue()"
7422
+ (input)="onInputChange($event)"
7423
+ (blur)="commitInput()"
7424
+ (keydown.enter)="commitInput()"
7425
+ />
7426
+ <button
7427
+ type="button"
7428
+ class="h-8 px-1.5 rounded-sm border border-border text-[10px] font-semibold uppercase hover:bg-accent transition-colors shrink-0"
7429
+ (click)="cycleFormat()"
7430
+ title="Switch format"
7431
+ >
7432
+ {{ currentFormat() }}
7433
+ </button>
7434
+ <button
7435
+ type="button"
7436
+ class="h-8 w-8 inline-flex items-center justify-center rounded-sm border border-border hover:bg-accent transition-colors shrink-0"
7437
+ (click)="copyColor()"
7438
+ [title]="copied() ? 'Copied!' : 'Copy color'"
7439
+ >
7440
+ @if (copied()) {
7441
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
7442
+ } @else {
7443
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
7444
+ }
7445
+ </button>
7446
+ </div>
7447
+ <!-- Secondary actions row -->
7448
+ <div class="mt-2 flex items-center gap-1.5">
7449
+ @if (showEyeDropper() && hasEyeDropper) {
7450
+ <button
7451
+ type="button"
7452
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7453
+ (click)="pickFromScreen()"
7454
+ title="Pick from screen"
7455
+ >
7456
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
7457
+ Pick
7458
+ </button>
7459
+ }
7460
+ @if (showFavorites()) {
7461
+ <button
7462
+ type="button"
7463
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7464
+ (click)="addFavorite()"
7465
+ title="Save to favorites"
7466
+ >
7467
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
7468
+ Save
7469
+ </button>
7470
+ }
7471
+ </div>
7472
+ }
7473
+
7474
+ <!-- Presets -->
7475
+ @for (preset of presets(); track $index) {
7476
+ <div class="mt-3">
7477
+ @if (preset.label) {
7478
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">{{ preset.label }}</p>
7479
+ }
7480
+ <div class="flex flex-wrap gap-1.5">
7481
+ @for (color of preset.colors; track color) {
7482
+ <button
7483
+ type="button"
7484
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7485
+ [style.backgroundColor]="color"
7486
+ [title]="color"
7487
+ (click)="selectColor(color)"
7488
+ ></button>
7489
+ }
7490
+ </div>
7491
+ </div>
7492
+ }
7493
+
7494
+ <!-- Favorites -->
7495
+ @if (showFavorites() && favorites().length > 0) {
7496
+ <div class="mt-3">
7497
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">Favorites</p>
7498
+ <div class="flex flex-wrap gap-1.5">
7499
+ @for (fav of favorites(); track fav) {
7500
+ <div class="relative group">
7501
+ <button
7502
+ type="button"
7503
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7504
+ [style.backgroundColor]="fav"
7505
+ [title]="fav"
7506
+ (click)="selectColor(fav)"
7507
+ ></button>
7508
+ <button
7509
+ type="button"
7510
+ class="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full bg-destructive text-destructive-foreground text-[8px] leading-none items-center justify-center hidden group-hover:inline-flex"
7511
+ (click)="removeFavorite(fav); $event.stopPropagation()"
7512
+ >×</button>
7513
+ </div>
7514
+ }
7515
+ </div>
7516
+ </div>
7517
+ }
7518
+ </div>
7519
+ }
7520
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
7521
+ }
7522
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyColorPickerComponent, decorators: [{
7523
+ type: Component,
7524
+ args: [{
7525
+ selector: 'sny-color-picker',
7526
+ standalone: true,
7527
+ changeDetection: ChangeDetectionStrategy.OnPush,
7528
+ host: { class: 'relative inline-block' },
7529
+ providers: [
7530
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyColorPickerComponent), multi: true },
7531
+ ],
7532
+ template: `
7533
+ <!-- Trigger -->
7534
+ @if (!inline()) {
7535
+ <button
7536
+ #triggerEl
7537
+ type="button"
7538
+ role="combobox"
7539
+ [attr.aria-expanded]="open()"
7540
+ aria-haspopup="dialog"
7541
+ [disabled]="isDisabled()"
7542
+ [class]="triggerClass()"
7543
+ (click)="toggle()"
7544
+ (blur)="onTouched()"
7545
+ >
7546
+ <div
7547
+ class="h-5 w-5 rounded-sm border border-border shrink-0"
7548
+ [style.backgroundColor]="displayValue()"
7549
+ ></div>
7550
+ <span class="truncate">{{ displayValue() || placeholder() }}</span>
7551
+ </button>
7552
+ }
7553
+
7554
+ <!-- Panel -->
7555
+ @if (open() || inline()) {
7556
+ <div
7557
+ #panelEl
7558
+ [class]="panelClass()"
7559
+ role="dialog"
7560
+ aria-modal="true"
7561
+ aria-label="Color picker"
7562
+ >
7563
+ <!-- Saturation/Brightness Panel -->
7564
+ <div
7565
+ #satPanel
7566
+ class="relative h-36 w-full rounded-md cursor-crosshair overflow-hidden"
7567
+ [style.background]="saturationBg()"
7568
+ (mousedown)="onSatPanelDown($event)"
7569
+ (touchstart)="onSatPanelTouch($event)"
7570
+ >
7571
+ <div class="absolute inset-0 bg-gradient-to-t from-black to-transparent"></div>
7572
+ <div
7573
+ class="absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7574
+ [style.left.%]="hsv().s * 100"
7575
+ [style.top.%]="(1 - hsv().v) * 100"
7576
+ ></div>
7577
+ </div>
7578
+
7579
+ <!-- Hue Slider -->
7580
+ <div
7581
+ #hueTrack
7582
+ class="relative h-3 w-full rounded-full cursor-pointer mt-3"
7583
+ style="background: linear-gradient(to right, hsl(0,100%,50%), hsl(60,100%,50%), hsl(120,100%,50%), hsl(180,100%,50%), hsl(240,100%,50%), hsl(300,100%,50%), hsl(360,100%,50%))"
7584
+ (mousedown)="onHueDown($event)"
7585
+ (touchstart)="onHueTouch($event)"
7586
+ >
7587
+ <div
7588
+ class="absolute top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7589
+ [style.left.%]="hsv().h / 360 * 100"
7590
+ [style.backgroundColor]="'hsl(' + hsv().h + ', 100%, 50%)'"
7591
+ ></div>
7592
+ </div>
7593
+
7594
+ <!-- Input + Format + Copy + Actions -->
7595
+ @if (showInput()) {
7596
+ <div class="mt-3 flex items-center gap-1.5">
7597
+ <div
7598
+ class="h-8 w-8 rounded-sm border border-border shrink-0"
7599
+ [style.backgroundColor]="displayValue()"
7600
+ ></div>
7601
+ <input
7602
+ class="flex-1 min-w-0 h-8 rounded-sm border border-border bg-background px-2 text-xs font-mono focus:outline-none focus:ring-1 focus:ring-ring"
7603
+ [value]="inputValue()"
7604
+ (input)="onInputChange($event)"
7605
+ (blur)="commitInput()"
7606
+ (keydown.enter)="commitInput()"
7607
+ />
7608
+ <button
7609
+ type="button"
7610
+ class="h-8 px-1.5 rounded-sm border border-border text-[10px] font-semibold uppercase hover:bg-accent transition-colors shrink-0"
7611
+ (click)="cycleFormat()"
7612
+ title="Switch format"
7613
+ >
7614
+ {{ currentFormat() }}
7615
+ </button>
7616
+ <button
7617
+ type="button"
7618
+ class="h-8 w-8 inline-flex items-center justify-center rounded-sm border border-border hover:bg-accent transition-colors shrink-0"
7619
+ (click)="copyColor()"
7620
+ [title]="copied() ? 'Copied!' : 'Copy color'"
7621
+ >
7622
+ @if (copied()) {
7623
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
7624
+ } @else {
7625
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
7626
+ }
7627
+ </button>
7628
+ </div>
7629
+ <!-- Secondary actions row -->
7630
+ <div class="mt-2 flex items-center gap-1.5">
7631
+ @if (showEyeDropper() && hasEyeDropper) {
7632
+ <button
7633
+ type="button"
7634
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7635
+ (click)="pickFromScreen()"
7636
+ title="Pick from screen"
7637
+ >
7638
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
7639
+ Pick
7640
+ </button>
7641
+ }
7642
+ @if (showFavorites()) {
7643
+ <button
7644
+ type="button"
7645
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7646
+ (click)="addFavorite()"
7647
+ title="Save to favorites"
7648
+ >
7649
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
7650
+ Save
7651
+ </button>
7652
+ }
7653
+ </div>
7654
+ }
7655
+
7656
+ <!-- Presets -->
7657
+ @for (preset of presets(); track $index) {
7658
+ <div class="mt-3">
7659
+ @if (preset.label) {
7660
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">{{ preset.label }}</p>
7661
+ }
7662
+ <div class="flex flex-wrap gap-1.5">
7663
+ @for (color of preset.colors; track color) {
7664
+ <button
7665
+ type="button"
7666
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7667
+ [style.backgroundColor]="color"
7668
+ [title]="color"
7669
+ (click)="selectColor(color)"
7670
+ ></button>
7671
+ }
7672
+ </div>
7673
+ </div>
7674
+ }
7675
+
7676
+ <!-- Favorites -->
7677
+ @if (showFavorites() && favorites().length > 0) {
7678
+ <div class="mt-3">
7679
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">Favorites</p>
7680
+ <div class="flex flex-wrap gap-1.5">
7681
+ @for (fav of favorites(); track fav) {
7682
+ <div class="relative group">
7683
+ <button
7684
+ type="button"
7685
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7686
+ [style.backgroundColor]="fav"
7687
+ [title]="fav"
7688
+ (click)="selectColor(fav)"
7689
+ ></button>
7690
+ <button
7691
+ type="button"
7692
+ class="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full bg-destructive text-destructive-foreground text-[8px] leading-none items-center justify-center hidden group-hover:inline-flex"
7693
+ (click)="removeFavorite(fav); $event.stopPropagation()"
7694
+ >×</button>
7695
+ </div>
7696
+ }
7697
+ </div>
7698
+ </div>
7699
+ }
7700
+ </div>
7701
+ }
7702
+ `,
7703
+ }]
7704
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], showInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInput", required: false }] }], showEyeDropper: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEyeDropper", required: false }] }], showFavorites: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFavorites", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], colorChange: [{ type: i0.Output, args: ["colorChange"] }], formatChange: [{ type: i0.Output, args: ["formatChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerEl', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panelEl', { isSignal: true }] }], satPanelRef: [{ type: i0.ViewChild, args: ['satPanel', { isSignal: true }] }], hueTrackRef: [{ type: i0.ViewChild, args: ['hueTrack', { isSignal: true }] }], onDocumentClick: [{
7705
+ type: HostListener,
7706
+ args: ['document:click', ['$event']]
7707
+ }], onEscape: [{
7708
+ type: HostListener,
7709
+ args: ['keydown.escape']
7710
+ }] } });
7711
+
7712
+ const otpCellVariants = cva('text-center font-mono font-semibold border border-border bg-background rounded-md transition-all focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed', {
7713
+ variants: {
7714
+ size: {
7715
+ sm: 'h-9 w-9 text-sm',
7716
+ md: 'h-11 w-11 text-lg',
7717
+ lg: 'h-14 w-14 text-2xl',
7718
+ },
7719
+ },
7720
+ defaultVariants: { size: 'md' },
7721
+ });
7722
+
7723
+ class SnyOtpInputComponent {
7724
+ // Public API
7725
+ length = input(6, ...(ngDevMode ? [{ debugName: "length" }] : /* istanbul ignore next */ []));
7726
+ type = input('number', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
7727
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
7728
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7729
+ mask = input(false, ...(ngDevMode ? [{ debugName: "mask" }] : /* istanbul ignore next */ []));
7730
+ autoFocus = input(true, ...(ngDevMode ? [{ debugName: "autoFocus" }] : /* istanbul ignore next */ []));
7731
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
7732
+ separator = input(null, ...(ngDevMode ? [{ debugName: "separator" }] : /* istanbul ignore next */ []));
7733
+ status = input('idle', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
7734
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
7735
+ completed = output();
7736
+ // Internal state
7737
+ digits = linkedSignal(() => Array(this.length()).fill(''), ...(ngDevMode ? [{ debugName: "digits" }] : /* istanbul ignore next */ []));
7738
+ focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : /* istanbul ignore next */ []));
7739
+ inputRefs = viewChildren('inputEl', ...(ngDevMode ? [{ debugName: "inputRefs" }] : /* istanbul ignore next */ []));
7740
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
7741
+ isDisabled = computed(() => this.disabled() || this._disabledByCva() || this.status() === 'loading', ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
7742
+ _onChange = () => { };
7743
+ _onTouched = () => { };
7744
+ // Computed
7745
+ fullValue = computed(() => this.digits().join(''), ...(ngDevMode ? [{ debugName: "fullValue" }] : /* istanbul ignore next */ []));
7746
+ isComplete = computed(() => {
7747
+ const d = this.digits();
7748
+ return d.length === this.length() && d.every((c) => c !== '');
7749
+ }, ...(ngDevMode ? [{ debugName: "isComplete" }] : /* istanbul ignore next */ []));
7750
+ constructor() {
7751
+ // Sync value → digits when value changes externally (e.g. reset)
7752
+ effect(() => {
7753
+ const val = this.value();
7754
+ untracked(() => {
7755
+ const chars = val.split('').slice(0, this.length());
7756
+ const padded = [...chars, ...Array(this.length() - chars.length).fill('')];
7757
+ const current = this.digits();
7758
+ if (padded.join('') !== current.join('')) {
7759
+ this.digits.set(padded);
7760
+ }
7761
+ });
7762
+ });
7763
+ afterNextRender(() => {
7764
+ if (this.autoFocus()) {
7765
+ this.focusInput(0);
7766
+ }
7767
+ });
7768
+ }
7769
+ // CVA
7770
+ writeValue(val) {
7771
+ const str = val ?? '';
7772
+ this.value.set(str);
7773
+ const chars = str.split('').slice(0, this.length());
7774
+ const padded = [...chars, ...Array(this.length() - chars.length).fill('')];
7775
+ this.digits.set(padded);
7776
+ }
7777
+ registerOnChange(fn) {
7778
+ this._onChange = fn;
7779
+ }
7780
+ registerOnTouched(fn) {
7781
+ this._onTouched = fn;
7782
+ }
7783
+ setDisabledState(isDisabled) {
7784
+ this._disabledByCva.set(isDisabled);
7785
+ }
7786
+ // Cell class
7787
+ cellClass(index) {
7788
+ const isFocused = this.focusedIndex() === index;
7789
+ const hasValue = this.digits()[index] !== '';
7790
+ const st = this.status();
7791
+ return cn(otpCellVariants({ size: this.size() }), st === 'idle' && isFocused && 'border-primary ring-2 ring-ring', st === 'idle' && hasValue && !isFocused && 'border-primary/50', st === 'loading' && 'border-muted-foreground/30 opacity-70', st === 'success' && 'border-green-500 bg-green-500/5', st === 'error' && 'border-destructive bg-destructive/5 animate-shake');
7792
+ }
7793
+ // Input handler
7794
+ onInput(event, index) {
7795
+ const input = event.target;
7796
+ const char = input.value.slice(-1);
7797
+ if (!this.isValidChar(char)) {
7798
+ input.value = this.digits()[index];
7799
+ return;
7800
+ }
7801
+ this.setDigit(index, char);
7802
+ if (index < this.length() - 1) {
7803
+ this.focusInput(index + 1);
7804
+ }
7805
+ this.emitValue();
7806
+ }
7807
+ // Keyboard handler
7808
+ onKeydown(event, index) {
7809
+ switch (event.key) {
7810
+ case 'Backspace':
7811
+ event.preventDefault();
7812
+ if (this.digits()[index] !== '') {
7813
+ this.setDigit(index, '');
7814
+ this.emitValue();
7815
+ }
7816
+ else if (index > 0) {
7817
+ this.setDigit(index - 1, '');
7818
+ this.focusInput(index - 1);
7819
+ this.emitValue();
7820
+ }
7821
+ break;
7822
+ case 'Delete':
7823
+ event.preventDefault();
7824
+ this.setDigit(index, '');
7825
+ this.emitValue();
7826
+ break;
7827
+ case 'ArrowLeft':
7828
+ event.preventDefault();
7829
+ if (index > 0)
7830
+ this.focusInput(index - 1);
7831
+ break;
7832
+ case 'ArrowRight':
7833
+ event.preventDefault();
7834
+ if (index < this.length() - 1)
7835
+ this.focusInput(index + 1);
7836
+ break;
7837
+ case 'Home':
7838
+ event.preventDefault();
7839
+ this.focusInput(0);
7840
+ break;
7841
+ case 'End':
7842
+ event.preventDefault();
7843
+ this.focusInput(this.length() - 1);
7844
+ break;
7845
+ }
7846
+ }
7847
+ // Paste handler
7848
+ onPaste(event, index) {
7849
+ event.preventDefault();
7850
+ const text = event.clipboardData?.getData('text') ?? '';
7851
+ const chars = text.split('').filter((c) => this.isValidChar(c));
7852
+ if (chars.length === 0)
7853
+ return;
7854
+ const newDigits = [...this.digits()];
7855
+ let lastFilledIndex = index;
7856
+ for (let i = 0; i < chars.length && index + i < this.length(); i++) {
7857
+ newDigits[index + i] = chars[i];
7858
+ lastFilledIndex = index + i;
7859
+ }
7860
+ this.digits.set(newDigits);
7861
+ const nextIndex = Math.min(lastFilledIndex + 1, this.length() - 1);
7862
+ this.focusInput(nextIndex);
7863
+ this.emitValue();
7864
+ }
7865
+ // Blur
7866
+ onBlur() {
7867
+ this.focusedIndex.set(-1);
7868
+ this._onTouched();
7869
+ }
7870
+ // Helpers
7871
+ setDigit(index, char) {
7872
+ this.digits.update((d) => {
7873
+ const next = [...d];
7874
+ next[index] = char;
7875
+ return next;
7876
+ });
7877
+ }
7878
+ emitValue() {
7879
+ const val = this.fullValue();
7880
+ this.value.set(val);
7881
+ this._onChange(val);
7882
+ if (this.isComplete()) {
7883
+ this.completed.emit(val);
7884
+ }
7885
+ }
7886
+ focusInput(index) {
7887
+ const refs = this.inputRefs();
7888
+ if (refs[index]) {
7889
+ const el = refs[index].nativeElement;
7890
+ el.focus();
7891
+ el.select();
7892
+ }
7893
+ }
7894
+ isValidChar(char) {
7895
+ if (!char || char.length !== 1)
7896
+ return false;
7897
+ if (this.type() === 'number')
7898
+ return /^[0-9]$/.test(char);
7899
+ return /^[a-zA-Z0-9]$/.test(char);
7900
+ }
7901
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyOtpInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7902
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyOtpInputComponent, isStandalone: true, selector: "sny-otp-input", inputs: { length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, mask: { classPropertyName: "mask", publicName: "mask", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", completed: "completed" }, providers: [
7903
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyOtpInputComponent), multi: true },
7904
+ ], viewQueries: [{ propertyName: "inputRefs", predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
7905
+ <div
7906
+ role="group"
7907
+ [attr.aria-label]="'OTP input, ' + length() + ' digits'"
7908
+ class="flex items-center gap-2"
7909
+ >
7910
+ @for (digit of digits(); track $index; let i = $index) {
7911
+ @if (separator() !== null && i === separator() && i > 0) {
7912
+ <span class="text-muted-foreground text-lg select-none" aria-hidden="true">—</span>
7913
+ }
7914
+ <input
7915
+ #inputEl
7916
+ [type]="mask() ? 'password' : 'text'"
7917
+ [inputMode]="type() === 'number' ? 'numeric' : 'text'"
7918
+ [attr.pattern]="type() === 'number' ? '[0-9]' : '[a-zA-Z0-9]'"
7919
+ maxlength="1"
7920
+ autocomplete="one-time-code"
7921
+ [value]="digit"
7922
+ [placeholder]="placeholder()"
7923
+ [disabled]="isDisabled()"
7924
+ [class]="cellClass(i)"
7925
+ [attr.aria-label]="'Digit ' + (i + 1) + ' of ' + length()"
7926
+ (input)="onInput($event, i)"
7927
+ (keydown)="onKeydown($event, i)"
7928
+ (paste)="onPaste($event, i)"
7929
+ (focus)="focusedIndex.set(i)"
7930
+ (blur)="onBlur()"
7931
+ />
7932
+ }
7933
+ </div>
7934
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
7935
+ }
7936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyOtpInputComponent, decorators: [{
7937
+ type: Component,
7938
+ args: [{
7939
+ selector: 'sny-otp-input',
7940
+ standalone: true,
7941
+ changeDetection: ChangeDetectionStrategy.OnPush,
7942
+ providers: [
7943
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyOtpInputComponent), multi: true },
7944
+ ],
7945
+ template: `
7946
+ <div
7947
+ role="group"
7948
+ [attr.aria-label]="'OTP input, ' + length() + ' digits'"
7949
+ class="flex items-center gap-2"
7950
+ >
7951
+ @for (digit of digits(); track $index; let i = $index) {
7952
+ @if (separator() !== null && i === separator() && i > 0) {
7953
+ <span class="text-muted-foreground text-lg select-none" aria-hidden="true">—</span>
7954
+ }
7955
+ <input
7956
+ #inputEl
7957
+ [type]="mask() ? 'password' : 'text'"
7958
+ [inputMode]="type() === 'number' ? 'numeric' : 'text'"
7959
+ [attr.pattern]="type() === 'number' ? '[0-9]' : '[a-zA-Z0-9]'"
7960
+ maxlength="1"
7961
+ autocomplete="one-time-code"
7962
+ [value]="digit"
7963
+ [placeholder]="placeholder()"
7964
+ [disabled]="isDisabled()"
7965
+ [class]="cellClass(i)"
7966
+ [attr.aria-label]="'Digit ' + (i + 1) + ' of ' + length()"
7967
+ (input)="onInput($event, i)"
7968
+ (keydown)="onKeydown($event, i)"
7969
+ (paste)="onPaste($event, i)"
7970
+ (focus)="focusedIndex.set(i)"
7971
+ (blur)="onBlur()"
7972
+ />
7973
+ }
7974
+ </div>
7975
+ `,
7976
+ }]
7977
+ }], ctorParameters: () => [], propDecorators: { length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], mask: [{ type: i0.Input, args: [{ isSignal: true, alias: "mask", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], completed: [{ type: i0.Output, args: ["completed"] }], inputRefs: [{ type: i0.ViewChildren, args: ['inputEl', { isSignal: true }] }] } });
7978
+
7979
+ class SnyCommandPaletteComponent {
7980
+ config = inject(SNY_DIALOG_DATA);
7981
+ dialogRef = inject(DialogRef);
7982
+ searchInput = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
7983
+ query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
7984
+ activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
7985
+ filteredGroups = computed(() => {
7986
+ const q = this.query().toLowerCase().trim();
7987
+ const commands = this.config.commands.filter((cmd) => {
7988
+ if (cmd.disabled)
7989
+ return false;
7990
+ if (!q)
7991
+ return true;
7992
+ return (cmd.label.toLowerCase().includes(q) ||
7993
+ cmd.description?.toLowerCase().includes(q) ||
7994
+ cmd.keywords?.some((k) => k.toLowerCase().includes(q)));
7995
+ });
7996
+ const groups = new Map();
7997
+ for (const cmd of commands) {
7998
+ const group = cmd.group ?? '';
7999
+ if (!groups.has(group))
8000
+ groups.set(group, []);
8001
+ groups.get(group).push(cmd);
8002
+ }
8003
+ return [...groups.entries()].map(([name, cmds]) => ({ name, commands: cmds }));
8004
+ }, ...(ngDevMode ? [{ debugName: "filteredGroups" }] : /* istanbul ignore next */ []));
8005
+ flatResults = computed(() => this.filteredGroups().flatMap((g) => g.commands), ...(ngDevMode ? [{ debugName: "flatResults" }] : /* istanbul ignore next */ []));
8006
+ constructor() {
8007
+ afterNextRender(() => {
8008
+ this.searchInput()?.nativeElement.focus();
8009
+ });
8010
+ }
8011
+ onQueryChange(value) {
8012
+ this.query.set(value);
8013
+ this.activeIndex.set(0);
8014
+ }
8015
+ flatIndex(cmd) {
8016
+ return this.flatResults().indexOf(cmd);
8017
+ }
8018
+ execute(cmd) {
8019
+ this.dialogRef.close();
8020
+ cmd.action();
8021
+ }
8022
+ onKeydown(event) {
8023
+ const results = this.flatResults();
8024
+ const len = results.length;
8025
+ if (len === 0 && event.key !== 'Escape')
8026
+ return;
8027
+ switch (event.key) {
8028
+ case 'ArrowDown':
8029
+ event.preventDefault();
8030
+ this.activeIndex.update((i) => (i + 1) % len);
8031
+ this.scrollActiveIntoView();
8032
+ break;
8033
+ case 'ArrowUp':
8034
+ event.preventDefault();
8035
+ this.activeIndex.update((i) => (i - 1 + len) % len);
8036
+ this.scrollActiveIntoView();
8037
+ break;
8038
+ case 'Enter':
8039
+ event.preventDefault();
8040
+ const active = results[this.activeIndex()];
8041
+ if (active)
8042
+ this.execute(active);
8043
+ break;
8044
+ }
8045
+ }
8046
+ scrollActiveIntoView() {
8047
+ requestAnimationFrame(() => {
8048
+ const el = document.querySelector(`[data-cmd-idx="${this.activeIndex()}"]`);
8049
+ el?.scrollIntoView({ block: 'nearest' });
8050
+ });
8051
+ }
8052
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8053
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyCommandPaletteComponent, isStandalone: true, selector: "sny-command-palette", host: { listeners: { "keydown": "onKeydown($event)" }, classAttribute: "block" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
8054
+ <!-- Search -->
8055
+ <div class="flex items-center gap-2 border-b border-border px-4 py-3">
8056
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground shrink-0"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
8057
+ <input
8058
+ #searchInput
8059
+ snyInput
8060
+ class="border-none shadow-none h-8 px-0 focus-visible:ring-0 focus-visible:ring-offset-0"
8061
+ [placeholder]="config.placeholder ?? 'Type a command...'"
8062
+ [value]="query()"
8063
+ (input)="onQueryChange(searchInput.value)"
8064
+ />
8065
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">Esc</kbd>
8066
+ </div>
8067
+
8068
+ <!-- Results -->
8069
+ <div class="max-h-[300px] overflow-y-auto p-2 sny-scrollbar">
8070
+ @if (flatResults().length === 0) {
8071
+ <p class="py-6 text-center text-sm text-muted-foreground">
8072
+ {{ config.emptyText ?? 'No results found.' }}
8073
+ </p>
8074
+ } @else {
8075
+ @for (group of filteredGroups(); track group.name) {
8076
+ @if (group.name) {
8077
+ <p class="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">{{ group.name }}</p>
8078
+ }
8079
+ @for (cmd of group.commands; track cmd.id) {
8080
+ @let idx = flatIndex(cmd);
8081
+ <button
8082
+ [attr.data-cmd-idx]="idx"
8083
+ [class]="
8084
+ 'w-full text-left rounded-sm px-3 py-2 text-sm transition-colors flex items-center gap-3 cursor-pointer ' +
8085
+ (idx === activeIndex()
8086
+ ? 'bg-accent text-accent-foreground'
8087
+ : 'text-foreground hover:bg-accent/50')
8088
+ "
8089
+ (click)="execute(cmd)"
8090
+ (mouseenter)="activeIndex.set(idx)"
8091
+ >
8092
+ @if (cmd.icon) {
8093
+ <span class="shrink-0 w-5 text-center text-muted-foreground" [innerHTML]="cmd.icon"></span>
8094
+ }
8095
+ <div class="flex-1 min-w-0">
8096
+ <span class="block truncate font-medium">{{ cmd.label }}</span>
8097
+ @if (cmd.description) {
8098
+ <span class="block text-xs text-muted-foreground truncate">{{ cmd.description }}</span>
8099
+ }
8100
+ </div>
8101
+ @if (cmd.shortcut) {
8102
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">{{ cmd.shortcut }}</kbd>
8103
+ }
8104
+ </button>
8105
+ }
8106
+ }
8107
+ }
8108
+ </div>
8109
+
8110
+ <!-- Footer -->
8111
+ <div class="border-t border-border px-4 py-2">
8112
+ <div class="flex items-center gap-3 text-xs text-muted-foreground">
8113
+ <span class="flex items-center gap-1">
8114
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&uarr;&darr;</kbd>
8115
+ Navigate
8116
+ </span>
8117
+ <span class="flex items-center gap-1">
8118
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&crarr;</kbd>
8119
+ Execute
8120
+ </span>
8121
+ <span class="flex items-center gap-1">
8122
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">Esc</kbd>
8123
+ Close
8124
+ </span>
8125
+ </div>
8126
+ </div>
8127
+ `, isInline: true, styles: [":host{display:block;background-color:var(--sny-background);border:1px solid var(--sny-border);border-radius:.5rem;box-shadow:0 25px 50px -12px #00000040;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: SnyInputDirective, selector: "input[snyInput], textarea[snyInput]", inputs: ["variant", "inputSize", "class", "ariaDescribedBy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8128
+ }
8129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteComponent, decorators: [{
8130
+ type: Component,
8131
+ args: [{ selector: 'sny-command-palette', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SnyInputDirective], host: {
8132
+ '(keydown)': 'onKeydown($event)',
8133
+ 'class': 'block',
8134
+ }, template: `
8135
+ <!-- Search -->
8136
+ <div class="flex items-center gap-2 border-b border-border px-4 py-3">
8137
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground shrink-0"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
8138
+ <input
8139
+ #searchInput
8140
+ snyInput
8141
+ class="border-none shadow-none h-8 px-0 focus-visible:ring-0 focus-visible:ring-offset-0"
8142
+ [placeholder]="config.placeholder ?? 'Type a command...'"
8143
+ [value]="query()"
8144
+ (input)="onQueryChange(searchInput.value)"
8145
+ />
8146
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">Esc</kbd>
8147
+ </div>
8148
+
8149
+ <!-- Results -->
8150
+ <div class="max-h-[300px] overflow-y-auto p-2 sny-scrollbar">
8151
+ @if (flatResults().length === 0) {
8152
+ <p class="py-6 text-center text-sm text-muted-foreground">
8153
+ {{ config.emptyText ?? 'No results found.' }}
8154
+ </p>
8155
+ } @else {
8156
+ @for (group of filteredGroups(); track group.name) {
8157
+ @if (group.name) {
8158
+ <p class="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">{{ group.name }}</p>
8159
+ }
8160
+ @for (cmd of group.commands; track cmd.id) {
8161
+ @let idx = flatIndex(cmd);
8162
+ <button
8163
+ [attr.data-cmd-idx]="idx"
8164
+ [class]="
8165
+ 'w-full text-left rounded-sm px-3 py-2 text-sm transition-colors flex items-center gap-3 cursor-pointer ' +
8166
+ (idx === activeIndex()
8167
+ ? 'bg-accent text-accent-foreground'
8168
+ : 'text-foreground hover:bg-accent/50')
8169
+ "
8170
+ (click)="execute(cmd)"
8171
+ (mouseenter)="activeIndex.set(idx)"
8172
+ >
8173
+ @if (cmd.icon) {
8174
+ <span class="shrink-0 w-5 text-center text-muted-foreground" [innerHTML]="cmd.icon"></span>
8175
+ }
8176
+ <div class="flex-1 min-w-0">
8177
+ <span class="block truncate font-medium">{{ cmd.label }}</span>
8178
+ @if (cmd.description) {
8179
+ <span class="block text-xs text-muted-foreground truncate">{{ cmd.description }}</span>
8180
+ }
8181
+ </div>
8182
+ @if (cmd.shortcut) {
8183
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">{{ cmd.shortcut }}</kbd>
8184
+ }
8185
+ </button>
8186
+ }
8187
+ }
8188
+ }
8189
+ </div>
8190
+
8191
+ <!-- Footer -->
8192
+ <div class="border-t border-border px-4 py-2">
8193
+ <div class="flex items-center gap-3 text-xs text-muted-foreground">
8194
+ <span class="flex items-center gap-1">
8195
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&uarr;&darr;</kbd>
8196
+ Navigate
8197
+ </span>
8198
+ <span class="flex items-center gap-1">
8199
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&crarr;</kbd>
8200
+ Execute
8201
+ </span>
8202
+ <span class="flex items-center gap-1">
8203
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">Esc</kbd>
8204
+ Close
8205
+ </span>
8206
+ </div>
8207
+ </div>
8208
+ `, styles: [":host{display:block;background-color:var(--sny-background);border:1px solid var(--sny-border);border-radius:.5rem;box-shadow:0 25px 50px -12px #00000040;overflow:hidden}\n"] }]
8209
+ }], ctorParameters: () => [], propDecorators: { searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
8210
+
8211
+ class SnyCommandPaletteService {
8212
+ dialogService = inject(SnyDialogService);
8213
+ isOpen = false;
8214
+ open(config) {
8215
+ if (this.isOpen)
8216
+ return null;
8217
+ this.isOpen = true;
8218
+ const ref = this.dialogService.open(SnyCommandPaletteComponent, {
8219
+ width: config.width ?? '32rem',
8220
+ data: config,
8221
+ });
8222
+ ref.closed.subscribe(() => {
8223
+ this.isOpen = false;
8224
+ });
8225
+ return ref;
8226
+ }
8227
+ close() {
8228
+ if (this.isOpen) {
8229
+ this.dialogService.closeAll();
8230
+ }
8231
+ }
8232
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8233
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, providedIn: 'root' });
8234
+ }
8235
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, decorators: [{
8236
+ type: Injectable,
8237
+ args: [{ providedIn: 'root' }]
8238
+ }] });
6050
8239
 
6051
8240
  class SnyValidatorDirective {
6052
8241
  control = input(null, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
@@ -6107,5 +8296,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
6107
8296
  * Generated bundle index. Do not edit.
6108
8297
  */
6109
8298
 
6110
- export { SNY_ACCORDION, SNY_ACCORDION_ITEM, SNY_CAROUSEL, SNY_CHAT_BUBBLE, SNY_CONFIG, SNY_DIALOG_DATA, SNY_DRAWER, SNY_DROPDOWN, SNY_FAB, SNY_SHEET_DATA, SNY_STEPS, SNY_TABLE, SNY_TABS, SNY_TIMELINE, SnyAccordionContentDirective, SnyAccordionDirective, SnyAccordionItemDirective, SnyAccordionTriggerDirective, SnyAlertDescriptionDirective, SnyAlertDirective, SnyAlertTitleDirective, SnyAvatarComponent, SnyBadgeDirective, SnyBreadcrumbDirective, SnyBreadcrumbItemDirective, SnyBreadcrumbLinkDirective, SnyBreadcrumbListDirective, SnyBreadcrumbPageDirective, SnyBreadcrumbSeparatorDirective, SnyBulkActionsDefDirective, SnyButtonDirective, SnyButtonGroupDirective, SnyCalendarComponent, SnyCardContentDirective, SnyCardDescriptionDirective, SnyCardDirective, SnyCardFooterDirective, SnyCardHeaderDirective, SnyCardTitleDirective, SnyCarouselContentDirective, SnyCarouselDirective, SnyCarouselItemDirective, SnyCarouselNextDirective, SnyCarouselPrevDirective, SnyCellDefDirective, SnyChatBubbleAvatarDirective, SnyChatBubbleBodyDirective, SnyChatBubbleContentDirective, SnyChatBubbleDirective, SnyChatBubbleFooterDirective, SnyChatBubbleHeaderDirective, SnyCheckboxDirective, SnyComboboxComponent, SnyDataTableComponent, SnyDialogCloseDirective, SnyDialogContentDirective, SnyDialogDescriptionDirective, SnyDialogFooterDirective, SnyDialogHeaderDirective, SnyDialogRef, SnyDialogService, SnyDialogTitleDirective, SnyDiffComponent, SnyDividerComponent, SnyDockDirective, SnyDockItemDirective, SnyDrawerContentDirective, SnyDrawerLayoutComponent, SnyDrawerLayoutDirective, SnyDrawerSideDirective, SnyDropdownContentDirective, SnyDropdownDirective, SnyDropdownTriggerDirective, SnyFabActionDirective, SnyFabDirective, SnyFabTriggerDirective, SnyFieldsetContentDirective, SnyFieldsetDirective, SnyFieldsetLegendDirective, SnyFileInputComponent, SnyHeaderCellDefDirective, SnyIndicatorBadgeDirective, SnyIndicatorDirective, SnyInputDirective, SnyKbdDirective, SnyLabelDirective, SnyLinkDirective, SnyListDirective, SnyListItemActionDirective, SnyListItemContentDirective, SnyListItemDirective, SnyListItemIconDirective, SnyLoaderComponent, SnyMenuContentDirective, SnyMenuItemDirective, SnyMenuLabelDirective, SnyMenuSeparatorDirective, SnyNavbarBrandDirective, SnyNavbarContentDirective, SnyNavbarDirective, SnyNavbarEndDirective, SnyPaginationComponent, SnyProgressComponent, SnyRadialProgressComponent, SnyRadioDirective, SnyRatingComponent, SnyRowExpandDefDirective, SnySelectComponent, SnySheetCloseDirective, SnySheetContentDirective, SnySheetDescriptionDirective, SnySheetHeaderDirective, SnySheetRef, SnySheetService, SnySheetTitleDirective, SnySkeletonDirective, SnySliderComponent, SnyStatDescriptionDirective, SnyStatDirective, SnyStatFigureDirective, SnyStatTitleDirective, SnyStatValueDirective, SnyStatusDirective, SnyStepDirective, SnyStepsDirective, SnySwitchComponent, SnyTableBodyDirective, SnyTableCaptionDirective, SnyTableCellDirective, SnyTableDirective, SnyTableFooterDirective, SnyTableHeadDirective, SnyTableHeaderDirective, SnyTableRowDirective, SnyTabsContentDirective, SnyTabsDirective, SnyTabsListDirective, SnyTabsTriggerDirective, SnyTextareaDirective, SnyTimelineDirective, SnyTimelineEndDirective, SnyTimelineItemDirective, SnyTimelineMiddleDirective, SnyTimelineStartDirective, SnyToastService, SnyToasterComponent, SnyToggleDirective, SnyTooltipDirective, SnyValidatorDirective, SnyValidatorHintDirective, ThemeService, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, cardVariants, checkboxVariants, cn, comboboxTriggerVariants, dividerVariants, dropdownContentVariants, dropdownItemVariants, fieldsetVariants, fileInputVariants, inputVariants, kbdVariants, labelVariants, linkVariants, loaderVariants, paginationItemVariants, progressBarVariants, progressTrackVariants, provideSonnyUI, radioVariants, ratingVariants, selectTriggerVariants, skeletonVariants, sliderTrackVariants, statusVariants, switchTrackVariants, tableCellVariants, tableVariants, tabsListVariants, tabsTriggerVariants, textareaVariants, toastVariants, toggleVariants, tooltipVariants };
8299
+ export { SNY_ACCORDION, SNY_ACCORDION_ITEM, SNY_CAROUSEL, SNY_CHAT_BUBBLE, SNY_CONFIG, SNY_DIALOG_DATA, SNY_DRAWER, SNY_DROPDOWN, SNY_FAB, SNY_SHEET_DATA, SNY_STEPS, SNY_TABLE, SNY_TABS, SNY_TIMELINE, SnyAccordionContentDirective, SnyAccordionDirective, SnyAccordionItemDirective, SnyAccordionTriggerDirective, SnyAlertDescriptionDirective, SnyAlertDirective, SnyAlertTitleDirective, SnyAvatarComponent, SnyBadgeDirective, SnyBreadcrumbDirective, SnyBreadcrumbItemDirective, SnyBreadcrumbLinkDirective, SnyBreadcrumbListDirective, SnyBreadcrumbPageDirective, SnyBreadcrumbSeparatorDirective, SnyBulkActionsDefDirective, SnyButtonDirective, SnyButtonGroupDirective, SnyCalendarComponent, SnyCardContentDirective, SnyCardDescriptionDirective, SnyCardDirective, SnyCardFooterDirective, SnyCardHeaderDirective, SnyCardTitleDirective, SnyCarouselContentDirective, SnyCarouselDirective, SnyCarouselItemDirective, SnyCarouselNextDirective, SnyCarouselPrevDirective, SnyCellDefDirective, SnyChatBubbleAvatarDirective, SnyChatBubbleBodyDirective, SnyChatBubbleContentDirective, SnyChatBubbleDirective, SnyChatBubbleFooterDirective, SnyChatBubbleHeaderDirective, SnyCheckboxDirective, SnyColorPickerComponent, SnyComboboxComponent, SnyCommandPaletteComponent, SnyCommandPaletteService, SnyDataTableComponent, SnyDatePickerComponent, SnyDateRangePickerComponent, SnyDialogCloseDirective, SnyDialogContentDirective, SnyDialogDescriptionDirective, SnyDialogFooterDirective, SnyDialogHeaderDirective, SnyDialogRef, SnyDialogService, SnyDialogTitleDirective, SnyDiffComponent, SnyDividerComponent, SnyDockDirective, SnyDockItemDirective, SnyDrawerContentDirective, SnyDrawerLayoutComponent, SnyDrawerLayoutDirective, SnyDrawerSideDirective, SnyDropdownContentDirective, SnyDropdownDirective, SnyDropdownTriggerDirective, SnyFabActionDirective, SnyFabDirective, SnyFabTriggerDirective, SnyFieldsetContentDirective, SnyFieldsetDirective, SnyFieldsetLegendDirective, SnyFileInputComponent, SnyHeaderCellDefDirective, SnyIndicatorBadgeDirective, SnyIndicatorDirective, SnyInputDirective, SnyKbdDirective, SnyLabelDirective, SnyLinkDirective, SnyListDirective, SnyListItemActionDirective, SnyListItemContentDirective, SnyListItemDirective, SnyListItemIconDirective, SnyLoaderComponent, SnyMenuContentDirective, SnyMenuItemDirective, SnyMenuLabelDirective, SnyMenuSeparatorDirective, SnyNavbarBrandDirective, SnyNavbarContentDirective, SnyNavbarDirective, SnyNavbarEndDirective, SnyOtpInputComponent, SnyPaginationComponent, SnyProgressComponent, SnyRadialProgressComponent, SnyRadioDirective, SnyRatingComponent, SnyRowExpandDefDirective, SnySelectComponent, SnySheetCloseDirective, SnySheetContentDirective, SnySheetDescriptionDirective, SnySheetHeaderDirective, SnySheetRef, SnySheetService, SnySheetTitleDirective, SnySkeletonDirective, SnySliderComponent, SnyStatDescriptionDirective, SnyStatDirective, SnyStatFigureDirective, SnyStatTitleDirective, SnyStatValueDirective, SnyStatusDirective, SnyStepDirective, SnyStepsDirective, SnySwitchComponent, SnyTableBodyDirective, SnyTableCaptionDirective, SnyTableCellDirective, SnyTableDirective, SnyTableFooterDirective, SnyTableHeadDirective, SnyTableHeaderDirective, SnyTableRowDirective, SnyTabsContentDirective, SnyTabsDirective, SnyTabsListDirective, SnyTabsTriggerDirective, SnyTextareaDirective, SnyTimelineDirective, SnyTimelineEndDirective, SnyTimelineItemDirective, SnyTimelineMiddleDirective, SnyTimelineStartDirective, SnyToastService, SnyToasterComponent, SnyToggleDirective, SnyTooltipDirective, SnyValidatorDirective, SnyValidatorHintDirective, ThemeService, alertVariants, avatarVariants, badgeVariants, buttonGroupVariants, buttonVariants, cardVariants, checkboxVariants, cn, colorPickerTriggerVariants, comboboxTriggerVariants, datePickerTriggerVariants, dividerVariants, dropdownContentVariants, dropdownItemVariants, fieldsetVariants, fileInputVariants, formatColor, hexToRgb, hslToRgb, hsvToRgb, inputVariants, isValidColor, kbdVariants, labelVariants, linkVariants, loaderVariants, otpCellVariants, paginationItemVariants, parseColor, progressBarVariants, progressTrackVariants, provideSonnyUI, radioVariants, ratingVariants, rgbToHex, rgbToHsl, rgbToHsv, selectTriggerVariants, skeletonVariants, sliderTrackVariants, statusVariants, switchTrackVariants, tableCellVariants, tableVariants, tabsListVariants, tabsTriggerVariants, textareaVariants, toastVariants, toggleVariants, tooltipVariants };
6111
8300
  //# sourceMappingURL=sonny-ui-core.mjs.map