@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.
- package/README.md +109 -55
- package/fesm2022/sonny-ui-core.mjs +1987 -4
- package/fesm2022/sonny-ui-core.mjs.map +1 -1
- package/package.json +1 -1
- package/src/lib/avatar-group/avatar-group.component.spec.ts +74 -0
- package/src/lib/avatar-group/avatar-group.component.ts +89 -0
- package/src/lib/avatar-group/index.ts +1 -0
- package/src/lib/color-picker/color-picker.component.spec.ts +328 -0
- package/src/lib/color-picker/color-picker.component.ts +537 -0
- package/src/lib/color-picker/color-picker.types.ts +24 -0
- package/src/lib/color-picker/color-picker.utils.ts +183 -0
- package/src/lib/color-picker/color-picker.variants.ts +17 -0
- package/src/lib/color-picker/index.ts +20 -0
- package/src/lib/command-palette/command-palette.component.spec.ts +178 -0
- package/src/lib/command-palette/command-palette.component.ts +195 -0
- package/src/lib/command-palette/command-palette.service.ts +36 -0
- package/src/lib/command-palette/command-palette.types.ts +23 -0
- package/src/lib/command-palette/index.ts +7 -0
- package/src/lib/number-input/index.ts +2 -0
- package/src/lib/number-input/number-input.component.spec.ts +151 -0
- package/src/lib/number-input/number-input.component.ts +153 -0
- package/src/lib/number-input/number-input.variants.ts +17 -0
- package/src/lib/otp-input/index.ts +2 -0
- package/src/lib/otp-input/otp-input.component.spec.ts +252 -0
- package/src/lib/otp-input/otp-input.component.ts +275 -0
- package/src/lib/otp-input/otp-input.variants.ts +18 -0
- package/src/lib/popover/index.ts +6 -0
- package/src/lib/popover/popover.directives.spec.ts +147 -0
- package/src/lib/popover/popover.directives.ts +155 -0
- package/src/lib/tag-input/index.ts +2 -0
- package/src/lib/tag-input/tag-input.component.spec.ts +190 -0
- package/src/lib/tag-input/tag-input.component.ts +173 -0
- package/src/lib/tag-input/tag-input.variants.ts +31 -0
- 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]">↑↓</kbd>
|
|
8115
|
+
Navigate
|
|
8116
|
+
</span>
|
|
8117
|
+
<span class="flex items-center gap-1">
|
|
8118
|
+
<kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">↵</kbd>
|
|
8119
|
+
Execute
|
|
8120
|
+
</span>
|
|
8121
|
+
<span class="flex items-center gap-1">
|
|
8122
|
+
<kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">Esc</kbd>
|
|
8123
|
+
Close
|
|
8124
|
+
</span>
|
|
8125
|
+
</div>
|
|
8126
|
+
</div>
|
|
8127
|
+
`, isInline: true, styles: [":host{display:block;background-color:var(--sny-background);border:1px solid var(--sny-border);border-radius:.5rem;box-shadow:0 25px 50px -12px #00000040;overflow:hidden}\n"], dependencies: [{ kind: "directive", type: SnyInputDirective, selector: "input[snyInput], textarea[snyInput]", inputs: ["variant", "inputSize", "class", "ariaDescribedBy"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8128
|
+
}
|
|
8129
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteComponent, decorators: [{
|
|
8130
|
+
type: Component,
|
|
8131
|
+
args: [{ selector: 'sny-command-palette', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, imports: [SnyInputDirective], host: {
|
|
8132
|
+
'(keydown)': 'onKeydown($event)',
|
|
8133
|
+
'class': 'block',
|
|
8134
|
+
}, template: `
|
|
8135
|
+
<!-- Search -->
|
|
8136
|
+
<div class="flex items-center gap-2 border-b border-border px-4 py-3">
|
|
8137
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="text-muted-foreground shrink-0"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
8138
|
+
<input
|
|
8139
|
+
#searchInput
|
|
8140
|
+
snyInput
|
|
8141
|
+
class="border-none shadow-none h-8 px-0 focus-visible:ring-0 focus-visible:ring-offset-0"
|
|
8142
|
+
[placeholder]="config.placeholder ?? 'Type a command...'"
|
|
8143
|
+
[value]="query()"
|
|
8144
|
+
(input)="onQueryChange(searchInput.value)"
|
|
8145
|
+
/>
|
|
8146
|
+
<kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">Esc</kbd>
|
|
8147
|
+
</div>
|
|
8148
|
+
|
|
8149
|
+
<!-- Results -->
|
|
8150
|
+
<div class="max-h-[300px] overflow-y-auto p-2 sny-scrollbar">
|
|
8151
|
+
@if (flatResults().length === 0) {
|
|
8152
|
+
<p class="py-6 text-center text-sm text-muted-foreground">
|
|
8153
|
+
{{ config.emptyText ?? 'No results found.' }}
|
|
8154
|
+
</p>
|
|
8155
|
+
} @else {
|
|
8156
|
+
@for (group of filteredGroups(); track group.name) {
|
|
8157
|
+
@if (group.name) {
|
|
8158
|
+
<p class="px-2 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider">{{ group.name }}</p>
|
|
8159
|
+
}
|
|
8160
|
+
@for (cmd of group.commands; track cmd.id) {
|
|
8161
|
+
@let idx = flatIndex(cmd);
|
|
8162
|
+
<button
|
|
8163
|
+
[attr.data-cmd-idx]="idx"
|
|
8164
|
+
[class]="
|
|
8165
|
+
'w-full text-left rounded-sm px-3 py-2 text-sm transition-colors flex items-center gap-3 cursor-pointer ' +
|
|
8166
|
+
(idx === activeIndex()
|
|
8167
|
+
? 'bg-accent text-accent-foreground'
|
|
8168
|
+
: 'text-foreground hover:bg-accent/50')
|
|
8169
|
+
"
|
|
8170
|
+
(click)="execute(cmd)"
|
|
8171
|
+
(mouseenter)="activeIndex.set(idx)"
|
|
8172
|
+
>
|
|
8173
|
+
@if (cmd.icon) {
|
|
8174
|
+
<span class="shrink-0 w-5 text-center text-muted-foreground" [innerHTML]="cmd.icon"></span>
|
|
8175
|
+
}
|
|
8176
|
+
<div class="flex-1 min-w-0">
|
|
8177
|
+
<span class="block truncate font-medium">{{ cmd.label }}</span>
|
|
8178
|
+
@if (cmd.description) {
|
|
8179
|
+
<span class="block text-xs text-muted-foreground truncate">{{ cmd.description }}</span>
|
|
8180
|
+
}
|
|
8181
|
+
</div>
|
|
8182
|
+
@if (cmd.shortcut) {
|
|
8183
|
+
<kbd class="rounded border border-border bg-muted px-1.5 py-0.5 font-mono text-[10px] text-muted-foreground shrink-0">{{ cmd.shortcut }}</kbd>
|
|
8184
|
+
}
|
|
8185
|
+
</button>
|
|
8186
|
+
}
|
|
8187
|
+
}
|
|
8188
|
+
}
|
|
8189
|
+
</div>
|
|
8190
|
+
|
|
8191
|
+
<!-- Footer -->
|
|
8192
|
+
<div class="border-t border-border px-4 py-2">
|
|
8193
|
+
<div class="flex items-center gap-3 text-xs text-muted-foreground">
|
|
8194
|
+
<span class="flex items-center gap-1">
|
|
8195
|
+
<kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">↑↓</kbd>
|
|
8196
|
+
Navigate
|
|
8197
|
+
</span>
|
|
8198
|
+
<span class="flex items-center gap-1">
|
|
8199
|
+
<kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">↵</kbd>
|
|
8200
|
+
Execute
|
|
8201
|
+
</span>
|
|
8202
|
+
<span class="flex items-center gap-1">
|
|
8203
|
+
<kbd class="rounded border border-border bg-muted px-1 py-0.5 font-mono text-[10px]">Esc</kbd>
|
|
8204
|
+
Close
|
|
8205
|
+
</span>
|
|
8206
|
+
</div>
|
|
8207
|
+
</div>
|
|
8208
|
+
`, styles: [":host{display:block;background-color:var(--sny-background);border:1px solid var(--sny-border);border-radius:.5rem;box-shadow:0 25px 50px -12px #00000040;overflow:hidden}\n"] }]
|
|
8209
|
+
}], ctorParameters: () => [], propDecorators: { searchInput: [{ type: i0.ViewChild, args: ['searchInput', { isSignal: true }] }] } });
|
|
8210
|
+
|
|
8211
|
+
class SnyCommandPaletteService {
|
|
8212
|
+
dialogService = inject(SnyDialogService);
|
|
8213
|
+
isOpen = false;
|
|
8214
|
+
open(config) {
|
|
8215
|
+
if (this.isOpen)
|
|
8216
|
+
return null;
|
|
8217
|
+
this.isOpen = true;
|
|
8218
|
+
const ref = this.dialogService.open(SnyCommandPaletteComponent, {
|
|
8219
|
+
width: config.width ?? '32rem',
|
|
8220
|
+
data: config,
|
|
8221
|
+
});
|
|
8222
|
+
ref.closed.subscribe(() => {
|
|
8223
|
+
this.isOpen = false;
|
|
8224
|
+
});
|
|
8225
|
+
return ref;
|
|
8226
|
+
}
|
|
8227
|
+
close() {
|
|
8228
|
+
if (this.isOpen) {
|
|
8229
|
+
this.dialogService.closeAll();
|
|
8230
|
+
}
|
|
8231
|
+
}
|
|
8232
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
8233
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, providedIn: 'root' });
|
|
8234
|
+
}
|
|
8235
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.5", ngImport: i0, type: SnyCommandPaletteService, decorators: [{
|
|
8236
|
+
type: Injectable,
|
|
8237
|
+
args: [{ providedIn: 'root' }]
|
|
8238
|
+
}] });
|
|
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
|