@sonny-ui/core 0.1.0-alpha.15 → 0.1.0-alpha.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +109 -55
  2. package/fesm2022/sonny-ui-core.mjs +1987 -4
  3. package/fesm2022/sonny-ui-core.mjs.map +1 -1
  4. package/package.json +1 -1
  5. package/src/lib/avatar-group/avatar-group.component.spec.ts +74 -0
  6. package/src/lib/avatar-group/avatar-group.component.ts +89 -0
  7. package/src/lib/avatar-group/index.ts +1 -0
  8. package/src/lib/color-picker/color-picker.component.spec.ts +328 -0
  9. package/src/lib/color-picker/color-picker.component.ts +537 -0
  10. package/src/lib/color-picker/color-picker.types.ts +24 -0
  11. package/src/lib/color-picker/color-picker.utils.ts +183 -0
  12. package/src/lib/color-picker/color-picker.variants.ts +17 -0
  13. package/src/lib/color-picker/index.ts +20 -0
  14. package/src/lib/command-palette/command-palette.component.spec.ts +178 -0
  15. package/src/lib/command-palette/command-palette.component.ts +195 -0
  16. package/src/lib/command-palette/command-palette.service.ts +36 -0
  17. package/src/lib/command-palette/command-palette.types.ts +23 -0
  18. package/src/lib/command-palette/index.ts +7 -0
  19. package/src/lib/number-input/index.ts +2 -0
  20. package/src/lib/number-input/number-input.component.spec.ts +151 -0
  21. package/src/lib/number-input/number-input.component.ts +153 -0
  22. package/src/lib/number-input/number-input.variants.ts +17 -0
  23. package/src/lib/otp-input/index.ts +2 -0
  24. package/src/lib/otp-input/otp-input.component.spec.ts +252 -0
  25. package/src/lib/otp-input/otp-input.component.ts +275 -0
  26. package/src/lib/otp-input/otp-input.variants.ts +18 -0
  27. package/src/lib/popover/index.ts +6 -0
  28. package/src/lib/popover/popover.directives.spec.ts +147 -0
  29. package/src/lib/popover/popover.directives.ts +155 -0
  30. package/src/lib/tag-input/index.ts +2 -0
  31. package/src/lib/tag-input/tag-input.component.spec.ts +190 -0
  32. package/src/lib/tag-input/tag-input.component.ts +173 -0
  33. package/src/lib/tag-input/tag-input.variants.ts +31 -0
  34. package/types/sonny-ui-core.d.ts +351 -3
@@ -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';
@@ -5606,7 +5606,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
5606
5606
  }]
