@tekus/design-system 5.31.0 → 5.32.0

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.
@@ -0,0 +1,1028 @@
1
+ import * as i0 from '@angular/core';
2
+ import { input, output, computed, inject, ElementRef, DestroyRef, ChangeDetectionStrategy, Component, model, viewChild, signal, effect, untracked } from '@angular/core';
3
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
+ import { NgControl, FormControl } from '@angular/forms';
5
+ import { ButtonModule } from 'primeng/button';
6
+ import * as i1 from 'primeng/inputtext';
7
+ import { InputTextModule } from 'primeng/inputtext';
8
+ import * as i2 from 'primeng/message';
9
+ import { MessageModule } from 'primeng/message';
10
+ import * as i3 from 'primeng/popover';
11
+ import { PopoverModule } from 'primeng/popover';
12
+ import { Subject, debounceTime } from 'rxjs';
13
+ import { ButtonComponent } from '@tekus/design-system/components/button';
14
+ import { IconComponent } from '@tekus/design-system/components/icon';
15
+ import { InputTextComponent } from '@tekus/design-system/components/input-text';
16
+ import { getContrastTextColor, getContrastResult } from '@tekus/design-system/utils/wcag-contrast';
17
+
18
+ /**
19
+ * @component ColorPickerSpectrumComponent
20
+ * @description
21
+ * Internal saturation/brightness canvas of `tk-color-picker` (NOT public API).
22
+ * A hue-colored area with white→transparent (saturation) and
23
+ * transparent→black (brightness) CSS overlays. Supports pointer dragging
24
+ * (tracked on `document` so the drag can leave the area) and full keyboard
25
+ * operation via a 2D slider pattern (arrow keys, Shift for 10% steps).
26
+ */
27
+ class ColorPickerSpectrumComponent {
28
+ constructor() {
29
+ /** Current hue (0–360) painting the base background. */
30
+ this.hue = input(0, ...(ngDevMode ? [{ debugName: "hue" }] : /* istanbul ignore next */ []));
31
+ /** Current saturation (0–1) → horizontal cursor position. */
32
+ this.saturation = input(1, ...(ngDevMode ? [{ debugName: "saturation" }] : /* istanbul ignore next */ []));
33
+ /** Current brightness/value (0–1) → vertical cursor position (inverted). */
34
+ this.brightness = input(1, ...(ngDevMode ? [{ debugName: "brightness" }] : /* istanbul ignore next */ []));
35
+ /** Current color painted inside the cursor. */
36
+ this.color = input('', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
37
+ /** Accessible label of the 2D slider. */
38
+ this.ariaLabel = input('Saturation and brightness', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
39
+ /** Emits on every pointer/keyboard change with the new saturation/brightness pair. */
40
+ this.changed = output();
41
+ this.Math = Math;
42
+ this.hueBackground = computed(() => `hsl(${this.hue()}, 100%, 50%)`, ...(ngDevMode ? [{ debugName: "hueBackground" }] : /* istanbul ignore next */ []));
43
+ this.valueText = computed(() => `${this.ariaLabel()}: ${Math.round(this.saturation() * 100)}%, ${Math.round(this.brightness() * 100)}%`, ...(ngDevMode ? [{ debugName: "valueText" }] : /* istanbul ignore next */ []));
44
+ this.elementRef = inject((ElementRef));
45
+ this.destroyRef = inject(DestroyRef);
46
+ this.onDragMove = (e) => this.applyPointer(e);
47
+ this.onDragEnd = () => this.removeDragListeners();
48
+ this.destroyRef.onDestroy(() => this.removeDragListeners());
49
+ }
50
+ onPointerDown(event) {
51
+ event.preventDefault();
52
+ event.currentTarget.focus();
53
+ this.applyPointer(event);
54
+ document.addEventListener('pointermove', this.onDragMove);
55
+ document.addEventListener('pointerup', this.onDragEnd);
56
+ }
57
+ onKeydown(event) {
58
+ const step = event.shiftKey ? 0.1 : 0.01;
59
+ let saturation = this.saturation();
60
+ let brightness = this.brightness();
61
+ switch (event.key) {
62
+ case 'ArrowRight':
63
+ saturation += step;
64
+ break;
65
+ case 'ArrowLeft':
66
+ saturation -= step;
67
+ break;
68
+ case 'ArrowUp':
69
+ brightness += step;
70
+ break;
71
+ case 'ArrowDown':
72
+ brightness -= step;
73
+ break;
74
+ default:
75
+ return;
76
+ }
77
+ event.preventDefault();
78
+ this.emitClamped(saturation, brightness);
79
+ }
80
+ applyPointer(event) {
81
+ const area = this.elementRef.nativeElement.querySelector('.tk-color-picker-spectrum__area');
82
+ if (!area) {
83
+ return;
84
+ }
85
+ const rect = area.getBoundingClientRect();
86
+ this.emitClamped((event.clientX - rect.left) / rect.width, 1 - (event.clientY - rect.top) / rect.height);
87
+ }
88
+ emitClamped(saturation, brightness) {
89
+ this.changed.emit({
90
+ saturation: Math.max(0, Math.min(1, saturation)),
91
+ brightness: Math.max(0, Math.min(1, brightness)),
92
+ });
93
+ }
94
+ removeDragListeners() {
95
+ document.removeEventListener('pointermove', this.onDragMove);
96
+ document.removeEventListener('pointerup', this.onDragEnd);
97
+ }
98
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: ColorPickerSpectrumComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
99
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.3", type: ColorPickerSpectrumComponent, isStandalone: true, selector: "tk-color-picker-spectrum", inputs: { hue: { classPropertyName: "hue", publicName: "hue", isSignal: true, isRequired: false, transformFunction: null }, saturation: { classPropertyName: "saturation", publicName: "saturation", isSignal: true, isRequired: false, transformFunction: null }, brightness: { classPropertyName: "brightness", publicName: "brightness", isSignal: true, isRequired: false, transformFunction: null }, color: { classPropertyName: "color", publicName: "color", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: `
100
+ <div
101
+ class="tk-color-picker-spectrum__area"
102
+ role="slider"
103
+ tabindex="0"
104
+ [style.background]="hueBackground()"
105
+ [attr.aria-label]="ariaLabel()"
106
+ [attr.aria-valuetext]="valueText()"
107
+ aria-valuemin="0"
108
+ aria-valuemax="100"
109
+ [attr.aria-valuenow]="Math.round(saturation() * 100)"
110
+ (pointerdown)="onPointerDown($event)"
111
+ (keydown)="onKeydown($event)"
112
+ >
113
+ <div
114
+ class="tk-color-picker-spectrum__cursor"
115
+ [style.left.%]="saturation() * 100"
116
+ [style.top.%]="(1 - brightness()) * 100"
117
+ [style.background]="color()"
118
+ ></div>
119
+ </div>
120
+ `, isInline: true, styles: [":host{display:block}.tk-color-picker-spectrum__area{width:100%;height:6.5rem;border-radius:var(--tk-borderRadius-s, .25rem);position:relative;overflow:hidden;cursor:crosshair;touch-action:none;-webkit-user-select:none;user-select:none;outline:none}.tk-color-picker-spectrum__area:before{content:\"\";position:absolute;inset:0;background:linear-gradient(to right,#fff,transparent)}.tk-color-picker-spectrum__area:after{content:\"\";position:absolute;inset:0;background:linear-gradient(to bottom,transparent,#000)}.tk-color-picker-spectrum__area:focus-visible{box-shadow:inset 0 0 0 2px var(--tk-color-border-focus, #16006f)}.tk-color-picker-spectrum__cursor{position:absolute;width:.875rem;height:.875rem;border-radius:var(--tk-borderRadius-full, 50%);border:2px solid var(--tk-color-background-default, #ffffff);box-shadow:0 0 0 1px var(--tk-color-border-strong, #424243);transform:translate(-50%,-50%);pointer-events:none;z-index:2}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
121
+ }
122
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: ColorPickerSpectrumComponent, decorators: [{
123
+ type: Component,
124
+ args: [{ selector: 'tk-color-picker-spectrum', template: `
125
+ <div
126
+ class="tk-color-picker-spectrum__area"
127
+ role="slider"
128
+ tabindex="0"
129
+ [style.background]="hueBackground()"
130
+ [attr.aria-label]="ariaLabel()"
131
+ [attr.aria-valuetext]="valueText()"
132
+ aria-valuemin="0"
133
+ aria-valuemax="100"
134
+ [attr.aria-valuenow]="Math.round(saturation() * 100)"
135
+ (pointerdown)="onPointerDown($event)"
136
+ (keydown)="onKeydown($event)"
137
+ >
138
+ <div
139
+ class="tk-color-picker-spectrum__cursor"
140
+ [style.left.%]="saturation() * 100"
141
+ [style.top.%]="(1 - brightness()) * 100"
142
+ [style.background]="color()"
143
+ ></div>
144
+ </div>
145
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.tk-color-picker-spectrum__area{width:100%;height:6.5rem;border-radius:var(--tk-borderRadius-s, .25rem);position:relative;overflow:hidden;cursor:crosshair;touch-action:none;-webkit-user-select:none;user-select:none;outline:none}.tk-color-picker-spectrum__area:before{content:\"\";position:absolute;inset:0;background:linear-gradient(to right,#fff,transparent)}.tk-color-picker-spectrum__area:after{content:\"\";position:absolute;inset:0;background:linear-gradient(to bottom,transparent,#000)}.tk-color-picker-spectrum__area:focus-visible{box-shadow:inset 0 0 0 2px var(--tk-color-border-focus, #16006f)}.tk-color-picker-spectrum__cursor{position:absolute;width:.875rem;height:.875rem;border-radius:var(--tk-borderRadius-full, 50%);border:2px solid var(--tk-color-background-default, #ffffff);box-shadow:0 0 0 1px var(--tk-color-border-strong, #424243);transform:translate(-50%,-50%);pointer-events:none;z-index:2}\n"] }]
146
+ }], ctorParameters: () => [], propDecorators: { hue: [{ type: i0.Input, args: [{ isSignal: true, alias: "hue", required: false }] }], saturation: [{ type: i0.Input, args: [{ isSignal: true, alias: "saturation", required: false }] }], brightness: [{ type: i0.Input, args: [{ isSignal: true, alias: "brightness", required: false }] }], color: [{ type: i0.Input, args: [{ isSignal: true, alias: "color", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
147
+
148
+ /**
149
+ * @component ColorPickerSwatchGridComponent
150
+ * @description
151
+ * Internal grid of color swatches of `tk-color-picker` (NOT public API).
152
+ * Used by both the predefined palette and the custom colors sections.
153
+ * Each swatch is a real button labelled with its hex value;
154
+ * the currently selected color is marked with `aria-pressed`.
155
+ */
156
+ class ColorPickerSwatchGridComponent {
157
+ constructor() {
158
+ /** Hex colors to render as swatches. */
159
+ this.colors = input([], ...(ngDevMode ? [{ debugName: "colors" }] : /* istanbul ignore next */ []));
160
+ /** Currently selected hex (normalized lowercase) to highlight. */
161
+ this.selected = input('', ...(ngDevMode ? [{ debugName: "selected" }] : /* istanbul ignore next */ []));
162
+ /** Accessible label of the swatch group. */
163
+ this.ariaLabel = input('', ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
164
+ /** Emits the picked hex color. */
165
+ this.picked = output();
166
+ }
167
+ isSelected(color) {
168
+ return color.toLowerCase() === this.selected().toLowerCase();
169
+ }
170
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: ColorPickerSwatchGridComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
171
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: ColorPickerSwatchGridComponent, isStandalone: true, selector: "tk-color-picker-swatch-grid", inputs: { colors: { classPropertyName: "colors", publicName: "colors", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { picked: "picked" }, ngImport: i0, template: `
172
+ <div class="tk-color-picker-swatch-grid__grid" role="group" [attr.aria-label]="ariaLabel()">
173
+ @for (color of colors(); track color) {
174
+ <button
175
+ type="button"
176
+ class="tk-color-picker-swatch-grid__swatch"
177
+ [class.tk-color-picker-swatch-grid__swatch--selected]="isSelected(color)"
178
+ [style.background-color]="color"
179
+ [attr.aria-label]="color"
180
+ [attr.aria-pressed]="isSelected(color)"
181
+ (mousedown)="$event.preventDefault()"
182
+ (click)="picked.emit(color)"
183
+ ></button>
184
+ }
185
+ <ng-content></ng-content>
186
+ </div>
187
+ `, isInline: true, styles: [":host{display:block}.tk-color-picker-swatch-grid__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(1.25rem,1fr));gap:var(--tk-spacing-gap-xs, .25rem)}.tk-color-picker-swatch-grid__swatch{appearance:none;padding:0;width:100%;aspect-ratio:1;border:1px solid var(--tk-color-border-subtle, #e4e4e4);border-radius:var(--tk-borderRadius-xs, .125rem);cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.tk-color-picker-swatch-grid__swatch:hover{transform:scale(1.12)}.tk-color-picker-swatch-grid__swatch:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker-swatch-grid__swatch--selected{box-shadow:0 0 0 2px var(--tk-color-background-default, #ffffff),0 0 0 4px var(--tk-color-accent-default, #6ad0bc)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
188
+ }
189
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: ColorPickerSwatchGridComponent, decorators: [{
190
+ type: Component,
191
+ args: [{ selector: 'tk-color-picker-swatch-grid', template: `
192
+ <div class="tk-color-picker-swatch-grid__grid" role="group" [attr.aria-label]="ariaLabel()">
193
+ @for (color of colors(); track color) {
194
+ <button
195
+ type="button"
196
+ class="tk-color-picker-swatch-grid__swatch"
197
+ [class.tk-color-picker-swatch-grid__swatch--selected]="isSelected(color)"
198
+ [style.background-color]="color"
199
+ [attr.aria-label]="color"
200
+ [attr.aria-pressed]="isSelected(color)"
201
+ (mousedown)="$event.preventDefault()"
202
+ (click)="picked.emit(color)"
203
+ ></button>
204
+ }
205
+ <ng-content></ng-content>
206
+ </div>
207
+ `, changeDetection: ChangeDetectionStrategy.OnPush, styles: [":host{display:block}.tk-color-picker-swatch-grid__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(1.25rem,1fr));gap:var(--tk-spacing-gap-xs, .25rem)}.tk-color-picker-swatch-grid__swatch{appearance:none;padding:0;width:100%;aspect-ratio:1;border:1px solid var(--tk-color-border-subtle, #e4e4e4);border-radius:var(--tk-borderRadius-xs, .125rem);cursor:pointer;transition:transform .12s ease,box-shadow .12s ease}.tk-color-picker-swatch-grid__swatch:hover{transform:scale(1.12)}.tk-color-picker-swatch-grid__swatch:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker-swatch-grid__swatch--selected{box-shadow:0 0 0 2px var(--tk-color-background-default, #ffffff),0 0 0 4px var(--tk-color-accent-default, #6ad0bc)}\n"] }]
208
+ }], propDecorators: { colors: [{ type: i0.Input, args: [{ isSignal: true, alias: "colors", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], picked: [{ type: i0.Output, args: ["picked"] }] } });
209
+
210
+ const HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
211
+ const DEFAULT_PRESET_COLORS = [
212
+ '#ffffff', // white
213
+ '#000000', // black
214
+ '#929292', // gray
215
+ '#ffea00', // yellow
216
+ '#fff9ab', // light yellow
217
+ '#0c5dbc', // blue
218
+ '#6aa7f0', // light blue
219
+ '#ff0000', // red
220
+ '#ff95a2', // pink
221
+ '#ff9f00', // orange
222
+ '#417505', // dark green
223
+ '#7ed321', // light green
224
+ '#7300d9', // purple
225
+ '#d4a4ff', // lilac
226
+ '#50e3c2', // turquoise
227
+ ];
228
+ const DEFAULT_TEXTS = {
229
+ swatchesLabel: 'Colores',
230
+ customColorsLabel: 'Colores personalizados',
231
+ hexLabel: 'HEX',
232
+ contrastLabel: 'Contraste',
233
+ contrastSampleText: 'Aa',
234
+ contrastSampleLabel: 'Texto sobre fondo',
235
+ contrastLowLabel: 'Bajo',
236
+ invalidHexError: 'Hexadecimal inválido',
237
+ requiredError: 'Este color es requerido',
238
+ eyedropperLabel: 'Selecciona un color de la pantalla',
239
+ addCustomColorLabel: 'Agregar color actual a colores personalizados',
240
+ removeCustomColorLabel: 'Remover color de colores personalizados',
241
+ openPickerLabel: 'Abrir selector de color',
242
+ spectrumLabel: 'Saturación y brillo',
243
+ hueLabel: 'Matiz',
244
+ dialogLabel: 'Selector de color',
245
+ };
246
+ const DEFAULT_SECTIONS = {
247
+ swatches: true,
248
+ customColors: true,
249
+ spectrum: true,
250
+ hue: true,
251
+ hex: true,
252
+ eyedropper: true,
253
+ contrast: true,
254
+ };
255
+ /**
256
+ * @component ColorPickerComponent
257
+ * @description
258
+ * Configurable color picker of the Tekus Design System. Renders a trigger
259
+ * (`input` variant with editable HEX, or compact `swatch` variant) that opens
260
+ * a PrimeNG `p-popover` composed of independently toggleable sections:
261
+ * predefined swatches, custom colors, saturation/brightness spectrum, hue bar,
262
+ * HEX field with EyeDropper support, WCAG contrast preview and a
263
+ * Cancel/Accept action bar with temporary selection state.
264
+ * Implements `ControlValueAccessor` for Reactive Forms integration.
265
+ *
266
+ * @usage
267
+ * ```html
268
+ * <tk-color-picker
269
+ * label="Color"
270
+ * [(value)]="color"
271
+ * [contrastColor]="'#ffffff'"
272
+ * (colorChange)="onColor($event)">
273
+ * </tk-color-picker>
274
+ * ```
275
+ */
276
+ class ColorPickerComponent {
277
+ static { this.instanceCount = 0; }
278
+ constructor() {
279
+ this.ngControl = inject(NgControl, { self: true, optional: true });
280
+ this.destroyRef = inject(DestroyRef);
281
+ this.el = inject(ElementRef);
282
+ this.init = (() => {
283
+ if (this.ngControl) {
284
+ this.ngControl.valueAccessor = this;
285
+ }
286
+ })();
287
+ /**
288
+ * @property {ColorPickerVariant} variant
289
+ * @description
290
+ * Trigger appearance: `input` shows swatch + editable HEX + chevron,
291
+ * `swatch` shows only swatch + chevron.
292
+ * @default `'input'`
293
+ */
294
+ this.variant = input('input', ...(ngDevMode ? [{ debugName: "variant" }] : /* istanbul ignore next */ []));
295
+ /**
296
+ * @property {string} label
297
+ * @description
298
+ * Label displayed above the trigger.
299
+ * @default `''`
300
+ */
301
+ this.label = input('', ...(ngDevMode ? [{ debugName: "label" }] : /* istanbul ignore next */ []));
302
+ /**
303
+ * @property {string} hint
304
+ * @description
305
+ * Hint text displayed below the trigger (hidden while an error is shown).
306
+ * @default `''`
307
+ */
308
+ this.hint = input('', ...(ngDevMode ? [{ debugName: "hint" }] : /* istanbul ignore next */ []));
309
+ /**
310
+ * @property {boolean} disabled
311
+ * @description
312
+ * Disables the trigger and the popover. Also controlled by Reactive Forms
313
+ * via `setDisabledState`.
314
+ * @default `false`
315
+ */
316
+ this.disabled = input(false, ...(ngDevMode ? [{ debugName: "disabled" }] : /* istanbul ignore next */ []));
317
+ /**
318
+ * @property {boolean} required
319
+ * @description
320
+ * When `true`, an empty HEX field shows the required error on blur.
321
+ * @default `false`
322
+ */
323
+ this.required = input(false, ...(ngDevMode ? [{ debugName: "required" }] : /* istanbul ignore next */ []));
324
+ /**
325
+ * @property {ColorPickerSections} sections
326
+ * @description
327
+ * Enables/disables each popover section. Missing flags default to `true`.
328
+ * Disabling `actionBar` switches the picker to live mode (every valid
329
+ * change is emitted immediately).
330
+ * @default `{}` (all sections enabled)
331
+ */
332
+ this.sections = input({}, ...(ngDevMode ? [{ debugName: "sections" }] : /* istanbul ignore next */ []));
333
+ /**
334
+ * @property {string[]} presetColors
335
+ * @description
336
+ * Colors of the predefined palette section.
337
+ * @default Design System base palette
338
+ */
339
+ this.presetColors = input(DEFAULT_PRESET_COLORS, ...(ngDevMode ? [{ debugName: "presetColors" }] : /* istanbul ignore next */ []));
340
+ /**
341
+ * @property {string | null} contrastColor
342
+ * @description
343
+ * Optional override of the text color used for the WCAG contrast pair.
344
+ * When `null` (default) the text color is resolved automatically from the
345
+ * picked background luminance (ITU-R BT.601): black over light colors,
346
+ * white over dark ones.
347
+ * @default `null`
348
+ */
349
+ this.contrastColor = input(null, ...(ngDevMode ? [{ debugName: "contrastColor" }] : /* istanbul ignore next */ []));
350
+ /**
351
+ * @property {number} maxCustomColors
352
+ * @description
353
+ * Maximum number of custom color slots. Once the list is at capacity, the
354
+ * oldest slot is replaced using a circular buffer — the "+" button stays
355
+ * visible at all times.
356
+ * @default `18`
357
+ */
358
+ this.maxCustomColors = input(18, ...(ngDevMode ? [{ debugName: "maxCustomColors" }] : /* istanbul ignore next */ []));
359
+ /**
360
+ * @property {'top' | 'bottom'} placement
361
+ * @description
362
+ * Preferred position of the popover relative to the trigger.
363
+ * @default `'bottom'`
364
+ */
365
+ this.placement = input('bottom', ...(ngDevMode ? [{ debugName: "placement" }] : /* istanbul ignore next */ []));
366
+ /**
367
+ * @property {string} errorMessage
368
+ * @description
369
+ * External error message that overrides the internal validation messages.
370
+ * @default `''`
371
+ */
372
+ this.errorMessage = input('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : /* istanbul ignore next */ []));
373
+ /**
374
+ * @property {ColorPickerTexts} texts
375
+ * @description
376
+ * UI texts (section labels, buttons, errors). Defaults are in English so
377
+ * consumers can localize with their own i18n solution.
378
+ * @default `{}` (English defaults)
379
+ */
380
+ this.texts = input({}, ...(ngDevMode ? [{ debugName: "texts" }] : /* istanbul ignore next */ []));
381
+ /**
382
+ * @property {ModelSignal<string>} value
383
+ * @description
384
+ * Confirmed color as a normalized lowercase 6-digit hex with `#`.
385
+ * Two-way bindable.
386
+ * @default `''`
387
+ */
388
+ this.value = model('', ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
389
+ /**
390
+ * @property {ModelSignal<string[]>} customColors
391
+ * @description
392
+ * User-saved custom colors. Two-way bindable so the consumer decides
393
+ * where to persist them.
394
+ * @default `[]`
395
+ */
396
+ this.customColors = model([], ...(ngDevMode ? [{ debugName: "customColors" }] : /* istanbul ignore next */ []));
397
+ /**
398
+ * @event colorChange
399
+ * @description
400
+ * Emits the confirmed background hex color: on Accept (action bar mode) or
401
+ * on every valid change (live mode / trigger HEX edit while closed). Never
402
+ * emits invalid values, on hydration, or while disabled.
403
+ */
404
+ this.colorChange = output();
405
+ /**
406
+ * @event textColorChange
407
+ * @description
408
+ * Emits together with `colorChange`: the ideal text color over the
409
+ * confirmed background (`#000000` on light colors, `#ffffff` on dark ones,
410
+ * or the `contrastColor` override when provided).
411
+ */
412
+ this.textColorChange = output();
413
+ /**
414
+ * @event validChange
415
+ * @description
416
+ * Emits only when the validity of the HEX value changes.
417
+ */
418
+ this.validChange = output();
419
+ /**
420
+ * @event opened
421
+ * @description
422
+ * Emits when the picker popover opens.
423
+ */
424
+ this.opened = output();
425
+ /**
426
+ * @event closed
427
+ * @description
428
+ * Emits when the popover closes, with the close reason
429
+ * (`accept` confirmed, `cancel` discarded/restored).
430
+ */
431
+ this.closed = output();
432
+ this.popover = viewChild('op', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
433
+ this.swatchBtnRef = viewChild('swatchBtn', { ...(ngDevMode ? { debugName: "swatchBtnRef" } : /* istanbul ignore next */ {}), read: ElementRef });
434
+ this.inputTriggerRef = viewChild('inputTrigger', { ...(ngDevMode ? { debugName: "inputTriggerRef" } : /* istanbul ignore next */ {}), read: ElementRef });
435
+ this.panelHexInput = viewChild('panelHexInput', { ...(ngDevMode ? { debugName: "panelHexInput" } : /* istanbul ignore next */ {}), read: ElementRef });
436
+ /**
437
+ * Internal FormControl handed to the `tk-input-text` trigger so the
438
+ * disabled state propagates through the Design System input.
439
+ */
440
+ this.triggerControl = new FormControl('');
441
+ this.isOpen = signal(false, ...(ngDevMode ? [{ debugName: "isOpen" }] : /* istanbul ignore next */ []));
442
+ this.hue = signal(0, ...(ngDevMode ? [{ debugName: "hue" }] : /* istanbul ignore next */ []));
443
+ this.saturation = signal(1, ...(ngDevMode ? [{ debugName: "saturation" }] : /* istanbul ignore next */ []));
444
+ this.brightness = signal(1, ...(ngDevMode ? [{ debugName: "brightness" }] : /* istanbul ignore next */ []));
445
+ this.draftHex = signal('', ...(ngDevMode ? [{ debugName: "draftHex" }] : /* istanbul ignore next */ []));
446
+ this.displayHexNoHash = signal('', ...(ngDevMode ? [{ debugName: "displayHexNoHash" }] : /* istanbul ignore next */ []));
447
+ this.isValid = signal(true, ...(ngDevMode ? [{ debugName: "isValid" }] : /* istanbul ignore next */ []));
448
+ this.errorType = signal('invalid', ...(ngDevMode ? [{ debugName: "errorType" }] : /* istanbul ignore next */ []));
449
+ this.cvaDisabled = signal(false, ...(ngDevMode ? [{ debugName: "cvaDisabled" }] : /* istanbul ignore next */ []));
450
+ this.isDisabled = computed(() => this.disabled() || this.cvaDisabled(), ...(ngDevMode ? [{ debugName: "isDisabled" }] : /* istanbul ignore next */ []));
451
+ this.resolvedSections = computed(() => ({
452
+ ...DEFAULT_SECTIONS,
453
+ ...this.sections(),
454
+ }), ...(ngDevMode ? [{ debugName: "resolvedSections" }] : /* istanbul ignore next */ []));
455
+ this.t = computed(() => ({
456
+ ...DEFAULT_TEXTS,
457
+ ...this.texts(),
458
+ }), ...(ngDevMode ? [{ debugName: "t" }] : /* istanbul ignore next */ []));
459
+ /** Valid draft hex to paint swatches, or null → neutral fallback via CSS. */
460
+ this.swatchColor = computed(() => {
461
+ const color = this.isOpen() ? this.draftHex() : this.value();
462
+ return this.isValidHex(color) ? color : null;
463
+ }, ...(ngDevMode ? [{ debugName: "swatchColor" }] : /* istanbul ignore next */ []));
464
+ /**
465
+ * Text color of the contrast pair: the `contrastColor` override when valid,
466
+ * otherwise resolved automatically from the draft background luminance.
467
+ */
468
+ this.contrastTextColor = computed(() => {
469
+ const override = this.contrastColor();
470
+ if (override) {
471
+ const norm = this.normalizeHex(override);
472
+ if (this.isValidHex(norm)) {
473
+ return norm;
474
+ }
475
+ }
476
+ const bg = this.draftHex();
477
+ return getContrastTextColor(this.isValidHex(bg) ? bg : '#ffffff');
478
+ }, ...(ngDevMode ? [{ debugName: "contrastTextColor" }] : /* istanbul ignore next */ []));
479
+ this.contrastResult = computed(() => {
480
+ const bg = this.draftHex();
481
+ if (!this.isValidHex(bg)) {
482
+ return null;
483
+ }
484
+ return getContrastResult(bg, this.contrastTextColor());
485
+ }, ...(ngDevMode ? [{ debugName: "contrastResult" }] : /* istanbul ignore next */ []));
486
+ this.showContrast = computed(() => this.resolvedSections().contrast && this.contrastResult() !== null, ...(ngDevMode ? [{ debugName: "showContrast" }] : /* istanbul ignore next */ []));
487
+ this.eyeDropperSupported = typeof globalThis !== 'undefined' && 'EyeDropper' in globalThis;
488
+ this.showEyedropper = computed(() => this.resolvedSections().hex &&
489
+ this.resolvedSections().eyedropper &&
490
+ this.eyeDropperSupported, ...(ngDevMode ? [{ debugName: "showEyedropper" }] : /* istanbul ignore next */ []));
491
+ this.canAddCustom = computed(() => this.isValidHex(this.draftHex()), ...(ngDevMode ? [{ debugName: "canAddCustom" }] : /* istanbul ignore next */ []));
492
+ this.errorText = computed(() => {
493
+ if (this.errorMessage()) {
494
+ return this.errorMessage();
495
+ }
496
+ return this.errorType() === 'required'
497
+ ? this.t().requiredError
498
+ : this.t().invalidHexError;
499
+ }, ...(ngDevMode ? [{ debugName: "errorText" }] : /* istanbul ignore next */ []));
500
+ this.originalValue = '';
501
+ // Circular buffer pointer: tracks the oldest slot index to replace when customColors is at capacity.
502
+ // Starts at 0 (correct for both empty lists and backend-loaded full arrays, assuming oldest-first order).
503
+ // Limitation: not reset when customColors is rebound externally at runtime without destroying the component.
504
+ this.customColorWritePtr = signal(0, ...(ngDevMode ? [{ debugName: "customColorWritePtr" }] : /* istanbul ignore next */ []));
505
+ this.hexInput$ = new Subject();
506
+ this.onChange = () => { };
507
+ this.onTouched = () => { };
508
+ ColorPickerComponent.instanceCount++;
509
+ this.fieldId = `tk-color-picker-${ColorPickerComponent.instanceCount}`;
510
+ this.panelFieldId = `${this.fieldId}-panel`;
511
+ this.errorId = `${this.fieldId}-error`;
512
+ effect(() => {
513
+ const incoming = this.value();
514
+ untracked(() => this.syncFromValue(incoming));
515
+ });
516
+ effect(() => {
517
+ if (this.isDisabled() && this.isOpen()) {
518
+ this.popover()?.hide();
519
+ }
520
+ });
521
+ effect(() => {
522
+ if (this.isDisabled()) {
523
+ this.triggerControl.disable({ emitEvent: false });
524
+ }
525
+ else {
526
+ this.triggerControl.enable({ emitEvent: false });
527
+ }
528
+ });
529
+ // Sync triggerControl validity so tk-input-text shows errors natively.
530
+ effect(() => {
531
+ const valid = this.isValid();
532
+ untracked(() => {
533
+ if (valid) {
534
+ this.triggerControl.setErrors(null);
535
+ }
536
+ else {
537
+ this.triggerControl.setErrors({ hex: true });
538
+ }
539
+ });
540
+ });
541
+ this.hexInput$
542
+ .pipe(debounceTime(200), takeUntilDestroyed(this.destroyRef))
543
+ .subscribe((v) => this.processHexInput(v, false));
544
+ }
545
+ // ── ControlValueAccessor ────────────────────────────────────────────────
546
+ /**
547
+ * @method writeValue
548
+ * @description Hydrates the picker from the form model without emitting.
549
+ */
550
+ writeValue(value) {
551
+ this.value.set(value || '');
552
+ }
553
+ /**
554
+ * @method registerOnChange
555
+ * @description Registers the Reactive Forms change callback.
556
+ */
557
+ registerOnChange(fn) {
558
+ this.onChange = fn;
559
+ }
560
+ /**
561
+ * @method registerOnTouched
562
+ * @description Registers the Reactive Forms touched callback.
563
+ */
564
+ registerOnTouched(fn) {
565
+ this.onTouched = fn;
566
+ }
567
+ /**
568
+ * @method setDisabledState
569
+ * @description Syncs the disabled state from Reactive Forms.
570
+ */
571
+ setDisabledState(isDisabled) {
572
+ this.cvaDisabled.set(isDisabled);
573
+ }
574
+ // ── Popover lifecycle ───────────────────────────────────────────────────
575
+ onTriggerClick(event) {
576
+ if (this.isDisabled()) {
577
+ return;
578
+ }
579
+ this.popover()?.toggle(event, this.getAnchorEl() ?? this.el.nativeElement);
580
+ }
581
+ /** Returns the precise anchor element for popover positioning.
582
+ * For the input variant we target the `p-floatlabel` child instead of the
583
+ * full `tk-input-text` host. The host includes a bottom section with
584
+ * `min-height: 1.25rem + margin-top: 0.25rem` that is invisible when empty
585
+ * but would push the panel ~1.5rem below the visible input field.
586
+ *
587
+ * Coupling note: `p-floatlabel` is PrimeNG's float-label host element selector.
588
+ * If PrimeNG renames it in a future major, the `?? host` fallback keeps
589
+ * positioning functional (panel opens ~1.5rem lower than ideal). */
590
+ getAnchorEl() {
591
+ if (this.variant() === 'swatch') {
592
+ return this.swatchBtnRef()?.nativeElement;
593
+ }
594
+ const host = this.inputTriggerRef()?.nativeElement;
595
+ return host?.querySelector('p-floatlabel') ?? host;
596
+ }
597
+ onPopoverShow() {
598
+ this.isOpen.set(true);
599
+ this.originalValue = this.value();
600
+ // Sync draft from current value
601
+ this.syncFromValue(this.value());
602
+ this.opened.emit();
603
+ // PrimeNG's absolutePosition flips the panel to "above" when it doesn't fit
604
+ // below, but clamps to scrollTop when there's no space above either — panel
605
+ // ends up covering the trigger. We override the position after PrimeNG runs.
606
+ setTimeout(() => this.fixPanelPosition(), 0);
607
+ }
608
+ fixPanelPosition() {
609
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
610
+ const container = this.popover()?.container;
611
+ if (!container)
612
+ return;
613
+ const anchor = this.getAnchorEl();
614
+ if (!anchor)
615
+ return;
616
+ const anchorRect = anchor.getBoundingClientRect();
617
+ const panelH = container.offsetHeight;
618
+ const panelW = container.offsetWidth;
619
+ const vH = window.innerHeight;
620
+ const vW = window.innerWidth;
621
+ const scrollY = window.scrollY;
622
+ const scrollX = window.scrollX;
623
+ const spaceBelow = vH - anchorRect.bottom;
624
+ const spaceAbove = anchorRect.top;
625
+ let top;
626
+ if (spaceBelow >= panelH) {
627
+ // Preferred: open below trigger
628
+ top = anchorRect.bottom + scrollY;
629
+ }
630
+ else if (spaceAbove >= panelH) {
631
+ // Fallback: open above trigger
632
+ top = anchorRect.top + scrollY - panelH;
633
+ }
634
+ else {
635
+ // Neither side fits: prefer below (scroll context), clamp to viewport edges.
636
+ // Anchor to trigger bottom and push up only as much as needed so the panel
637
+ // stays close to the trigger rather than jumping to the top of the page.
638
+ const idealBelow = anchorRect.bottom + scrollY;
639
+ const maxTop = scrollY + vH - panelH - 8;
640
+ const minTop = scrollY + 8;
641
+ top = Math.max(minTop, Math.min(idealBelow, maxTop));
642
+ }
643
+ let left = anchorRect.left + scrollX;
644
+ if (left + panelW > scrollX + vW - 8) {
645
+ left = scrollX + vW - panelW - 8;
646
+ }
647
+ left = Math.max(scrollX + 8, left);
648
+ // Zero out PrimeNG's arrow margin so the panel sits flush with the trigger.
649
+ container.style.margin = '0';
650
+ container.style.top = `${top}px`;
651
+ container.style.insetInlineStart = `${left}px`;
652
+ }
653
+ onPopoverHide() {
654
+ this.isOpen.set(false);
655
+ this.onTouched();
656
+ this.triggerControl.markAsTouched();
657
+ this.closed.emit('accept');
658
+ }
659
+ onPanelEnter(event) {
660
+ this.confirmAndClose();
661
+ event.preventDefault();
662
+ event.stopPropagation();
663
+ }
664
+ confirmAndClose() {
665
+ if (!this.isValidHex(this.draftHex())) {
666
+ return;
667
+ }
668
+ this.commitDraft();
669
+ this.popover()?.hide();
670
+ }
671
+ // ── Draft state updates ─────────────────────────────────────────────────
672
+ onSpectrumChange(change) {
673
+ this.saturation.set(change.saturation);
674
+ this.brightness.set(change.brightness);
675
+ this.applyHsv();
676
+ }
677
+ onHueChange(event) {
678
+ this.hue.set(Number(event.target.value));
679
+ this.applyHsv();
680
+ }
681
+ onSwatchPick(color) {
682
+ const hex = this.normalizeHex(color);
683
+ if (this.isValidHex(hex)) {
684
+ this.setFromHex(hex, true);
685
+ }
686
+ }
687
+ addCustomColor() {
688
+ const hex = this.draftHex();
689
+ if (!this.isValidHex(hex))
690
+ return;
691
+ const list = this.customColors();
692
+ if (list.some((c) => c.toLowerCase() === hex))
693
+ return;
694
+ if (list.length >= this.maxCustomColors()) {
695
+ // Circular buffer: replace the oldest slot (writePtr) and advance the
696
+ // pointer. Only one cell changes per add — no grid shift.
697
+ // Both signal mutations are kept outside update() to avoid side effects
698
+ // inside a state-transition callback.
699
+ const ptr = this.customColorWritePtr();
700
+ const next = [...list];
701
+ next[ptr] = hex;
702
+ this.customColors.set(next);
703
+ this.customColorWritePtr.set((ptr + 1) % this.maxCustomColors());
704
+ }
705
+ else {
706
+ this.customColors.update((l) => [...l, hex]);
707
+ }
708
+ }
709
+ isCustomColorSelected() {
710
+ const selected = this.draftHex().toLowerCase();
711
+ return this.customColors().some((c) => c.toLowerCase() === selected);
712
+ }
713
+ removeCustomColor() {
714
+ const hex = this.draftHex().toLowerCase();
715
+ this.customColors.update((list) => list.filter((c) => c.toLowerCase() !== hex));
716
+ }
717
+ openEyeDropper() {
718
+ const ctor = globalThis
719
+ .EyeDropper;
720
+ if (!ctor) {
721
+ return;
722
+ }
723
+ new ctor()
724
+ .open()
725
+ .then((result) => {
726
+ const hex = this.normalizeHex(result.sRGBHex);
727
+ if (this.isValidHex(hex)) {
728
+ this.setFromHex(hex, true);
729
+ // Focus HEX input so Enter closes modal without opening eyedropper again
730
+ setTimeout(() => {
731
+ const inputEl = this.panelHexInput();
732
+ if (inputEl) {
733
+ const hexInput = inputEl.nativeElement.querySelector('input');
734
+ if (hexInput) {
735
+ hexInput.focus();
736
+ }
737
+ }
738
+ }, 0);
739
+ }
740
+ })
741
+ .catch(() => {
742
+ // The user dismissed the eyedropper — nothing to do.
743
+ });
744
+ }
745
+ // ── HEX input pipeline ──────────────────────────────────────────────────
746
+ onHexInput(clean) {
747
+ this.displayHexNoHash.set(clean);
748
+ this.hexInput$.next('#' + clean);
749
+ }
750
+ /**
751
+ * Sanitizes typing inside a `tk-input-text` hex field (trigger or popover):
752
+ * strips non-hex characters at the DOM level (keeping the caret behavior of
753
+ * the native input) and feeds the shared validation pipeline.
754
+ */
755
+ onHexNativeInput(event) {
756
+ const target = event.target;
757
+ if (!target || target.tagName !== 'INPUT') {
758
+ return;
759
+ }
760
+ const clean = target.value.replace(/[^A-Fa-f0-9]/g, '').toUpperCase().slice(0, 6);
761
+ if (clean !== target.value) {
762
+ target.value = clean;
763
+ }
764
+ this.onHexInput(clean);
765
+ }
766
+ /** Blocks non-hex keypresses and enforces 6-char max before char enters the DOM. Enter confirms selection. */
767
+ onHexKeydown(event) {
768
+ if (event.key === 'Enter') {
769
+ event.preventDefault();
770
+ event.stopPropagation();
771
+ const hex = this.draftHex();
772
+ if (this.isValidHex(hex)) {
773
+ this.commitDraft();
774
+ }
775
+ // Close popover
776
+ const pop = this.popover();
777
+ if (pop) {
778
+ pop.hide();
779
+ }
780
+ return;
781
+ }
782
+ if (event.ctrlKey || event.metaKey || event.altKey) {
783
+ return;
784
+ }
785
+ const navigationKeys = [
786
+ 'Backspace', 'Delete', 'Tab',
787
+ 'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown',
788
+ 'Home', 'End',
789
+ ];
790
+ if (navigationKeys.includes(event.key)) {
791
+ return;
792
+ }
793
+ if (!/^[A-Fa-f0-9]$/.test(event.key)) {
794
+ event.preventDefault();
795
+ return;
796
+ }
797
+ // Enforce 6-char max: block if no selection to replace and already full
798
+ const target = event.target;
799
+ if (target && target.tagName === 'INPUT') {
800
+ const selectionLength = (target.selectionEnd ?? 0) - (target.selectionStart ?? 0);
801
+ if (target.value.length >= 6 && selectionLength === 0) {
802
+ event.preventDefault();
803
+ }
804
+ }
805
+ }
806
+ /** Strips non-hex chars from pasted text before it reaches the input model. */
807
+ onHexPaste(event) {
808
+ event.preventDefault();
809
+ const text = event.clipboardData?.getData('text/plain') ?? '';
810
+ const clean = text.replace(/[^A-Fa-f0-9]/g, '').toUpperCase();
811
+ const target = event.target;
812
+ if (!target || target.tagName !== 'INPUT') {
813
+ return;
814
+ }
815
+ const start = target.selectionStart ?? 0;
816
+ const end = target.selectionEnd ?? 0;
817
+ const merged = (target.value.slice(0, start) + clean + target.value.slice(end)).slice(0, 6);
818
+ target.value = merged;
819
+ this.onHexInput(merged);
820
+ }
821
+ onHexBlur() {
822
+ this.processHexInput('#' + this.displayHexNoHash(), true);
823
+ this.onTouched();
824
+ }
825
+ processHexInput(raw, forceNormalize) {
826
+ const hex = this.parseHex(raw);
827
+ if (hex === null) {
828
+ return;
829
+ }
830
+ if (this.isValidHex(hex)) {
831
+ this.applyValidHex(hex, forceNormalize);
832
+ }
833
+ else {
834
+ this.applyInvalidHex(hex.slice(1), forceNormalize);
835
+ }
836
+ }
837
+ parseHex(raw) {
838
+ const text = raw?.trim() ?? '';
839
+ const body = (text.startsWith('#') ? text.slice(1) : text).trim();
840
+ if (body.length === 0) {
841
+ if (this.required()) {
842
+ this.errorType.set('required');
843
+ this.setValid(false);
844
+ }
845
+ else {
846
+ this.setValid(true);
847
+ }
848
+ return null;
849
+ }
850
+ return ('#' + body).toLowerCase();
851
+ }
852
+ applyValidHex(hex, forceNormalize) {
853
+ if (hex.length === 4 && !forceNormalize) {
854
+ this.setValid(true);
855
+ return;
856
+ }
857
+ const norm = this.normalizeHex(hex);
858
+ this.setValid(true);
859
+ if (norm !== this.draftHex()) {
860
+ this.setFromHex(norm, true);
861
+ }
862
+ else {
863
+ this.displayHexNoHash.set(norm.slice(1).toUpperCase());
864
+ }
865
+ }
866
+ applyInvalidHex(rawText, forceNormalize) {
867
+ this.errorType.set('invalid');
868
+ const partial = !forceNormalize && /^[A-Fa-f0-9]*$/.test(rawText) && rawText.length <= 6;
869
+ this.setValid(partial);
870
+ }
871
+ setValid(value) {
872
+ if (this.isValid() === value) {
873
+ return;
874
+ }
875
+ this.isValid.set(value);
876
+ this.validChange.emit(value);
877
+ }
878
+ // ── Internal state helpers ──────────────────────────────────────────────
879
+ syncFromValue(incoming, restoring = false) {
880
+ if (!incoming) {
881
+ if (restoring) {
882
+ this.draftHex.set('');
883
+ this.displayHexNoHash.set('');
884
+ }
885
+ // Validate empty color (fails if required)
886
+ this.parseHex('');
887
+ return;
888
+ }
889
+ const hex = this.normalizeHex(incoming);
890
+ if (this.isValidHex(hex) && hex !== this.draftHex()) {
891
+ this.setFromHex(hex, false);
892
+ }
893
+ }
894
+ setFromHex(hex, interactive) {
895
+ this.draftHex.set(hex);
896
+ this.displayHexNoHash.set(hex.slice(1).toUpperCase());
897
+ this.setValid(true);
898
+ const hsv = this.hexToHsv(hex);
899
+ this.hue.set(hsv.h);
900
+ this.saturation.set(hsv.s);
901
+ this.brightness.set(hsv.v);
902
+ if (interactive) {
903
+ this.maybeCommit();
904
+ }
905
+ }
906
+ applyHsv() {
907
+ const hex = this.hsvToHex(this.hue(), this.saturation(), this.brightness());
908
+ this.draftHex.set(hex);
909
+ this.displayHexNoHash.set(hex.slice(1).toUpperCase());
910
+ this.setValid(true);
911
+ this.maybeCommit();
912
+ }
913
+ /**
914
+ * Commits the draft color immediately (live mode).
915
+ */
916
+ maybeCommit() {
917
+ this.commitDraft();
918
+ }
919
+ commitDraft() {
920
+ const hex = this.draftHex();
921
+ if (!this.isValidHex(hex) || this.isDisabled()) {
922
+ return;
923
+ }
924
+ this.value.set(hex);
925
+ this.onChange(hex);
926
+ this.colorChange.emit(hex);
927
+ this.textColorChange.emit(this.contrastTextColor());
928
+ }
929
+ // ── Color math ──────────────────────────────────────────────────────────
930
+ normalizeHex(value) {
931
+ if (!value?.trim()) {
932
+ return '';
933
+ }
934
+ let v = value.trim();
935
+ v = v.startsWith('#') ? v : '#' + v;
936
+ if (/^#[A-Fa-f0-9]{3}$/.test(v)) {
937
+ v = '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3];
938
+ }
939
+ return v.toLowerCase();
940
+ }
941
+ isValidHex(value) {
942
+ return HEX_REGEX.test(value);
943
+ }
944
+ hexToHsv(hex) {
945
+ const r = Number.parseInt(hex.slice(1, 3), 16) / 255;
946
+ const g = Number.parseInt(hex.slice(3, 5), 16) / 255;
947
+ const b = Number.parseInt(hex.slice(5, 7), 16) / 255;
948
+ const max = Math.max(r, g, b);
949
+ const min = Math.min(r, g, b);
950
+ const delta = max - min;
951
+ let h = 0;
952
+ if (delta !== 0) {
953
+ if (max === r) {
954
+ h = ((g - b) / delta) % 6;
955
+ }
956
+ else if (max === g) {
957
+ h = (b - r) / delta + 2;
958
+ }
959
+ else {
960
+ h = (r - g) / delta + 4;
961
+ }
962
+ h = Math.round(h * 60);
963
+ if (h < 0) {
964
+ h += 360;
965
+ }
966
+ }
967
+ return { h, s: max === 0 ? 0 : delta / max, v: max };
968
+ }
969
+ hsvToHex(h, s, v) {
970
+ const c = v * s;
971
+ const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
972
+ const m = v - c;
973
+ let r = 0;
974
+ let g = 0;
975
+ let b = 0;
976
+ if (h < 60) {
977
+ r = c;
978
+ g = x;
979
+ }
980
+ else if (h < 120) {
981
+ r = x;
982
+ g = c;
983
+ }
984
+ else if (h < 180) {
985
+ g = c;
986
+ b = x;
987
+ }
988
+ else if (h < 240) {
989
+ g = x;
990
+ b = c;
991
+ }
992
+ else if (h < 300) {
993
+ r = x;
994
+ b = c;
995
+ }
996
+ else {
997
+ r = c;
998
+ b = x;
999
+ }
1000
+ const toHex = (n) => Math.round((n + m) * 255)
1001
+ .toString(16)
1002
+ .padStart(2, '0');
1003
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
1004
+ }
1005
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: ColorPickerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1006
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.3", type: ColorPickerComponent, isStandalone: true, selector: "tk-color-picker", inputs: { variant: { classPropertyName: "variant", publicName: "variant", isSignal: true, isRequired: false, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, hint: { classPropertyName: "hint", publicName: "hint", isSignal: true, isRequired: false, transformFunction: null }, disabled: { classPropertyName: "disabled", publicName: "disabled", isSignal: true, isRequired: false, transformFunction: null }, required: { classPropertyName: "required", publicName: "required", isSignal: true, isRequired: false, transformFunction: null }, sections: { classPropertyName: "sections", publicName: "sections", isSignal: true, isRequired: false, transformFunction: null }, presetColors: { classPropertyName: "presetColors", publicName: "presetColors", isSignal: true, isRequired: false, transformFunction: null }, contrastColor: { classPropertyName: "contrastColor", publicName: "contrastColor", isSignal: true, isRequired: false, transformFunction: null }, maxCustomColors: { classPropertyName: "maxCustomColors", publicName: "maxCustomColors", isSignal: true, isRequired: false, transformFunction: null }, placement: { classPropertyName: "placement", publicName: "placement", isSignal: true, isRequired: false, transformFunction: null }, errorMessage: { classPropertyName: "errorMessage", publicName: "errorMessage", isSignal: true, isRequired: false, transformFunction: null }, texts: { classPropertyName: "texts", publicName: "texts", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, customColors: { classPropertyName: "customColors", publicName: "customColors", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { value: "valueChange", customColors: "customColorsChange", colorChange: "colorChange", textColorChange: "textColorChange", validChange: "validChange", opened: "opened", closed: "closed" }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["op"], descendants: true, isSignal: true }, { propertyName: "swatchBtnRef", first: true, predicate: ["swatchBtn"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "inputTriggerRef", first: true, predicate: ["inputTrigger"], descendants: true, read: ElementRef, isSignal: true }, { propertyName: "panelHexInput", first: true, predicate: ["panelHexInput"], descendants: true, read: ElementRef, isSignal: true }], ngImport: i0, template: "<div\n class=\"tk-color-picker\"\n [class.tk-color-picker--disabled]=\"isDisabled()\"\n [class.tk-color-picker--open]=\"isOpen()\"\n [class.tk-color-picker--invalid]=\"!isValid()\">\n @if (variant() === 'swatch') {\n @if (label()) {\n <span class=\"tk-color-picker__label\">{{ label() }}</span>\n }\n <button\n #swatchBtn\n type=\"button\"\n class=\"tk-color-picker__trigger tk-color-picker__trigger--swatch\"\n [disabled]=\"isDisabled()\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-label]=\"t().openPickerLabel + (label() ? ': ' + label() : '')\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onTriggerClick($event)\">\n <span\n class=\"tk-color-picker__swatch\"\n [class.tk-color-picker__swatch--fallback]=\"!swatchColor()\"\n [style.background-color]=\"swatchColor()\"\n aria-hidden=\"true\"></span>\n <tk-icon\n class=\"tk-color-picker__chevron\"\n [class.tk-color-picker__chevron--open]=\"isOpen()\"\n icon=\"chevron-down\"\n styleIcon=\"regular\"\n size=\"xs\"></tk-icon>\n </button>\n } @else {\n <div\n #inputTrigger\n class=\"tk-color-picker__trigger-input\"\n [class.tk-color-picker__trigger-input--invalid]=\"!isValid()\">\n @if (label()) {\n <label\n class=\"tk-color-picker__input-label\"\n [for]=\"fieldId\"\n [title]=\"label()\"\n >{{ label() }}</label\n >\n }\n <div class=\"tk-color-picker__input-wrap\">\n <button\n type=\"button\"\n class=\"tk-color-picker__input-swatch\"\n [class.tk-color-picker__input-swatch--fallback]=\"!swatchColor()\"\n [style.background-color]=\"swatchColor() ?? null\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"t().openPickerLabel\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onTriggerClick($event)\"></button>\n <span class=\"tk-color-picker__input-prefix\" aria-hidden=\"true\">#</span>\n <input\n pInputText\n [id]=\"fieldId\"\n [value]=\"displayHexNoHash()\"\n [disabled]=\"isDisabled()\"\n [class.ng-invalid]=\"triggerControl.invalid\"\n [class.ng-dirty]=\"triggerControl.dirty\"\n [class.ng-touched]=\"triggerControl.touched\"\n [attr.aria-describedby]=\"!isValid() ? errorId : null\"\n [attr.aria-invalid]=\"!isValid()\"\n autocomplete=\"off\"\n (input)=\"onHexNativeInput($event)\"\n (keydown)=\"onHexKeydown($event)\"\n (paste)=\"onHexPaste($event)\"\n (blur)=\"onHexBlur()\" />\n </div>\n <div class=\"tk-color-picker__input-bottom\">\n @if (!isValid()) {\n <p-message\n severity=\"error\"\n size=\"small\"\n variant=\"simple\"\n [id]=\"errorId\"\n >{{ errorText() }}</p-message\n >\n } @else if (hint()) {\n <p-message severity=\"secondary\" size=\"small\" variant=\"simple\">{{\n hint()\n }}</p-message>\n }\n </div>\n </div>\n }\n\n @if (variant() === 'swatch') {\n @if (!isValid()) {\n <span\n [id]=\"errorId\"\n class=\"tk-color-picker__error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorText() }}\n </span>\n } @else if (hint()) {\n <span class=\"tk-color-picker__hint\">{{ hint() }}</span>\n }\n }\n</div>\n\n<p-popover\n #op\n [styleClass]=\"\n 'tk-color-picker-popover' +\n (variant() === 'input' ? ' tk-color-picker-popover--input' : '')\n \"\n position=\"bottom\"\n appendTo=\"body\"\n (onShow)=\"onPopoverShow()\"\n (onHide)=\"onPopoverHide()\">\n <div\n class=\"tk-color-picker__panel\"\n role=\"group\"\n [attr.aria-label]=\"t().dialogLabel\"\n tabindex=\"-1\"\n autofocus\n (keydown.enter)=\"onPanelEnter($event)\">\n @if (resolvedSections().swatches && presetColors().length) {\n <section class=\"tk-color-picker__section\">\n <span class=\"tk-color-picker__section-label\">{{\n t().swatchesLabel\n }}</span>\n <tk-color-picker-swatch-grid\n [colors]=\"presetColors()\"\n [selected]=\"draftHex()\"\n [ariaLabel]=\"t().swatchesLabel\"\n (picked)=\"onSwatchPick($event)\" />\n </section>\n }\n\n @if (resolvedSections().customColors) {\n <section class=\"tk-color-picker__section\">\n <span class=\"tk-color-picker__section-label\">{{\n t().customColorsLabel\n }}</span>\n <tk-color-picker-swatch-grid\n [colors]=\"customColors()\"\n [selected]=\"draftHex()\"\n [ariaLabel]=\"t().customColorsLabel\"\n (picked)=\"onSwatchPick($event)\">\n @if (canAddCustom()) {\n <button\n type=\"button\"\n class=\"tk-color-picker__add-custom\"\n [attr.aria-label]=\"t().addCustomColorLabel\"\n (click)=\"addCustomColor()\">\n <tk-icon icon=\"plus\" styleIcon=\"regular\" size=\"xs\"></tk-icon>\n </button>\n }\n @if (isCustomColorSelected()) {\n <button\n type=\"button\"\n class=\"tk-color-picker__remove-custom\"\n [attr.aria-label]=\"t().removeCustomColorLabel\"\n (click)=\"removeCustomColor()\">\n \u2212\n </button>\n }\n </tk-color-picker-swatch-grid>\n </section>\n }\n\n @if (\n resolvedSections().customColors &&\n (resolvedSections().spectrum ||\n resolvedSections().hue ||\n resolvedSections().hex)\n ) {\n <div class=\"tk-color-picker__divider\" role=\"separator\"></div>\n }\n\n @if (resolvedSections().spectrum) {\n <tk-color-picker-spectrum\n [hue]=\"hue()\"\n [saturation]=\"saturation()\"\n [brightness]=\"brightness()\"\n [color]=\"swatchColor() ?? ''\"\n [ariaLabel]=\"t().spectrumLabel\"\n (changed)=\"onSpectrumChange($event)\" />\n }\n\n @if (resolvedSections().hue) {\n <input\n class=\"tk-color-picker__hue\"\n type=\"range\"\n min=\"0\"\n max=\"360\"\n [value]=\"hue()\"\n [attr.aria-label]=\"t().hueLabel\"\n (input)=\"onHueChange($event)\" />\n }\n\n @if (\n (resolvedSections().spectrum || resolvedSections().hue) &&\n resolvedSections().hex\n ) {\n <div class=\"tk-color-picker__divider\" role=\"separator\"></div>\n }\n\n @if (resolvedSections().hex) {\n <section class=\"tk-color-picker__section tk-color-picker__hex-row\">\n <span\n class=\"tk-color-picker__swatch tk-color-picker__swatch--large\"\n [class.tk-color-picker__swatch--fallback]=\"!swatchColor()\"\n [style.background-color]=\"swatchColor()\"\n aria-hidden=\"true\"></span>\n <tk-input-text\n #panelHexInput\n class=\"tk-color-picker__panel-hex\"\n [class.tk-color-picker__trigger-input--invalid]=\"!isValid()\"\n [label]=\"t().hexLabel\"\n [id]=\"panelFieldId\"\n [value]=\"displayHexNoHash()\"\n prefixText=\"#\"\n (input)=\"onHexNativeInput($event)\"\n (keydown)=\"onHexKeydown($event)\"\n (paste)=\"onHexPaste($event)\"\n (focusout)=\"onHexBlur()\" />\n @if (showEyedropper()) {\n <tk-button\n class=\"tk-color-picker__eyedropper\"\n tabindex=\"-1\"\n severity=\"secondary\"\n variant=\"outlined\"\n icon=\"eye-dropper\"\n styleIcon=\"regular\"\n [attr.aria-label]=\"t().eyedropperLabel\"\n (clicked)=\"openEyeDropper()\">\n </tk-button>\n }\n </section>\n }\n\n @if (resolvedSections().hex && showContrast()) {\n <div class=\"tk-color-picker__divider\" role=\"separator\"></div>\n }\n\n @if (showContrast()) {\n <section class=\"tk-color-picker__section tk-color-picker__contrast\">\n <span class=\"tk-color-picker__section-label\">{{\n t().contrastLabel\n }}</span>\n <div class=\"tk-color-picker__contrast-row\">\n <span\n class=\"tk-color-picker__contrast-pill\"\n [style.background-color]=\"draftHex()\"\n [attr.aria-label]=\"t().contrastSampleLabel\">\n <span\n class=\"tk-color-picker__contrast-sample\"\n [style.color]=\"contrastTextColor()\">\n {{ t().contrastSampleText }}\n </span>\n </span>\n <div class=\"tk-color-picker__contrast-info\">\n <span class=\"tk-color-picker__contrast-label\">{{\n t().contrastSampleLabel\n }}</span>\n <span class=\"tk-color-picker__contrast-ratio\"\n >{{ contrastResult()!.ratio }}:1</span\n >\n </div>\n @switch (contrastResult()!.level) {\n @case ('AAA') {\n <span\n class=\"tk-color-picker__contrast-badge tk-color-picker__contrast-badge--aaa\"\n >AAA</span\n >\n }\n @case ('AA') {\n <span\n class=\"tk-color-picker__contrast-badge tk-color-picker__contrast-badge--aa\"\n >AA</span\n >\n }\n @case ('fail') {\n <span\n class=\"tk-color-picker__contrast-badge tk-color-picker__contrast-badge--fail\">\n {{ t().contrastLowLabel }}\n </span>\n }\n }\n </div>\n </section>\n }\n </div>\n</p-popover>\n", styles: [":host{display:inline-block;font-family:var(--tk-font-family, sans-serif)}.tk-color-picker{display:flex;flex-direction:column;gap:var(--tk-spacing-gap-xs, .25rem)}.tk-color-picker__label{font-size:var(--tk-font-size-paragraph-m, 1rem);font-weight:var(--tk-font-weight-400, 400);color:var(--tk-color-base-surface-950, #191a1b);margin-bottom:.25rem}.tk-color-picker__trigger{display:flex;align-items:center;gap:var(--tk-spacing-gap-s, .5rem);padding:var(--tk-spacing-padding-xs, .25rem) var(--tk-spacing-padding-s, .5rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4);border-radius:var(--tk-borderRadius-s, .25rem);background:var(--tk-color-background-default, #ffffff);cursor:pointer;transition:border-color .14s ease,background .14s ease}.tk-color-picker__trigger:hover{border-color:var(--tk-color-border-default, #cecdcd)}.tk-color-picker__trigger:focus-within{border-color:var(--tk-color-border-focus, #16006f)}.tk-color-picker__trigger--swatch{appearance:none;width:fit-content;font:inherit;-webkit-user-select:none;user-select:none}.tk-color-picker__trigger--swatch>*{pointer-events:none}.tk-color-picker__trigger--swatch:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__trigger--swatch:disabled{cursor:not-allowed}.tk-color-picker__trigger-input{display:flex;flex-direction:column;position:relative}.tk-color-picker__input-label{position:absolute;left:var(--tk-spacing-base-300, 3rem);top:.75rem;font-size:var(--tk-font-size-paragraph-m, 1rem);font-weight:var(--tk-font-weight-400, 400);color:var(--tk-color-base-surface-500, #8a8a8b);background:var(--tk-color-background-default, #ffffff);padding:0 .25rem;transition:top .2s ease,left .2s ease,color .2s ease;pointer-events:none;z-index:1;max-width:calc(100% - 4rem);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tk-color-picker--disabled .tk-color-picker__input-label{display:none}.tk-color-picker--invalid .tk-color-picker__input-label{color:var(--tk-color-base-red-700, #cf2604)}.tk-color-picker__input-wrap{position:relative;display:flex;align-items:center;width:100%}.tk-color-picker__input-wrap input.p-inputtext{width:100%;flex:1;border:none;border-bottom:.0625rem solid var(--tk-color-base-surface-300, #d2d2d2);border-radius:0;padding:var(--tk-spacing-base-75, .75rem);padding-left:var(--tk-spacing-base-300, 3rem);color:var(--tk-color-base-surface-950, #191a1b);background-color:transparent;outline:none}.tk-color-picker__input-wrap input.p-inputtext:focus{border-color:var(--tk-color-base-primary-600, #140065);box-shadow:none}.tk-color-picker__input-wrap input.p-inputtext.ng-invalid.ng-dirty,.tk-color-picker__input-wrap input.p-inputtext.ng-invalid.ng-touched{border-color:var(--tk-color-base-red-700, #cf2604)}.tk-color-picker__input-wrap input.p-inputtext:disabled{background-color:var(--tk-color-base-surface-200, #e4e4e4);color:var(--tk-color-base-surface-500, #8a8a8b);opacity:1}.tk-color-picker__input-swatch{appearance:none;padding:0;position:absolute;left:var(--tk-spacing-base-25, .25rem);top:50%;transform:translateY(-50%);width:1.25rem;height:1.25rem;border-radius:var(--tk-borderRadius-xs, .125rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4);flex-shrink:0;cursor:pointer;z-index:1;-webkit-user-select:none;user-select:none;background:transparent}.tk-color-picker__input-swatch--fallback{background:var(--tk-color-base-surface-400, #cecdcd)}.tk-color-picker__input-swatch:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__input-swatch:disabled{cursor:not-allowed;opacity:.5}.tk-color-picker__input-prefix{position:absolute;left:calc(var(--tk-spacing-base-25, .25rem) + 1.25rem + .25rem);top:50%;transform:translateY(-50%);color:var(--tk-color-text-muted, #8a8a8b);font-size:var(--tk-font-size-paragraph-s, .875rem);line-height:1;pointer-events:none;z-index:1}.tk-color-picker__input-bottom{display:flex;margin-top:.25rem;min-height:1.25rem}.tk-color-picker--open .tk-color-picker__trigger{border-color:var(--tk-color-border-strong, #424243)}.tk-color-picker--invalid .tk-color-picker__trigger{border-color:var(--tk-color-feedback-danger-default, #ff6640)}.tk-color-picker--disabled .tk-color-picker__trigger{opacity:.55;cursor:not-allowed;pointer-events:none;background:var(--tk-color-background-soft, #f2f1f1)}.tk-color-picker__swatch{flex-shrink:0;width:1.25rem;height:1.25rem;border-radius:var(--tk-borderRadius-xs, .125rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4)}.tk-color-picker__swatch--fallback{background:var(--tk-color-base-surface-400, #cecdcd)}.tk-color-picker__swatch--large{width:2rem;height:2rem;border-radius:var(--tk-borderRadius-s, .25rem)}.tk-color-picker__chevron{transition:transform .2s ease}.tk-color-picker__chevron--open{transform:rotate(180deg)}.tk-color-picker__error{font-size:var(--tk-font-size-legal-s, .625rem);color:var(--tk-color-feedback-danger-default, #ff6640)}.tk-color-picker__hint{font-size:var(--tk-font-size-legal-s, .625rem);color:var(--tk-color-text-muted, #8a8a8b)}.tk-color-picker__panel{display:flex;flex-direction:column;gap:var(--tk-spacing-gap-m, .75rem);width:16rem;padding:var(--tk-spacing-padding-m, 1rem);box-sizing:border-box}.tk-color-picker__panel:focus,.tk-color-picker__panel:focus-visible{outline:none}.tk-color-picker__section{display:flex;flex-direction:column;gap:var(--tk-spacing-gap-xs, .25rem)}.tk-color-picker__section-label{font-size:var(--tk-font-size-legal-m, .75rem);font-weight:var(--tk-font-weight-600, 600);color:var(--tk-color-text-subtle, #5d5d5e)}.tk-color-picker__add-custom{appearance:none;padding:0;width:100%;aspect-ratio:1;border:1px dashed var(--tk-color-border-default, #cecdcd);border-radius:var(--tk-borderRadius-xs, .125rem);background:var(--tk-color-background-default, #ffffff);color:var(--tk-color-text-muted, #8a8a8b);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:border-color .12s ease,color .12s ease}.tk-color-picker__add-custom:hover{border-color:var(--tk-color-accent-default, #6ad0bc);color:var(--tk-color-accent-default, #6ad0bc)}.tk-color-picker__add-custom:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__remove-custom{appearance:none;padding:0;width:100%;aspect-ratio:1;border:1px dashed var(--tk-color-border-default, #cecdcd);border-radius:var(--tk-borderRadius-xs, .125rem);background:var(--tk-color-background-default, #ffffff);color:var(--tk-color-text-muted, #8a8a8b);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:border-color .12s ease,color .12s ease}.tk-color-picker__remove-custom:hover{border-color:var(--tk-color-accent-default, #6ad0bc);color:var(--tk-color-accent-default, #6ad0bc)}.tk-color-picker__remove-custom:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__hue{appearance:none;width:100%;height:.625rem;border-radius:var(--tk-borderRadius-full, 9999px);outline:none;cursor:pointer;background:linear-gradient(to right,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.tk-color-picker__hue::-webkit-slider-thumb{appearance:none;width:1.125rem;height:1.125rem;border-radius:var(--tk-borderRadius-full, 50%);background:var(--tk-color-background-default, #ffffff);border:1px solid var(--tk-color-border-default, #cecdcd);cursor:grab}.tk-color-picker__hue::-moz-range-thumb{width:1.125rem;height:1.125rem;border-radius:var(--tk-borderRadius-full, 50%);background:var(--tk-color-background-default, #ffffff);border:1px solid var(--tk-color-border-default, #cecdcd);cursor:grab}.tk-color-picker__hue:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:2px}.tk-color-picker__divider{width:100%;height:1px;background:var(--tk-color-border-subtle, #e4e4e4);flex-shrink:0}.tk-color-picker__hex-row{display:flex;flex-direction:row;align-items:center;gap:var(--tk-spacing-gap-s, .5rem);margin-top:var(--tk-spacing-gap-s, .5rem)}.tk-color-picker__hex-row>span{flex-shrink:0}.tk-color-picker__hex-row tk-button{flex-shrink:0;min-width:2.5rem;min-height:2.5rem}.tk-color-picker__eyedropper{margin-top:-12px}.tk-color-picker__eyedropper:focus,.tk-color-picker__eyedropper:focus-visible{outline:none;box-shadow:none}.tk-color-picker__panel-hex{flex:1;min-width:0}.tk-color-picker__contrast-row{display:flex;align-items:center;gap:var(--tk-spacing-gap-s, .5rem)}.tk-color-picker__contrast-pill{flex-shrink:0;width:2.5rem;height:2rem;border-radius:var(--tk-borderRadius-s, .25rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4);display:inline-flex;align-items:center;justify-content:center}.tk-color-picker__contrast-sample{font-size:var(--tk-font-size-paragraph-s, .875rem);font-weight:var(--tk-font-weight-600, 600);line-height:1}.tk-color-picker__contrast-info{flex:1;min-width:0;display:flex;flex-direction:column}.tk-color-picker__contrast-label{font-size:var(--tk-font-size-legal-m, .75rem);font-weight:var(--tk-font-weight-600, 600);color:var(--tk-color-text-default, #222324)}.tk-color-picker__contrast-ratio{font-size:var(--tk-font-size-legal-s, .625rem);color:var(--tk-color-text-muted, #8a8a8b)}.tk-color-picker__contrast-badge{flex-shrink:0;font-size:var(--tk-font-size-legal-s, .625rem);font-weight:var(--tk-font-weight-600, 600);padding:var(--tk-spacing-padding-xs, .25rem) var(--tk-spacing-padding-s, .5rem);border-radius:var(--tk-borderRadius-full, 9999px)}.tk-color-picker__contrast-badge--aaa,.tk-color-picker__contrast-badge--aa{background:var(--tk-color-feedback-success-muted, #d1fadf);color:var(--tk-color-feedback-success-strong, #114a3f)}.tk-color-picker__contrast-badge--fail{background:var(--tk-color-feedback-danger-muted, #feede8);color:var(--tk-color-feedback-danger-strong, #7f1d1d)}::ng-deep .tk-color-picker-popover{margin:0!important}::ng-deep .tk-color-picker-popover:before,::ng-deep .tk-color-picker-popover:after{display:none!important}::ng-deep .tk-color-picker-popover--input{margin-top:-22px!important}:host ::ng-deep .tk-color-picker__trigger-input--invalid input.p-inputtext{border-color:var(--tk-color-feedback-danger-default, #ff6640)}:host ::ng-deep .tk-color-picker__trigger-input:has(input:focus) .tk-color-picker__input-label{top:-.75rem;left:0;font-size:var(--tk-font-size-legal-m, .75rem);color:var(--tk-color-base-primary-600, #140065);max-width:none;overflow:visible;text-overflow:clip}:host ::ng-deep .tk-color-picker__trigger-input:has(input.p-filled) .tk-color-picker__input-label{top:-.75rem;left:0;font-size:var(--tk-font-size-legal-m, .75rem);color:var(--tk-color-base-surface-950, #191a1b);max-width:none;overflow:visible;text-overflow:clip}:host ::ng-deep .tk-color-picker__trigger-input:has(input.ng-invalid.ng-dirty) .tk-color-picker__input-label,:host ::ng-deep .tk-color-picker__trigger-input:has(input.ng-invalid.ng-touched) .tk-color-picker__input-label{color:var(--tk-color-base-red-700, #cf2604)}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i1.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i2.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant", "motionOptions"], outputs: ["onClose"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "component", type: i3.Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: ButtonComponent, selector: "tk-button", inputs: ["label", "disabled", "type", "severity", "variant", "link", "icon", "iconPosition", "tooltipText", "size"], outputs: ["clicked"] }, { kind: "component", type: IconComponent, selector: "tk-icon", inputs: ["icon", "styleIcon", "color", "size", "disabled"] }, { kind: "component", type: InputTextComponent, selector: "tk-input-text", inputs: ["value", "control", "label", "type", "id", "icon", "clearable", "errorMessage", "hint", "maxLength"], outputs: ["valueChange"] }, { kind: "component", type: ColorPickerSpectrumComponent, selector: "tk-color-picker-spectrum", inputs: ["hue", "saturation", "brightness", "color", "ariaLabel"], outputs: ["changed"] }, { kind: "component", type: ColorPickerSwatchGridComponent, selector: "tk-color-picker-swatch-grid", inputs: ["colors", "selected", "ariaLabel"], outputs: ["picked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
1007
+ }
1008
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.3", ngImport: i0, type: ColorPickerComponent, decorators: [{
1009
+ type: Component,
1010
+ args: [{ selector: 'tk-color-picker', imports: [
1011
+ ButtonModule,
1012
+ InputTextModule,
1013
+ MessageModule,
1014
+ PopoverModule,
1015
+ ButtonComponent,
1016
+ IconComponent,
1017
+ InputTextComponent,
1018
+ ColorPickerSpectrumComponent,
1019
+ ColorPickerSwatchGridComponent,
1020
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div\n class=\"tk-color-picker\"\n [class.tk-color-picker--disabled]=\"isDisabled()\"\n [class.tk-color-picker--open]=\"isOpen()\"\n [class.tk-color-picker--invalid]=\"!isValid()\">\n @if (variant() === 'swatch') {\n @if (label()) {\n <span class=\"tk-color-picker__label\">{{ label() }}</span>\n }\n <button\n #swatchBtn\n type=\"button\"\n class=\"tk-color-picker__trigger tk-color-picker__trigger--swatch\"\n [disabled]=\"isDisabled()\"\n aria-haspopup=\"dialog\"\n [attr.aria-expanded]=\"isOpen()\"\n [attr.aria-label]=\"t().openPickerLabel + (label() ? ': ' + label() : '')\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onTriggerClick($event)\">\n <span\n class=\"tk-color-picker__swatch\"\n [class.tk-color-picker__swatch--fallback]=\"!swatchColor()\"\n [style.background-color]=\"swatchColor()\"\n aria-hidden=\"true\"></span>\n <tk-icon\n class=\"tk-color-picker__chevron\"\n [class.tk-color-picker__chevron--open]=\"isOpen()\"\n icon=\"chevron-down\"\n styleIcon=\"regular\"\n size=\"xs\"></tk-icon>\n </button>\n } @else {\n <div\n #inputTrigger\n class=\"tk-color-picker__trigger-input\"\n [class.tk-color-picker__trigger-input--invalid]=\"!isValid()\">\n @if (label()) {\n <label\n class=\"tk-color-picker__input-label\"\n [for]=\"fieldId\"\n [title]=\"label()\"\n >{{ label() }}</label\n >\n }\n <div class=\"tk-color-picker__input-wrap\">\n <button\n type=\"button\"\n class=\"tk-color-picker__input-swatch\"\n [class.tk-color-picker__input-swatch--fallback]=\"!swatchColor()\"\n [style.background-color]=\"swatchColor() ?? null\"\n [disabled]=\"isDisabled()\"\n [attr.aria-label]=\"t().openPickerLabel\"\n (mousedown)=\"$event.preventDefault()\"\n (click)=\"onTriggerClick($event)\"></button>\n <span class=\"tk-color-picker__input-prefix\" aria-hidden=\"true\">#</span>\n <input\n pInputText\n [id]=\"fieldId\"\n [value]=\"displayHexNoHash()\"\n [disabled]=\"isDisabled()\"\n [class.ng-invalid]=\"triggerControl.invalid\"\n [class.ng-dirty]=\"triggerControl.dirty\"\n [class.ng-touched]=\"triggerControl.touched\"\n [attr.aria-describedby]=\"!isValid() ? errorId : null\"\n [attr.aria-invalid]=\"!isValid()\"\n autocomplete=\"off\"\n (input)=\"onHexNativeInput($event)\"\n (keydown)=\"onHexKeydown($event)\"\n (paste)=\"onHexPaste($event)\"\n (blur)=\"onHexBlur()\" />\n </div>\n <div class=\"tk-color-picker__input-bottom\">\n @if (!isValid()) {\n <p-message\n severity=\"error\"\n size=\"small\"\n variant=\"simple\"\n [id]=\"errorId\"\n >{{ errorText() }}</p-message\n >\n } @else if (hint()) {\n <p-message severity=\"secondary\" size=\"small\" variant=\"simple\">{{\n hint()\n }}</p-message>\n }\n </div>\n </div>\n }\n\n @if (variant() === 'swatch') {\n @if (!isValid()) {\n <span\n [id]=\"errorId\"\n class=\"tk-color-picker__error\"\n role=\"alert\"\n aria-live=\"polite\">\n {{ errorText() }}\n </span>\n } @else if (hint()) {\n <span class=\"tk-color-picker__hint\">{{ hint() }}</span>\n }\n }\n</div>\n\n<p-popover\n #op\n [styleClass]=\"\n 'tk-color-picker-popover' +\n (variant() === 'input' ? ' tk-color-picker-popover--input' : '')\n \"\n position=\"bottom\"\n appendTo=\"body\"\n (onShow)=\"onPopoverShow()\"\n (onHide)=\"onPopoverHide()\">\n <div\n class=\"tk-color-picker__panel\"\n role=\"group\"\n [attr.aria-label]=\"t().dialogLabel\"\n tabindex=\"-1\"\n autofocus\n (keydown.enter)=\"onPanelEnter($event)\">\n @if (resolvedSections().swatches && presetColors().length) {\n <section class=\"tk-color-picker__section\">\n <span class=\"tk-color-picker__section-label\">{{\n t().swatchesLabel\n }}</span>\n <tk-color-picker-swatch-grid\n [colors]=\"presetColors()\"\n [selected]=\"draftHex()\"\n [ariaLabel]=\"t().swatchesLabel\"\n (picked)=\"onSwatchPick($event)\" />\n </section>\n }\n\n @if (resolvedSections().customColors) {\n <section class=\"tk-color-picker__section\">\n <span class=\"tk-color-picker__section-label\">{{\n t().customColorsLabel\n }}</span>\n <tk-color-picker-swatch-grid\n [colors]=\"customColors()\"\n [selected]=\"draftHex()\"\n [ariaLabel]=\"t().customColorsLabel\"\n (picked)=\"onSwatchPick($event)\">\n @if (canAddCustom()) {\n <button\n type=\"button\"\n class=\"tk-color-picker__add-custom\"\n [attr.aria-label]=\"t().addCustomColorLabel\"\n (click)=\"addCustomColor()\">\n <tk-icon icon=\"plus\" styleIcon=\"regular\" size=\"xs\"></tk-icon>\n </button>\n }\n @if (isCustomColorSelected()) {\n <button\n type=\"button\"\n class=\"tk-color-picker__remove-custom\"\n [attr.aria-label]=\"t().removeCustomColorLabel\"\n (click)=\"removeCustomColor()\">\n \u2212\n </button>\n }\n </tk-color-picker-swatch-grid>\n </section>\n }\n\n @if (\n resolvedSections().customColors &&\n (resolvedSections().spectrum ||\n resolvedSections().hue ||\n resolvedSections().hex)\n ) {\n <div class=\"tk-color-picker__divider\" role=\"separator\"></div>\n }\n\n @if (resolvedSections().spectrum) {\n <tk-color-picker-spectrum\n [hue]=\"hue()\"\n [saturation]=\"saturation()\"\n [brightness]=\"brightness()\"\n [color]=\"swatchColor() ?? ''\"\n [ariaLabel]=\"t().spectrumLabel\"\n (changed)=\"onSpectrumChange($event)\" />\n }\n\n @if (resolvedSections().hue) {\n <input\n class=\"tk-color-picker__hue\"\n type=\"range\"\n min=\"0\"\n max=\"360\"\n [value]=\"hue()\"\n [attr.aria-label]=\"t().hueLabel\"\n (input)=\"onHueChange($event)\" />\n }\n\n @if (\n (resolvedSections().spectrum || resolvedSections().hue) &&\n resolvedSections().hex\n ) {\n <div class=\"tk-color-picker__divider\" role=\"separator\"></div>\n }\n\n @if (resolvedSections().hex) {\n <section class=\"tk-color-picker__section tk-color-picker__hex-row\">\n <span\n class=\"tk-color-picker__swatch tk-color-picker__swatch--large\"\n [class.tk-color-picker__swatch--fallback]=\"!swatchColor()\"\n [style.background-color]=\"swatchColor()\"\n aria-hidden=\"true\"></span>\n <tk-input-text\n #panelHexInput\n class=\"tk-color-picker__panel-hex\"\n [class.tk-color-picker__trigger-input--invalid]=\"!isValid()\"\n [label]=\"t().hexLabel\"\n [id]=\"panelFieldId\"\n [value]=\"displayHexNoHash()\"\n prefixText=\"#\"\n (input)=\"onHexNativeInput($event)\"\n (keydown)=\"onHexKeydown($event)\"\n (paste)=\"onHexPaste($event)\"\n (focusout)=\"onHexBlur()\" />\n @if (showEyedropper()) {\n <tk-button\n class=\"tk-color-picker__eyedropper\"\n tabindex=\"-1\"\n severity=\"secondary\"\n variant=\"outlined\"\n icon=\"eye-dropper\"\n styleIcon=\"regular\"\n [attr.aria-label]=\"t().eyedropperLabel\"\n (clicked)=\"openEyeDropper()\">\n </tk-button>\n }\n </section>\n }\n\n @if (resolvedSections().hex && showContrast()) {\n <div class=\"tk-color-picker__divider\" role=\"separator\"></div>\n }\n\n @if (showContrast()) {\n <section class=\"tk-color-picker__section tk-color-picker__contrast\">\n <span class=\"tk-color-picker__section-label\">{{\n t().contrastLabel\n }}</span>\n <div class=\"tk-color-picker__contrast-row\">\n <span\n class=\"tk-color-picker__contrast-pill\"\n [style.background-color]=\"draftHex()\"\n [attr.aria-label]=\"t().contrastSampleLabel\">\n <span\n class=\"tk-color-picker__contrast-sample\"\n [style.color]=\"contrastTextColor()\">\n {{ t().contrastSampleText }}\n </span>\n </span>\n <div class=\"tk-color-picker__contrast-info\">\n <span class=\"tk-color-picker__contrast-label\">{{\n t().contrastSampleLabel\n }}</span>\n <span class=\"tk-color-picker__contrast-ratio\"\n >{{ contrastResult()!.ratio }}:1</span\n >\n </div>\n @switch (contrastResult()!.level) {\n @case ('AAA') {\n <span\n class=\"tk-color-picker__contrast-badge tk-color-picker__contrast-badge--aaa\"\n >AAA</span\n >\n }\n @case ('AA') {\n <span\n class=\"tk-color-picker__contrast-badge tk-color-picker__contrast-badge--aa\"\n >AA</span\n >\n }\n @case ('fail') {\n <span\n class=\"tk-color-picker__contrast-badge tk-color-picker__contrast-badge--fail\">\n {{ t().contrastLowLabel }}\n </span>\n }\n }\n </div>\n </section>\n }\n </div>\n</p-popover>\n", styles: [":host{display:inline-block;font-family:var(--tk-font-family, sans-serif)}.tk-color-picker{display:flex;flex-direction:column;gap:var(--tk-spacing-gap-xs, .25rem)}.tk-color-picker__label{font-size:var(--tk-font-size-paragraph-m, 1rem);font-weight:var(--tk-font-weight-400, 400);color:var(--tk-color-base-surface-950, #191a1b);margin-bottom:.25rem}.tk-color-picker__trigger{display:flex;align-items:center;gap:var(--tk-spacing-gap-s, .5rem);padding:var(--tk-spacing-padding-xs, .25rem) var(--tk-spacing-padding-s, .5rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4);border-radius:var(--tk-borderRadius-s, .25rem);background:var(--tk-color-background-default, #ffffff);cursor:pointer;transition:border-color .14s ease,background .14s ease}.tk-color-picker__trigger:hover{border-color:var(--tk-color-border-default, #cecdcd)}.tk-color-picker__trigger:focus-within{border-color:var(--tk-color-border-focus, #16006f)}.tk-color-picker__trigger--swatch{appearance:none;width:fit-content;font:inherit;-webkit-user-select:none;user-select:none}.tk-color-picker__trigger--swatch>*{pointer-events:none}.tk-color-picker__trigger--swatch:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__trigger--swatch:disabled{cursor:not-allowed}.tk-color-picker__trigger-input{display:flex;flex-direction:column;position:relative}.tk-color-picker__input-label{position:absolute;left:var(--tk-spacing-base-300, 3rem);top:.75rem;font-size:var(--tk-font-size-paragraph-m, 1rem);font-weight:var(--tk-font-weight-400, 400);color:var(--tk-color-base-surface-500, #8a8a8b);background:var(--tk-color-background-default, #ffffff);padding:0 .25rem;transition:top .2s ease,left .2s ease,color .2s ease;pointer-events:none;z-index:1;max-width:calc(100% - 4rem);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.tk-color-picker--disabled .tk-color-picker__input-label{display:none}.tk-color-picker--invalid .tk-color-picker__input-label{color:var(--tk-color-base-red-700, #cf2604)}.tk-color-picker__input-wrap{position:relative;display:flex;align-items:center;width:100%}.tk-color-picker__input-wrap input.p-inputtext{width:100%;flex:1;border:none;border-bottom:.0625rem solid var(--tk-color-base-surface-300, #d2d2d2);border-radius:0;padding:var(--tk-spacing-base-75, .75rem);padding-left:var(--tk-spacing-base-300, 3rem);color:var(--tk-color-base-surface-950, #191a1b);background-color:transparent;outline:none}.tk-color-picker__input-wrap input.p-inputtext:focus{border-color:var(--tk-color-base-primary-600, #140065);box-shadow:none}.tk-color-picker__input-wrap input.p-inputtext.ng-invalid.ng-dirty,.tk-color-picker__input-wrap input.p-inputtext.ng-invalid.ng-touched{border-color:var(--tk-color-base-red-700, #cf2604)}.tk-color-picker__input-wrap input.p-inputtext:disabled{background-color:var(--tk-color-base-surface-200, #e4e4e4);color:var(--tk-color-base-surface-500, #8a8a8b);opacity:1}.tk-color-picker__input-swatch{appearance:none;padding:0;position:absolute;left:var(--tk-spacing-base-25, .25rem);top:50%;transform:translateY(-50%);width:1.25rem;height:1.25rem;border-radius:var(--tk-borderRadius-xs, .125rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4);flex-shrink:0;cursor:pointer;z-index:1;-webkit-user-select:none;user-select:none;background:transparent}.tk-color-picker__input-swatch--fallback{background:var(--tk-color-base-surface-400, #cecdcd)}.tk-color-picker__input-swatch:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__input-swatch:disabled{cursor:not-allowed;opacity:.5}.tk-color-picker__input-prefix{position:absolute;left:calc(var(--tk-spacing-base-25, .25rem) + 1.25rem + .25rem);top:50%;transform:translateY(-50%);color:var(--tk-color-text-muted, #8a8a8b);font-size:var(--tk-font-size-paragraph-s, .875rem);line-height:1;pointer-events:none;z-index:1}.tk-color-picker__input-bottom{display:flex;margin-top:.25rem;min-height:1.25rem}.tk-color-picker--open .tk-color-picker__trigger{border-color:var(--tk-color-border-strong, #424243)}.tk-color-picker--invalid .tk-color-picker__trigger{border-color:var(--tk-color-feedback-danger-default, #ff6640)}.tk-color-picker--disabled .tk-color-picker__trigger{opacity:.55;cursor:not-allowed;pointer-events:none;background:var(--tk-color-background-soft, #f2f1f1)}.tk-color-picker__swatch{flex-shrink:0;width:1.25rem;height:1.25rem;border-radius:var(--tk-borderRadius-xs, .125rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4)}.tk-color-picker__swatch--fallback{background:var(--tk-color-base-surface-400, #cecdcd)}.tk-color-picker__swatch--large{width:2rem;height:2rem;border-radius:var(--tk-borderRadius-s, .25rem)}.tk-color-picker__chevron{transition:transform .2s ease}.tk-color-picker__chevron--open{transform:rotate(180deg)}.tk-color-picker__error{font-size:var(--tk-font-size-legal-s, .625rem);color:var(--tk-color-feedback-danger-default, #ff6640)}.tk-color-picker__hint{font-size:var(--tk-font-size-legal-s, .625rem);color:var(--tk-color-text-muted, #8a8a8b)}.tk-color-picker__panel{display:flex;flex-direction:column;gap:var(--tk-spacing-gap-m, .75rem);width:16rem;padding:var(--tk-spacing-padding-m, 1rem);box-sizing:border-box}.tk-color-picker__panel:focus,.tk-color-picker__panel:focus-visible{outline:none}.tk-color-picker__section{display:flex;flex-direction:column;gap:var(--tk-spacing-gap-xs, .25rem)}.tk-color-picker__section-label{font-size:var(--tk-font-size-legal-m, .75rem);font-weight:var(--tk-font-weight-600, 600);color:var(--tk-color-text-subtle, #5d5d5e)}.tk-color-picker__add-custom{appearance:none;padding:0;width:100%;aspect-ratio:1;border:1px dashed var(--tk-color-border-default, #cecdcd);border-radius:var(--tk-borderRadius-xs, .125rem);background:var(--tk-color-background-default, #ffffff);color:var(--tk-color-text-muted, #8a8a8b);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:border-color .12s ease,color .12s ease}.tk-color-picker__add-custom:hover{border-color:var(--tk-color-accent-default, #6ad0bc);color:var(--tk-color-accent-default, #6ad0bc)}.tk-color-picker__add-custom:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__remove-custom{appearance:none;padding:0;width:100%;aspect-ratio:1;border:1px dashed var(--tk-color-border-default, #cecdcd);border-radius:var(--tk-borderRadius-xs, .125rem);background:var(--tk-color-background-default, #ffffff);color:var(--tk-color-text-muted, #8a8a8b);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;transition:border-color .12s ease,color .12s ease}.tk-color-picker__remove-custom:hover{border-color:var(--tk-color-accent-default, #6ad0bc);color:var(--tk-color-accent-default, #6ad0bc)}.tk-color-picker__remove-custom:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:1px}.tk-color-picker__hue{appearance:none;width:100%;height:.625rem;border-radius:var(--tk-borderRadius-full, 9999px);outline:none;cursor:pointer;background:linear-gradient(to right,red,#ff0,#0f0,#0ff,#00f,#f0f,red)}.tk-color-picker__hue::-webkit-slider-thumb{appearance:none;width:1.125rem;height:1.125rem;border-radius:var(--tk-borderRadius-full, 50%);background:var(--tk-color-background-default, #ffffff);border:1px solid var(--tk-color-border-default, #cecdcd);cursor:grab}.tk-color-picker__hue::-moz-range-thumb{width:1.125rem;height:1.125rem;border-radius:var(--tk-borderRadius-full, 50%);background:var(--tk-color-background-default, #ffffff);border:1px solid var(--tk-color-border-default, #cecdcd);cursor:grab}.tk-color-picker__hue:focus-visible{outline:2px solid var(--tk-color-border-focus, #16006f);outline-offset:2px}.tk-color-picker__divider{width:100%;height:1px;background:var(--tk-color-border-subtle, #e4e4e4);flex-shrink:0}.tk-color-picker__hex-row{display:flex;flex-direction:row;align-items:center;gap:var(--tk-spacing-gap-s, .5rem);margin-top:var(--tk-spacing-gap-s, .5rem)}.tk-color-picker__hex-row>span{flex-shrink:0}.tk-color-picker__hex-row tk-button{flex-shrink:0;min-width:2.5rem;min-height:2.5rem}.tk-color-picker__eyedropper{margin-top:-12px}.tk-color-picker__eyedropper:focus,.tk-color-picker__eyedropper:focus-visible{outline:none;box-shadow:none}.tk-color-picker__panel-hex{flex:1;min-width:0}.tk-color-picker__contrast-row{display:flex;align-items:center;gap:var(--tk-spacing-gap-s, .5rem)}.tk-color-picker__contrast-pill{flex-shrink:0;width:2.5rem;height:2rem;border-radius:var(--tk-borderRadius-s, .25rem);border:1px solid var(--tk-color-border-subtle, #e4e4e4);display:inline-flex;align-items:center;justify-content:center}.tk-color-picker__contrast-sample{font-size:var(--tk-font-size-paragraph-s, .875rem);font-weight:var(--tk-font-weight-600, 600);line-height:1}.tk-color-picker__contrast-info{flex:1;min-width:0;display:flex;flex-direction:column}.tk-color-picker__contrast-label{font-size:var(--tk-font-size-legal-m, .75rem);font-weight:var(--tk-font-weight-600, 600);color:var(--tk-color-text-default, #222324)}.tk-color-picker__contrast-ratio{font-size:var(--tk-font-size-legal-s, .625rem);color:var(--tk-color-text-muted, #8a8a8b)}.tk-color-picker__contrast-badge{flex-shrink:0;font-size:var(--tk-font-size-legal-s, .625rem);font-weight:var(--tk-font-weight-600, 600);padding:var(--tk-spacing-padding-xs, .25rem) var(--tk-spacing-padding-s, .5rem);border-radius:var(--tk-borderRadius-full, 9999px)}.tk-color-picker__contrast-badge--aaa,.tk-color-picker__contrast-badge--aa{background:var(--tk-color-feedback-success-muted, #d1fadf);color:var(--tk-color-feedback-success-strong, #114a3f)}.tk-color-picker__contrast-badge--fail{background:var(--tk-color-feedback-danger-muted, #feede8);color:var(--tk-color-feedback-danger-strong, #7f1d1d)}::ng-deep .tk-color-picker-popover{margin:0!important}::ng-deep .tk-color-picker-popover:before,::ng-deep .tk-color-picker-popover:after{display:none!important}::ng-deep .tk-color-picker-popover--input{margin-top:-22px!important}:host ::ng-deep .tk-color-picker__trigger-input--invalid input.p-inputtext{border-color:var(--tk-color-feedback-danger-default, #ff6640)}:host ::ng-deep .tk-color-picker__trigger-input:has(input:focus) .tk-color-picker__input-label{top:-.75rem;left:0;font-size:var(--tk-font-size-legal-m, .75rem);color:var(--tk-color-base-primary-600, #140065);max-width:none;overflow:visible;text-overflow:clip}:host ::ng-deep .tk-color-picker__trigger-input:has(input.p-filled) .tk-color-picker__input-label{top:-.75rem;left:0;font-size:var(--tk-font-size-legal-m, .75rem);color:var(--tk-color-base-surface-950, #191a1b);max-width:none;overflow:visible;text-overflow:clip}:host ::ng-deep .tk-color-picker__trigger-input:has(input.ng-invalid.ng-dirty) .tk-color-picker__input-label,:host ::ng-deep .tk-color-picker__trigger-input:has(input.ng-invalid.ng-touched) .tk-color-picker__input-label{color:var(--tk-color-base-red-700, #cf2604)}\n"] }]
1021
+ }], ctorParameters: () => [], propDecorators: { variant: [{ type: i0.Input, args: [{ isSignal: true, alias: "variant", required: false }] }], label: [{ type: i0.Input, args: [{ isSignal: true, alias: "label", required: false }] }], hint: [{ type: i0.Input, args: [{ isSignal: true, alias: "hint", required: false }] }], disabled: [{ type: i0.Input, args: [{ isSignal: true, alias: "disabled", required: false }] }], required: [{ type: i0.Input, args: [{ isSignal: true, alias: "required", required: false }] }], sections: [{ type: i0.Input, args: [{ isSignal: true, alias: "sections", required: false }] }], presetColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "presetColors", required: false }] }], contrastColor: [{ type: i0.Input, args: [{ isSignal: true, alias: "contrastColor", required: false }] }], maxCustomColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "maxCustomColors", required: false }] }], placement: [{ type: i0.Input, args: [{ isSignal: true, alias: "placement", required: false }] }], errorMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMessage", required: false }] }], texts: [{ type: i0.Input, args: [{ isSignal: true, alias: "texts", required: false }] }], value: [{ type: i0.Input, args: [{ isSignal: true, alias: "value", required: false }] }, { type: i0.Output, args: ["valueChange"] }], customColors: [{ type: i0.Input, args: [{ isSignal: true, alias: "customColors", required: false }] }, { type: i0.Output, args: ["customColorsChange"] }], colorChange: [{ type: i0.Output, args: ["colorChange"] }], textColorChange: [{ type: i0.Output, args: ["textColorChange"] }], validChange: [{ type: i0.Output, args: ["validChange"] }], opened: [{ type: i0.Output, args: ["opened"] }], closed: [{ type: i0.Output, args: ["closed"] }], popover: [{ type: i0.ViewChild, args: ['op', { isSignal: true }] }], swatchBtnRef: [{ type: i0.ViewChild, args: ['swatchBtn', { ...{ read: ElementRef }, isSignal: true }] }], inputTriggerRef: [{ type: i0.ViewChild, args: ['inputTrigger', { ...{ read: ElementRef }, isSignal: true }] }], panelHexInput: [{ type: i0.ViewChild, args: ['panelHexInput', { ...{ read: ElementRef }, isSignal: true }] }] } });
1022
+
1023
+ /**
1024
+ * Generated bundle index. Do not edit.
1025
+ */
1026
+
1027
+ export { ColorPickerComponent };
1028
+ //# sourceMappingURL=tekus-design-system-components-color-picker.mjs.map