|
22 | 22 | let open = $state(false) |
23 | 23 | let inputValue = $state(value) |
24 | 24 | let isDragging = $state(false) |
| 25 | + let selectedTailwindColorName = $state(null) |
25 | 26 | let colorState = $state({ |
26 | 27 | oklch: {}, |
27 | 28 | hsl: {}, |
|
39 | 40 | if (event.key === "Escape") { |
40 | 41 | closeModal() |
41 | 42 | } else if (event.key === "Enter") { |
| 43 | + event.preventDefault() |
42 | 44 | if (validateColor(inputValue)) { |
43 | | - value = inputValue |
44 | | - inputValue = "" |
| 45 | + if (value !== inputValue) { |
| 46 | + const v = inputValue |
| 47 | + inputValue = "" |
| 48 | + value = v |
| 49 | + } |
45 | 50 | closeModal() |
46 | 51 | } |
47 | 52 | } |
48 | 53 | } |
49 | 54 |
|
50 | | - function handleDragStart(color) { |
| 55 | + function handleDragStart(color, name) { |
51 | 56 | isDragging = true |
52 | 57 | dragPreviewColor = color |
53 | 58 | inputValue = color |
| 59 | + selectedTailwindColorName = name |
54 | 60 | } |
55 | 61 |
|
56 | | - function handleDragOver(color) { |
| 62 | + function handleDragOver(color, name) { |
57 | 63 | if (isDragging) { |
58 | 64 | dragPreviewColor = color |
59 | 65 | inputValue = color |
| 66 | + selectedTailwindColorName = name |
60 | 67 | } |
61 | 68 | } |
62 | 69 |
|
63 | 70 | function handleDragEnd(color) { |
64 | 71 | if (isDragging) { |
65 | | - value = color |
66 | | - inputValue = "" |
| 72 | + if (value !== color) { |
| 73 | + inputValue = "" |
| 74 | + value = color |
| 75 | + } else { |
| 76 | + inputValue = color |
| 77 | + } |
67 | 78 | isDragging = false |
68 | 79 | dragPreviewColor = null |
69 | 80 | } |
70 | 81 | } |
71 | 82 |
|
72 | 83 | function handleGlobalMouseUp() { |
73 | 84 | if (isDragging && dragPreviewColor) { |
74 | | - value = dragPreviewColor // Ensure value is updated if drag ends elsewhere |
75 | | - inputValue = "" |
| 85 | + if (value !== dragPreviewColor) { |
| 86 | + inputValue = "" |
| 87 | + value = dragPreviewColor // Ensure value is updated if drag ends elsewhere |
| 88 | + } else { |
| 89 | + inputValue = dragPreviewColor |
| 90 | + } |
76 | 91 | } |
77 | 92 | isDragging = false |
78 | 93 | dragPreviewColor = null |
79 | 94 | } |
80 | 95 |
|
| 96 | + let inputDebounce = null |
81 | 97 | function handleInput(event) { |
82 | | - const newValue = event.target.value |
83 | | - if (validateColor(newValue)) { |
84 | | - value = newValue |
85 | | - inputValue = "" |
86 | | - } else { |
87 | | - inputValue = newValue |
| 98 | + if (inputDebounce != null) { |
| 99 | + clearTimeout(inputDebounce) |
88 | 100 | } |
| 101 | + inputDebounce = setTimeout(() => { |
| 102 | + inputDebounce = null |
| 103 | +
|
| 104 | + const newValue = event.target.value |
| 105 | + if (validateColor(newValue) && value !== newValue) { |
| 106 | + inputValue = "" |
| 107 | + value = newValue |
| 108 | + selectedTailwindColorName = null |
| 109 | + } else { |
| 110 | + inputValue = newValue |
| 111 | + } |
| 112 | + }, 300) |
89 | 113 | } |
90 | 114 |
|
91 | 115 | function toggleModal() { |
|
111 | 135 | onModalStateChange(false) |
112 | 136 | } |
113 | 137 |
|
114 | | - $effect(() => { |
115 | | - untrack(() => { |
116 | | - if (value !== inputValue) { |
117 | | - updateColorState(value) |
118 | | - } |
119 | | - }) |
120 | | - inputValue = value |
| 138 | + $effect.pre(() => { |
| 139 | + if (open) { |
| 140 | + untrack(() => { |
| 141 | + if (value !== inputValue) { |
| 142 | + updateColorState(value) |
| 143 | + } |
| 144 | + }) |
| 145 | + inputValue = value |
| 146 | + } |
121 | 147 | }) |
122 | 148 |
|
123 | 149 | // Update inputValue when colorState changes |
124 | | - $effect(() => { |
| 150 | + $effect.pre(() => { |
125 | 151 | const newValue = generateColorValue() |
126 | 152 |
|
127 | 153 | untrack(() => { |
128 | | - if (inputValue !== newValue) { |
| 154 | + if (newValue != null && inputValue !== newValue) { |
129 | 155 | if (!colorState.changed) { |
130 | 156 | colorState.value = newValue |
131 | 157 | inputValue = newValue |
132 | 158 | } |
133 | 159 | value = newValue |
| 160 | + if (colorState.changed) { |
| 161 | + selectedTailwindColorName = null |
| 162 | + } |
134 | 163 | } |
135 | 164 | }) |
136 | 165 | }) |
137 | 166 |
|
138 | 167 | // Get the color to display (preview during dragging, actual value otherwise) |
139 | 168 | const displayColor = $derived(dragPreviewColor || value) |
140 | 169 |
|
141 | | - const colorName = $derived(colorDetails.find(([, color]) => color === inputValue)?.[0]) |
| 170 | + const colorName = $derived( |
| 171 | + selectedTailwindColorName || colorDetails.find(([, color]) => color === inputValue)?.[0], |
| 172 | + ) |
142 | 173 | const colorPairsMap = $derived.by(() => { |
143 | 174 | const map = { |
144 | 175 | color: {}, |
|
247 | 278 | try { |
248 | 279 | if (colorState.mode === "oklch") { |
249 | 280 | const { l, c, h } = colorState.oklch |
250 | | - return `oklch(${(l * 100).toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(1)})` |
| 281 | + return `oklch(${(l * 100).toFixed(1)}% ${c.toFixed(3)} ${h.toFixed(3)})` |
251 | 282 | } |
252 | 283 |
|
253 | 284 | if (colorState.mode === "hsl") { |
|
268 | 299 |
|
269 | 300 | return `rgb(${colorState.rgb.r} ${colorState.rgb.g} ${colorState.rgb.b})` |
270 | 301 | } catch { |
271 | | - return inputValue // Fallback to current value if conversion fails |
| 302 | + return null |
272 | 303 | } |
273 | 304 | } |
274 | 305 | </script> |
|
398 | 429 | <button |
399 | 430 | class="appearance-none p-px [writing-mode:lr]" |
400 | 431 | aria-label={name} |
401 | | - onmousedown={() => handleDragStart(color)} |
402 | | - onmouseover={() => handleDragOver(color)} |
403 | | - onfocus={() => handleDragOver(color)} |
| 432 | + onmousedown={() => handleDragStart(color, name)} |
| 433 | + onmouseover={() => handleDragOver(color, name)} |
| 434 | + onfocus={() => handleDragOver(color, name)} |
404 | 435 | onmouseup={() => handleDragEnd(color)} |
405 | | - onkeypress={() => handleDragStart(color)} |
| 436 | + onkeypress={() => handleDragStart(color, name)} |
406 | 437 | > |
407 | 438 | <div |
408 | 439 | class="border-base-content/10 relative grid aspect-square w-5 place-items-center rounded-full border bg-transparent select-none sm:m-px sm:w-7" |
409 | | - class:[box-shadow:0_0_0_2px_white,0_0_0_4px_black]={displayColor === color} |
410 | | - class:outline-white={displayColor === color} |
411 | | - class:outline-offset-[-3px]={displayColor === color} |
| 440 | + class:[box-shadow:0_0_0_2px_white,0_0_0_4px_black]={colorName === name} |
| 441 | + class:outline-white={colorName === name} |
| 442 | + class:outline-offset-[-3px]={colorName === name} |
412 | 443 | style:background-color={color} |
413 | 444 | > |
414 | 445 | {#if initials != null} |
|
514 | 545 | <input |
515 | 546 | type="text" |
516 | 547 | class="grow xl:font-mono xl:normal-nums" |
517 | | - bind:value={inputValue} |
| 548 | + value={inputValue} |
518 | 549 | oninput={handleInput} |
519 | | - onkeydown={(event) => { |
520 | | - if (event.key === "Enter") { |
521 | | - event.preventDefault() |
522 | | - if (validateColor(inputValue)) { |
523 | | - value = inputValue |
524 | | - closeModal() |
525 | | - } |
526 | | - } |
527 | | - }} |
528 | 550 | aria-label={`${name} value`} |
529 | 551 | /> |
530 | 552 | {#if colorName} |
|
554 | 576 | <div |
555 | 577 | class="modal-backdrop" |
556 | 578 | onclick={closeModal} |
557 | | - onkeydown={(e) => e.key === "Enter" && closeModal()} |
| 579 | + onkeydown={handleKeydown} |
558 | 580 | role="button" |
559 | 581 | tabindex="0" |
560 | 582 | aria-label="Close modal" |
|
0 commit comments