@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.
- package/fesm2022/sonny-ui-core.mjs +2257 -68
- package/fesm2022/sonny-ui-core.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/calendar/calendar.component.spec.ts +87 -0
- package/src/lib/calendar/calendar.component.ts +184 -61
- package/src/lib/calendar/calendar.types.ts +24 -0
- package/src/lib/calendar/index.ts +6 -0
- package/src/lib/color-picker/color-picker.component.spec.ts +328 -0
- package/src/lib/color-picker/color-picker.component.ts +537 -0
- package/src/lib/color-picker/color-picker.types.ts +24 -0
- package/src/lib/color-picker/color-picker.utils.ts +183 -0
- package/src/lib/color-picker/color-picker.variants.ts +17 -0
- package/src/lib/color-picker/index.ts +20 -0
- package/src/lib/command-palette/command-palette.component.spec.ts +178 -0
- package/src/lib/command-palette/command-palette.component.ts +195 -0
- package/src/lib/command-palette/command-palette.service.ts +36 -0
- package/src/lib/command-palette/command-palette.types.ts +23 -0
- package/src/lib/command-palette/index.ts +7 -0
- package/src/lib/date-picker/date-picker.component.spec.ts +131 -0
- package/src/lib/date-picker/date-picker.component.ts +220 -0
- package/src/lib/date-picker/date-picker.variants.ts +17 -0
- package/src/lib/date-picker/index.ts +2 -0
- package/src/lib/date-range-picker/date-range-picker.component.spec.ts +151 -0
- package/src/lib/date-range-picker/date-range-picker.component.ts +340 -0
- package/src/lib/date-range-picker/index.ts +1 -0
- package/src/lib/otp-input/index.ts +2 -0
- package/src/lib/otp-input/otp-input.component.spec.ts +252 -0
- package/src/lib/otp-input/otp-input.component.ts +275 -0
- package/src/lib/otp-input/otp-input.variants.ts +18 -0
- 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
|
-
|
|
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.
|
|
5854
|
-
|
|
5855
|
-
|
|
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.
|
|
5927
|
+
this.viewDate.set(new Date(this.viewDate().getFullYear(), this.viewDate().getMonth() - 1, 1));
|
|
5904
5928
|
}
|
|
5905
5929
|
nextMonth() {
|
|
5906
|
-
this.viewDate.
|
|
5930
|
+
this.viewDate.set(new Date(this.viewDate().getFullYear(), this.viewDate().getMonth() + 1, 1));
|
|
5907
5931
|
}
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
this.
|
|
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
|
-
|
|
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) ||
|
|
5950
|
-
|
|
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": "
|
|
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
|
-
|
|
5962
|
-
<
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
class="
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
|
|
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"
|
|
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
|
|
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
|
-
|
|
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]': '
|
|
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
|
-
|
|
6013
|
-
<
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6019
|
-
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
class="
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
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"
|
|
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
|
|
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
|
-
|
|
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]">↑↓</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]">↵</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]">↑↓</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]">↵</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
|