@sonny-ui/core 0.1.0-alpha.15 → 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.
@@ -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, linkedSignal } 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';
@@ -6850,6 +6850,1393 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
6850
6850
  args: ['keydown.escape']
6851
6851
  }] } });
6852
6852
 
6853
+ function hexToRgb(hex) {
6854
+ const clean = hex.replace(/^#/, '');
6855
+ if (!/^[0-9a-fA-F]+$/.test(clean))
6856
+ return null;
6857
+ if (clean.length === 3) {
6858
+ const r = parseInt(clean[0] + clean[0], 16);
6859
+ const g = parseInt(clean[1] + clean[1], 16);
6860
+ const b = parseInt(clean[2] + clean[2], 16);
6861
+ return { r, g, b };
6862
+ }
6863
+ if (clean.length === 6) {
6864
+ const r = parseInt(clean.slice(0, 2), 16);
6865
+ const g = parseInt(clean.slice(2, 4), 16);
6866
+ const b = parseInt(clean.slice(4, 6), 16);
6867
+ return { r, g, b };
6868
+ }
6869
+ return null;
6870
+ }
6871
+ function rgbToHex(rgb) {
6872
+ const toHex = (n) => Math.round(Math.max(0, Math.min(255, n))).toString(16).padStart(2, '0');
6873
+ return `#${toHex(rgb.r)}${toHex(rgb.g)}${toHex(rgb.b)}`;
6874
+ }
6875
+ function rgbToHsl(rgb) {
6876
+ const r = rgb.r / 255;
6877
+ const g = rgb.g / 255;
6878
+ const b = rgb.b / 255;
6879
+ const max = Math.max(r, g, b);
6880
+ const min = Math.min(r, g, b);
6881
+ const l = (max + min) / 2;
6882
+ let h = 0;
6883
+ let s = 0;
6884
+ if (max !== min) {
6885
+ const d = max - min;
6886
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
6887
+ switch (max) {
6888
+ case r:
6889
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
6890
+ break;
6891
+ case g:
6892
+ h = ((b - r) / d + 2) / 6;
6893
+ break;
6894
+ case b:
6895
+ h = ((r - g) / d + 4) / 6;
6896
+ break;
6897
+ }
6898
+ }
6899
+ return {
6900
+ h: Math.round(h * 360),
6901
+ s: Math.round(s * 100),
6902
+ l: Math.round(l * 100),
6903
+ };
6904
+ }
6905
+ function hslToRgb(hsl) {
6906
+ const h = hsl.h / 360;
6907
+ const s = hsl.s / 100;
6908
+ const l = hsl.l / 100;
6909
+ if (s === 0) {
6910
+ const v = Math.round(l * 255);
6911
+ return { r: v, g: v, b: v };
6912
+ }
6913
+ const hue2rgb = (p, q, t) => {
6914
+ if (t < 0)
6915
+ t += 1;
6916
+ if (t > 1)
6917
+ t -= 1;
6918
+ if (t < 1 / 6)
6919
+ return p + (q - p) * 6 * t;
6920
+ if (t < 1 / 2)
6921
+ return q;
6922
+ if (t < 2 / 3)
6923
+ return p + (q - p) * (2 / 3 - t) * 6;
6924
+ return p;
6925
+ };
6926
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
6927
+ const p = 2 * l - q;
6928
+ return {
6929
+ r: Math.round(hue2rgb(p, q, h + 1 / 3) * 255),
6930
+ g: Math.round(hue2rgb(p, q, h) * 255),
6931
+ b: Math.round(hue2rgb(p, q, h - 1 / 3) * 255),
6932
+ };
6933
+ }
6934
+ function rgbToHsv(rgb) {
6935
+ const r = rgb.r / 255;
6936
+ const g = rgb.g / 255;
6937
+ const b = rgb.b / 255;
6938
+ const max = Math.max(r, g, b);
6939
+ const min = Math.min(r, g, b);
6940
+ const d = max - min;
6941
+ let h = 0;
6942
+ const s = max === 0 ? 0 : d / max;
6943
+ const v = max;
6944
+ if (max !== min) {
6945
+ switch (max) {
6946
+ case r:
6947
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
6948
+ break;
6949
+ case g:
6950
+ h = ((b - r) / d + 2) / 6;
6951
+ break;
6952
+ case b:
6953
+ h = ((r - g) / d + 4) / 6;
6954
+ break;
6955
+ }
6956
+ }
6957
+ return { h: Math.round(h * 360), s, v };
6958
+ }
6959
+ function hsvToRgb(hsv) {
6960
+ const h = hsv.h / 360;
6961
+ const s = hsv.s;
6962
+ const v = hsv.v;
6963
+ const i = Math.floor(h * 6);
6964
+ const f = h * 6 - i;
6965
+ const p = v * (1 - s);
6966
+ const q = v * (1 - f * s);
6967
+ const t = v * (1 - (1 - f) * s);
6968
+ let r, g, b;
6969
+ switch (i % 6) {
6970
+ case 0:
6971
+ r = v;
6972
+ g = t;
6973
+ b = p;
6974
+ break;
6975
+ case 1:
6976
+ r = q;
6977
+ g = v;
6978
+ b = p;
6979
+ break;
6980
+ case 2:
6981
+ r = p;
6982
+ g = v;
6983
+ b = t;
6984
+ break;
6985
+ case 3:
6986
+ r = p;
6987
+ g = q;
6988
+ b = v;
6989
+ break;
6990
+ case 4:
6991
+ r = t;
6992
+ g = p;
6993
+ b = v;
6994
+ break;
6995
+ default:
6996
+ r = v;
6997
+ g = p;
6998
+ b = q;
6999
+ break;
7000
+ }
7001
+ return {
7002
+ r: Math.round(r * 255),
7003
+ g: Math.round(g * 255),
7004
+ b: Math.round(b * 255),
7005
+ };
7006
+ }
7007
+ function parseColor(input) {
7008
+ const trimmed = input.trim().toLowerCase();
7009
+ // HEX
7010
+ if (trimmed.startsWith('#')) {
7011
+ return hexToRgb(trimmed);
7012
+ }
7013
+ // rgb(r, g, b)
7014
+ const rgbMatch = trimmed.match(/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/);
7015
+ if (rgbMatch) {
7016
+ return {
7017
+ r: Math.min(255, parseInt(rgbMatch[1])),
7018
+ g: Math.min(255, parseInt(rgbMatch[2])),
7019
+ b: Math.min(255, parseInt(rgbMatch[3])),
7020
+ };
7021
+ }
7022
+ // hsl(h, s%, l%)
7023
+ const hslMatch = trimmed.match(/^hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)$/);
7024
+ if (hslMatch) {
7025
+ return hslToRgb({
7026
+ h: Math.min(360, parseInt(hslMatch[1])),
7027
+ s: Math.min(100, parseInt(hslMatch[2])),
7028
+ l: Math.min(100, parseInt(hslMatch[3])),
7029
+ });
7030
+ }
7031
+ return null;
7032
+ }
7033
+ function formatColor(rgb, format, alpha) {
7034
+ switch (format) {
7035
+ case 'hex':
7036
+ return rgbToHex(rgb);
7037
+ case 'rgb':
7038
+ if (alpha !== undefined && alpha < 1) {
7039
+ return `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${alpha.toFixed(2)})`;
7040
+ }
7041
+ return `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
7042
+ case 'hsl': {
7043
+ const hsl = rgbToHsl(rgb);
7044
+ if (alpha !== undefined && alpha < 1) {
7045
+ return `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${alpha.toFixed(2)})`;
7046
+ }
7047
+ return `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
7048
+ }
7049
+ }
7050
+ }
7051
+ function isValidColor(input) {
7052
+ return parseColor(input) !== null;
7053
+ }
7054
+
7055
+ const colorPickerTriggerVariants = cva('inline-flex w-full items-center gap-2 whitespace-nowrap rounded-sm border border-border bg-background px-3 py-2 text-sm ring-offset-background transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', {
7056
+ variants: {
7057
+ size: {
7058
+ sm: 'h-9 text-xs',
7059
+ md: 'h-10 text-sm',
7060
+ lg: 'h-11 text-base',
7061
+ },
7062
+ },
7063
+ defaultVariants: { size: 'md' },
7064
+ });
7065
+
7066
+ class SnyColorPickerComponent {
7067
+ // Public API
7068
+ value = model('#000000', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
7069
+ format = input('hex', ...(ngDevMode ? [{ debugName: "format" }] : /* istanbul ignore next */ []));
7070
+ presets = input([], ...(ngDevMode ? [{ debugName: "presets" }] : /* istanbul ignore next */ []));
7071
+ showInput = input(true, ...(ngDevMode ? [{ debugName: "showInput" }] : /* istanbul ignore next */ []));
7072
+ showEyeDropper = input(true, ...(ngDevMode ? [{ debugName: "showEyeDropper" }] : /* istanbul ignore next */ []));
7073
+ showFavorites = input(false, ...(ngDevMode ? [{ debugName: "showFavorites" }] : /* istanbul ignore next */ []));
7074
+ inline = input(false, ...(ngDevMode ? [{ debugName: "inline" }] : /* istanbul ignore next */ []));
7075
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7076
+ placeholder = input('Pick a color...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
7077
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
7078
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
7079
+ colorChange = output();
7080
+ formatChange = output();
7081
+ // Internal state
7082
+ hsv = signal({ h: 0, s: 0, v: 0 }, ...(ngDevMode ? [{ debugName: "hsv" }] : /* istanbul ignore next */ []));
7083
+ currentFormat = signal('hex', ...(ngDevMode ? [{ debugName: "currentFormat" }] : /* istanbul ignore next */ []));
7084
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
7085
+ open = signal(false, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
7086
+ favorites = signal([], ...(ngDevMode ? [{ debugName: "favorites" }] : /* istanbul ignore next */ []));
7087
+ copied = signal(false, ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
7088
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
7089
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
7090
+ triggerRef = viewChild('triggerEl', ...(ngDevMode ? [{ debugName: "triggerRef" }] : /* istanbul ignore next */ []));
7091
+ panelRef = viewChild('panelEl', ...(ngDevMode ? [{ debugName: "panelRef" }] : /* istanbul ignore next */ []));
7092
+ satPanelRef = viewChild('satPanel', ...(ngDevMode ? [{ debugName: "satPanelRef" }] : /* istanbul ignore next */ []));
7093
+ hueTrackRef = viewChild('hueTrack', ...(ngDevMode ? [{ debugName: "hueTrackRef" }] : /* istanbul ignore next */ []));
7094
+ elRef = inject(ElementRef);
7095
+ moveHandler = null;
7096
+ upHandler = null;
7097
+ scrollHandler = null;
7098
+ resizeHandler = null;
7099
+ _onChange = () => { };
7100
+ onTouched = () => { };
7101
+ hasEyeDropper = typeof window !== 'undefined' && 'EyeDropper' in window;
7102
+ // Computed
7103
+ rgb = computed(() => hsvToRgb(this.hsv()), ...(ngDevMode ? [{ debugName: "rgb" }] : /* istanbul ignore next */ []));
7104
+ displayValue = computed(() => formatColor(this.rgb(), this.currentFormat()), ...(ngDevMode ? [{ debugName: "displayValue" }] : /* istanbul ignore next */ []));
7105
+ saturationBg = computed(() => `linear-gradient(to right, #fff, hsl(${this.hsv().h}, 100%, 50%))`, ...(ngDevMode ? [{ debugName: "saturationBg" }] : /* istanbul ignore next */ []));
7106
+ triggerClass = computed(() => cn(colorPickerTriggerVariants({ size: this.size() }), this.class()), ...(ngDevMode ? [{ debugName: "triggerClass" }] : /* istanbul ignore next */ []));
7107
+ panelClass = computed(() => this.inline()
7108
+ ? 'inline-block p-3 rounded-md border border-border bg-popover text-popover-foreground w-60'
7109
+ : 'fixed z-50 p-3 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95 w-60', ...(ngDevMode ? [{ debugName: "panelClass" }] : /* istanbul ignore next */ []));
7110
+ constructor() {
7111
+ // Sync format input
7112
+ effect(() => {
7113
+ const fmt = this.format();
7114
+ untracked(() => this.currentFormat.set(fmt));
7115
+ });
7116
+ // Sync value → HSV when value changes externally
7117
+ effect(() => {
7118
+ const val = this.value();
7119
+ untracked(() => {
7120
+ const rgb = parseColor(val);
7121
+ if (rgb) {
7122
+ this.hsv.set(rgbToHsv(rgb));
7123
+ this.inputValue.set(this.displayValue());
7124
+ }
7125
+ });
7126
+ });
7127
+ }
7128
+ // CVA
7129
+ writeValue(val) {
7130
+ this.value.set(val ?? '#000000');
7131
+ }
7132
+ registerOnChange(fn) {
7133
+ this._onChange = fn;
7134
+ }
7135
+ registerOnTouched(fn) {
7136
+ this.onTouched = fn;
7137
+ }
7138
+ setDisabledState(isDisabled) {
7139
+ this._disabledByCva.set(isDisabled);
7140
+ }
7141
+ // Emit helper
7142
+ emitColor() {
7143
+ const formatted = this.displayValue();
7144
+ this.value.set(formatted);
7145
+ this.inputValue.set(formatted);
7146
+ this._onChange(formatted);
7147
+ this.colorChange.emit(formatted);
7148
+ }
7149
+ // Saturation panel
7150
+ onSatPanelDown(event) {
7151
+ event.preventDefault();
7152
+ this.updateSatFromPosition(event.clientX, event.clientY);
7153
+ this.startDrag((e) => {
7154
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7155
+ const y = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
7156
+ this.updateSatFromPosition(x, y);
7157
+ });
7158
+ }
7159
+ onSatPanelTouch(event) {
7160
+ this.updateSatFromPosition(event.touches[0].clientX, event.touches[0].clientY);
7161
+ this.startDrag((e) => {
7162
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7163
+ const y = e instanceof MouseEvent ? e.clientY : e.touches[0].clientY;
7164
+ this.updateSatFromPosition(x, y);
7165
+ }, true);
7166
+ }
7167
+ updateSatFromPosition(clientX, clientY) {
7168
+ const panel = this.satPanelRef()?.nativeElement;
7169
+ if (!panel)
7170
+ return;
7171
+ const rect = panel.getBoundingClientRect();
7172
+ const s = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
7173
+ const v = Math.max(0, Math.min(1, 1 - (clientY - rect.top) / rect.height));
7174
+ this.hsv.update((prev) => ({ ...prev, s, v }));
7175
+ this.emitColor();
7176
+ }
7177
+ // Hue slider
7178
+ onHueDown(event) {
7179
+ event.preventDefault();
7180
+ this.updateHueFromPosition(event.clientX);
7181
+ this.startDrag((e) => {
7182
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7183
+ this.updateHueFromPosition(x);
7184
+ });
7185
+ }
7186
+ onHueTouch(event) {
7187
+ this.updateHueFromPosition(event.touches[0].clientX);
7188
+ this.startDrag((e) => {
7189
+ const x = e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
7190
+ this.updateHueFromPosition(x);
7191
+ }, true);
7192
+ }
7193
+ updateHueFromPosition(clientX) {
7194
+ const track = this.hueTrackRef()?.nativeElement;
7195
+ if (!track)
7196
+ return;
7197
+ const rect = track.getBoundingClientRect();
7198
+ const percent = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
7199
+ const h = Math.round(percent * 360);
7200
+ this.hsv.update((prev) => ({ ...prev, h }));
7201
+ this.emitColor();
7202
+ }
7203
+ // Drag helpers (same pattern as slider component)
7204
+ startDrag(handler, touch = false) {
7205
+ this.removeDragListeners();
7206
+ this.moveHandler = handler;
7207
+ this.upHandler = () => {
7208
+ this.onTouched();
7209
+ this.removeDragListeners();
7210
+ };
7211
+ if (touch) {
7212
+ document.addEventListener('touchmove', this.moveHandler, { passive: true });
7213
+ document.addEventListener('touchend', this.upHandler);
7214
+ }
7215
+ else {
7216
+ document.addEventListener('mousemove', this.moveHandler);
7217
+ document.addEventListener('mouseup', this.upHandler);
7218
+ }
7219
+ }
7220
+ removeDragListeners() {
7221
+ if (this.moveHandler) {
7222
+ document.removeEventListener('mousemove', this.moveHandler);
7223
+ document.removeEventListener('touchmove', this.moveHandler);
7224
+ this.moveHandler = null;
7225
+ }
7226
+ if (this.upHandler) {
7227
+ document.removeEventListener('mouseup', this.upHandler);
7228
+ document.removeEventListener('touchend', this.upHandler);
7229
+ this.upHandler = null;
7230
+ }
7231
+ }
7232
+ // Input
7233
+ onInputChange(event) {
7234
+ this.inputValue.set(event.target.value);
7235
+ }
7236
+ commitInput() {
7237
+ const val = this.inputValue().trim();
7238
+ if (isValidColor(val)) {
7239
+ const rgb = parseColor(val);
7240
+ this.hsv.set(rgbToHsv(rgb));
7241
+ this.emitColor();
7242
+ }
7243
+ else {
7244
+ this.inputValue.set(this.displayValue());
7245
+ }
7246
+ }
7247
+ // Format
7248
+ cycleFormat() {
7249
+ const formats = ['hex', 'rgb', 'hsl'];
7250
+ const idx = formats.indexOf(this.currentFormat());
7251
+ const next = formats[(idx + 1) % formats.length];
7252
+ this.currentFormat.set(next);
7253
+ this.inputValue.set(this.displayValue());
7254
+ this.formatChange.emit(next);
7255
+ }
7256
+ // Copy
7257
+ copyColor() {
7258
+ navigator.clipboard.writeText(this.displayValue());
7259
+ this.copied.set(true);
7260
+ setTimeout(() => this.copied.set(false), 2000);
7261
+ }
7262
+ // Presets & favorites
7263
+ selectColor(color) {
7264
+ const rgb = parseColor(color);
7265
+ if (rgb) {
7266
+ this.hsv.set(rgbToHsv(rgb));
7267
+ this.emitColor();
7268
+ }
7269
+ }
7270
+ addFavorite() {
7271
+ const hex = rgbToHex(this.rgb());
7272
+ this.favorites.update((favs) => favs.includes(hex) ? favs : [...favs, hex]);
7273
+ }
7274
+ removeFavorite(color) {
7275
+ this.favorites.update((favs) => favs.filter((f) => f !== color));
7276
+ }
7277
+ // EyeDropper
7278
+ async pickFromScreen() {
7279
+ if (!this.hasEyeDropper)
7280
+ return;
7281
+ try {
7282
+ const dropper = new window.EyeDropper();
7283
+ const result = await dropper.open();
7284
+ this.selectColor(result.sRGBHex);
7285
+ }
7286
+ catch {
7287
+ // User cancelled
7288
+ }
7289
+ }
7290
+ // Popover
7291
+ toggle() {
7292
+ if (this.open()) {
7293
+ this.close();
7294
+ }
7295
+ else {
7296
+ this.open.set(true);
7297
+ this.addPositionListeners();
7298
+ setTimeout(() => this.updatePanelPosition());
7299
+ }
7300
+ }
7301
+ close() {
7302
+ this.open.set(false);
7303
+ this.removePositionListeners();
7304
+ }
7305
+ updatePanelPosition() {
7306
+ if (this.inline())
7307
+ return;
7308
+ const trigger = this.triggerRef()?.nativeElement;
7309
+ if (!trigger)
7310
+ return;
7311
+ const rect = trigger.getBoundingClientRect();
7312
+ const panel = this.panelRef()?.nativeElement;
7313
+ if (panel) {
7314
+ panel.style.top = `${rect.bottom + 4}px`;
7315
+ panel.style.left = `${rect.left}px`;
7316
+ }
7317
+ }
7318
+ addPositionListeners() {
7319
+ this.removePositionListeners();
7320
+ this.scrollHandler = () => requestAnimationFrame(() => this.updatePanelPosition());
7321
+ this.resizeHandler = () => requestAnimationFrame(() => this.updatePanelPosition());
7322
+ document.addEventListener('scroll', this.scrollHandler, { capture: true, passive: true });
7323
+ window.addEventListener('resize', this.resizeHandler, { passive: true });
7324
+ }
7325
+ removePositionListeners() {
7326
+ if (this.scrollHandler) {
7327
+ document.removeEventListener('scroll', this.scrollHandler, { capture: true });
7328
+ this.scrollHandler = null;
7329
+ }
7330
+ if (this.resizeHandler) {
7331
+ window.removeEventListener('resize', this.resizeHandler);
7332
+ this.resizeHandler = null;
7333
+ }
7334
+ }
7335
+ onDocumentClick(event) {
7336
+ if (!this.elRef.nativeElement.contains(event.target)) {
7337
+ this.close();
7338
+ }
7339
+ }
7340
+ onEscape() {
7341
+ this.close();
7342
+ }
7343
+ ngOnDestroy() {
7344
+ this.removeDragListeners();
7345
+ this.removePositionListeners();
7346
+ }
7347
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyColorPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7348
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyColorPickerComponent, isStandalone: true, selector: "sny-color-picker", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, format: { classPropertyName: "format", publicName: "format", isSignal: true, isRequired: false, transformFunction: null }, presets: { classPropertyName: "presets", publicName: "presets", isSignal: true, isRequired: false, transformFunction: null }, showInput: { classPropertyName: "showInput", publicName: "showInput", isSignal: true, isRequired: false, transformFunction: null }, showEyeDropper: { classPropertyName: "showEyeDropper", publicName: "showEyeDropper", isSignal: true, isRequired: false, transformFunction: null }, showFavorites: { classPropertyName: "showFavorites", publicName: "showFavorites", isSignal: true, isRequired: false, transformFunction: null }, inline: { classPropertyName: "inline", publicName: "inline", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", colorChange: "colorChange", formatChange: "formatChange" }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, classAttribute: "relative inline-block" }, providers: [
7349
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyColorPickerComponent), multi: true },
7350
+ ], viewQueries: [{ propertyName: "triggerRef", first: true, predicate: ["triggerEl"], descendants: true, isSignal: true }, { propertyName: "panelRef", first: true, predicate: ["panelEl"], descendants: true, isSignal: true }, { propertyName: "satPanelRef", first: true, predicate: ["satPanel"], descendants: true, isSignal: true }, { propertyName: "hueTrackRef", first: true, predicate: ["hueTrack"], descendants: true, isSignal: true }], ngImport: i0, template: `
7351
+ <!-- Trigger -->
7352
+ @if (!inline()) {
7353
+ <button
7354
+ #triggerEl
7355
+ type="button"
7356
+ role="combobox"
7357
+ [attr.aria-expanded]="open()"
7358
+ aria-haspopup="dialog"
7359
+ [disabled]="isDisabled()"
7360
+ [class]="triggerClass()"
7361
+ (click)="toggle()"
7362
+ (blur)="onTouched()"
7363
+ >
7364
+ <div
7365
+ class="h-5 w-5 rounded-sm border border-border shrink-0"
7366
+ [style.backgroundColor]="displayValue()"
7367
+ ></div>
7368
+ <span class="truncate">{{ displayValue() || placeholder() }}</span>
7369
+ </button>
7370
+ }
7371
+
7372
+ <!-- Panel -->
7373
+ @if (open() || inline()) {
7374
+ <div
7375
+ #panelEl
7376
+ [class]="panelClass()"
7377
+ role="dialog"
7378
+ aria-modal="true"
7379
+ aria-label="Color picker"
7380
+ >
7381
+ <!-- Saturation/Brightness Panel -->
7382
+ <div
7383
+ #satPanel
7384
+ class="relative h-36 w-full rounded-md cursor-crosshair overflow-hidden"
7385
+ [style.background]="saturationBg()"
7386
+ (mousedown)="onSatPanelDown($event)"
7387
+ (touchstart)="onSatPanelTouch($event)"
7388
+ >
7389
+ <div class="absolute inset-0 bg-gradient-to-t from-black to-transparent"></div>
7390
+ <div
7391
+ class="absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7392
+ [style.left.%]="hsv().s * 100"
7393
+ [style.top.%]="(1 - hsv().v) * 100"
7394
+ ></div>
7395
+ </div>
7396
+
7397
+ <!-- Hue Slider -->
7398
+ <div
7399
+ #hueTrack
7400
+ class="relative h-3 w-full rounded-full cursor-pointer mt-3"
7401
+ style="background: linear-gradient(to right, hsl(0,100%,50%), hsl(60,100%,50%), hsl(120,100%,50%), hsl(180,100%,50%), hsl(240,100%,50%), hsl(300,100%,50%), hsl(360,100%,50%))"
7402
+ (mousedown)="onHueDown($event)"
7403
+ (touchstart)="onHueTouch($event)"
7404
+ >
7405
+ <div
7406
+ class="absolute top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7407
+ [style.left.%]="hsv().h / 360 * 100"
7408
+ [style.backgroundColor]="'hsl(' + hsv().h + ', 100%, 50%)'"
7409
+ ></div>
7410
+ </div>
7411
+
7412
+ <!-- Input + Format + Copy + Actions -->
7413
+ @if (showInput()) {
7414
+ <div class="mt-3 flex items-center gap-1.5">
7415
+ <div
7416
+ class="h-8 w-8 rounded-sm border border-border shrink-0"
7417
+ [style.backgroundColor]="displayValue()"
7418
+ ></div>
7419
+ <input
7420
+ class="flex-1 min-w-0 h-8 rounded-sm border border-border bg-background px-2 text-xs font-mono focus:outline-none focus:ring-1 focus:ring-ring"
7421
+ [value]="inputValue()"
7422
+ (input)="onInputChange($event)"
7423
+ (blur)="commitInput()"
7424
+ (keydown.enter)="commitInput()"
7425
+ />
7426
+ <button
7427
+ type="button"
7428
+ class="h-8 px-1.5 rounded-sm border border-border text-[10px] font-semibold uppercase hover:bg-accent transition-colors shrink-0"
7429
+ (click)="cycleFormat()"
7430
+ title="Switch format"
7431
+ >
7432
+ {{ currentFormat() }}
7433
+ </button>
7434
+ <button
7435
+ type="button"
7436
+ class="h-8 w-8 inline-flex items-center justify-center rounded-sm border border-border hover:bg-accent transition-colors shrink-0"
7437
+ (click)="copyColor()"
7438
+ [title]="copied() ? 'Copied!' : 'Copy color'"
7439
+ >
7440
+ @if (copied()) {
7441
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
7442
+ } @else {
7443
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
7444
+ }
7445
+ </button>
7446
+ </div>
7447
+ <!-- Secondary actions row -->
7448
+ <div class="mt-2 flex items-center gap-1.5">
7449
+ @if (showEyeDropper() && hasEyeDropper) {
7450
+ <button
7451
+ type="button"
7452
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7453
+ (click)="pickFromScreen()"
7454
+ title="Pick from screen"
7455
+ >
7456
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
7457
+ Pick
7458
+ </button>
7459
+ }
7460
+ @if (showFavorites()) {
7461
+ <button
7462
+ type="button"
7463
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7464
+ (click)="addFavorite()"
7465
+ title="Save to favorites"
7466
+ >
7467
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
7468
+ Save
7469
+ </button>
7470
+ }
7471
+ </div>
7472
+ }
7473
+
7474
+ <!-- Presets -->
7475
+ @for (preset of presets(); track $index) {
7476
+ <div class="mt-3">
7477
+ @if (preset.label) {
7478
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">{{ preset.label }}</p>
7479
+ }
7480
+ <div class="flex flex-wrap gap-1.5">
7481
+ @for (color of preset.colors; track color) {
7482
+ <button
7483
+ type="button"
7484
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7485
+ [style.backgroundColor]="color"
7486
+ [title]="color"
7487
+ (click)="selectColor(color)"
7488
+ ></button>
7489
+ }
7490
+ </div>
7491
+ </div>
7492
+ }
7493
+
7494
+ <!-- Favorites -->
7495
+ @if (showFavorites() && favorites().length > 0) {
7496
+ <div class="mt-3">
7497
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">Favorites</p>
7498
+ <div class="flex flex-wrap gap-1.5">
7499
+ @for (fav of favorites(); track fav) {
7500
+ <div class="relative group">
7501
+ <button
7502
+ type="button"
7503
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7504
+ [style.backgroundColor]="fav"
7505
+ [title]="fav"
7506
+ (click)="selectColor(fav)"
7507
+ ></button>
7508
+ <button
7509
+ type="button"
7510
+ class="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full bg-destructive text-destructive-foreground text-[8px] leading-none items-center justify-center hidden group-hover:inline-flex"
7511
+ (click)="removeFavorite(fav); $event.stopPropagation()"
7512
+ >×</button>
7513
+ </div>
7514
+ }
7515
+ </div>
7516
+ </div>
7517
+ }
7518
+ </div>
7519
+ }
7520
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
7521
+ }
7522
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyColorPickerComponent, decorators: [{
7523
+ type: Component,
7524
+ args: [{
7525
+ selector: 'sny-color-picker',
7526
+ standalone: true,
7527
+ changeDetection: ChangeDetectionStrategy.OnPush,
7528
+ host: { class: 'relative inline-block' },
7529
+ providers: [
7530
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyColorPickerComponent), multi: true },
7531
+ ],
7532
+ template: `
7533
+ <!-- Trigger -->
7534
+ @if (!inline()) {
7535
+ <button
7536
+ #triggerEl
7537
+ type="button"
7538
+ role="combobox"
7539
+ [attr.aria-expanded]="open()"
7540
+ aria-haspopup="dialog"
7541
+ [disabled]="isDisabled()"
7542
+ [class]="triggerClass()"
7543
+ (click)="toggle()"
7544
+ (blur)="onTouched()"
7545
+ >
7546
+ <div
7547
+ class="h-5 w-5 rounded-sm border border-border shrink-0"
7548
+ [style.backgroundColor]="displayValue()"
7549
+ ></div>
7550
+ <span class="truncate">{{ displayValue() || placeholder() }}</span>
7551
+ </button>
7552
+ }
7553
+
7554
+ <!-- Panel -->
7555
+ @if (open() || inline()) {
7556
+ <div
7557
+ #panelEl
7558
+ [class]="panelClass()"
7559
+ role="dialog"
7560
+ aria-modal="true"
7561
+ aria-label="Color picker"
7562
+ >
7563
+ <!-- Saturation/Brightness Panel -->
7564
+ <div
7565
+ #satPanel
7566
+ class="relative h-36 w-full rounded-md cursor-crosshair overflow-hidden"
7567
+ [style.background]="saturationBg()"
7568
+ (mousedown)="onSatPanelDown($event)"
7569
+ (touchstart)="onSatPanelTouch($event)"
7570
+ >
7571
+ <div class="absolute inset-0 bg-gradient-to-t from-black to-transparent"></div>
7572
+ <div
7573
+ class="absolute h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7574
+ [style.left.%]="hsv().s * 100"
7575
+ [style.top.%]="(1 - hsv().v) * 100"
7576
+ ></div>
7577
+ </div>
7578
+
7579
+ <!-- Hue Slider -->
7580
+ <div
7581
+ #hueTrack
7582
+ class="relative h-3 w-full rounded-full cursor-pointer mt-3"
7583
+ style="background: linear-gradient(to right, hsl(0,100%,50%), hsl(60,100%,50%), hsl(120,100%,50%), hsl(180,100%,50%), hsl(240,100%,50%), hsl(300,100%,50%), hsl(360,100%,50%))"
7584
+ (mousedown)="onHueDown($event)"
7585
+ (touchstart)="onHueTouch($event)"
7586
+ >
7587
+ <div
7588
+ class="absolute top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-md pointer-events-none"
7589
+ [style.left.%]="hsv().h / 360 * 100"
7590
+ [style.backgroundColor]="'hsl(' + hsv().h + ', 100%, 50%)'"
7591
+ ></div>
7592
+ </div>
7593
+
7594
+ <!-- Input + Format + Copy + Actions -->
7595
+ @if (showInput()) {
7596
+ <div class="mt-3 flex items-center gap-1.5">
7597
+ <div
7598
+ class="h-8 w-8 rounded-sm border border-border shrink-0"
7599
+ [style.backgroundColor]="displayValue()"
7600
+ ></div>
7601
+ <input
7602
+ class="flex-1 min-w-0 h-8 rounded-sm border border-border bg-background px-2 text-xs font-mono focus:outline-none focus:ring-1 focus:ring-ring"
7603
+ [value]="inputValue()"
7604
+ (input)="onInputChange($event)"
7605
+ (blur)="commitInput()"
7606
+ (keydown.enter)="commitInput()"
7607
+ />
7608
+ <button
7609
+ type="button"
7610
+ class="h-8 px-1.5 rounded-sm border border-border text-[10px] font-semibold uppercase hover:bg-accent transition-colors shrink-0"
7611
+ (click)="cycleFormat()"
7612
+ title="Switch format"
7613
+ >
7614
+ {{ currentFormat() }}
7615
+ </button>
7616
+ <button
7617
+ type="button"
7618
+ class="h-8 w-8 inline-flex items-center justify-center rounded-sm border border-border hover:bg-accent transition-colors shrink-0"
7619
+ (click)="copyColor()"
7620
+ [title]="copied() ? 'Copied!' : 'Copy color'"
7621
+ >
7622
+ @if (copied()) {
7623
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
7624
+ } @else {
7625
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
7626
+ }
7627
+ </button>
7628
+ </div>
7629
+ <!-- Secondary actions row -->
7630
+ <div class="mt-2 flex items-center gap-1.5">
7631
+ @if (showEyeDropper() && hasEyeDropper) {
7632
+ <button
7633
+ type="button"
7634
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7635
+ (click)="pickFromScreen()"
7636
+ title="Pick from screen"
7637
+ >
7638
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m2 22 1-1h3l9-9"/><path d="M3 21v-3l9-9"/><path d="m15 6 3.4-3.4a2.1 2.1 0 1 1 3 3L18 9l.4.4a2.1 2.1 0 1 1-3 3l-3.8-3.8a2.1 2.1 0 1 1 3-3l.4.4Z"/></svg>
7639
+ Pick
7640
+ </button>
7641
+ }
7642
+ @if (showFavorites()) {
7643
+ <button
7644
+ type="button"
7645
+ class="h-7 px-2 inline-flex items-center gap-1.5 rounded-sm border border-border text-xs text-muted-foreground hover:bg-accent hover:text-foreground transition-colors"
7646
+ (click)="addFavorite()"
7647
+ title="Save to favorites"
7648
+ >
7649
+ <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg>
7650
+ Save
7651
+ </button>
7652
+ }
7653
+ </div>
7654
+ }
7655
+
7656
+ <!-- Presets -->
7657
+ @for (preset of presets(); track $index) {
7658
+ <div class="mt-3">
7659
+ @if (preset.label) {
7660
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">{{ preset.label }}</p>
7661
+ }
7662
+ <div class="flex flex-wrap gap-1.5">
7663
+ @for (color of preset.colors; track color) {
7664
+ <button
7665
+ type="button"
7666
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7667
+ [style.backgroundColor]="color"
7668
+ [title]="color"
7669
+ (click)="selectColor(color)"
7670
+ ></button>
7671
+ }
7672
+ </div>
7673
+ </div>
7674
+ }
7675
+
7676
+ <!-- Favorites -->
7677
+ @if (showFavorites() && favorites().length > 0) {
7678
+ <div class="mt-3">
7679
+ <p class="text-xs font-medium text-muted-foreground mb-1.5">Favorites</p>
7680
+ <div class="flex flex-wrap gap-1.5">
7681
+ @for (fav of favorites(); track fav) {
7682
+ <div class="relative group">
7683
+ <button
7684
+ type="button"
7685
+ class="h-6 w-6 rounded-sm border border-border hover:scale-110 transition-transform cursor-pointer"
7686
+ [style.backgroundColor]="fav"
7687
+ [title]="fav"
7688
+ (click)="selectColor(fav)"
7689
+ ></button>
7690
+ <button
7691
+ type="button"
7692
+ class="absolute -top-1 -right-1 h-3.5 w-3.5 rounded-full bg-destructive text-destructive-foreground text-[8px] leading-none items-center justify-center hidden group-hover:inline-flex"
7693
+ (click)="removeFavorite(fav); $event.stopPropagation()"
7694
+ >×</button>
7695
+ </div>
7696
+ }
7697
+ </div>
7698
+ </div>
7699
+ }
7700
+ </div>
7701
+ }
7702
+ `,
7703
+ }]
7704
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], format: [{ type: i0.Input, args: [{ isSignal: true, alias: "format", required: false }] }], presets: [{ type: i0.Input, args: [{ isSignal: true, alias: "presets", required: false }] }], showInput: [{ type: i0.Input, args: [{ isSignal: true, alias: "showInput", required: false }] }], showEyeDropper: [{ type: i0.Input, args: [{ isSignal: true, alias: "showEyeDropper", required: false }] }], showFavorites: [{ type: i0.Input, args: [{ isSignal: true, alias: "showFavorites", required: false }] }], inline: [{ type: i0.Input, args: [{ isSignal: true, alias: "inline", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], colorChange: [{ type: i0.Output, args: ["colorChange"] }], formatChange: [{ type: i0.Output, args: ["formatChange"] }], triggerRef: [{ type: i0.ViewChild, args: ['triggerEl', { isSignal: true }] }], panelRef: [{ type: i0.ViewChild, args: ['panelEl', { isSignal: true }] }], satPanelRef: [{ type: i0.ViewChild, args: ['satPanel', { isSignal: true }] }], hueTrackRef: [{ type: i0.ViewChild, args: ['hueTrack', { isSignal: true }] }], onDocumentClick: [{
7705
+ type: HostListener,
7706
+ args: ['document:click', ['$event']]
7707
+ }], onEscape: [{
7708
+ type: HostListener,
7709
+ args: ['keydown.escape']
7710
+ }] } });
7711
+
7712
+ const otpCellVariants = cva('text-center font-mono font-semibold border border-border bg-background rounded-md transition-all focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed', {
7713
+ variants: {
7714
+ size: {
7715
+ sm: 'h-9 w-9 text-sm',
7716
+ md: 'h-11 w-11 text-lg',
7717
+ lg: 'h-14 w-14 text-2xl',
7718
+ },
7719
+ },
7720
+ defaultVariants: { size: 'md' },
7721
+ });
7722
+
7723
+ class SnyOtpInputComponent {
7724
+ // Public API
7725
+ length = input(6, ...(ngDevMode ? [{ debugName: "length" }] : /* istanbul ignore next */ []));
7726
+ type = input('number', ...(ngDevMode ? [{ debugName: "type" }] : /* istanbul ignore next */ []));
7727
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
7728
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
7729
+ mask = input(false, ...(ngDevMode ? [{ debugName: "mask" }] : /* istanbul ignore next */ []));
7730
+ autoFocus = input(true, ...(ngDevMode ? [{ debugName: "autoFocus" }] : /* istanbul ignore next */ []));
7731
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
7732
+ separator = input(null, ...(ngDevMode ? [{ debugName: "separator" }] : /* istanbul ignore next */ []));
7733
+ status = input('idle', ...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
7734
+ value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
7735
+ completed = output();
7736
+ // Internal state
7737
+ digits = linkedSignal(() => Array(this.length()).fill(''), ...(ngDevMode ? [{ debugName: "digits" }] : /* istanbul ignore next */ []));
7738
+ focusedIndex = signal(-1, ...(ngDevMode ? [{ debugName: "focusedIndex" }] : /* istanbul ignore next */ []));
7739
+ inputRefs = viewChildren('inputEl', ...(ngDevMode ? [{ debugName: "inputRefs" }] : /* istanbul ignore next */ []));
7740
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
7741
+ isDisabled = computed(() => this.disabled() || this._disabledByCva() || this.status() === 'loading', ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
7742
+ _onChange = () => { };
7743
+ _onTouched = () => { };
7744
+ // Computed
7745
+ fullValue = computed(() => this.digits().join(''), ...(ngDevMode ? [{ debugName: "fullValue" }] : /* istanbul ignore next */ []));
7746
+ isComplete = computed(() => {
7747
+ const d = this.digits();
7748
+ return d.length === this.length() && d.every((c) => c !== '');
7749
+ }, ...(ngDevMode ? [{ debugName: "isComplete" }] : /* istanbul ignore next */ []));
7750
+ constructor() {
7751
+ // Sync value → digits when value changes externally (e.g. reset)
7752
+ effect(() => {
7753
+ const val = this.value();
7754
+ untracked(() => {
7755
+ const chars = val.split('').slice(0, this.length());
7756
+ const padded = [...chars, ...Array(this.length() - chars.length).fill('')];
7757
+ const current = this.digits();
7758
+ if (padded.join('') !== current.join('')) {
7759
+ this.digits.set(padded);
7760
+ }
7761
+ });
7762
+ });
7763
+ afterNextRender(() => {
7764
+ if (this.autoFocus()) {
7765
+ this.focusInput(0);
7766
+ }
7767
+ });
7768
+ }
7769
+ // CVA
7770
+ writeValue(val) {
7771
+ const str = val ?? '';
7772
+ this.value.set(str);
7773
+ const chars = str.split('').slice(0, this.length());
7774
+ const padded = [...chars, ...Array(this.length() - chars.length).fill('')];
7775
+ this.digits.set(padded);
7776
+ }
7777
+ registerOnChange(fn) {
7778
+ this._onChange = fn;
7779
+ }
7780
+ registerOnTouched(fn) {
7781
+ this._onTouched = fn;
7782
+ }
7783
+ setDisabledState(isDisabled) {
7784
+ this._disabledByCva.set(isDisabled);
7785
+ }
7786
+ // Cell class
7787
+ cellClass(index) {
7788
+ const isFocused = this.focusedIndex() === index;
7789
+ const hasValue = this.digits()[index] !== '';
7790
+ const st = this.status();
7791
+ return cn(otpCellVariants({ size: this.size() }), st === 'idle' && isFocused && 'border-primary ring-2 ring-ring', st === 'idle' && hasValue && !isFocused && 'border-primary/50', st === 'loading' && 'border-muted-foreground/30 opacity-70', st === 'success' && 'border-green-500 bg-green-500/5', st === 'error' && 'border-destructive bg-destructive/5 animate-shake');
7792
+ }
7793
+ // Input handler
7794
+ onInput(event, index) {
7795
+ const input = event.target;
7796
+ const char = input.value.slice(-1);
7797
+ if (!this.isValidChar(char)) {
7798
+ input.value = this.digits()[index];
7799
+ return;
7800
+ }
7801
+ this.setDigit(index, char);
7802
+ if (index < this.length() - 1) {
7803
+ this.focusInput(index + 1);
7804
+ }
7805
+ this.emitValue();
7806
+ }
7807
+ // Keyboard handler
7808
+ onKeydown(event, index) {
7809
+ switch (event.key) {
7810
+ case 'Backspace':
7811
+ event.preventDefault();
7812
+ if (this.digits()[index] !== '') {
7813
+ this.setDigit(index, '');
7814
+ this.emitValue();
7815
+ }
7816
+ else if (index > 0) {
7817
+ this.setDigit(index - 1, '');
7818
+ this.focusInput(index - 1);
7819
+ this.emitValue();
7820
+ }
7821
+ break;
7822
+ case 'Delete':
7823
+ event.preventDefault();
7824
+ this.setDigit(index, '');
7825
+ this.emitValue();
7826
+ break;
7827
+ case 'ArrowLeft':
7828
+ event.preventDefault();
7829
+ if (index > 0)
7830
+ this.focusInput(index - 1);
7831
+ break;
7832
+ case 'ArrowRight':
7833
+ event.preventDefault();
7834
+ if (index < this.length() - 1)
7835
+ this.focusInput(index + 1);
7836
+ break;
7837
+ case 'Home':
7838
+ event.preventDefault();
7839
+ this.focusInput(0);
7840
+ break;
7841
+ case 'End':
7842
+ event.preventDefault();
7843
+ this.focusInput(this.length() - 1);
7844
+ break;
7845
+ }
7846
+ }
7847
+ // Paste handler
7848
+ onPaste(event, index) {
7849
+ event.preventDefault();
7850
+ const text = event.clipboardData?.getData('text') ?? '';
7851
+ const chars = text.split('').filter((c) => this.isValidChar(c));
7852
+ if (chars.length === 0)
7853
+ return;
7854
+ const newDigits = [...this.digits()];
7855
+ let lastFilledIndex = index;
7856
+ for (let i = 0; i < chars.length && index + i < this.length(); i++) {
7857
+ newDigits[index + i] = chars[i];
7858
+ lastFilledIndex = index + i;
7859
+ }
7860
+ this.digits.set(newDigits);
7861
+ const nextIndex = Math.min(lastFilledIndex + 1, this.length() - 1);
7862
+ this.focusInput(nextIndex);
7863
+ this.emitValue();
7864
+ }
7865
+ // Blur
7866
+ onBlur() {
7867
+ this.focusedIndex.set(-1);
7868
+ this._onTouched();
7869
+ }
7870
+ // Helpers
7871
+ setDigit(index, char) {
7872
+ this.digits.update((d) => {
7873
+ const next = [...d];
7874
+ next[index] = char;
7875
+ return next;
7876
+ });
7877
+ }
7878
+ emitValue() {
7879
+ const val = this.fullValue();
7880
+ this.value.set(val);
7881
+ this._onChange(val);
7882
+ if (this.isComplete()) {
7883
+ this.completed.emit(val);
7884
+ }
7885
+ }
7886
+ focusInput(index) {
7887
+ const refs = this.inputRefs();
7888
+ if (refs[index]) {
7889
+ const el = refs[index].nativeElement;
7890
+ el.focus();
7891
+ el.select();
7892
+ }
7893
+ }
7894
+ isValidChar(char) {
7895
+ if (!char || char.length !== 1)
7896
+ return false;
7897
+ if (this.type() === 'number')
7898
+ return /^[0-9]$/.test(char);
7899
+ return /^[a-zA-Z0-9]$/.test(char);
7900
+ }
7901
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyOtpInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7902
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyOtpInputComponent, isStandalone: true, selector: "sny-otp-input", inputs: { length: { classPropertyName: "length", publicName: "length", isSignal: true, isRequired: false, transformFunction: null }, type: { classPropertyName: "type", publicName: "type", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, mask: { classPropertyName: "mask", publicName: "mask", isSignal: true, isRequired: false, transformFunction: null }, autoFocus: { classPropertyName: "autoFocus", publicName: "autoFocus", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, separator: { classPropertyName: "separator", publicName: "separator", isSignal: true, isRequired: false, transformFunction: null }, status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", completed: "completed" }, providers: [
7903
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyOtpInputComponent), multi: true },
7904
+ ], viewQueries: [{ propertyName: "inputRefs", predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
7905
+ <div
7906
+ role="group"
7907
+ [attr.aria-label]="'OTP input, ' + length() + ' digits'"
7908
+ class="flex items-center gap-2"
7909
+ >
7910
+ @for (digit of digits(); track $index; let i = $index) {
7911
+ @if (separator() !== null && i === separator() && i > 0) {
7912
+ <span class="text-muted-foreground text-lg select-none" aria-hidden="true">—</span>
7913
+ }
7914
+ <input
7915
+ #inputEl
7916
+ [type]="mask() ? 'password' : 'text'"
7917
+ [inputMode]="type() === 'number' ? 'numeric' : 'text'"
7918
+ [attr.pattern]="type() === 'number' ? '[0-9]' : '[a-zA-Z0-9]'"
7919
+ maxlength="1"
7920
+ autocomplete="one-time-code"
7921
+ [value]="digit"
7922
+ [placeholder]="placeholder()"
7923
+ [disabled]="isDisabled()"
7924
+ [class]="cellClass(i)"
7925
+ [attr.aria-label]="'Digit ' + (i + 1) + ' of ' + length()"
7926
+ (input)="onInput($event, i)"
7927
+ (keydown)="onKeydown($event, i)"
7928
+ (paste)="onPaste($event, i)"
7929
+ (focus)="focusedIndex.set(i)"
7930
+ (blur)="onBlur()"
7931
+ />
7932
+ }
7933
+ </div>
7934
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
7935
+ }
7936
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyOtpInputComponent, decorators: [{
7937
+ type: Component,
7938
+ args: [{
7939
+ selector: 'sny-otp-input',
7940
+ standalone: true,
7941
+ changeDetection: ChangeDetectionStrategy.OnPush,
7942
+ providers: [
7943
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyOtpInputComponent), multi: true },
7944
+ ],
7945
+ template: `
7946
+ <div
7947
+ role="group"
7948
+ [attr.aria-label]="'OTP input, ' + length() + ' digits'"
7949
+ class="flex items-center gap-2"
7950
+ >
7951
+ @for (digit of digits(); track $index; let i = $index) {
7952
+ @if (separator() !== null && i === separator() && i > 0) {
7953
+ <span class="text-muted-foreground text-lg select-none" aria-hidden="true">—</span>
7954
+ }
7955
+ <input
7956
+ #inputEl
7957
+ [type]="mask() ? 'password' : 'text'"
7958
+ [inputMode]="type() === 'number' ? 'numeric' : 'text'"
7959
+ [attr.pattern]="type() === 'number' ? '[0-9]' : '[a-zA-Z0-9]'"
7960
+ maxlength="1"
7961
+ autocomplete="one-time-code"
7962
+ [value]="digit"
7963
+ [placeholder]="placeholder()"
7964
+ [disabled]="isDisabled()"
7965
+ [class]="cellClass(i)"
7966
+ [attr.aria-label]="'Digit ' + (i + 1) + ' of ' + length()"
7967
+ (input)="onInput($event, i)"
7968
+ (keydown)="onKeydown($event, i)"
7969
+ (paste)="onPaste($event, i)"
7970
+ (focus)="focusedIndex.set(i)"
7971
+ (blur)="onBlur()"
7972
+ />
7973
+ }
7974
+ </div>
7975
+ `,
7976
+ }]
7977
+ }], ctorParameters: () => [], propDecorators: { length: [{ type: i0.Input, args: [{ isSignal: true, alias: "length", required: false }] }], type: [{ type: i0.Input, args: [{ isSignal: true, alias: "type", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], mask: [{ type: i0.Input, args: [{ isSignal: true, alias: "mask", required: false }] }], autoFocus: [{ type: i0.Input, args: [{ isSignal: true, alias: "autoFocus", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], separator: [{ type: i0.Input, args: [{ isSignal: true, alias: "separator", required: false }] }], status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], completed: [{ type: i0.Output, args: ["completed"] }], inputRefs: [{ type: i0.ViewChildren, args: ['inputEl', { isSignal: true }] }] } });
7978
+
7979
+ class SnyCommandPaletteComponent {
7980
+ config = inject(SNY_DIALOG_DATA);
7981
+ dialogRef = inject(DialogRef);
7982
+ searchInput = viewChild('searchInput', ...(ngDevMode ? [{ debugName: "searchInput" }] : /* istanbul ignore next */ []));
7983
+ query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : /* istanbul ignore next */ []));
7984
+ activeIndex = signal(0, ...(ngDevMode ? [{ debugName: "activeIndex" }] : /* istanbul ignore next */ []));
7985
+ filteredGroups = computed(() => {
7986
+ const q = this.query().toLowerCase().trim();
7987
+ const commands = this.config.commands.filter((cmd) => {
7988
+ if (cmd.disabled)
7989
+ return false;
7990
+ if (!q)
7991
+ return true;
7992
+ return (cmd.label.toLowerCase().includes(q) ||
7993
+ cmd.description?.toLowerCase().includes(q) ||
7994
+ cmd.keywords?.some((k) => k.toLowerCase().includes(q)));
7995
+ });
7996
+ const groups = new Map();
7997
+ for (const cmd of commands) {
7998
+ const group = cmd.group ?? '';
7999
+ if (!groups.has(group))
8000
+ groups.set(group, []);
8001
+ groups.get(group).push(cmd);
8002
+ }
8003
+ return [...groups.entries()].map(([name, cmds]) => ({ name, commands: cmds }));
8004
+ }, ...(ngDevMode ? [{ debugName: "filteredGroups" }] : /* istanbul ignore next */ []));
8005
+ flatResults = computed(() => this.filteredGroups().flatMap((g) => g.commands), ...(ngDevMode ? [{ debugName: "flatResults" }] : /* istanbul ignore next */ []));
8006
+ constructor() {
8007
+ afterNextRender(() => {
8008
+ this.searchInput()?.nativeElement.focus();
8009
+ });
8010
+ }
8011
+ onQueryChange(value) {
8012
+ this.query.set(value);
8013
+ this.activeIndex.set(0);
8014
+ }
8015
+ flatIndex(cmd) {
8016
+ return this.flatResults().indexOf(cmd);
8017
+ }
8018
+ execute(cmd) {
8019
+ this.dialogRef.close();
8020
+ cmd.action();
8021
+ }
8022
+ onKeydown(event) {
8023
+ const results = this.flatResults();
8024
+ const len = results.length;
8025
+ if (len === 0 && event.key !== 'Escape')
8026
+ return;
8027
+ switch (event.key) {
8028
+ case 'ArrowDown':
8029
+ event.preventDefault();
8030
+ this.activeIndex.update((i) => (i + 1) % len);
8031
+ this.scrollActiveIntoView();
8032
+ break;
8033
+ case 'ArrowUp':
8034
+ event.preventDefault();
8035
+ this.activeIndex.update((i) => (i - 1 + len) % len);
8036
+ this.scrollActiveIntoView();
8037
+ break;
8038
+ case 'Enter':
8039
+ event.preventDefault();
8040
+ const active = results[this.activeIndex()];
8041
+ if (active)
8042
+ this.execute(active);
8043
+ break;
8044
+ }
8045
+ }
8046
+ scrollActiveIntoView() {
8047
+ requestAnimationFrame(() => {
8048
+ const el = document.querySelector(`[data-cmd-idx="${this.activeIndex()}"]`);
8049
+ el?.scrollIntoView({ block: 'nearest' });
8050
+ });
8051
+ }
8052
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8053
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyCommandPaletteComponent, isStandalone: true, selector: "sny-command-palette", host: { listeners: { "keydown": "onKeydown($event)" }, classAttribute: "block" }, viewQueries: [{ propertyName: "searchInput", first: true, predicate: ["searchInput"], descendants: true, isSignal: true }], ngImport: i0, template: `
8054
+ <!-- Search -->
8055
+ <div class="flex items-center gap-2 border-b border-border px-4 py-3">
8056
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground shrink-0"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
8057
+ <input
8058
+ #searchInput
8059
+ snyInput
8060
+ class="border-none shadow-none h-8 px-0 focus-visible:ring-0 focus-visible:ring-offset-0"
8061
+ [placeholder]="config.placeholder ?? 'Type a command...'"
8062
+ [value]="query()"
8063
+ (input)="onQueryChange(searchInput.value)"
8064
+ />
8065
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">Esc</kbd>
8066
+ </div>
8067
+
8068
+ <!-- Results -->
8069
+ <div class="max-h-[300px] overflow-y-auto p-2 sny-scrollbar">
8070
+ @if (flatResults().length === 0) {
8071
+ <p class="py-6 text-center text-sm text-muted-foreground">
8072
+ {{ config.emptyText ?? 'No results found.' }}
8073
+ </p>
8074
+ } @else {
8075
+ @for (group of filteredGroups(); track group.name) {
8076
+ @if (group.name) {
8077
+ <p class="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">{{ group.name }}</p>
8078
+ }
8079
+ @for (cmd of group.commands; track cmd.id) {
8080
+ @let idx = flatIndex(cmd);
8081
+ <button
8082
+ [attr.data-cmd-idx]="idx"
8083
+ [class]="
8084
+ 'w-full text-left rounded-sm px-3 py-2 text-sm transition-colors flex items-center gap-3 cursor-pointer ' +
8085
+ (idx === activeIndex()
8086
+ ? 'bg-accent text-accent-foreground'
8087
+ : 'text-foreground hover:bg-accent/50')
8088
+ "
8089
+ (click)="execute(cmd)"
8090
+ (mouseenter)="activeIndex.set(idx)"
8091
+ >
8092
+ @if (cmd.icon) {
8093
+ <span class="shrink-0 w-5 text-center text-muted-foreground" [innerHTML]="cmd.icon"></span>
8094
+ }
8095
+ <div class="flex-1 min-w-0">
8096
+ <span class="block truncate font-medium">{{ cmd.label }}</span>
8097
+ @if (cmd.description) {
8098
+ <span class="block text-xs text-muted-foreground truncate">{{ cmd.description }}</span>
8099
+ }
8100
+ </div>
8101
+ @if (cmd.shortcut) {
8102
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">{{ cmd.shortcut }}</kbd>
8103
+ }
8104
+ </button>
8105
+ }
8106
+ }
8107
+ }
8108
+ </div>
8109
+
8110
+ <!-- Footer -->
8111
+ <div class="border-t border-border px-4 py-2">
8112
+ <div class="flex items-center gap-3 text-xs text-muted-foreground">
8113
+ <span class="flex items-center gap-1">
8114
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&uarr;&darr;</kbd>
8115
+ Navigate
8116
+ </span>
8117
+ <span class="flex items-center gap-1">
8118
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&crarr;</kbd>
8119
+ Execute
8120
+ </span>
8121
+ <span class="flex items-center gap-1">
8122
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">Esc</kbd>
8123
+ Close
8124
+ </span>
8125
+ </div>
8126
+ </div>
8127
+ `, isInline: true, styles: [":host{display:block;background-color:var(--sny-background);border:1px solid var(--sny-border);border-radius:.5rem;box-shadow:0 25px 50px -12px #00000040;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: SnyInputDirective, selector: "input[snyInput], textarea[snyInput]", inputs: ["variant", "inputSize", "class", "ariaDescribedBy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8128
+ }
8129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteComponent, decorators: [{
8130
+ type: Component,
8131
+ args: [{ selector: 'sny-command-palette', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SnyInputDirective], host: {
8132
+ '(keydown)': 'onKeydown($event)',
8133
+ 'class': 'block',
8134
+ }, template: `
8135
+ <!-- Search -->
8136
+ <div class="flex items-center gap-2 border-b border-border px-4 py-3">
8137
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground shrink-0"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
8138
+ <input
8139
+ #searchInput
8140
+ snyInput
8141
+ class="border-none shadow-none h-8 px-0 focus-visible:ring-0 focus-visible:ring-offset-0"
8142
+ [placeholder]="config.placeholder ?? 'Type a command...'"
8143
+ [value]="query()"
8144
+ (input)="onQueryChange(searchInput.value)"
8145
+ />
8146
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">Esc</kbd>
8147
+ </div>
8148
+
8149
+ <!-- Results -->
8150
+ <div class="max-h-[300px] overflow-y-auto p-2 sny-scrollbar">
8151
+ @if (flatResults().length === 0) {
8152
+ <p class="py-6 text-center text-sm text-muted-foreground">
8153
+ {{ config.emptyText ?? 'No results found.' }}
8154
+ </p>
8155
+ } @else {
8156
+ @for (group of filteredGroups(); track group.name) {
8157
+ @if (group.name) {
8158
+ <p class="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">{{ group.name }}</p>
8159
+ }
8160
+ @for (cmd of group.commands; track cmd.id) {
8161
+ @let idx = flatIndex(cmd);
8162
+ <button
8163
+ [attr.data-cmd-idx]="idx"
8164
+ [class]="
8165
+ 'w-full text-left rounded-sm px-3 py-2 text-sm transition-colors flex items-center gap-3 cursor-pointer ' +
8166
+ (idx === activeIndex()
8167
+ ? 'bg-accent text-accent-foreground'
8168
+ : 'text-foreground hover:bg-accent/50')
8169
+ "
8170
+ (click)="execute(cmd)"
8171
+ (mouseenter)="activeIndex.set(idx)"
8172
+ >
8173
+ @if (cmd.icon) {
8174
+ <span class="shrink-0 w-5 text-center text-muted-foreground" [innerHTML]="cmd.icon"></span>
8175
+ }
8176
+ <div class="flex-1 min-w-0">
8177
+ <span class="block truncate font-medium">{{ cmd.label }}</span>
8178
+ @if (cmd.description) {
8179
+ <span class="block text-xs text-muted-foreground truncate">{{ cmd.description }}</span>
8180
+ }
8181
+ </div>
8182
+ @if (cmd.shortcut) {
8183
+ <kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">{{ cmd.shortcut }}</kbd>
8184
+ }
8185
+ </button>
8186
+ }
8187
+ }
8188
+ }
8189
+ </div>
8190
+
8191
+ <!-- Footer -->
8192
+ <div class="border-t border-border px-4 py-2">
8193
+ <div class="flex items-center gap-3 text-xs text-muted-foreground">
8194
+ <span class="flex items-center gap-1">
8195
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&uarr;&darr;</kbd>
8196
+ Navigate
8197
+ </span>
8198
+ <span class="flex items-center gap-1">
8199
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">&crarr;</kbd>
8200
+ Execute
8201
+ </span>
8202
+ <span class="flex items-center gap-1">
8203
+ <kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">Esc</kbd>
8204
+ Close
8205
+ </span>
8206
+ </div>
8207
+ </div>
8208
+ `, styles: [":host{display:block;background-color:var(--sny-background);border:1px solid var(--sny-border);border-radius:.5rem;box-shadow:0 25px 50px -12px #00000040;overflow:hidden}\n"] }]
8209
+ }], ctorParameters: () => [], propDecorators: { searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
8210
+
8211
+ class SnyCommandPaletteService {
8212
+ dialogService = inject(SnyDialogService);
8213
+ isOpen = false;
8214
+ open(config) {
8215
+ if (this.isOpen)
8216
+ return null;
8217
+ this.isOpen = true;
8218
+ const ref = this.dialogService.open(SnyCommandPaletteComponent, {
8219
+ width: config.width ?? '32rem',
8220
+ data: config,
8221
+ });
8222
+ ref.closed.subscribe(() => {
8223
+ this.isOpen = false;
8224
+ });
8225
+ return ref;
8226
+ }
8227
+ close() {
8228
+ if (this.isOpen) {
8229
+ this.dialogService.closeAll();
8230
+ }
8231
+ }
8232
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
8233
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, providedIn: 'root' });
8234
+ }
8235
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, decorators: [{
8236
+ type: Injectable,
8237
+ args: [{ providedIn: 'root' }]
8238
+ }] });
8239
+
6853
8240
  class SnyValidatorDirective {
6854
8241
  control = input(null, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
6855
8242
  state = input('default', ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
@@ -6909,5 +8296,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
6909
8296
  * Generated bundle index. Do not edit.
6910
8297
  */
6911
8298
 
6912
- 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, 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, 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, datePickerTriggerVariants, 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 };
6913
8300
  //# sourceMappingURL=sonny-ui-core.mjs.map