@oscarpalmer/oui 0.36.0 → 0.38.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.
Files changed (104) hide show
  1. package/dist/css/a11y.css +1 -0
  2. package/dist/css/badge.css +19 -15
  3. package/dist/css/button.css +40 -31
  4. package/dist/css/color-picker.css +377 -0
  5. package/dist/css/color.css +20 -21
  6. package/dist/css/combobox.css +140 -34
  7. package/dist/css/content.css +30 -10
  8. package/dist/css/floatable.css +12 -10
  9. package/dist/css/form.css +400 -129
  10. package/dist/css/listbox.css +43 -17
  11. package/dist/css/message.css +15 -10
  12. package/dist/css/mixins/badge.mixins.css +2 -0
  13. package/dist/css/mixins/button.mixins.css +2 -0
  14. package/dist/css/mixins/message.mixins.css +1 -0
  15. package/dist/css/mixins/misc.mixins.css +2 -0
  16. package/dist/css/mixins/shadow.mixins.css +1 -0
  17. package/dist/css/reset.css +31 -90
  18. package/dist/css/splitter.css +3 -3
  19. package/dist/css/tabs.css +103 -0
  20. package/dist/js/color-picker.d.mts +213 -0
  21. package/dist/js/color-picker.mjs +924 -0
  22. package/dist/js/combobox/dom.combobox.mjs +8 -4
  23. package/dist/js/combobox/event.combobox.mjs +1 -1
  24. package/dist/js/combobox/index.d.mts +1 -1
  25. package/dist/js/combobox/index.mjs +56 -2
  26. package/dist/js/combobox/misc.combobox.d.mts +1 -1
  27. package/dist/js/combobox/misc.combobox.mjs +1 -1
  28. package/dist/js/combobox/state.combobox.d.mts +1 -1
  29. package/dist/js/combobox/state.combobox.mjs +1 -1
  30. package/dist/js/dialog.mjs +2 -2
  31. package/dist/js/floatable/floatable.embedded.mjs +13 -10
  32. package/dist/js/focus-trap/focus-trap.embedded.mjs +9 -13
  33. package/dist/js/{index-EfkUSals.d.mts → index-DJdFlTLm.d.mts} +39 -1
  34. package/dist/js/index.d.mts +4 -2
  35. package/dist/js/index.mjs +5 -3
  36. package/dist/js/internal/draggable.mjs +20 -26
  37. package/dist/js/internal/listbox/listbox.data.mjs +1 -1
  38. package/dist/js/internal/listbox/listbox.dom.mjs +1 -1
  39. package/dist/js/internal/listbox/listbox.event.mjs +1 -1
  40. package/dist/js/internal/listbox/listbox.form.mjs +17 -16
  41. package/dist/js/internal/listbox/listbox.misc.mjs +1 -1
  42. package/dist/js/internal/listbox/listbox.state.mjs +4 -4
  43. package/dist/js/internal/navigable.d.mts +11 -5
  44. package/dist/js/internal/navigable.mjs +46 -19
  45. package/dist/js/listbox/event.listbox.mjs +1 -1
  46. package/dist/js/listbox/index.d.mts +38 -0
  47. package/dist/js/listbox/index.mjs +56 -2
  48. package/dist/js/listbox/misc.listbox.mjs +1 -1
  49. package/dist/js/listbox/state.listbox.mjs +1 -1
  50. package/dist/js/{listbox.data-BWd0PCy7.mjs → listbox.data-C7CRa5Ya.mjs} +5 -4
  51. package/dist/js/menu.mjs +1 -1
  52. package/dist/js/{misc.listbox-BI-XsTOf.mjs → misc.listbox-Cyz2CRDy.mjs} +2 -3
  53. package/dist/js/movable/movable.embedded.mjs +5 -8
  54. package/dist/js/popover.mjs +2 -2
  55. package/dist/js/resizable/resizable.embedded.mjs +21 -31
  56. package/dist/js/sortable/sortable.embedded.mjs +4 -4
  57. package/dist/js/splitter.mjs +20 -17
  58. package/dist/js/{state.combobox-4xq0FPCF.mjs → state.combobox-Qm3FWzYW.mjs} +3 -3
  59. package/dist/js/tabs.d.mts +25 -0
  60. package/dist/js/tabs.mjs +237 -0
  61. package/dist/js/tooltip.mjs +16 -15
  62. package/package.json +7 -5
  63. package/src/css/a11y.scss +2 -2
  64. package/src/css/badge.scss +8 -20
  65. package/src/css/button.scss +8 -47
  66. package/src/css/color-picker.scss +344 -0
  67. package/src/css/color.scss +20 -22
  68. package/src/css/combobox.scss +29 -57
  69. package/src/css/content.scss +34 -6
  70. package/src/css/floatable.scss +22 -19
  71. package/src/css/form.scss +94 -80
  72. package/src/css/listbox.scss +6 -34
  73. package/src/css/message.scss +16 -30
  74. package/src/css/mixins/badge.mixins.scss +53 -0
  75. package/src/css/mixins/button.mixins.scss +112 -0
  76. package/src/css/mixins/input.mixins.scss +114 -0
  77. package/src/css/mixins/message.mixins.scss +41 -0
  78. package/src/css/mixins/misc.mixins.scss +48 -0
  79. package/src/css/mixins/shadow.mixins.scss +57 -0
  80. package/src/css/reset.scss +39 -88
  81. package/src/css/splitter.scss +3 -3
  82. package/src/css/tabs.scss +74 -0
  83. package/src/js/color-picker.ts +1653 -0
  84. package/src/js/combobox/dom.combobox.ts +4 -0
  85. package/src/js/combobox/index.ts +63 -1
  86. package/src/js/dialog.ts +1 -1
  87. package/src/js/floatable/floatable.embedded.ts +18 -14
  88. package/src/js/focus-trap/focus-trap.embedded.ts +11 -19
  89. package/src/js/index.ts +2 -0
  90. package/src/js/internal/draggable.ts +29 -33
  91. package/src/js/internal/listbox/listbox.dom.ts +2 -1
  92. package/src/js/internal/listbox/listbox.form.ts +32 -22
  93. package/src/js/internal/listbox/listbox.state.ts +3 -3
  94. package/src/js/internal/navigable.ts +82 -23
  95. package/src/js/listbox/index.ts +62 -0
  96. package/src/js/listbox/state.listbox.ts +0 -2
  97. package/src/js/movable/movable.embedded.ts +11 -13
  98. package/src/js/popover.ts +1 -1
  99. package/src/js/resizable/resizable.embedded.ts +25 -39
  100. package/src/js/splitter.ts +21 -18
  101. package/src/js/tabs.ts +441 -0
  102. package/src/js/tooltip.ts +20 -17
  103. package/src/css/mixins.scss +0 -194
  104. /package/dist/css/{mixins.css → mixins/input.mixins.css} +0 -0
