@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.
- package/fesm2022/tekus-design-system-components-color-picker.mjs +1028 -0
- package/fesm2022/tekus-design-system-components-color-picker.mjs.map +1 -0
- package/fesm2022/tekus-design-system-components-date-picker.mjs +269 -204
- package/fesm2022/tekus-design-system-components-date-picker.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-icon.mjs +119 -7
- package/fesm2022/tekus-design-system-components-icon.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-table.mjs +38 -6
- package/fesm2022/tekus-design-system-components-table.mjs.map +1 -1
- package/fesm2022/tekus-design-system-components-time-picker.mjs +443 -0
- package/fesm2022/tekus-design-system-components-time-picker.mjs.map +1 -0
- package/fesm2022/tekus-design-system-utils-wcag-contrast.mjs +97 -0
- package/fesm2022/tekus-design-system-utils-wcag-contrast.mjs.map +1 -0
- package/package.json +13 -1
- package/types/tekus-design-system-components-color-picker.d.ts +356 -0
- package/types/tekus-design-system-components-date-picker.d.ts +191 -141
- package/types/tekus-design-system-components-icon.d.ts +10 -1
- package/types/tekus-design-system-components-table.d.ts +26 -1
- package/types/tekus-design-system-components-time-picker.d.ts +203 -0
- package/types/tekus-design-system-utils-wcag-contrast.d.ts +55 -0
|
@@ -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
|