@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,924 @@
1
+ import { createEmbeddedFloatable } from "./floatable/floatable.embedded.mjs";
2
+ import { getLocalization, getLocalizations, localize, updateLocalizations } from "./l10n.mjs";
3
+ import { getColor, getForegroundColor, isHexColor, isHslColor, isHslaColor, isHwbColor, isHwbaColor, isRgbColor, isRgbaColor } from "@oscarpalmer/atoms/color";
4
+ import { round } from "@oscarpalmer/atoms/math";
5
+ import { clamp } from "@oscarpalmer/atoms/number";
6
+ import { getAria, setAria } from "@oscarpalmer/toretto/aria";
7
+ import { getAttribute, setAttributes } from "@oscarpalmer/toretto/attribute";
8
+ import { createElement } from "@oscarpalmer/toretto/create";
9
+ import { dispatch, getEventPosition, on } from "@oscarpalmer/toretto/event";
10
+ import { findAncestor } from "@oscarpalmer/toretto/find";
11
+ import { setProperty } from "@oscarpalmer/toretto/property";
12
+ import { toggleStyles } from "@oscarpalmer/toretto/style";
13
+ import supportsTouch from "@oscarpalmer/toretto/touch";
14
+ //#region src/js/color-picker.ts
15
+ var OuiColorPickerElement = class extends HTMLElement {
16
+ static formAssociated = true;
17
+ static observedAttributes = [
18
+ "disabled",
19
+ "required",
20
+ "value"
21
+ ];
22
+ #internals;
23
+ #restored;
24
+ #state;
25
+ /**
26
+ * Is the color picker disabled?
27
+ */
28
+ get disabled() {
29
+ return this.#state?.disabled ?? false;
30
+ }
31
+ /**
32
+ * Should the color picker be disabled?
33
+ */
34
+ set disabled(value) {
35
+ if (this.#state != null && typeof value === "boolean" && value !== this.#state.disabled) setDisabled(this.#state, "disabled", value);
36
+ }
37
+ /**
38
+ * Form element associated with the color picker
39
+ */
40
+ get form() {
41
+ return this.#internals.form;
42
+ }
43
+ /**
44
+ * Color value as hexadecimal string
45
+ */
46
+ get hex() {
47
+ return this.#state?.color.value.hex ?? "";
48
+ }
49
+ /**
50
+ * Set color value from a hexadecimal string
51
+ */
52
+ set hex(value) {
53
+ setValue(this.#state, value, "hex");
54
+ }
55
+ /**
56
+ * Color value as hexadecimal string with alpha channel
57
+ */
58
+ get hexa() {
59
+ return this.#state?.color.value.hexa ?? "";
60
+ }
61
+ /**
62
+ * Set color value from a hexadecimal string with alpha channel
63
+ */
64
+ set hexa(value) {
65
+ setValue(this.#state, value, "hexa");
66
+ }
67
+ /**
68
+ * Color value as an _HSL_ color object
69
+ */
70
+ get hsl() {
71
+ return this.#state?.color.value.hsl ?? {
72
+ hue: 0,
73
+ lightness: 0,
74
+ saturation: 0
75
+ };
76
+ }
77
+ /**
78
+ * Set color value from an _HSL_ color object
79
+ */
80
+ set hsl(value) {
81
+ setValue(this.#state, value, "hsl");
82
+ }
83
+ /**
84
+ * Color value as an _HSLA_ color object _(HSL with alpha channel)_
85
+ */
86
+ get hsla() {
87
+ return this.#state?.color.value.hsla ?? {
88
+ hue: 0,
89
+ lightness: 0,
90
+ saturation: 0,
91
+ alpha: 100
92
+ };
93
+ }
94
+ /**
95
+ * Set color value from an _HSLA_ color object _(HSL with alpha channel)_
96
+ */
97
+ set hsla(value) {
98
+ setValue(this.#state, value, "hsla");
99
+ }
100
+ /**
101
+ * Color value as an _HWB_ color object
102
+ */
103
+ get hwb() {
104
+ return this.#state?.color.value.hwb ?? {
105
+ hue: 0,
106
+ whiteness: 0,
107
+ blackness: 0
108
+ };
109
+ }
110
+ /**
111
+ * Set color value from an _HWB_ color object
112
+ */
113
+ set hwb(value) {
114
+ setValue(this.#state, value, "hwb");
115
+ }
116
+ /**
117
+ * Color value as an _HWBA_ color object _(HWB with alpha channel)_
118
+ */
119
+ get hwba() {
120
+ return this.#state?.color.value.hwba ?? {
121
+ hue: 0,
122
+ whiteness: 0,
123
+ blackness: 0,
124
+ alpha: 100
125
+ };
126
+ }
127
+ /**
128
+ * Set color value from an _HWBA_ color object _(HWB with alpha channel)_
129
+ */
130
+ set hwba(value) {
131
+ setValue(this.#state, value, "hwba");
132
+ }
133
+ /**
134
+ * Text input value
135
+ */
136
+ get input() {
137
+ return this.#state?.anchor.input.value ?? "";
138
+ }
139
+ /**
140
+ * Label elements associated with the color picker
141
+ */
142
+ get labels() {
143
+ return this.#internals.labels;
144
+ }
145
+ /**
146
+ * Is the color picker readonly?
147
+ */
148
+ get readonly() {
149
+ return this.#state?.readonly ?? false;
150
+ }
151
+ /**
152
+ * Should the color picker be readonly?
153
+ */
154
+ set readonly(value) {
155
+ if (this.#state != null && typeof value === "boolean" && value !== this.#state.readonly) setDisabled(this.#state, "readonly", value);
156
+ }
157
+ /**
158
+ * Is the color picker required?
159
+ */
160
+ get required() {
161
+ return this.#state?.required ?? false;
162
+ }
163
+ /**
164
+ * Should the color picker be required?
165
+ */
166
+ set required(value) {
167
+ if (this.#state != null && typeof value === "boolean" && value !== this.#state.required) setRequired(this.#state, value);
168
+ }
169
+ /**
170
+ * Color value as an _RGB_ color object
171
+ */
172
+ get rgb() {
173
+ return this.#state?.color.value.rgb ?? {
174
+ red: 0,
175
+ green: 0,
176
+ blue: 0
177
+ };
178
+ }
179
+ /**
180
+ * Set color value from an _RGB_ color object
181
+ */
182
+ set rgb(value) {
183
+ setValue(this.#state, value, "rgb");
184
+ }
185
+ /**
186
+ * Color value as an _RGBA_ color object _(RGB with alpha channel)_
187
+ */
188
+ get rgba() {
189
+ return this.#state?.color.value.rgba ?? {
190
+ red: 0,
191
+ green: 0,
192
+ blue: 0,
193
+ alpha: 100
194
+ };
195
+ }
196
+ /**
197
+ * Set color value from an _RGBA_ color object _(RGB with alpha channel)_
198
+ */
199
+ set rgba(value) {
200
+ setValue(this.#state, value, "rgba");
201
+ }
202
+ /**
203
+ * Validation message for the color picker
204
+ */
205
+ get validationMessage() {
206
+ return this.#internals.validationMessage;
207
+ }
208
+ /**
209
+ * Validity state for the color picker
210
+ */
211
+ get validity() {
212
+ return this.#internals.validity;
213
+ }
214
+ /**
215
+ * Current color value
216
+ */
217
+ get value() {
218
+ return this.#state?.value.current ?? "";
219
+ }
220
+ /**
221
+ * Set color value from a string, _HSL(A)_, _HWB(A)_, or _RGB(A)_ color object
222
+ */
223
+ set value(value) {
224
+ if (typeof value === "string") {
225
+ this.#state.anchor.input.value = value;
226
+ onInput(this.#state);
227
+ } else setValue(this.#state, value);
228
+ }
229
+ /**
230
+ * Can the color picker be validated?
231
+ */
232
+ get willValidate() {
233
+ return this.#internals.willValidate;
234
+ }
235
+ constructor() {
236
+ super();
237
+ this.#internals = this.attachInternals();
238
+ }
239
+ attributeChangedCallback(name, _, value) {
240
+ if (this.#state == null) return;
241
+ switch (name) {
242
+ case "disabled":
243
+ this.disabled = value != null;
244
+ break;
245
+ case "required":
246
+ this.required = value != null;
247
+ break;
248
+ case "value": this.value = value ?? "";
249
+ }
250
+ }
251
+ /**
252
+ * Check if the color picker is valid
253
+ *
254
+ * @returns `true` if the color picker is valid, otherwise `false`
255
+ */
256
+ checkValidity() {
257
+ return this.#internals.checkValidity();
258
+ }
259
+ /**
260
+ * Clear the color picker's value
261
+ */
262
+ clear() {
263
+ this.value = "";
264
+ }
265
+ connectedCallback() {
266
+ if (this.#state == null) this.#initialize();
267
+ else attach(this.#state);
268
+ }
269
+ disconnectedCallback() {
270
+ if (this.#state != null) detach(this.#state);
271
+ }
272
+ formDisabledCallback(disabled) {
273
+ this.disabled = disabled;
274
+ }
275
+ formResetCallback() {
276
+ this.value = this.#state?.value.initial ?? "";
277
+ }
278
+ formStateRestoreCallback(state, reason) {
279
+ if (reason !== "restore" || typeof state !== "string" || this.#state == null) return;
280
+ if (this.#state == null) this.#restored = state;
281
+ else this.value = state;
282
+ }
283
+ /**
284
+ * Check if the color picker is valid and report any validation errors
285
+ *
286
+ * @returns `true` if the color picker is valid, otherwise `false`
287
+ */
288
+ reportValidity() {
289
+ return this.#internals.reportValidity();
290
+ }
291
+ /**
292
+ * Set a custom validation message for the color picker
293
+ *
294
+ * @param message Validation message
295
+ */
296
+ setCustomValidity(message) {
297
+ this.#internals.setValidity({ customError: message !== "" }, message, this);
298
+ }
299
+ setLocalizations(first, second) {
300
+ updateColorPickerLocalizations(localizations, this.#state.localizations.component, first, second);
301
+ }
302
+ #initialize() {
303
+ this.#state = initialize(this, this.#internals);
304
+ if (this.#restored != null) {
305
+ this.value = this.#restored;
306
+ this.#restored = void 0;
307
+ }
308
+ }
309
+ };
310
+ var OuiColorPickerEvent = class extends Event {
311
+ valid;
312
+ value;
313
+ constructor(type, color, valid) {
314
+ super(type, {
315
+ bubbles: true,
316
+ cancelable: true
317
+ });
318
+ this.valid = valid;
319
+ this.value = {
320
+ hex: color.hex,
321
+ hexa: color.hexa,
322
+ hsl: color.hsl,
323
+ hsla: color.hsla,
324
+ hwb: color.hwb,
325
+ hwba: color.hwba,
326
+ rgb: color.rgb,
327
+ rgba: color.rgba
328
+ };
329
+ }
330
+ };
331
+ function attach(state) {
332
+ const { anchor, content, host, id, inline, input, label, listeners, style } = state;
333
+ if (!inline) {
334
+ state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.container, content.element, {
335
+ position: "below-start",
336
+ reusable: false
337
+ });
338
+ state.floatable.update();
339
+ }
340
+ const labelId = `${id}_label`;
341
+ setAria(host, {
342
+ describedby: getAria(input, "describedby"),
343
+ labelledby: labelId
344
+ });
345
+ setAttributes(host, { name: input.name === "" ? input.id : input.name });
346
+ label.element.htmlFor = anchor.input.id;
347
+ label.element.id = labelId;
348
+ if (anchor.button != null) setAria(anchor.button, "expanded", false);
349
+ input.before(style, anchor.container, content.element);
350
+ input.remove();
351
+ listeners.push(on(host, "focusout", () => {
352
+ setTimeout(() => {
353
+ if (moved == null && !host.contains(document.activeElement)) state.floatable?.hide();
354
+ });
355
+ }), on(anchor.input, "change", () => {
356
+ onInput(state);
357
+ }), on(anchor.input, "input", () => {
358
+ onInput(state);
359
+ }), on(content.element, "beforetoggle", (event) => {
360
+ if (state.disabled) event.preventDefault();
361
+ }, { passive: false }), on(content.element, "toggle", (event) => {
362
+ const open = event.newState === "open";
363
+ if (anchor.button != null) setAria(anchor.button, "expanded", open);
364
+ if (open) content.well.knob.focus();
365
+ }), on(content.hue.input, "input", () => {
366
+ onHue(state);
367
+ }));
368
+ if (content.opacity != null) listeners.push(on(content.opacity.input, "input", () => {
369
+ onOpacity(state);
370
+ }));
371
+ instances.set(host, state);
372
+ states.add(state);
373
+ }
374
+ function cancelMove(parameters) {
375
+ parameters.state.color.value.hexa = parameters.value;
376
+ setValues(parameters.state, true);
377
+ moved = void 0;
378
+ styleToggler.remove();
379
+ }
380
+ function createAnchor(parameters) {
381
+ const { alpha, color, id, inline } = parameters;
382
+ const container = createElement("div", { attribute: { [`${TAGNAME}-anchor`]: "" } });
383
+ const input = createElement("input", {
384
+ attribute: {
385
+ [`${TAGNAME}-input`]: "",
386
+ autocomplete: "off",
387
+ autocorrect: "off",
388
+ form: "",
389
+ inputmode: "text",
390
+ placeholder: parameters.input.placeholder,
391
+ spellcheck: "false"
392
+ },
393
+ property: {
394
+ id,
395
+ type: "text",
396
+ value: alpha ? color.hexa : color.hex
397
+ }
398
+ });
399
+ let button;
400
+ if (!inline) button = createElement("button", {
401
+ aria: { label: "" },
402
+ attribute: {
403
+ [`${TAGNAME}-button`]: "",
404
+ popovertarget: `${id}_content`
405
+ },
406
+ property: { type: "button" }
407
+ });
408
+ container.append(input);
409
+ if (button != null) container.append(button);
410
+ return {
411
+ button,
412
+ container,
413
+ input
414
+ };
415
+ }
416
+ function createContent(parameters) {
417
+ const { id, inline } = parameters;
418
+ const element = createElement("div", {
419
+ attribute: {
420
+ [`${TAGNAME}-content`]: "",
421
+ popover: inline ? void 0 : ""
422
+ },
423
+ property: { id: `${id}_content` }
424
+ });
425
+ const hue = createHue(parameters);
426
+ const opacity = createOpacity(parameters);
427
+ const well = createWell(parameters);
428
+ element.append(well.element, hue.container);
429
+ if (opacity != null) element.append(opacity.container);
430
+ return {
431
+ element,
432
+ hue,
433
+ opacity,
434
+ well
435
+ };
436
+ }
437
+ function createHue(parameters) {
438
+ const { color, id } = parameters;
439
+ const container = createElement("div", { attribute: { [`${TAGNAME}-hue`]: "" } });
440
+ const label = createElement("label", {
441
+ attribute: { [`${TAGNAME}-hue-label`]: "" },
442
+ property: {
443
+ htmlFor: `${id}_hue`,
444
+ id: `${id}_hue_label`
445
+ }
446
+ });
447
+ const input = createElement("input", {
448
+ aria: { labelledby: `${id}_label ${label.id}` },
449
+ attribute: {
450
+ [`${TAGNAME}-hue-input`]: "",
451
+ form: "",
452
+ inputmode: "numeric",
453
+ max: 360,
454
+ min: 0
455
+ },
456
+ property: {
457
+ id: `${id}_hue`,
458
+ type: "range",
459
+ value: color.hsl.hue.toString()
460
+ }
461
+ });
462
+ const knob = createElement("span", {
463
+ aria: { hidden: true },
464
+ attribute: { [`${TAGNAME}-hue-knob`]: "" }
465
+ });
466
+ const track = createElement("div", {
467
+ aria: { hidden: true },
468
+ attribute: { [`${TAGNAME}-hue-track`]: "" }
469
+ });
470
+ track.append(knob);
471
+ container.append(label, input, track);
472
+ return {
473
+ container,
474
+ input,
475
+ knob,
476
+ label,
477
+ track
478
+ };
479
+ }
480
+ function createOpacity(parameters) {
481
+ const { alpha, color, id } = parameters;
482
+ if (!alpha) return;
483
+ const container = createElement("div", { attribute: { [`${TAGNAME}-opacity`]: "" } });
484
+ const label = createElement("label", {
485
+ attribute: { [`${TAGNAME}-opacity-label`]: "" },
486
+ property: {
487
+ htmlFor: `${id}_opacity`,
488
+ id: `${id}_opacity_label`
489
+ }
490
+ });
491
+ const input = createElement("input", {
492
+ aria: { labelledby: `${id}_label ${label.id}` },
493
+ attribute: {
494
+ [`${TAGNAME}-opacity-input`]: "",
495
+ form: "",
496
+ max: 100,
497
+ min: 0
498
+ },
499
+ property: {
500
+ id: `${id}_opacity`,
501
+ type: "range",
502
+ value: color.alpha.toString()
503
+ }
504
+ });
505
+ const knob = createElement("span", {
506
+ aria: { hidden: true },
507
+ attribute: { [`${TAGNAME}-opacity-knob`]: "" }
508
+ });
509
+ const track = createElement("div", {
510
+ aria: { hidden: true },
511
+ attribute: { [`${TAGNAME}-opacity-track`]: "" }
512
+ });
513
+ track.append(knob);
514
+ container.append(label, input, track);
515
+ return {
516
+ container,
517
+ input,
518
+ label
519
+ };
520
+ }
521
+ function createWell(parameters) {
522
+ const { id } = parameters;
523
+ const element = createElement("div", { attribute: { [ATTRIBUTE_WELL]: "" } });
524
+ const label = createElement("span", { property: {
525
+ id: `${id}_well_label`,
526
+ hidden: true
527
+ } });
528
+ const knob = createElement("span", {
529
+ aria: { labelledby: `${id}_label ${label.id}` },
530
+ attribute: { [ATTRIBUTE_WELL_KNOB]: "" },
531
+ property: {
532
+ role: "slider",
533
+ tabIndex: 0
534
+ }
535
+ });
536
+ element.append(label, knob);
537
+ return {
538
+ element,
539
+ knob,
540
+ label
541
+ };
542
+ }
543
+ function detach(state) {
544
+ const { anchor, content, floatable, host, input, label, style } = state;
545
+ floatable?.destroy();
546
+ setAria(host, {
547
+ describedby: void 0,
548
+ labelledby: void 0
549
+ });
550
+ setAttributes(host, { name: void 0 });
551
+ label.element.htmlFor = input.id;
552
+ label.element.id = label.id;
553
+ anchor.container.before(input);
554
+ anchor.container.remove();
555
+ content.element.remove();
556
+ style.remove();
557
+ const listeners = state.listeners.splice(0);
558
+ const { length } = listeners;
559
+ for (let index = 0; index < length; index += 1) listeners[index]();
560
+ instances.set(host, state);
561
+ states.add(state);
562
+ }
563
+ function getId() {
564
+ index += 1;
565
+ return `oui_color_picker_${index}`;
566
+ }
567
+ function getOutput(element) {
568
+ const attribute = getAttribute(element, "output");
569
+ return OUTPUTS.has(attribute) ? attribute : "none";
570
+ }
571
+ function getValueType(state, value) {
572
+ switch (true) {
573
+ case isHexColor(value, state.alpha): return state.alpha ? "hexa" : "hex";
574
+ case isHslColor(value): return "hsl";
575
+ case isHslaColor(value): return "hsla";
576
+ case isHwbColor(value): return "hwb";
577
+ case isHwbaColor(value): return "hwba";
578
+ case isRgbColor(value): return "rgb";
579
+ case isRgbaColor(value): return "rgba";
580
+ }
581
+ }
582
+ function initialize(element, internals) {
583
+ const input = element.querySelector("input");
584
+ if (input == null || !TYPES.has(input.type)) throw new Error(MESSAGE_INPUT);
585
+ const label = element.querySelector(`label[for="${input.id}"]`);
586
+ if (label == null) throw new Error(MESSAGE_LABEL);
587
+ element.id = input.id;
588
+ const id = getId();
589
+ const labelId = label.id;
590
+ const alpha = element.hasAttribute("alpha");
591
+ const color = getColor(input.value);
592
+ const disabled = element.hasAttribute("disabled") || input.disabled;
593
+ const inline = element.hasAttribute("inline");
594
+ const output = getOutput(element);
595
+ const readonly = element.hasAttribute("readonly") || input.readOnly;
596
+ const required = element.hasAttribute("required") || input.required;
597
+ const parameters = {
598
+ alpha,
599
+ color,
600
+ id,
601
+ inline,
602
+ input,
603
+ output
604
+ };
605
+ const anchor = createAnchor(parameters);
606
+ const content = createContent(parameters);
607
+ const formatted = color.toHexString(alpha);
608
+ const state = {
609
+ alpha,
610
+ anchor,
611
+ content,
612
+ disabled,
613
+ id,
614
+ inline,
615
+ input,
616
+ internals,
617
+ output,
618
+ readonly,
619
+ required,
620
+ color: {
621
+ foreground: "",
622
+ value: color
623
+ },
624
+ host: element,
625
+ label: {
626
+ element: label,
627
+ id: labelId
628
+ },
629
+ listeners: [],
630
+ localizations: {
631
+ component: {},
632
+ global: localizations
633
+ },
634
+ style: createElement("style"),
635
+ value: {
636
+ current: formatted,
637
+ initial: formatted
638
+ }
639
+ };
640
+ attach(state);
641
+ setAttributes(element, {
642
+ output,
643
+ alpha: alpha ? "" : void 0,
644
+ inline: inline ? "" : void 0
645
+ });
646
+ setDisabled(state, "disabled", disabled);
647
+ setDisabled(state, "readonly", readonly);
648
+ setRequired(state, required);
649
+ setValues(state, false);
650
+ updateLabels(state);
651
+ updateValidity(state, isHexColor(state.value.current, alpha));
652
+ return state;
653
+ }
654
+ function onEnter(state) {
655
+ const { alpha, anchor, color, internals } = state;
656
+ const input = anchor.input.value.replace(EXPRESSION_HEX_PREFIX, "");
657
+ const short = alpha ? input.length === 4 : input.length === 3;
658
+ if (isHexColor(input, alpha) && short) anchor.input.value = color.value.toHexString(alpha);
659
+ internals.form?.requestSubmit();
660
+ }
661
+ function onHue(state) {
662
+ const hue = state.content.hue.input.valueAsNumber;
663
+ state.color.value.hsla = {
664
+ ...state.color.value.hsla,
665
+ hue
666
+ };
667
+ setValues(state, false);
668
+ }
669
+ function onInput(state) {
670
+ const { alpha, anchor, color, host } = state;
671
+ const value = anchor.input.value.replace(EXPRESSION_HEX_PREFIX, "");
672
+ if (value.toLowerCase() === (alpha ? color.value.hexa : color.value.hex).toLowerCase()) return;
673
+ state.value.current = anchor.input.value;
674
+ const { length } = value;
675
+ let valid;
676
+ if (alpha) valid = isHexColor(value, true) && (length === 4 || length === 8);
677
+ else valid = isHexColor(value) && (length === 3 || length === 6);
678
+ if (valid) {
679
+ if (length === 3 || length === 6) color.value.hex = value;
680
+ else color.value.hexa = value;
681
+ }
682
+ setValues(state, false, anchor.input.value);
683
+ dispatch(host, new OuiColorPickerEvent("oui:input", color.value, valid));
684
+ }
685
+ function onKeydown(event) {
686
+ if (moved != null && event.key === "Escape") {
687
+ cancelMove(moved);
688
+ return;
689
+ }
690
+ const knob = findAncestor(event, SELECTOR_WELL_KNOB);
691
+ if (!(event.key === "Enter" && findAncestor(event, SELECTOR_INPUT) != null || KEYS.has(event.key) && knob != null)) return;
692
+ const state = instances.get(findAncestor(event, TAGNAME));
693
+ if (state == null || state.disabled || state.readonly) return;
694
+ event.preventDefault();
695
+ if (event.key === "Enter") {
696
+ onEnter(state);
697
+ return;
698
+ }
699
+ if (knob == null) return;
700
+ const modifier = event.shiftKey ? 10 : 1;
701
+ let { lightness, saturation } = state.color.value.hsl;
702
+ switch (event.key) {
703
+ case "ArrowDown":
704
+ lightness -= 1 * modifier;
705
+ break;
706
+ case "ArrowLeft":
707
+ saturation -= 1 * modifier;
708
+ break;
709
+ case "ArrowRight":
710
+ saturation += 1 * modifier;
711
+ break;
712
+ case "ArrowUp":
713
+ lightness += 1 * modifier;
714
+ break;
715
+ case "End":
716
+ lightness = 0;
717
+ break;
718
+ case "Home":
719
+ lightness = 100;
720
+ break;
721
+ case "PageDown":
722
+ lightness -= 10;
723
+ break;
724
+ case "PageUp": lightness += 10;
725
+ }
726
+ state.color.value.hsl = {
727
+ ...state.color.value.hsl,
728
+ lightness: clamp(lightness, 0, 100),
729
+ saturation: clamp(saturation, 0, 100)
730
+ };
731
+ setValues(state, false);
732
+ }
733
+ function onOpacity(state) {
734
+ const opacity = state.content.opacity?.input.valueAsNumber;
735
+ if (opacity == null) return;
736
+ state.color.value.alpha = opacity;
737
+ setValues(state, false);
738
+ }
739
+ function onPointerdown(event) {
740
+ if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || (supportsTouch.value ? event.type === "mousedown" : event.type === "touchstart" || event.button !== 0)) return;
741
+ const well = findAncestor(event, SELECTOR_WELL);
742
+ if (well == null) return;
743
+ const element = findAncestor(event, TAGNAME);
744
+ if (element == null) return;
745
+ if (supportsTouch.value) event.preventDefault();
746
+ if (getEventPosition(event) == null) return;
747
+ const state = instances.get(element);
748
+ if (state == null || state.disabled || state.readonly) return;
749
+ moved = {
750
+ state,
751
+ rectangle: well.getBoundingClientRect(),
752
+ value: state.color.value.hexa
753
+ };
754
+ styleToggler.set();
755
+ }
756
+ function onPointermove(event) {
757
+ if (moved == null) return;
758
+ const position = getEventPosition(event);
759
+ if (position != null) updateValues(moved, position);
760
+ }
761
+ function onPointerup(event) {
762
+ if (moved == null) return;
763
+ const position = getEventPosition(event);
764
+ if (position != null) updateValues(moved, position);
765
+ moved = void 0;
766
+ styleToggler.remove();
767
+ }
768
+ function setColorPickerLocalizations(first, second) {
769
+ updateColorPickerLocalizations(localizations, localizations.values, first, second);
770
+ }
771
+ function setDisabled(state, type, value) {
772
+ state[type] = value;
773
+ setAria(state.host, type, value);
774
+ state.anchor.input.readOnly = value;
775
+ setAria(state.anchor.input, type, value);
776
+ setProperty(state.content.hue.input, "disabled", state.disabled || state.readonly);
777
+ if (state.anchor.button != null) setAria(state.anchor.button, "disabled", state.disabled);
778
+ if (state.content.opacity != null) setProperty(state.content.opacity.input, "disabled", state.disabled || state.readonly);
779
+ }
780
+ function setRequired(state, value) {
781
+ state.required = value;
782
+ setAria(state.host, "required", value);
783
+ setAria(state.anchor.input, "required", value);
784
+ updateValidity(state, isHexColor(state.value.current, state.alpha));
785
+ }
786
+ function setValue(state, value, type) {
787
+ if (state == null) return;
788
+ const actual = type ?? getValueType(state, value);
789
+ if (actual == null || type != null && type !== actual) return;
790
+ state.color.value[actual] = value;
791
+ setValues(state, true);
792
+ }
793
+ function setValues(state, focus, input) {
794
+ const { anchor, color, content, host, style } = state;
795
+ color.foreground = getForegroundColor(color.value).hex;
796
+ const { alpha, hex, hsl, rgb } = color.value;
797
+ const { hue, lightness, saturation } = hsl;
798
+ const { blue, green, red } = rgb;
799
+ const value = input ?? color.value.toHexString(state.alpha);
800
+ anchor.input.value = value;
801
+ content.hue.input.valueAsNumber = hue;
802
+ if (content.opacity != null) content.opacity.input.valueAsNumber = alpha;
803
+ state.value.current = value;
804
+ style.textContent = `#${host.id} {
805
+ --oui-color-picker-alpha: ${alpha};
806
+ --oui-color-picker-blue: ${blue};
807
+ --oui-color-picker-green: ${green};
808
+ --oui-color-picker-foreground: #${color.foreground};
809
+ --oui-color-picker-hex: #${hex};
810
+ --oui-color-picker-hue: ${hue};
811
+ --oui-color-picker-lightness: ${lightness};
812
+ --oui-color-picker-red: ${red};
813
+ --oui-color-picker-saturation: ${saturation};
814
+ }`;
815
+ updateForm(state, isHexColor(state.value.current, state.alpha));
816
+ updateWell(state);
817
+ if (focus) content.well.knob.focus();
818
+ }
819
+ function updateForm(state, valid) {
820
+ const { color, host, internals, value } = state;
821
+ internals.setFormValue(valid ? value.current : "");
822
+ updateValidity(state, valid);
823
+ dispatch(host, new OuiColorPickerEvent("oui:change", color.value, valid));
824
+ }
825
+ function updateLabels(state) {
826
+ const { anchor, content, host, localizations } = state;
827
+ const { component, global } = localizations;
828
+ if (anchor.button != null) setAria(anchor.button, "label", localize(getLocalization(global, host, "toggle", component), {}, []));
829
+ content.hue.label.textContent = localize(getLocalization(global, host, "hue", component), {}, []);
830
+ content.well.label.textContent = localize(getLocalization(global, host, "lightnessSaturationLabel", component), {}, []);
831
+ updateWell(state);
832
+ if (content.opacity != null) content.opacity.label.textContent = localize(getLocalization(global, host, "opacity", component), {}, []);
833
+ }
834
+ function updateColorPickerLocalizations(instance, destination, first, second) {
835
+ updateLocalizations(instance, typeof first === "string" ? { [first]: second } : first, destination, () => {
836
+ for (const state of states) updateLabels(state);
837
+ });
838
+ }
839
+ function updateValidity(state, valid) {
840
+ const { anchor, host, internals, localizations } = state;
841
+ if (valid) {
842
+ internals.setValidity({});
843
+ internals.checkValidity();
844
+ return;
845
+ }
846
+ const localization = getLocalization(localizations.global, host, "invalid", localizations.component);
847
+ const message = localization == null ? void 0 : localize(localization, {}, []);
848
+ internals.setValidity({ typeMismatch: true }, message, anchor.input);
849
+ internals.checkValidity();
850
+ }
851
+ function updateValues(parameters, position) {
852
+ const { rectangle, state } = parameters;
853
+ const { x, y } = position;
854
+ const top = clamp((y - rectangle.top) / rectangle.height, 0, 1);
855
+ const left = clamp((x - rectangle.left) / rectangle.width, 0, 1);
856
+ state.color.value.hsl = {
857
+ ...state.color.value.hsl,
858
+ lightness: (1 - top) * 100,
859
+ saturation: left * 100
860
+ };
861
+ setValues(state, true);
862
+ }
863
+ function updateWell(state) {
864
+ const { color, content, host, localizations } = state;
865
+ const { component, global } = localizations;
866
+ const { lightness, saturation } = color.value.hsl;
867
+ setAria(content.well.knob, "valuetext", localize(getLocalization(global, host, "lightnessSaturationValue", component), {
868
+ lightness,
869
+ saturation
870
+ }, [lightness, saturation]));
871
+ }
872
+ const TAGNAME = "oui-color-picker";
873
+ const ATTRIBUTE_INPUT = `${TAGNAME}-input`;
874
+ const ATTRIBUTE_WELL = `${TAGNAME}-well`;
875
+ const ATTRIBUTE_WELL_KNOB = `${TAGNAME}-well-knob`;
876
+ const EXPRESSION_HEX_PREFIX = /^#/;
877
+ const KEYS = /* @__PURE__ */ new Set([
878
+ "ArrowDown",
879
+ "ArrowLeft",
880
+ "ArrowRight",
881
+ "ArrowUp",
882
+ "End",
883
+ "Home",
884
+ "PageDown",
885
+ "PageUp"
886
+ ]);
887
+ const MESSAGE_INPUT = `<${TAGNAME}> must contain a valid <input> element`;
888
+ const MESSAGE_LABEL = `<${TAGNAME}> must contain a <label> element for the <input> element`;
889
+ const OUTPUTS = /* @__PURE__ */ new Set([
890
+ "all",
891
+ "none",
892
+ "hsl",
893
+ "rgb"
894
+ ]);
895
+ const SELECTOR_INPUT = `[${ATTRIBUTE_INPUT}]`;
896
+ const SELECTOR_WELL = `[${ATTRIBUTE_WELL}]`;
897
+ const SELECTOR_WELL_KNOB = `[${ATTRIBUTE_WELL_KNOB}]`;
898
+ const TYPES = /* @__PURE__ */ new Set(["color", "text"]);
899
+ const instances = /* @__PURE__ */ new WeakMap();
900
+ const localizations = getLocalizations({
901
+ hue: "Hue",
902
+ invalid: "Please enter or select a color",
903
+ lightnessSaturationLabel: "Lightness and saturation",
904
+ lightnessSaturationValue: (lightness, saturation) => `Lightness: ${round(lightness, 1)}%, saturation: ${round(saturation, 1)}%`,
905
+ opacity: "Opacity",
906
+ toggle: "Toggle color picker"
907
+ });
908
+ const states = /* @__PURE__ */ new Set();
909
+ const styleToggler = toggleStyles(document.body, {
910
+ touchAction: "none",
911
+ userSelect: "none",
912
+ webkitUserSelect: "none"
913
+ });
914
+ let index = 0;
915
+ let moved;
916
+ on(document, "keydown", onKeydown, { passive: false });
917
+ on(document, "mousedown", onPointerdown);
918
+ on(document, "pointermove", onPointermove);
919
+ on(document, "pointerup", onPointerup);
920
+ on(document, "touchcancel", onPointerup);
921
+ on(document, "touchstart", onPointerdown, { passive: false });
922
+ customElements.define(TAGNAME, OuiColorPickerElement);
923
+ //#endregion
924
+ export { OuiColorPickerElement, OuiColorPickerEvent, setColorPickerLocalizations };