@@ -0,0 +1,1653 @@
1
+ import {
2
+ getColor,
3
+ getForegroundColor,
4
+ isHexColor,
5
+ isHslaColor,
6
+ isHslColor,
7
+ isHwbaColor,
8
+ isHwbColor,
9
+ isRgbaColor,
10
+ isRgbColor,
11
+ type Color,
12
+ type HSLAColor,
13
+ type HSLColor,
14
+ type HWBAColor,
15
+ type HWBColor,
16
+ type RGBAColor,
17
+ type RGBColor,
18
+ } from '@oscarpalmer/atoms/color';
19
+ import {round} from '@oscarpalmer/atoms/math';
20
+ import {clamp} from '@oscarpalmer/atoms/number';
21
+ import {getAria, setAria} from '@oscarpalmer/toretto/aria';
22
+ import {getAttribute, setAttribute, setAttributes} from '@oscarpalmer/toretto/attribute';
23
+ import {createElement} from '@oscarpalmer/toretto/create';
24
+ import {dispatch, getEventPosition, on} from '@oscarpalmer/toretto/event';
25
+ import {findAncestor} from '@oscarpalmer/toretto/find';
26
+ import type {EventPosition, RemovableEventListener} from '@oscarpalmer/toretto/models';
27
+ import {setProperty} from '@oscarpalmer/toretto/property';
28
+ import {toggleStyles} from '@oscarpalmer/toretto/style';
29
+ import supportsTouch from '@oscarpalmer/toretto/touch';
30
+ import {createEmbeddedFloatable, type OuiFloatable} from './floatable/floatable.embedded';
31
+ import {
32
+ getLocalization,
33
+ getLocalizations,
34
+ localize,
35
+ OuiLocalizations,
36
+ updateLocalizations,
37
+ } from './l10n';
38
+
39
+ // #region Types
40
+
41
+ declare global {
42
+ interface HTMLElementTagNameMap {
43
+ [TAGNAME]: OuiColorPickerElement;
44
+ }
45
+ }
46
+
47
+ type ColorValueType = 'hex' | 'hexa' | 'hsl' | 'hsla' | 'hwb' | 'hwba' | 'rgb' | 'rgba';
48
+
49
+ type CreateParameters = {
50
+ alpha: boolean;
51
+ color: Color;
52
+ id: string;
53
+ inline: boolean;
54
+ input: HTMLInputElement;
55
+ output: OuiColorPickerOutput;
56
+ };
57
+
58
+ type MoveParameters = {
59
+ rectangle: DOMRect;
60
+ state: OuiColorPickerState;
61
+ value: string;
62
+ };
63
+
64
+ type OuiColorPickerAnchor = {
65
+ button?: HTMLButtonElement;
66
+ container: HTMLDivElement;
67
+ input: HTMLInputElement;
68
+ };
69
+
70
+ type OuiColorPickerColor = {
71
+ foreground: string;
72
+ value: Color;
73
+ };
74
+
75
+ type OuiColorPickerContent = {
76
+ element: HTMLDivElement;
77
+ hue: OuiColorPickerContentHue;
78
+ opacity?: OuiColorPickerContentOpacity;
79
+ well: OuiColorPickerContentWell;
80
+ };
81
+
82
+ type OuiColorPickerContentHue = {
83
+ container: HTMLDivElement;
84
+ input: HTMLInputElement;
85
+ label: HTMLLabelElement;
86
+ knob: HTMLSpanElement;
87
+ track: HTMLDivElement;
88
+ };
89
+
90
+ type OuiColorPickerContentOpacity = {
91
+ container: HTMLDivElement;
92
+ input: HTMLInputElement;
93
+ label: HTMLLabelElement;
94
+ };
95
+
96
+ type OuiColorPickerContentWell = {
97
+ element: HTMLDivElement;
98
+ knob: HTMLSpanElement;
99
+ label: HTMLSpanElement;
100
+ };
101
+
102
+ export class OuiColorPickerElement extends HTMLElement {
103
+ static formAssociated = true;
104
+
105
+ static observedAttributes = ['disabled', 'required', 'value'];
106
+
107
+ readonly #internals: ElementInternals;
108
+
109
+ #restored?: string;
110
+
111
+ #state!: OuiColorPickerState;
112
+
113
+ /**
114
+ * Is the color picker disabled?
115
+ */
116
+ get disabled(): boolean {
117
+ return this.#state?.disabled ?? false;
118
+ }
119
+
120
+ /**
121
+ * Should the color picker be disabled?
122
+ */
123
+ set disabled(value: boolean) {
124
+ if (this.#state != null && typeof value === 'boolean' && value !== this.#state.disabled) {
125
+ setDisabled(this.#state, 'disabled', value);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Form element associated with the color picker
131
+ */
132
+ get form(): HTMLFormElement | null {
133
+ return this.#internals.form;
134
+ }
135
+
136
+ /**
137
+ * Color value as hexadecimal string
138
+ */
139
+ get hex(): string {
140
+ return this.#state?.color.value.hex ?? '';
141
+ }
142
+
143
+ /**
144
+ * Set color value from a hexadecimal string
145
+ */
146
+ set hex(value: string) {
147
+ setValue(this.#state, value, 'hex');
148
+ }
149
+
150
+ /**
151
+ * Color value as hexadecimal string with alpha channel
152
+ */
153
+ get hexa(): string {
154
+ return this.#state?.color.value.hexa ?? '';
155
+ }
156
+
157
+ /**
158
+ * Set color value from a hexadecimal string with alpha channel
159
+ */
160
+ set hexa(value: string) {
161
+ setValue(this.#state, value, 'hexa');
162
+ }
163
+
164
+ /**
165
+ * Color value as an _HSL_ color object
166
+ */
167
+ get hsl(): HSLColor {
168
+ return this.#state?.color.value.hsl ?? {hue: 0, lightness: 0, saturation: 0};
169
+ }
170
+
171
+ /**
172
+ * Set color value from an _HSL_ color object
173
+ */
174
+ set hsl(value: HSLColor) {
175
+ setValue(this.#state, value, 'hsl');
176
+ }
177
+
178
+ /**
179
+ * Color value as an _HSLA_ color object _(HSL with alpha channel)_
180
+ */
181
+ get hsla(): HSLAColor {
182
+ return this.#state?.color.value.hsla ?? {hue: 0, lightness: 0, saturation: 0, alpha: 100};
183
+ }
184
+
185
+ /**
186
+ * Set color value from an _HSLA_ color object _(HSL with alpha channel)_
187
+ */
188
+ set hsla(value: HSLAColor) {
189
+ setValue(this.#state, value, 'hsla');
190
+ }
191
+
192
+ /**
193
+ * Color value as an _HWB_ color object
194
+ */
195
+ get hwb(): HWBColor {
196
+ return this.#state?.color.value.hwb ?? {hue: 0, whiteness: 0, blackness: 0};
197
+ }
198
+
199
+ /**
200
+ * Set color value from an _HWB_ color object
201
+ */
202
+ set hwb(value: HWBColor) {
203
+ setValue(this.#state, value, 'hwb');
204
+ }
205
+
206
+ /**
207
+ * Color value as an _HWBA_ color object _(HWB with alpha channel)_
208
+ */
209
+ get hwba(): HWBAColor {
210
+ return this.#state?.color.value.hwba ?? {hue: 0, whiteness: 0, blackness: 0, alpha: 100};
211
+ }
212
+
213
+ /**
214
+ * Set color value from an _HWBA_ color object _(HWB with alpha channel)_
215
+ */
216
+ set hwba(value: HWBAColor) {
217
+ setValue(this.#state, value, 'hwba');
218
+ }
219
+
220
+ /**
221
+ * Text input value
222
+ */
223
+ get input(): string {
224
+ return this.#state?.anchor.input.value ?? '';
225
+ }
226
+
227
+ /**
228
+ * Label elements associated with the color picker
229
+ */
230
+ get labels(): NodeList {
231
+ return this.#internals.labels;
232
+ }
233
+
234
+ /**
235
+ * Is the color picker readonly?
236
+ */
237
+ get readonly(): boolean {
238
+ return this.#state?.readonly ?? false;
239
+ }
240
+
241
+ /**
242
+ * Should the color picker be readonly?
243
+ */
244
+ set readonly(value: boolean) {
245
+ if (this.#state != null && typeof value === 'boolean' && value !== this.#state.readonly) {
246
+ setDisabled(this.#state, 'readonly', value);
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Is the color picker required?
252
+ */
253
+ get required(): boolean {
254
+ return this.#state?.required ?? false;
255
+ }
256
+
257
+ /**
258
+ * Should the color picker be required?
259
+ */
260
+ set required(value: boolean) {
261
+ if (this.#state != null && typeof value === 'boolean' && value !== this.#state.required) {
262
+ setRequired(this.#state, value);
263
+ }
264
+ }
265
+
266
+ /**
267
+ * Color value as an _RGB_ color object
268
+ */
269
+ get rgb(): RGBColor {
270
+ return this.#state?.color.value.rgb ?? {red: 0, green: 0, blue: 0};
271
+ }
272
+
273
+ /**
274
+ * Set color value from an _RGB_ color object
275
+ */
276
+ set rgb(value: RGBColor) {
277
+ setValue(this.#state, value, 'rgb');
278
+ }
279
+
280
+ /**
281
+ * Color value as an _RGBA_ color object _(RGB with alpha channel)_
282
+ */
283
+ get rgba(): RGBAColor {
284
+ return this.#state?.color.value.rgba ?? {red: 0, green: 0, blue: 0, alpha: 100};
285
+ }
286
+
287
+ /**
288
+ * Set color value from an _RGBA_ color object _(RGB with alpha channel)_
289
+ */
290
+ set rgba(value: RGBAColor) {
291
+ setValue(this.#state, value, 'rgba');
292
+ }
293
+
294
+ /**
295
+ * Validation message for the color picker
296
+ */
297
+ get validationMessage(): string {
298
+ return this.#internals.validationMessage;
299
+ }
300
+
301
+ /**
302
+ * Validity state for the color picker
303
+ */
304
+ get validity(): ValidityState {
305
+ return this.#internals.validity;
306
+ }
307
+
308
+ /**
309
+ * Current color value
310
+ */
311
+ get value(): string {
312
+ return this.#state?.value.current ?? '';
313
+ }
314
+
315
+ /**
316
+ * Set color value from a string, _HSL(A)_, _HWB(A)_, or _RGB(A)_ color object
317
+ */
318
+ set value(value: string | HSLColor | HSLAColor | HWBColor | HWBAColor | RGBColor | RGBAColor) {
319
+ if (typeof value === 'string') {
320
+ this.#state.anchor.input.value = value;
321
+
322
+ onInput(this.#state);
323
+ } else {
324
+ setValue(this.#state, value);
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Can the color picker be validated?
330
+ */
331
+ get willValidate(): boolean {
332
+ return this.#internals.willValidate;
333
+ }
334
+
335
+ constructor() {
336
+ super();
337
+
338
+ this.#internals = this.attachInternals();
339
+ }
340
+
341
+ attributeChangedCallback(name: string, _: string | null, value: string | null): void {
342
+ if (this.#state == null) {
343
+ return;
344
+ }
345
+
346
+ switch (name) {
347
+ case 'disabled':
348
+ this.disabled = value != null;
349
+ break;
350
+
351
+ case 'required':
352
+ this.required = value != null;
353
+ break;
354
+
355
+ case 'value':
356
+ this.value = value ?? '';
357
+ break;
358
+ }
359
+ }
360
+
361
+ /**
362
+ * Check if the color picker is valid
363
+ *
364
+ * @returns `true` if the color picker is valid, otherwise `false`
365
+ */
366
+ checkValidity(): boolean {
367
+ return this.#internals.checkValidity();
368
+ }
369
+
370
+ /**
371
+ * Clear the color picker's value
372
+ */
373
+ clear(): void {
374
+ this.value = '';
375
+ }
376
+
377
+ connectedCallback(): void {
378
+ if (this.#state == null) {
379
+ this.#initialize();
380
+ } else {
381
+ attach(this.#state);
382
+ }
383
+ }
384
+
385
+ disconnectedCallback(): void {
386
+ if (this.#state != null) {
387
+ detach(this.#state);
388
+ }
389
+ }
390
+
391
+ formDisabledCallback(disabled: boolean): void {
392
+ this.disabled = disabled;
393
+ }
394
+
395
+ formResetCallback(): void {
396
+ this.value = this.#state?.value.initial ?? '';
397
+ }
398
+
399
+ formStateRestoreCallback(state: unknown, reason: unknown): void {
400
+ if (reason !== 'restore' || typeof state !== 'string' || this.#state == null) {
401
+ return;
402
+ }
403
+
404
+ if (this.#state == null) {
405
+ this.#restored = state;
406
+ } else {
407
+ this.value = state;
408
+ }
409
+ }
410
+
411
+ /**
412
+ * Check if the color picker is valid and report any validation errors
413
+ *
414
+ * @returns `true` if the color picker is valid, otherwise `false`
415
+ */
416
+ reportValidity(): boolean {
417
+ return this.#internals.reportValidity();
418
+ }
419
+
420
+ /**
421
+ * Set a custom validation message for the color picker
422
+ *
423
+ * @param message Validation message
424
+ */
425
+ setCustomValidity(message: string): void {
426
+ this.#internals.setValidity({customError: message !== ''}, message, this);
427
+ }
428
+
429
+ /**
430
+ * Set localizations for a specific language
431
+ *
432
+ * @param language Language code
433
+ * @param localizations Localizations for language
434
+ */
435
+ setLocalizations(language: string, localizations: Partial<OuiColorPickerLocalizations>): void;
436
+
437
+ /**
438
+ * Set localizations for one or more languages
439
+ *
440
+ * @param localizations Localizations for one or more languages
441
+ */
442
+ setLocalizations(localizations: Record<string, Partial<OuiColorPickerLocalizations>>): void;
443
+
444
+ setLocalizations(
445
+ first: string | Record<string, Partial<OuiColorPickerLocalizations>>,
446
+ second?: Partial<OuiColorPickerLocalizations>,
447
+ ): void {
448
+ updateColorPickerLocalizations(
449
+ localizations,
450
+ this.#state.localizations.component,
451
+ first,
452
+ second,
453
+ );
454
+ }
455
+
456
+ #initialize(): void {
457
+ this.#state = initialize(this, this.#internals);
458
+
459
+ if (this.#restored != null) {
460
+ this.value = this.#restored;
461
+
462
+ this.#restored = undefined;
463
+ }
464
+ }
465
+ }
466
+
467
+ export class OuiColorPickerEvent extends Event {
468
+ readonly value: OuiColorPickerEventValue;
469
+ constructor(
470
+ type: 'oui:change' | 'oui:input',
471
+ color: Color,
472
+ readonly valid: boolean,
473
+ ) {
474
+ super(type, {
475
+ bubbles: true,
476
+ cancelable: true,
477
+ });
478
+
479
+ this.value = {
480
+ hex: color.hex,
481
+ hexa: color.hexa,
482
+ hsl: color.hsl,
483
+ hsla: color.hsla,
484
+ hwb: color.hwb,
485
+ hwba: color.hwba,
486
+ rgb: color.rgb,
487
+ rgba: color.rgba,
488
+ };
489
+ }
490
+ }
491
+
492
+ type OuiColorPickerEventValue = {
493
+ hex: string;
494
+ hexa: string;
495
+ hsl: HSLColor;
496
+ hsla: HSLAColor;
497
+ hwb: HWBColor;
498
+ hwba: HWBAColor;
499
+ rgb: RGBColor;
500
+ rgba: RGBAColor;
501
+ };
502
+
503
+ type OuiColorPickerLabel = {
504
+ element: HTMLLabelElement;
505
+ id: string;
506
+ };
507
+
508
+ type OuiColorPickerLocalizations = {
509
+ hue: string;
510
+ invalid: string;
511
+ lightnessSaturationLabel: string;
512
+ lightnessSaturationValue: string | ((lightness: number, saturation: number) => string);
513
+ opacity: string;
514
+ toggle: string;
515
+ };
516
+
517
+ type OuiColorPickerOutput = 'all' | 'none' | OuiColorPickerOutputColor;
518
+
519
+ type OuiColorPickerOutputColor = 'hsl' | 'hwb' | 'rgb';
520
+
521
+ type OuiColorPickerState = {
522
+ alpha: boolean;
523
+ anchor: OuiColorPickerAnchor;
524
+ color: OuiColorPickerColor;
525
+ content: OuiColorPickerContent;
526
+ disabled: boolean;
527
+ floatable?: OuiFloatable;
528
+ host: OuiColorPickerElement;
529
+ id: string;
530
+ inline: boolean;
531
+ input: HTMLInputElement;
532
+ internals: ElementInternals;
533
+ label: OuiColorPickerLabel;
534
+ listeners: RemovableEventListener[];
535
+ localizations: OuiColorPickerStateLocalizations;
536
+ output: OuiColorPickerOutput;
537
+ readonly: boolean;
538
+ required: boolean;
539
+ style: HTMLStyleElement;
540
+ value: OuiColorPickerValue;
541
+ };
542
+
543
+ type OuiColorPickerStateLocalizations = {
544
+ component: Record<string, Partial<OuiColorPickerLocalizations>>;
545
+ global: OuiLocalizations<OuiColorPickerLocalizations>;
546
+ };
547
+
548
+ type OuiColorPickerValue = {
549
+ current: string;
550
+ initial: string;
551
+ };
552
+
553
+ // #endregion
554
+
555
+ // #region Functions
556
+
557
+ function attach(state: OuiColorPickerState): void {
558
+ const {anchor, content, host, id, inline, input, label, listeners, style} = state;
559
+
560
+ if (!inline) {
561
+ state.floatable = createEmbeddedFloatable(
562
+ anchor.container ?? anchor.container,
563
+ content.element,
564
+ {
565
+ position: 'below-start',
566
+ reusable: false,
567
+ },
568
+ );
569
+
570
+ state.floatable.update();
571
+ }
572
+
573
+ const labelId = `${id}_label`;
574
+
575
+ setAria(host, {
576
+ describedby: getAria(input, 'describedby'),
577
+ labelledby: labelId,
578
+ });
579
+
580
+ setAttributes(host, {
581
+ name: input.name === '' ? input.id : input.name,
582
+ });
583
+
584
+ label.element.htmlFor = anchor.input.id;
585
+ label.element.id = labelId;
586
+
587
+ if (anchor.button != null) {
588
+ setAria(anchor.button, 'expanded', false);
589
+ }
590
+
591
+ input.before(style, anchor.container, content.element);
592
+
593
+ input.remove();
594
+
595
+ listeners.push(
596
+ on(host, 'focusout', () => {
597
+ setTimeout(() => {
598
+ if (moved == null && !host.contains(document.activeElement)) {
599
+ state.floatable?.hide();
600
+ }
601
+ });
602
+ }),
603
+ on(anchor.input, 'change', () => {
604
+ onInput(state);
605
+ }),
606
+ on(anchor.input, 'input', () => {
607
+ onInput(state);
608
+ }),
609
+ on(
610
+ content.element,
611
+ 'beforetoggle',
612
+ event => {
613
+ if (state.disabled) {
614
+ event.preventDefault();
615
+ }
616
+ },
617
+ {
618
+ passive: false,
619
+ },
620
+ ),
621
+ on(content.element, 'toggle', event => {
622
+ const open = event.newState === 'open';
623
+
624
+ if (anchor.button != null) {
625
+ setAria(anchor.button, 'expanded', open);
626
+ }
627
+
628
+ if (open) {
629
+ content.well.knob.focus();
630
+ }
631
+ }),
632
+ on(content.hue.input, 'input', () => {
633
+ onHue(state);
634
+ }),
635
+ );
636
+
637
+ if (content.opacity != null) {
638
+ listeners.push(
639
+ on(content.opacity.input, 'input', () => {
640
+ onOpacity(state);
641
+ }),
642
+ );
643
+ }
644
+
645
+ instances.set(host, state);
646
+ states.add(state);
647
+ }
648
+
649
+ function cancelMove(parameters: MoveParameters): void {
650
+ parameters.state.color.value.hexa = parameters.value;
651
+
652
+ setValues(parameters.state, true);
653
+
654
+ moved = undefined;
655
+
656
+ styleToggler.remove();
657
+ }
658
+
659
+ function createAnchor(parameters: CreateParameters): OuiColorPickerAnchor {
660
+ const {alpha, color, id, inline} = parameters;
661
+
662
+ const container = createElement('div', {
663
+ attribute: {
664
+ [`${TAGNAME}-anchor`]: '',
665
+ },
666
+ });
667
+
668
+ const input = createElement('input', {
669
+ attribute: {
670
+ [`${TAGNAME}-input`]: '',
671
+ autocomplete: 'off',
672
+ autocorrect: 'off',
673
+ form: '',
674
+ inputmode: 'text',
675
+ placeholder: parameters.input.placeholder,
676
+ spellcheck: 'false',
677
+ },
678
+ property: {
679
+ id,
680
+ type: 'text',
681
+ value: alpha ? color.hexa : color.hex,
682
+ },
683
+ });
684
+
685
+ let button: HTMLButtonElement | undefined;
686
+
687
+ if (!inline) {
688
+ button = createElement('button', {
689
+ aria: {
690
+ label: '',
691
+ },
692
+ attribute: {
693
+ [`${TAGNAME}-button`]: '',
694
+ popovertarget: `${id}_content`,
695
+ },
696
+ property: {
697
+ type: 'button',
698
+ },
699
+ });
700
+ }
701
+
702
+ container.append(input);
703
+
704
+ if (button != null) {
705
+ container.append(button);
706
+ }
707
+
708
+ return {
709
+ button,
710
+ container,
711
+ input,
712
+ };
713
+ }
714
+
715
+ function createContent(parameters: CreateParameters): OuiColorPickerContent {
716
+ const {id, inline} = parameters;
717
+
718
+ const element = createElement('div', {
719
+ attribute: {
720
+ [`${TAGNAME}-content`]: '',
721
+ popover: inline ? undefined : '',
722
+ },
723
+ property: {
724
+ id: `${id}_content`,
725
+ },
726
+ });
727
+
728
+ const hue = createHue(parameters);
729
+ const opacity = createOpacity(parameters);
730
+ const well = createWell(parameters);
731
+
732
+ element.append(well.element, hue.container);
733
+
734
+ if (opacity != null) {
735
+ element.append(opacity.container);
736
+ }
737
+
738
+ return {
739
+ element,
740
+ hue,
741
+ opacity,
742
+ well,
743
+ };
744
+ }
745
+
746
+ function createHue(parameters: CreateParameters): OuiColorPickerContentHue {
747
+ const {color, id} = parameters;
748
+
749
+ const container = createElement('div', {
750
+ attribute: {
751
+ [`${TAGNAME}-hue`]: '',
752
+ },
753
+ });
754
+
755
+ const label = createElement('label', {
756
+ attribute: {
757
+ [`${TAGNAME}-hue-label`]: '',
758
+ },
759
+ property: {
760
+ htmlFor: `${id}_hue`,
761
+ id: `${id}_hue_label`,
762
+ },
763
+ });
764
+
765
+ const input = createElement('input', {
766
+ aria: {
767
+ labelledby: `${id}_label ${label.id}`,
768
+ },
769
+ attribute: {
770
+ [`${TAGNAME}-hue-input`]: '',
771
+ form: '',
772
+ inputmode: 'numeric',
773
+ max: 360,
774
+ min: 0,
775
+ },
776
+ property: {
777
+ id: `${id}_hue`,
778
+ type: 'range',
779
+ value: color.hsl.hue.toString(),
780
+ },
781
+ });
782
+
783
+ const knob = createElement('span', {
784
+ aria: {
785
+ hidden: true,
786
+ },
787
+ attribute: {
788
+ [`${TAGNAME}-hue-knob`]: '',
789
+ },
790
+ });
791
+
792
+ const track = createElement('div', {
793
+ aria: {
794
+ hidden: true,
795
+ },
796
+ attribute: {
797
+ [`${TAGNAME}-hue-track`]: '',
798
+ },
799
+ });
800
+
801
+ track.append(knob);
802
+
803
+ container.append(label, input, track);
804
+
805
+ return {
806
+ container,
807
+ input,
808
+ knob,
809
+ label,
810
+ track,
811
+ };
812
+ }
813
+
814
+ function createOpacity(parameters: CreateParameters): OuiColorPickerContentOpacity | undefined {
815
+ const {alpha, color, id} = parameters;
816
+
817
+ if (!alpha) {
818
+ return;
819
+ }
820
+
821
+ const container = createElement('div', {
822
+ attribute: {
823
+ [`${TAGNAME}-opacity`]: '',
824
+ },
825
+ });
826
+
827
+ const label = createElement('label', {
828
+ attribute: {
829
+ [`${TAGNAME}-opacity-label`]: '',
830
+ },
831
+ property: {
832
+ htmlFor: `${id}_opacity`,
833
+ id: `${id}_opacity_label`,
834
+ },
835
+ });
836
+
837
+ const input = createElement('input', {
838
+ aria: {
839
+ labelledby: `${id}_label ${label.id}`,
840
+ },
841
+ attribute: {
842
+ [`${TAGNAME}-opacity-input`]: '',
843
+ form: '',
844
+ max: 100,
845
+ min: 0,
846
+ },
847
+ property: {
848
+ id: `${id}_opacity`,
849
+ type: 'range',
850
+ value: color.alpha.toString(),
851
+ },
852
+ });
853
+
854
+ const knob = createElement('span', {
855
+ aria: {
856
+ hidden: true,
857
+ },
858
+ attribute: {
859
+ [`${TAGNAME}-opacity-knob`]: '',
860
+ },
861
+ });
862
+
863
+ const track = createElement('div', {
864
+ aria: {
865
+ hidden: true,
866
+ },
867
+ attribute: {
868
+ [`${TAGNAME}-opacity-track`]: '',
869
+ },
870
+ });
871
+
872
+ track.append(knob);
873
+
874
+ container.append(label, input, track);
875
+
876
+ return {
877
+ container,
878
+ input,
879
+ label,
880
+ };
881
+ }
882
+
883
+ function createWell(parameters: CreateParameters): OuiColorPickerContentWell {
884
+ const {id} = parameters;
885
+
886
+ const element = createElement('div', {
887
+ attribute: {
888
+ [ATTRIBUTE_WELL]: '',
889
+ },
890
+ });
891
+
892
+ const label = createElement('span', {
893
+ property: {
894
+ id: `${id}_well_label`,
895
+ hidden: true,
896
+ },
897
+ });
898
+
899
+ const knob = createElement('span', {
900
+ aria: {
901
+ labelledby: `${id}_label ${label.id}`,
902
+ },
903
+ attribute: {
904
+ [ATTRIBUTE_WELL_KNOB]: '',
905
+ },
906
+ property: {
907
+ role: 'slider',
908
+ tabIndex: 0,
909
+ },
910
+ });
911
+
912
+ element.append(label, knob);
913
+
914
+ return {
915
+ element,
916
+ knob,
917
+ label,
918
+ };
919
+ }
920
+
921
+ function detach(state: OuiColorPickerState): void {
922
+ const {anchor, content, floatable, host, input, label, style} = state;
923
+
924
+ floatable?.destroy();
925
+
926
+ setAria(host, {
927
+ describedby: undefined,
928
+ labelledby: undefined,
929
+ });
930
+
931
+ setAttributes(host, {
932
+ name: undefined,
933
+ });
934
+
935
+ label.element.htmlFor = input.id;
936
+ label.element.id = label.id;
937
+
938
+ anchor.container.before(input);
939
+
940
+ anchor.container.remove();
941
+ content.element.remove();
942
+ style.remove();
943
+
944
+ const listeners = state.listeners.splice(0);
945
+ const {length} = listeners;
946
+
947
+ for (let index = 0; index < length; index += 1) {
948
+ listeners[index]();
949
+ }
950
+
951
+ instances.set(host, state);
952
+ states.add(state);
953
+ }
954
+
955
+ function getId(): string {
956
+ index += 1;
957
+
958
+ return `oui_color_picker_${index}`;
959
+ }
960
+
961
+ function getOutput(element: OuiColorPickerElement): OuiColorPickerOutput {
962
+ const attribute = getAttribute(element, 'output');
963
+
964
+ return OUTPUTS.has(attribute as OuiColorPickerOutput)
965
+ ? (attribute as OuiColorPickerOutput)
966
+ : 'none';
967
+ }
968
+
969
+ function getValueType(state: OuiColorPickerState, value: unknown): ColorValueType | undefined {
970
+ switch (true) {
971
+ case isHexColor(value, state.alpha):
972
+ return state.alpha ? 'hexa' : 'hex';
973
+
974
+ case isHslColor(value):
975
+ return 'hsl';
976
+
977
+ case isHslaColor(value):
978
+ return 'hsla';
979
+
980
+ case isHwbColor(value):
981
+ return 'hwb';
982
+
983
+ case isHwbaColor(value):
984
+ return 'hwba';
985
+
986
+ case isRgbColor(value):
987
+ return 'rgb';
988
+
989
+ case isRgbaColor(value):
990
+ return 'rgba';
991
+
992
+ default:
993
+ break;
994
+ }
995
+ }
996
+
997
+ function initialize(
998
+ element: OuiColorPickerElement,
999
+ internals: ElementInternals,
1000
+ ): OuiColorPickerState {
1001
+ const input = element.querySelector('input');
1002
+
1003
+ if (input == null || !TYPES.has(input.type)) {
1004
+ throw new Error(MESSAGE_INPUT);
1005
+ }
1006
+
1007
+ const label = element.querySelector(`label[for="${input.id}"]`) as HTMLLabelElement | null;
1008
+
1009
+ if (label == null) {
1010
+ throw new Error(MESSAGE_LABEL);
1011
+ }
1012
+
1013
+ element.id = input.id;
1014
+
1015
+ const id = getId();
1016
+
1017
+ const labelId = label.id;
1018
+
1019
+ const alpha = element.hasAttribute('alpha');
1020
+ const color = getColor(input.value);
1021
+ const disabled = element.hasAttribute('disabled') || input.disabled;
1022
+ const inline = element.hasAttribute('inline');
1023
+ const output = getOutput(element);
1024
+ const readonly = element.hasAttribute('readonly') || input.readOnly;
1025
+ const required = element.hasAttribute('required') || input.required;
1026
+
1027
+ const parameters: CreateParameters = {
1028
+ alpha,
1029
+ color,
1030
+ id,
1031
+ inline,
1032
+ input,
1033
+ output,
1034
+ };
1035
+
1036
+ const anchor = createAnchor(parameters);
1037
+ const content = createContent(parameters);
1038
+
1039
+ const formatted = color.toHexString(alpha);
1040
+
1041
+ const state: OuiColorPickerState = {
1042
+ alpha,
1043
+ anchor,
1044
+ content,
1045
+ disabled,
1046
+ id,
1047
+ inline,
1048
+ input,
1049
+ internals,
1050
+ output,
1051
+ readonly,
1052
+ required,
1053
+ color: {
1054
+ foreground: '',
1055
+ value: color,
1056
+ },
1057
+ host: element,
1058
+ label: {
1059
+ element: label,
1060
+ id: labelId,
1061
+ },
1062
+ listeners: [],
1063
+ localizations: {
1064
+ component: {},
1065
+ global: localizations,
1066
+ },
1067
+ style: createElement('style'),
1068
+ value: {
1069
+ current: formatted,
1070
+ initial: formatted,
1071
+ },
1072
+ };
1073
+
1074
+ attach(state);
1075
+
1076
+ setAttributes(element, {
1077
+ output,
1078
+ alpha: alpha ? '' : undefined,
1079
+ inline: inline ? '' : undefined,
1080
+ });
1081
+
1082
+ setDisabled(state, 'disabled', disabled);
1083
+ setDisabled(state, 'readonly', readonly);
1084
+ setRequired(state, required);
1085
+
1086
+ setValues(state, false);
1087
+
1088
+ updateLabels(state);
1089
+ updateValidity(state, isHexColor(state.value.current, alpha));
1090
+
1091
+ return state;
1092
+ }
1093
+
1094
+ function onEnter(state: OuiColorPickerState): void {
1095
+ const {alpha, anchor, color, internals} = state;
1096
+
1097
+ const input = anchor.input.value.replace(EXPRESSION_HEX_PREFIX, '');
1098
+
1099
+ const short = alpha ? input.length === 4 : input.length === 3;
1100
+
1101
+ const valid = isHexColor(input, alpha) && short;
1102
+
1103
+ if (valid) {
1104
+ anchor.input.value = color.value.toHexString(alpha);
1105
+ }
1106
+
1107
+ internals.form?.requestSubmit();
1108
+ }
1109
+
1110
+ function onHue(state: OuiColorPickerState): void {
1111
+ const hue = state.content.hue.input.valueAsNumber;
1112
+
1113
+ state.color.value.hsla = {
1114
+ ...state.color.value.hsla,
1115
+ hue,
1116
+ };
1117
+
1118
+ setValues(state, false);
1119
+ }
1120
+
1121
+ function onInput(state: OuiColorPickerState): void {
1122
+ const {alpha, anchor, color, host} = state;
1123
+
1124
+ const value = anchor.input.value.replace(EXPRESSION_HEX_PREFIX, '');
1125
+
1126
+ if (value.toLowerCase() === (alpha ? color.value.hexa : color.value.hex).toLowerCase()) {
1127
+ return;
1128
+ }
1129
+
1130
+ state.value.current = anchor.input.value;
1131
+
1132
+ const {length} = value;
1133
+
1134
+ let valid: boolean;
1135
+
1136
+ if (alpha) {
1137
+ valid = isHexColor(value, true) && (length === 4 || length === 8);
1138
+ } else {
1139
+ valid = isHexColor(value) && (length === 3 || length === 6);
1140
+ }
1141
+
1142
+ if (valid) {
1143
+ if (length === 3 || length === 6) {
1144
+ color.value.hex = value;
1145
+ } else {
1146
+ color.value.hexa = value;
1147
+ }
1148
+ }
1149
+
1150
+ setValues(state, false, anchor.input.value);
1151
+
1152
+ dispatch(host, new OuiColorPickerEvent('oui:input', color.value, valid));
1153
+ }
1154
+
1155
+ function onKeydown(event: KeyboardEvent): void {
1156
+ if (moved != null && event.key === 'Escape') {
1157
+ cancelMove(moved);
1158
+
1159
+ return;
1160
+ }
1161
+
1162
+ const knob = findAncestor(event, SELECTOR_WELL_KNOB);
1163
+
1164
+ if (
1165
+ !(
1166
+ (event.key === 'Enter' && findAncestor(event, SELECTOR_INPUT) != null) ||
1167
+ (KEYS.has(event.key) && knob != null)
1168
+ )
1169
+ ) {
1170
+ return;
1171
+ }
1172
+
1173
+ const state = instances.get(findAncestor(event, TAGNAME)!);
1174
+
1175
+ if (state == null || state.disabled || state.readonly) {
1176
+ return;
1177
+ }
1178
+
1179
+ event.preventDefault();
1180
+
1181
+ if (event.key === 'Enter') {
1182
+ onEnter(state);
1183
+
1184
+ return;
1185
+ }
1186
+
1187
+ if (knob == null) {
1188
+ return;
1189
+ }
1190
+
1191
+ const modifier = event.shiftKey ? 10 : 1;
1192
+
1193
+ let {lightness, saturation} = state.color.value.hsl;
1194
+
1195
+ switch (event.key) {
1196
+ case 'ArrowDown':
1197
+ lightness -= 1 * modifier;
1198
+ break;
1199
+
1200
+ case 'ArrowLeft':
1201
+ saturation -= 1 * modifier;
1202
+ break;
1203
+
1204
+ case 'ArrowRight':
1205
+ saturation += 1 * modifier;
1206
+ break;
1207
+
1208
+ case 'ArrowUp':
1209
+ lightness += 1 * modifier;
1210
+ break;
1211
+
1212
+ case 'End':
1213
+ lightness = 0;
1214
+ break;
1215
+
1216
+ case 'Home':
1217
+ lightness = 100;
1218
+ break;
1219
+
1220
+ case 'PageDown':
1221
+ lightness -= 10;
1222
+ break;
1223
+
1224
+ case 'PageUp':
1225
+ lightness += 10;
1226
+ break;
1227
+ }
1228
+
1229
+ state.color.value.hsl = {
1230
+ ...state.color.value.hsl,
1231
+ lightness: clamp(lightness, 0, 100),
1232
+ saturation: clamp(saturation, 0, 100),
1233
+ };
1234
+
1235
+ setValues(state, false);
1236
+ }
1237
+
1238
+ function onOpacity(state: OuiColorPickerState): void {
1239
+ const opacity = state.content.opacity?.input.valueAsNumber;
1240
+
1241
+ if (opacity == null) {
1242
+ return;
1243
+ }
1244
+
1245
+ state.color.value.alpha = opacity;
1246
+
1247
+ setValues(state, false);
1248
+ }
1249
+
1250
+ function onPointerdown(event: MouseEvent | TouchEvent): void {
1251
+ if (
1252
+ event.altKey ||
1253
+ event.ctrlKey ||
1254
+ event.metaKey ||
1255
+ event.shiftKey ||
1256
+ (supportsTouch.value
1257
+ ? event.type === 'mousedown'
1258
+ : event.type === 'touchstart' || (event as MouseEvent).button !== 0)
1259
+ ) {
1260
+ return;
1261
+ }
1262
+
1263
+ const well = findAncestor(event, SELECTOR_WELL);
1264
+
1265
+ if (well == null) {
1266
+ return;
1267
+ }
1268
+
1269
+ const element = findAncestor(event, TAGNAME);
1270
+
1271
+ if (element == null) {
1272
+ return;
1273
+ }
1274
+
1275
+ if (supportsTouch.value) {
1276
+ event.preventDefault();
1277
+ }
1278
+
1279
+ const position = getEventPosition(event);
1280
+
1281
+ if (position == null) {
1282
+ return;
1283
+ }
1284
+
1285
+ const state = instances.get(element);
1286
+
1287
+ if (state == null || state.disabled || state.readonly) {
1288
+ return;
1289
+ }
1290
+
1291
+ moved = {
1292
+ state,
1293
+ rectangle: well.getBoundingClientRect(),
1294
+ value: state.color.value.hexa,
1295
+ };
1296
+
1297
+ styleToggler.set();
1298
+ }
1299
+
1300
+ function onPointermove(event: MouseEvent | TouchEvent): void {
1301
+ if (moved == null) {
1302
+ return;
1303
+ }
1304
+
1305
+ const position = getEventPosition(event);
1306
+
1307
+ if (position != null) {
1308
+ updateValues(moved, position);
1309
+ }
1310
+ }
1311
+
1312
+ function onPointerup(event: MouseEvent | TouchEvent): void {
1313
+ if (moved == null) {
1314
+ return;
1315
+ }
1316
+
1317
+ const position = getEventPosition(event);
1318
+
1319
+ if (position != null) {
1320
+ updateValues(moved, position);
1321
+ }
1322
+
1323
+ moved = undefined;
1324
+
1325
+ styleToggler.remove();
1326
+ }
1327
+
1328
+ /**
1329
+ * Set localizations for a specific language
1330
+ *
1331
+ * @param language Language code
1332
+ * @param localizations Localizations for language
1333
+ */
1334
+ export function setColorPickerLocalizations(
1335
+ language: string,
1336
+ localizations: Partial<OuiColorPickerLocalizations>,
1337
+ ): void;
1338
+
1339
+ /**
1340
+ * Set localizations for one or more languages
1341
+ *
1342
+ * @param localizations Localizations for one or more languages
1343
+ */
1344
+ export function setColorPickerLocalizations(
1345
+ localizations: Record<string, Partial<OuiColorPickerLocalizations>>,
1346
+ ): void;
1347
+
1348
+ export function setColorPickerLocalizations(
1349
+ first: string | Record<string, Partial<OuiColorPickerLocalizations>>,
1350
+ second?: Partial<OuiColorPickerLocalizations>,
1351
+ ): void {
1352
+ updateColorPickerLocalizations(localizations, localizations.values, first, second);
1353
+ }
1354
+
1355
+ function setDisabled(
1356
+ state: OuiColorPickerState,
1357
+ type: 'disabled' | 'readonly',
1358
+ value: boolean,
1359
+ ): void {
1360
+ state[type] = value;
1361
+
1362
+ setAria(state.host, type, value);
1363
+
1364
+ state.anchor.input.readOnly = value;
1365
+
1366
+ setAria(state.anchor.input, type, value);
1367
+ setProperty(state.content.hue.input, 'disabled', state.disabled || state.readonly);
1368
+
1369
+ if (state.anchor.button != null) {
1370
+ setAria(state.anchor.button, 'disabled', state.disabled);
1371
+ }
1372
+
1373
+ if (state.content.opacity != null) {
1374
+ setProperty(state.content.opacity.input, 'disabled', state.disabled || state.readonly);
1375
+ }
1376
+ }
1377
+
1378
+ function setRequired(state: OuiColorPickerState, value: boolean): void {
1379
+ state.required = value;
1380
+
1381
+ setAria(state.host, 'required', value);
1382
+ setAria(state.anchor.input, 'required', value);
1383
+
1384
+ updateValidity(state, isHexColor(state.value.current, state.alpha));
1385
+ }
1386
+
1387
+ function setValue<Type extends ColorValueType>(
1388
+ state: OuiColorPickerState,
1389
+ value: unknown,
1390
+ type?: Type,
1391
+ ): void {
1392
+ if (state == null) {
1393
+ return;
1394
+ }
1395
+
1396
+ const actual = type ?? getValueType(state, value);
1397
+
1398
+ if (actual == null || (type != null && type !== actual)) {
1399
+ return;
1400
+ }
1401
+
1402
+ state.color.value[actual] = value as never;
1403
+
1404
+ setValues(state, true);
1405
+ }
1406
+
1407
+ function setValues(state: OuiColorPickerState, focus: boolean, input?: string): void {
1408
+ const {anchor, color, content, host, style} = state;
1409
+
1410
+ color.foreground = getForegroundColor(color.value).hex;
1411
+
1412
+ const {alpha, hex, hsl, rgb} = color.value;
1413
+ const {hue, lightness, saturation} = hsl;
1414
+ const {blue, green, red} = rgb;
1415
+
1416
+ const value = input ?? color.value.toHexString(state.alpha);
1417
+
1418
+ anchor.input.value = value;
1419
+ content.hue.input.valueAsNumber = hue;
1420
+
1421
+ if (content.opacity != null) {
1422
+ content.opacity.input.valueAsNumber = alpha;
1423
+ }
1424
+
1425
+ state.value.current = value;
1426
+
1427
+ style.textContent = `#${host.id} {
1428
+ --oui-color-picker-alpha: ${alpha};
1429
+ --oui-color-picker-blue: ${blue};
1430
+ --oui-color-picker-green: ${green};
1431
+ --oui-color-picker-foreground: #${color.foreground};
1432
+ --oui-color-picker-hex: #${hex};
1433
+ --oui-color-picker-hue: ${hue};
1434
+ --oui-color-picker-lightness: ${lightness};
1435
+ --oui-color-picker-red: ${red};
1436
+ --oui-color-picker-saturation: ${saturation};
1437
+ }`;
1438
+
1439
+ updateForm(state, isHexColor(state.value.current, state.alpha));
1440
+ updateWell(state);
1441
+
1442
+ if (focus) {
1443
+ content.well.knob.focus();
1444
+ }
1445
+ }
1446
+
1447
+ function updateForm(state: OuiColorPickerState, valid: boolean): void {
1448
+ const {color, host, internals, value} = state;
1449
+
1450
+ internals.setFormValue(valid ? value.current : '');
1451
+
1452
+ updateValidity(state, valid);
1453
+
1454
+ dispatch(host, new OuiColorPickerEvent('oui:change', color.value, valid));
1455
+ }
1456
+
1457
+ function updateLabels(state: OuiColorPickerState): void {
1458
+ const {anchor, content, host, localizations} = state;
1459
+ const {component, global} = localizations;
1460
+
1461
+ if (anchor.button != null) {
1462
+ setAria(
1463
+ anchor.button,
1464
+ 'label',
1465
+ localize(getLocalization(global, host, 'toggle', component), {}, []),
1466
+ );
1467
+ }
1468
+
1469
+ content.hue.label.textContent = localize(getLocalization(global, host, 'hue', component), {}, []);
1470
+
1471
+ content.well.label.textContent = localize(
1472
+ getLocalization(global, host, 'lightnessSaturationLabel', component),
1473
+ {},
1474
+ [],
1475
+ );
1476
+
1477
+ updateWell(state);
1478
+
1479
+ if (content.opacity != null) {
1480
+ content.opacity.label.textContent = localize(
1481
+ getLocalization(global, host, 'opacity', component),
1482
+ {},
1483
+ [],
1484
+ );
1485
+ }
1486
+ }
1487
+
1488
+ function updateColorPickerLocalizations(
1489
+ instance: OuiLocalizations<OuiColorPickerLocalizations>,
1490
+ destination: Record<string, Partial<OuiColorPickerLocalizations>>,
1491
+ first: string | Record<string, Partial<OuiColorPickerLocalizations>>,
1492
+ second?: Partial<OuiColorPickerLocalizations>,
1493
+ ): void {
1494
+ updateLocalizations(
1495
+ instance,
1496
+ typeof first === 'string'
1497
+ ? {
1498
+ [first]: second!,
1499
+ }
1500
+ : first,
1501
+ destination,
1502
+ () => {
1503
+ for (const state of states) {
1504
+ updateLabels(state);
1505
+ }
1506
+ },
1507
+ );
1508
+ }
1509
+
1510
+ function updateValidity(state: OuiColorPickerState, valid: boolean): void {
1511
+ const {anchor, host, internals, localizations} = state;
1512
+
1513
+ if (valid) {
1514
+ internals.setValidity({});
1515
+ internals.checkValidity();
1516
+
1517
+ return;
1518
+ }
1519
+
1520
+ const localization = getLocalization(
1521
+ localizations.global,
1522
+ host,
1523
+ 'invalid',
1524
+ localizations.component,
1525
+ );
1526
+
1527
+ const message = localization == null ? undefined : localize(localization, {}, []);
1528
+
1529
+ internals.setValidity(
1530
+ {
1531
+ typeMismatch: true,
1532
+ },
1533
+ message,
1534
+ anchor.input,
1535
+ );
1536
+
1537
+ internals.checkValidity();
1538
+ }
1539
+
1540
+ function updateValues(parameters: MoveParameters, position: EventPosition): void {
1541
+ const {rectangle, state} = parameters;
1542
+ const {x, y} = position;
1543
+
1544
+ const top = clamp((y - rectangle.top) / rectangle.height, 0, 1);
1545
+ const left = clamp((x - rectangle.left) / rectangle.width, 0, 1);
1546
+
1547
+ state.color.value.hsl = {
1548
+ ...state.color.value.hsl,
1549
+ lightness: (1 - top) * 100,
1550
+ saturation: left * 100,
1551
+ };
1552
+
1553
+ setValues(state, true);
1554
+ }
1555
+
1556
+ function updateWell(state: OuiColorPickerState): void {
1557
+ const {color, content, host, localizations} = state;
1558
+ const {component, global} = localizations;
1559
+ const {lightness, saturation} = color.value.hsl;
1560
+
1561
+ setAria(
1562
+ content.well.knob,
1563
+ 'valuetext',
1564
+ localize(
1565
+ getLocalization(global, host, 'lightnessSaturationValue', component),
1566
+ {
1567
+ lightness,
1568
+ saturation,
1569
+ },
1570
+ [lightness, saturation],
1571
+ ),
1572
+ );
1573
+ }
1574
+
1575
+ // #endregion
1576
+
1577
+ // #region Variables
1578
+
1579
+ const TAGNAME = 'oui-color-picker';
1580
+
1581
+ const ATTRIBUTE_INPUT = `${TAGNAME}-input`;
1582
+
1583
+ const ATTRIBUTE_WELL = `${TAGNAME}-well`;
1584
+
1585
+ const ATTRIBUTE_WELL_KNOB = `${TAGNAME}-well-knob`;
1586
+
1587
+ const EXPRESSION_HEX_PREFIX = /^#/;
1588
+
1589
+ const KEYS = new Set([
1590
+ 'ArrowDown',
1591
+ 'ArrowLeft',
1592
+ 'ArrowRight',
1593
+ 'ArrowUp',
1594
+ 'End',
1595
+ 'Home',
1596
+ 'PageDown',
1597
+ 'PageUp',
1598
+ ]);
1599
+
1600
+ const MESSAGE_INPUT = `<${TAGNAME}> must contain a valid <input> element`;
1601
+
1602
+ const MESSAGE_LABEL = `<${TAGNAME}> must contain a <label> element for the <input> element`;
1603
+
1604
+ const OUTPUTS = new Set<OuiColorPickerOutput>(['all', 'none', 'hsl', 'rgb']);
1605
+
1606
+ const SELECTOR_INPUT = `[${ATTRIBUTE_INPUT}]`;
1607
+
1608
+ const SELECTOR_WELL = `[${ATTRIBUTE_WELL}]`;
1609
+
1610
+ const SELECTOR_WELL_KNOB = `[${ATTRIBUTE_WELL_KNOB}]`;
1611
+
1612
+ const TYPES = new Set(['color', 'text']);
1613
+
1614
+ const instances = new WeakMap<OuiColorPickerElement, OuiColorPickerState>();
1615
+
1616
+ const localizations = getLocalizations<OuiColorPickerLocalizations>({
1617
+ hue: 'Hue',
1618
+ invalid: 'Please enter or select a color',
1619
+ lightnessSaturationLabel: 'Lightness and saturation',
1620
+ lightnessSaturationValue: (lightness: number, saturation: number) =>
1621
+ `Lightness: ${round(lightness, 1)}%, saturation: ${round(saturation, 1)}%`,
1622
+ opacity: 'Opacity',
1623
+ toggle: 'Toggle color picker',
1624
+ });
1625
+
1626
+ const states = new Set<OuiColorPickerState>();
1627
+
1628
+ const styles = {
1629
+ touchAction: 'none',
1630
+ userSelect: 'none',
1631
+ webkitUserSelect: 'none',
1632
+ };
1633
+
1634
+ const styleToggler = toggleStyles(document.body, styles);
1635
+
1636
+ let index = 0;
1637
+
1638
+ let moved: MoveParameters | undefined;
1639
+
1640
+ // #endregion
1641
+
1642
+ // #region Initialization
1643
+
1644
+ on(document, 'keydown', onKeydown, {passive: false});
1645
+ on(document, 'mousedown', onPointerdown);
1646
+ on(document, 'pointermove', onPointermove);
1647
+ on(document, 'pointerup', onPointerup);
1648
+ on(document, 'touchcancel', onPointerup);
1649
+ on(document, 'touchstart', onPointerdown, {passive: false});
1650
+
1651
+ customElements.define(TAGNAME, OuiColorPickerElement);
1652
+
1653
+ // #endregion