5607
5607
  }], propDecorators: { ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
5608
5608
 
5609
- const sizeMap = { sm: 48, md: 72, lg: 96 };
5609
+ const sizeMap$1 = { sm: 48, md: 72, lg: 96 };
5610
5610
  const variantColorMap = {
5611
5611
  default: 'stroke-primary',
5612
5612
  success: 'stroke-green-600 dark:stroke-green-500',
@@ -5620,7 +5620,7 @@ class SnyRadialProgressComponent {
5620
5620
  thickness = input(4, ...(ngDevMode ? [{ debugName: "thickness" }] : /* istanbul ignore next */ []));
5621
5621
  variant = input('default', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
5622
5622
  class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
5623
- svgSize = computed(() => sizeMap[this.size()], ...(ngDevMode ? [{ debugName: "svgSize" }] : /* istanbul ignore next */ []));
5623
+ svgSize = computed(() => sizeMap$1[this.size()], ...(ngDevMode ? [{ debugName: "svgSize" }] : /* istanbul ignore next */ []));
5624
5624
  radius = computed(() => (this.svgSize() - this.thickness()) / 2, ...(ngDevMode ? [{ debugName: "radius" }] : /* istanbul ignore next */ []));
5625
5625
  circumference = computed(() => 2 * Math.PI * this.radius(), ...(ngDevMode ? [{ debugName: "circumference" }] : /* istanbul ignore next */ []));
5626
5626
  offset = computed(() => this.circumference() - (this.value() / 100) * this.circumference(), ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
@@ -6850,6 +6850,1989 @@ 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
+
8240
+ const numberInputVariants = cva('inline-flex items-center border border-border rounded-md bg-background transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2', {
8241
+ variants: {
8242
+ size: {
8243
+ sm: 'h-9 text-xs',
8244
+ md: 'h-10 text-sm',
8245
+ lg: 'h-11 text-base',
8246
+ },
8247
+ },
8248
+ defaultVariants: { size: 'md' },
8249
+ });
8250
+
8251
+ class SnyNumberInputComponent {
8252
+ value = model(0, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
8253
+ min = input(null, ...(ngDevMode ? [{ debugName: "min" }] : /* istanbul ignore next */ []));
8254
+ max = input(null, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
8255
+ step = input(1, ...(ngDevMode ? [{ debugName: "step" }] : /* istanbul ignore next */ []));
8256
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
8257
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
8258
+ placeholder = input('', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
8259
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
8260
+ inputValue = signal('0', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
8261
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
8262
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
8263
+ atMin = computed(() => this.min() !== null && this.value() <= this.min(), ...(ngDevMode ? [{ debugName: "atMin" }] : /* istanbul ignore next */ []));
8264
+ atMax = computed(() => this.max() !== null && this.value() >= this.max(), ...(ngDevMode ? [{ debugName: "atMax" }] : /* istanbul ignore next */ []));
8265
+ containerClass = computed(() => cn(numberInputVariants({ size: this.size() }), this.isDisabled() && 'opacity-50', this.class()), ...(ngDevMode ? [{ debugName: "containerClass" }] : /* istanbul ignore next */ []));
8266
+ _onChange = () => { };
8267
+ _onTouched = () => { };
8268
+ constructor() {
8269
+ effect(() => {
8270
+ const val = this.value();
8271
+ untracked(() => this.inputValue.set(String(val)));
8272
+ });
8273
+ }
8274
+ writeValue(val) {
8275
+ this.value.set(val ?? 0);
8276
+ }
8277
+ registerOnChange(fn) {
8278
+ this._onChange = fn;
8279
+ }
8280
+ registerOnTouched(fn) {
8281
+ this._onTouched = fn;
8282
+ }
8283
+ setDisabledState(isDisabled) {
8284
+ this._disabledByCva.set(isDisabled);
8285
+ }
8286
+ increment() {
8287
+ this.setValue(this.value() + this.step());
8288
+ }
8289
+ decrement() {
8290
+ this.setValue(this.value() - this.step());
8291
+ }
8292
+ onInput(event) {
8293
+ this.inputValue.set(event.target.value);
8294
+ }
8295
+ commitValue() {
8296
+ const parsed = parseFloat(this.inputValue());
8297
+ if (isNaN(parsed)) {
8298
+ this.inputValue.set(String(this.value()));
8299
+ }
8300
+ else {
8301
+ this.setValue(parsed);
8302
+ }
8303
+ this._onTouched();
8304
+ }
8305
+ onKeydown(event) {
8306
+ switch (event.key) {
8307
+ case 'ArrowUp':
8308
+ event.preventDefault();
8309
+ this.increment();
8310
+ break;
8311
+ case 'ArrowDown':
8312
+ event.preventDefault();
8313
+ this.decrement();
8314
+ break;
8315
+ }
8316
+ }
8317
+ setValue(raw) {
8318
+ let clamped = raw;
8319
+ if (this.min() !== null)
8320
+ clamped = Math.max(this.min(), clamped);
8321
+ if (this.max() !== null)
8322
+ clamped = Math.min(this.max(), clamped);
8323
+ const rounded = parseFloat(clamped.toFixed(10));
8324
+ this.value.set(rounded);
8325
+ this._onChange(rounded);
8326
+ }
8327
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyNumberInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8328
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.5", type: SnyNumberInputComponent, isStandalone: true, selector: "sny-number-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, min: { classPropertyName: "min", publicName: "min", isSignal: true, isRequired: false, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, step: { classPropertyName: "step", publicName: "step", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange" }, providers: [
8329
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyNumberInputComponent), multi: true },
8330
+ ], ngImport: i0, template: `
8331
+ <div [class]="containerClass()">
8332
+ <button
8333
+ type="button"
8334
+ class="px-2.5 hover:bg-accent transition-colors border-r border-border disabled:opacity-40 disabled:cursor-not-allowed"
8335
+ [disabled]="isDisabled() || atMin()"
8336
+ (click)="decrement()"
8337
+ aria-label="Decrease"
8338
+ >
8339
+ <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="M5 12h14"/></svg>
8340
+ </button>
8341
+ <input
8342
+ #inputEl
8343
+ type="text"
8344
+ inputmode="decimal"
8345
+ class="flex-1 w-14 text-center outline-none bg-transparent font-medium"
8346
+ [value]="inputValue()"
8347
+ [disabled]="isDisabled()"
8348
+ [placeholder]="placeholder()"
8349
+ [attr.aria-label]="'Number input'"
8350
+ [attr.aria-valuemin]="min() ?? null"
8351
+ [attr.aria-valuemax]="max() ?? null"
8352
+ [attr.aria-valuenow]="value()"
8353
+ role="spinbutton"
8354
+ (input)="onInput($event)"
8355
+ (blur)="commitValue()"
8356
+ (keydown)="onKeydown($event)"
8357
+ />
8358
+ <button
8359
+ type="button"
8360
+ class="px-2.5 hover:bg-accent transition-colors border-l border-border disabled:opacity-40 disabled:cursor-not-allowed"
8361
+ [disabled]="isDisabled() || atMax()"
8362
+ (click)="increment()"
8363
+ aria-label="Increase"
8364
+ >
8365
+ <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="M5 12h14"/><path d="M12 5v14"/></svg>
8366
+ </button>
8367
+ </div>
8368
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
8369
+ }
8370
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyNumberInputComponent, decorators: [{
8371
+ type: Component,
8372
+ args: [{
8373
+ selector: 'sny-number-input',
8374
+ standalone: true,
8375
+ changeDetection: ChangeDetectionStrategy.OnPush,
8376
+ providers: [
8377
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyNumberInputComponent), multi: true },
8378
+ ],
8379
+ template: `
8380
+ <div [class]="containerClass()">
8381
+ <button
8382
+ type="button"
8383
+ class="px-2.5 hover:bg-accent transition-colors border-r border-border disabled:opacity-40 disabled:cursor-not-allowed"
8384
+ [disabled]="isDisabled() || atMin()"
8385
+ (click)="decrement()"
8386
+ aria-label="Decrease"
8387
+ >
8388
+ <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="M5 12h14"/></svg>
8389
+ </button>
8390
+ <input
8391
+ #inputEl
8392
+ type="text"
8393
+ inputmode="decimal"
8394
+ class="flex-1 w-14 text-center outline-none bg-transparent font-medium"
8395
+ [value]="inputValue()"
8396
+ [disabled]="isDisabled()"
8397
+ [placeholder]="placeholder()"
8398
+ [attr.aria-label]="'Number input'"
8399
+ [attr.aria-valuemin]="min() ?? null"
8400
+ [attr.aria-valuemax]="max() ?? null"
8401
+ [attr.aria-valuenow]="value()"
8402
+ role="spinbutton"
8403
+ (input)="onInput($event)"
8404
+ (blur)="commitValue()"
8405
+ (keydown)="onKeydown($event)"
8406
+ />
8407
+ <button
8408
+ type="button"
8409
+ class="px-2.5 hover:bg-accent transition-colors border-l border-border disabled:opacity-40 disabled:cursor-not-allowed"
8410
+ [disabled]="isDisabled() || atMax()"
8411
+ (click)="increment()"
8412
+ aria-label="Increase"
8413
+ >
8414
+ <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="M5 12h14"/><path d="M12 5v14"/></svg>
8415
+ </button>
8416
+ </div>
8417
+ `,
8418
+ }]
8419
+ }], ctorParameters: () => [], propDecorators: { value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], min: [{ type: i0.Input, args: [{ isSignal: true, alias: "min", required: false }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], step: [{ type: i0.Input, args: [{ isSignal: true, alias: "step", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], placeholder: [{ type: i0.Input, args: [{ isSignal: true, alias: "placeholder", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
8420
+
8421
+ const sizeMap = {
8422
+ sm: { avatar: 'h-7 w-7 text-xs', counter: 'h-7 w-7 text-[10px]' },
8423
+ md: { avatar: 'h-9 w-9 text-sm', counter: 'h-9 w-9 text-xs' },
8424
+ lg: { avatar: 'h-11 w-11 text-base', counter: 'h-11 w-11 text-sm' },
8425
+ };
8426
+ const spacingMap = {
8427
+ tight: '-space-x-3',
8428
+ normal: '-space-x-2',
8429
+ };
8430
+ class SnyAvatarGroupComponent {
8431
+ items = input.required(...(ngDevMode ? [{ debugName: "items" }] : /* istanbul ignore next */ []));
8432
+ max = input(3, ...(ngDevMode ? [{ debugName: "max" }] : /* istanbul ignore next */ []));
8433
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
8434
+ spacing = input('normal', ...(ngDevMode ? [{ debugName: "spacing" }] : /* istanbul ignore next */ []));
8435
+ visibleItems = computed(() => this.items().slice(0, this.max()), ...(ngDevMode ? [{ debugName: "visibleItems" }] : /* istanbul ignore next */ []));
8436
+ overflowCount = computed(() => Math.max(0, this.items().length - this.max()), ...(ngDevMode ? [{ debugName: "overflowCount" }] : /* istanbul ignore next */ []));
8437
+ containerClass = computed(() => cn('flex items-center', spacingMap[this.spacing()]), ...(ngDevMode ? [{ debugName: "containerClass" }] : /* istanbul ignore next */ []));
8438
+ avatarClass = computed(() => cn('inline-block rounded-full object-cover ring-2 ring-background', sizeMap[this.size()].avatar), ...(ngDevMode ? [{ debugName: "avatarClass" }] : /* istanbul ignore next */ []));
8439
+ fallbackClass = computed(() => cn('inline-flex items-center justify-center rounded-full bg-muted text-muted-foreground font-medium ring-2 ring-background', sizeMap[this.size()].avatar), ...(ngDevMode ? [{ debugName: "fallbackClass" }] : /* istanbul ignore next */ []));
8440
+ counterClass = computed(() => cn('inline-flex items-center justify-center rounded-full bg-muted text-muted-foreground font-semibold ring-2 ring-background', sizeMap[this.size()].counter), ...(ngDevMode ? [{ debugName: "counterClass" }] : /* istanbul ignore next */ []));
8441
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyAvatarGroupComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8442
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyAvatarGroupComponent, isStandalone: true, selector: "sny-avatar-group", inputs: { items: { classPropertyName: "items", publicName: "items", isSignal: true, isRequired: true, transformFunction: null }, max: { classPropertyName: "max", publicName: "max", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, spacing: { classPropertyName: "spacing", publicName: "spacing", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
8443
+ <div [class]="containerClass()" role="group" [attr.aria-label]="'Group of ' + items().length + ' users'">
8444
+ @for (item of visibleItems(); track $index) {
8445
+ @if (item.src) {
8446
+ <img
8447
+ [src]="item.src"
8448
+ [alt]="item.alt ?? ''"
8449
+ [class]="avatarClass()"
8450
+ />
8451
+ } @else {
8452
+ <div [class]="fallbackClass()">
8453
+ {{ item.fallback ?? '?' }}
8454
+ </div>
8455
+ }
8456
+ }
8457
+ @if (overflowCount() > 0) {
8458
+ <div [class]="counterClass()" [title]="overflowCount() + ' more'">
8459
+ +{{ overflowCount() }}
8460
+ </div>
8461
+ }
8462
+ </div>
8463
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
8464
+ }
8465
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyAvatarGroupComponent, decorators: [{
8466
+ type: Component,
8467
+ args: [{
8468
+ selector: 'sny-avatar-group',
8469
+ standalone: true,
8470
+ changeDetection: ChangeDetectionStrategy.OnPush,
8471
+ template: `
8472
+ <div [class]="containerClass()" role="group" [attr.aria-label]="'Group of ' + items().length + ' users'">
8473
+ @for (item of visibleItems(); track $index) {
8474
+ @if (item.src) {
8475
+ <img
8476
+ [src]="item.src"
8477
+ [alt]="item.alt ?? ''"
8478
+ [class]="avatarClass()"
8479
+ />
8480
+ } @else {
8481
+ <div [class]="fallbackClass()">
8482
+ {{ item.fallback ?? '?' }}
8483
+ </div>
8484
+ }
8485
+ }
8486
+ @if (overflowCount() > 0) {
8487
+ <div [class]="counterClass()" [title]="overflowCount() + ' more'">
8488
+ +{{ overflowCount() }}
8489
+ </div>
8490
+ }
8491
+ </div>
8492
+ `,
8493
+ }]
8494
+ }], propDecorators: { items: [{ type: i0.Input, args: [{ isSignal: true, alias: "items", required: true }] }], max: [{ type: i0.Input, args: [{ isSignal: true, alias: "max", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], spacing: [{ type: i0.Input, args: [{ isSignal: true, alias: "spacing", required: false }] }] } });
8495
+
8496
+ const tagInputContainerVariants = cva('flex flex-wrap gap-1.5 border border-border rounded-md bg-background px-2 cursor-text transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2', {
8497
+ variants: {
8498
+ size: {
8499
+ sm: 'min-h-[36px] py-1 text-xs',
8500
+ md: 'min-h-[40px] py-1.5 text-sm',
8501
+ lg: 'min-h-[44px] py-2 text-base',
8502
+ },
8503
+ },
8504
+ defaultVariants: { size: 'md' },
8505
+ });
8506
+ const tagVariants = cva('inline-flex items-center gap-1 rounded-md font-medium', {
8507
+ variants: {
8508
+ size: {
8509
+ sm: 'px-1.5 py-0.5 text-xs',
8510
+ md: 'px-2 py-0.5 text-sm',
8511
+ lg: 'px-2.5 py-1 text-sm',
8512
+ },
8513
+ },
8514
+ defaultVariants: { size: 'md' },
8515
+ });
8516
+
8517
+ class SnyTagInputComponent {
8518
+ value = model([], ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
8519
+ placeholder = input('Add tag...', ...(ngDevMode ? [{ debugName: "placeholder" }] : /* istanbul ignore next */ []));
8520
+ maxTags = input(null, ...(ngDevMode ? [{ debugName: "maxTags" }] : /* istanbul ignore next */ []));
8521
+ allowDuplicates = input(false, ...(ngDevMode ? [{ debugName: "allowDuplicates" }] : /* istanbul ignore next */ []));
8522
+ removable = input(true, ...(ngDevMode ? [{ debugName: "removable" }] : /* istanbul ignore next */ []));
8523
+ addOnBlur = input(true, ...(ngDevMode ? [{ debugName: "addOnBlur" }] : /* istanbul ignore next */ []));
8524
+ separators = input(['Enter', ','], ...(ngDevMode ? [{ debugName: "separators" }] : /* istanbul ignore next */ []));
8525
+ validate = input(null, ...(ngDevMode ? [{ debugName: "validate" }] : /* istanbul ignore next */ []));
8526
+ disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
8527
+ size = input('md', ...(ngDevMode ? [{ debugName: "size" }] : /* istanbul ignore next */ []));
8528
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
8529
+ tagAdded = output();
8530
+ tagRemoved = output();
8531
+ inputValue = signal('', ...(ngDevMode ? [{ debugName: "inputValue" }] : /* istanbul ignore next */ []));
8532
+ _disabledByCva = signal(false, ...(ngDevMode ? [{ debugName: "_disabledByCva" }] : /* istanbul ignore next */ []));
8533
+ isDisabled = computed(() => this.disabled() || this._disabledByCva(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
8534
+ atMax = computed(() => this.maxTags() !== null && this.value().length >= this.maxTags(), ...(ngDevMode ? [{ debugName: "atMax" }] : /* istanbul ignore next */ []));
8535
+ containerClass = computed(() => cn(tagInputContainerVariants({ size: this.size() }), this.isDisabled() && 'opacity-50 cursor-not-allowed', this.class()), ...(ngDevMode ? [{ debugName: "containerClass" }] : /* istanbul ignore next */ []));
8536
+ tagClass = computed(() => cn(tagVariants({ size: this.size() }), 'bg-secondary text-secondary-foreground'), ...(ngDevMode ? [{ debugName: "tagClass" }] : /* istanbul ignore next */ []));
8537
+ inputRef = viewChild('inputEl', ...(ngDevMode ? [{ debugName: "inputRef" }] : /* istanbul ignore next */ []));
8538
+ _onChange = () => { };
8539
+ _onTouched = () => { };
8540
+ writeValue(val) {
8541
+ this.value.set(val ?? []);
8542
+ }
8543
+ registerOnChange(fn) {
8544
+ this._onChange = fn;
8545
+ }
8546
+ registerOnTouched(fn) {
8547
+ this._onTouched = fn;
8548
+ }
8549
+ setDisabledState(isDisabled) {
8550
+ this._disabledByCva.set(isDisabled);
8551
+ }
8552
+ focusInput() {
8553
+ this.inputRef()?.nativeElement.focus();
8554
+ }
8555
+ onInput(event) {
8556
+ const val = event.target.value;
8557
+ // Check if separator character was typed (e.g. comma)
8558
+ const seps = this.separators().filter((s) => s.length === 1);
8559
+ for (const sep of seps) {
8560
+ if (val.includes(sep)) {
8561
+ const parts = val.split(sep);
8562
+ for (const part of parts) {
8563
+ this.addTag(part);
8564
+ }
8565
+ this.inputValue.set('');
8566
+ return;
8567
+ }
8568
+ }
8569
+ this.inputValue.set(val);
8570
+ }
8571
+ onKeydown(event) {
8572
+ if (this.separators().includes(event.key) && event.key !== ',') {
8573
+ event.preventDefault();
8574
+ this.addTag(this.inputValue());
8575
+ this.inputValue.set('');
8576
+ return;
8577
+ }
8578
+ if (event.key === 'Backspace' && this.inputValue() === '') {
8579
+ const tags = this.value();
8580
+ if (tags.length > 0) {
8581
+ this.removeTag(tags.length - 1);
8582
+ }
8583
+ }
8584
+ }
8585
+ onBlur() {
8586
+ if (this.addOnBlur() && this.inputValue().trim()) {
8587
+ this.addTag(this.inputValue());
8588
+ this.inputValue.set('');
8589
+ }
8590
+ this._onTouched();
8591
+ }
8592
+ addTag(raw) {
8593
+ const tag = raw.trim();
8594
+ if (!tag)
8595
+ return;
8596
+ if (this.atMax())
8597
+ return;
8598
+ if (!this.allowDuplicates() && this.value().includes(tag))
8599
+ return;
8600
+ const validateFn = this.validate();
8601
+ if (validateFn && !validateFn(tag))
8602
+ return;
8603
+ this.value.update((tags) => [...tags, tag]);
8604
+ this._onChange(this.value());
8605
+ this.tagAdded.emit(tag);
8606
+ }
8607
+ removeTag(index) {
8608
+ const removed = this.value()[index];
8609
+ this.value.update((tags) => tags.filter((_, i) => i !== index));
8610
+ this._onChange(this.value());
8611
+ this.tagRemoved.emit(removed);
8612
+ }
8613
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyTagInputComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8614
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.5", type: SnyTagInputComponent, isStandalone: true, selector: "sny-tag-input", inputs: { value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, maxTags: { classPropertyName: "maxTags", publicName: "maxTags", isSignal: true, isRequired: false, transformFunction: null }, allowDuplicates: { classPropertyName: "allowDuplicates", publicName: "allowDuplicates", isSignal: true, isRequired: false, transformFunction: null }, removable: { classPropertyName: "removable", publicName: "removable", isSignal: true, isRequired: false, transformFunction: null }, addOnBlur: { classPropertyName: "addOnBlur", publicName: "addOnBlur", isSignal: true, isRequired: false, transformFunction: null }, separators: { classPropertyName: "separators", publicName: "separators", isSignal: true, isRequired: false, transformFunction: null }, validate: { classPropertyName: "validate", publicName: "validate", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, size: { classPropertyName: "size", publicName: "size", isSignal: true, isRequired: false, transformFunction: null }, class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", tagAdded: "tagAdded", tagRemoved: "tagRemoved" }, providers: [
8615
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyTagInputComponent), multi: true },
8616
+ ], viewQueries: [{ propertyName: "inputRef", first: true, predicate: ["inputEl"], descendants: true, isSignal: true }], ngImport: i0, template: `
8617
+ <div [class]="containerClass()" (click)="focusInput()">
8618
+ @for (tag of value(); track tag; let i = $index) {
8619
+ <span [class]="tagClass()">
8620
+ {{ tag }}
8621
+ @if (removable() && !isDisabled()) {
8622
+ <button
8623
+ type="button"
8624
+ class="hover:text-destructive transition-colors leading-none"
8625
+ (click)="removeTag(i); $event.stopPropagation()"
8626
+ [attr.aria-label]="'Remove ' + tag"
8627
+ >
8628
+ <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="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
8629
+ </button>
8630
+ }
8631
+ </span>
8632
+ }
8633
+ @if (!atMax()) {
8634
+ <input
8635
+ #inputEl
8636
+ type="text"
8637
+ class="flex-1 min-w-[80px] outline-none bg-transparent"
8638
+ [placeholder]="value().length === 0 ? placeholder() : ''"
8639
+ [disabled]="isDisabled()"
8640
+ [value]="inputValue()"
8641
+ (input)="onInput($event)"
8642
+ (keydown)="onKeydown($event)"
8643
+ (blur)="onBlur()"
8644
+ [attr.aria-label]="'Add tag'"
8645
+ />
8646
+ }
8647
+ </div>
8648
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
8649
+ }
8650
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyTagInputComponent, decorators: [{
8651
+ type: Component,
8652
+ args: [{
8653
+ selector: 'sny-tag-input',
8654
+ standalone: true,
8655
+ changeDetection: ChangeDetectionStrategy.OnPush,
8656
+ providers: [
8657
+ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SnyTagInputComponent), multi: true },
8658
+ ],
8659
+ template: `
8660
+ <div [class]="containerClass()" (click)="focusInput()">
8661
+ @for (tag of value(); track tag; let i = $index) {
8662
+ <span [class]="tagClass()">
8663
+ {{ tag }}
8664
+ @if (removable() && !isDisabled()) {
8665
+ <button
8666
+ type="button"
8667
+ class="hover:text-destructive transition-colors leading-none"
8668
+ (click)="removeTag(i); $event.stopPropagation()"
8669
+ [attr.aria-label]="'Remove ' + tag"
8670
+ >
8671
+ <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="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
8672
+ </button>
8673
+ }
8674
+ </span>
8675
+ }
8676
+ @if (!atMax()) {
8677
+ <input
8678
+ #inputEl
8679
+ type="text"
8680
+ class="flex-1 min-w-[80px] outline-none bg-transparent"
8681
+ [placeholder]="value().length === 0 ? placeholder() : ''"
8682
+ [disabled]="isDisabled()"
8683
+ [value]="inputValue()"
8684
+ (input)="onInput($event)"
8685
+ (keydown)="onKeydown($event)"
8686
+ (blur)="onBlur()"
8687
+ [attr.aria-label]="'Add tag'"
8688
+ />
8689
+ }
8690
+ </div>
8691
+ `,
8692
+ }]
8693
+ }], 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 }] }], maxTags: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxTags", required: false }] }], allowDuplicates: [{ type: i0.Input, args: [{ isSignal: true, alias: "allowDuplicates", required: false }] }], removable: [{ type: i0.Input, args: [{ isSignal: true, alias: "removable", required: false }] }], addOnBlur: [{ type: i0.Input, args: [{ isSignal: true, alias: "addOnBlur", required: false }] }], separators: [{ type: i0.Input, args: [{ isSignal: true, alias: "separators", required: false }] }], validate: [{ type: i0.Input, args: [{ isSignal: true, alias: "validate", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], size: [{ type: i0.Input, args: [{ isSignal: true, alias: "size", required: false }] }], class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }], tagAdded: [{ type: i0.Output, args: ["tagAdded"] }], tagRemoved: [{ type: i0.Output, args: ["tagRemoved"] }], inputRef: [{ type: i0.ViewChild, args: ['inputEl', { isSignal: true }] }] } });
8694
+
8695
+ const SNY_POPOVER = new InjectionToken('SnyPopover');
8696
+ class SnyPopoverDirective {
8697
+ elRef = inject(ElementRef);
8698
+ matchWidth = input(false, ...(ngDevMode ? [{ debugName: "matchWidth" }] : /* istanbul ignore next */ []));
8699
+ offset = input(4, ...(ngDevMode ? [{ debugName: "offset" }] : /* istanbul ignore next */ []));
8700
+ closeOnOutside = input(true, ...(ngDevMode ? [{ debugName: "closeOnOutside" }] : /* istanbul ignore next */ []));
8701
+ closeOnEscape = input(true, ...(ngDevMode ? [{ debugName: "closeOnEscape" }] : /* istanbul ignore next */ []));
8702
+ isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
8703
+ triggerEl = signal(null, ...(ngDevMode ? [{ debugName: "triggerEl" }] : /* istanbul ignore next */ []));
8704
+ panelEl = signal(null, ...(ngDevMode ? [{ debugName: "panelEl" }] : /* istanbul ignore next */ []));
8705
+ scrollHandler = null;
8706
+ resizeHandler = null;
8707
+ toggle() {
8708
+ if (this.isOpen()) {
8709
+ this.close();
8710
+ }
8711
+ else {
8712
+ this.open();
8713
+ }
8714
+ }
8715
+ open() {
8716
+ this.isOpen.set(true);
8717
+ this.addListeners();
8718
+ setTimeout(() => this.updatePosition());
8719
+ }
8720
+ close() {
8721
+ this.isOpen.set(false);
8722
+ this.removeListeners();
8723
+ }
8724
+ updatePosition() {
8725
+ const trigger = this.triggerEl();
8726
+ const panel = this.panelEl();
8727
+ if (!trigger || !panel)
8728
+ return;
8729
+ const rect = trigger.getBoundingClientRect();
8730
+ panel.style.top = `${rect.bottom + this.offset()}px`;
8731
+ panel.style.left = `${rect.left}px`;
8732
+ if (this.matchWidth()) {
8733
+ panel.style.width = `${rect.width}px`;
8734
+ }
8735
+ }
8736
+ addListeners() {
8737
+ this.removeListeners();
8738
+ this.scrollHandler = () => {
8739
+ requestAnimationFrame(() => this.updatePosition());
8740
+ };
8741
+ this.resizeHandler = () => {
8742
+ requestAnimationFrame(() => this.updatePosition());
8743
+ };
8744
+ document.addEventListener('scroll', this.scrollHandler, { capture: true, passive: true });
8745
+ window.addEventListener('resize', this.resizeHandler, { passive: true });
8746
+ }
8747
+ removeListeners() {
8748
+ if (this.scrollHandler) {
8749
+ document.removeEventListener('scroll', this.scrollHandler, { capture: true });
8750
+ this.scrollHandler = null;
8751
+ }
8752
+ if (this.resizeHandler) {
8753
+ window.removeEventListener('resize', this.resizeHandler);
8754
+ this.resizeHandler = null;
8755
+ }
8756
+ }
8757
+ onDocumentClick(event) {
8758
+ if (this.closeOnOutside() && this.isOpen() && !this.elRef.nativeElement.contains(event.target)) {
8759
+ this.close();
8760
+ }
8761
+ }
8762
+ onEscape() {
8763
+ if (this.closeOnEscape() && this.isOpen()) {
8764
+ this.close();
8765
+ }
8766
+ }
8767
+ ngOnDestroy() {
8768
+ this.removeListeners();
8769
+ }
8770
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPopoverDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8771
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyPopoverDirective, isStandalone: true, selector: "[snyPopover]", inputs: { matchWidth: { classPropertyName: "matchWidth", publicName: "matchWidth", isSignal: true, isRequired: false, transformFunction: null }, offset: { classPropertyName: "offset", publicName: "offset", isSignal: true, isRequired: false, transformFunction: null }, closeOnOutside: { classPropertyName: "closeOnOutside", publicName: "closeOnOutside", isSignal: true, isRequired: false, transformFunction: null }, closeOnEscape: { classPropertyName: "closeOnEscape", publicName: "closeOnEscape", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:click": "onDocumentClick($event)", "keydown.escape": "onEscape()" }, properties: { "class": "\"relative inline-block\"" } }, providers: [{ provide: SNY_POPOVER, useExisting: SnyPopoverDirective }], exportAs: ["snyPopover"], ngImport: i0 });
8772
+ }
8773
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPopoverDirective, decorators: [{
8774
+ type: Directive,
8775
+ args: [{
8776
+ selector: '[snyPopover]',
8777
+ standalone: true,
8778
+ exportAs: 'snyPopover',
8779
+ providers: [{ provide: SNY_POPOVER, useExisting: SnyPopoverDirective }],
8780
+ host: {
8781
+ '[class]': '"relative inline-block"',
8782
+ },
8783
+ }]
8784
+ }], propDecorators: { matchWidth: [{ type: i0.Input, args: [{ isSignal: true, alias: "matchWidth", required: false }] }], offset: [{ type: i0.Input, args: [{ isSignal: true, alias: "offset", required: false }] }], closeOnOutside: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnOutside", required: false }] }], closeOnEscape: [{ type: i0.Input, args: [{ isSignal: true, alias: "closeOnEscape", required: false }] }], onDocumentClick: [{
8785
+ type: HostListener,
8786
+ args: ['document:click', ['$event']]
8787
+ }], onEscape: [{
8788
+ type: HostListener,
8789
+ args: ['keydown.escape']
8790
+ }] } });
8791
+ class SnyPopoverTriggerDirective {
8792
+ popover = inject(SNY_POPOVER);
8793
+ elRef = inject(ElementRef);
8794
+ constructor() {
8795
+ this.popover.triggerEl.set(this.elRef.nativeElement);
8796
+ }
8797
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPopoverTriggerDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8798
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.5", type: SnyPopoverTriggerDirective, isStandalone: true, selector: "[snyPopoverTrigger]", host: { attributes: { "aria-haspopup": "dialog" }, listeners: { "click": "popover.toggle()" }, properties: { "attr.aria-expanded": "popover.isOpen()" } }, ngImport: i0 });
8799
+ }
8800
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPopoverTriggerDirective, decorators: [{
8801
+ type: Directive,
8802
+ args: [{
8803
+ selector: '[snyPopoverTrigger]',
8804
+ standalone: true,
8805
+ host: {
8806
+ '(click)': 'popover.toggle()',
8807
+ '[attr.aria-expanded]': 'popover.isOpen()',
8808
+ 'aria-haspopup': 'dialog',
8809
+ },
8810
+ }]
8811
+ }], ctorParameters: () => [] });
8812
+ class SnyPopoverContentDirective {
8813
+ popover = inject(SNY_POPOVER);
8814
+ elRef = inject(ElementRef);
8815
+ class = input('', ...(ngDevMode ? [{ debugName: "class" }] : /* istanbul ignore next */ []));
8816
+ computedClass = computed(() => cn('fixed z-50 rounded-md border border-border bg-popover text-popover-foreground shadow-lg animate-in fade-in-0 zoom-in-95', this.class()), ...(ngDevMode ? [{ debugName: "computedClass" }] : /* istanbul ignore next */ []));
8817
+ constructor() {
8818
+ this.popover.panelEl.set(this.elRef.nativeElement);
8819
+ }
8820
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPopoverContentDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
8821
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "17.1.0", version: "21.2.5", type: SnyPopoverContentDirective, isStandalone: true, selector: "[snyPopoverContent]", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null } }, host: { attributes: { "role": "dialog" }, properties: { "style.display": "popover.isOpen() ? \"\" : \"none\"", "class": "computedClass()" } }, ngImport: i0 });
8822
+ }
8823
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyPopoverContentDirective, decorators: [{
8824
+ type: Directive,
8825
+ args: [{
8826
+ selector: '[snyPopoverContent]',
8827
+ standalone: true,
8828
+ host: {
8829
+ 'role': 'dialog',
8830
+ '[style.display]': 'popover.isOpen() ? "" : "none"',
8831
+ '[class]': 'computedClass()',
8832
+ },
8833
+ }]
8834
+ }], ctorParameters: () => [], propDecorators: { class: [{ type: i0.Input, args: [{ isSignal: true, alias: "class", required: false }] }] } });
8835
+
6853
8836
  class SnyValidatorDirective {
6854
8837
  control = input(null, ...(ngDevMode ? [{ debugName: "control" }] : /* istanbul ignore next */ []));
6855
8838
  state = input('default', ...(ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []));
@@ -6909,5 +8892,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImpor
6909
8892
  * Generated bundle index. Do not edit.
6910
8893
  */
6911
8894
 
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 };
8895
+ export { SNY_ACCORDION, SNY_ACCORDION_ITEM, SNY_CAROUSEL, SNY_CHAT_BUBBLE, SNY_CONFIG, SNY_DIALOG_DATA, SNY_DRAWER, SNY_DROPDOWN, SNY_FAB, SNY_POPOVER, SNY_SHEET_DATA, SNY_STEPS, SNY_TABLE, SNY_TABS, SNY_TIMELINE, SnyAccordionContentDirective, SnyAccordionDirective, SnyAccordionItemDirective, SnyAccordionTriggerDirective, SnyAlertDescriptionDirective, SnyAlertDirective, SnyAlertTitleDirective, SnyAvatarComponent, SnyAvatarGroupComponent, 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, SnyNumberInputComponent, SnyOtpInputComponent, SnyPaginationComponent, SnyPopoverContentDirective, SnyPopoverDirective, SnyPopoverTriggerDirective, 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, SnyTagInputComponent, 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, numberInputVariants, otpCellVariants, paginationItemVariants, parseColor, progressBarVariants, progressTrackVariants, provideSonnyUI, radioVariants, ratingVariants, rgbToHex, rgbToHsl, rgbToHsv, selectTriggerVariants, skeletonVariants, sliderTrackVariants, statusVariants, switchTrackVariants, tableCellVariants, tableVariants, tabsListVariants, tabsTriggerVariants, tagInputContainerVariants, tagVariants, textareaVariants, toastVariants, toggleVariants, tooltipVariants };
6913
8896
  //# sourceMappingURL=sonny-ui-core.mjs.map