@foxui/colors 0.4.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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -0
  3. package/dist/components/color-gradient-picker/ColorGradientPicker.svelte +229 -0
  4. package/dist/components/color-gradient-picker/ColorGradientPicker.svelte.d.ts +16 -0
  5. package/dist/components/color-gradient-picker/index.d.ts +1 -0
  6. package/dist/components/color-gradient-picker/index.js +1 -0
  7. package/dist/components/color-picker/LICENSE +42 -0
  8. package/dist/components/color-picker/base/ColorPicker.svelte +312 -0
  9. package/dist/components/color-picker/base/ColorPicker.svelte.d.ts +23 -0
  10. package/dist/components/color-picker/base/color.d.ts +47 -0
  11. package/dist/components/color-picker/base/color.js +96 -0
  12. package/dist/components/color-picker/base/constants.d.ts +5 -0
  13. package/dist/components/color-picker/base/constants.js +5 -0
  14. package/dist/components/color-picker/base/index.d.ts +3 -0
  15. package/dist/components/color-picker/base/index.js +3 -0
  16. package/dist/components/color-picker/base/render.d.ts +5 -0
  17. package/dist/components/color-picker/base/render.js +77 -0
  18. package/dist/components/color-picker/base/shaders.d.ts +2 -0
  19. package/dist/components/color-picker/base/shaders.js +8 -0
  20. package/dist/components/color-picker/index.d.ts +2 -0
  21. package/dist/components/color-picker/index.js +2 -0
  22. package/dist/components/color-picker/popover/PopoverColorPicker.svelte +75 -0
  23. package/dist/components/color-picker/popover/PopoverColorPicker.svelte.d.ts +26 -0
  24. package/dist/components/color-select/ColorSelect.svelte +82 -0
  25. package/dist/components/color-select/ColorSelect.svelte.d.ts +17 -0
  26. package/dist/components/color-select/index.d.ts +1 -0
  27. package/dist/components/color-select/index.js +1 -0
  28. package/dist/components/index.d.ts +5 -0
  29. package/dist/components/index.js +5 -0
  30. package/dist/components/select-theme/SelectTheme.svelte +124 -0
  31. package/dist/components/select-theme/SelectTheme.svelte.d.ts +10 -0
  32. package/dist/components/select-theme/SelectThemePopover.svelte +49 -0
  33. package/dist/components/select-theme/SelectThemePopover.svelte.d.ts +10 -0
  34. package/dist/components/select-theme/index.d.ts +2 -0
  35. package/dist/components/select-theme/index.js +2 -0
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.js +2 -0
  38. package/dist/types.d.ts +1 -0
  39. package/package.json +76 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License Copyright (c) 2025 flo-bit
2
+
3
+ Permission is hereby granted, free of
4
+ charge, to any person obtaining a copy of this software and associated
5
+ documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice
12
+ (including the next paragraph) shall be included in all copies or substantial
13
+ portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
16
+ ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
18
+ EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # 🦊 fox ui
2
+
3
+ svelte 5 + tailwind 4 ui kit, colors components
4
+
5
+ - [Color Gradient Picker](https://flo-bit.dev/ui-kit/components/colors/color-gradient-picker)
6
+ - [Color Picker](https://flo-bit.dev/ui-kit/components/colors/color-picker)
7
+ - [Color Select](https://flo-bit.dev/ui-kit/components/colors/color-select)
8
+
9
+ > **This is a public alpha release. Expect bugs and breaking changes.**
10
+
11
+ [See all components here](https://flo-bit.dev/ui-kit)
12
+
13
+ For a guide on how to use this ui kit, see the [Quickstart Guide](https://flo-bit.dev/ui-kit/docs/quick-start).
14
+
15
+ Read more about [the philosophy/aim of this project here](https://flo-bit.dev/ui-kit/docs/philosophy).
16
+
17
+ For more information about development, contributing and the like, see the main [README](https://github.com/flo-bit/ui-kit/blob/main/README.md).
@@ -0,0 +1,229 @@
1
+ <script lang="ts">
2
+ import { cn, Button, Popover } from '@foxui/core';
3
+
4
+ import { ColorPicker, type RGB } from '../color-picker/base';
5
+ import { DragGesture } from '@use-gesture/vanilla';
6
+
7
+ type ColorStop = { rgb: RGB; position: number };
8
+
9
+ let initialColors: ColorStop[] = $state([
10
+ { rgb: { r: 0, g: 0, b: 1 }, position: 0 },
11
+ { rgb: { r: 1, g: 0, b: 0 }, position: 1 }
12
+ ]);
13
+
14
+ let {
15
+ colors = $bindable(initialColors),
16
+ class: className,
17
+ defaultNewColor = { r: 0, g: 0, b: 0 },
18
+ size = 'default',
19
+ onchange
20
+ }: {
21
+ rgb?: RGB;
22
+
23
+ colors?: ColorStop[];
24
+
25
+ class?: string;
26
+
27
+ defaultNewColor?: RGB;
28
+
29
+ size?: 'default' | 'sm';
30
+
31
+ onchange?: (colors: ColorStop[]) => void;
32
+ } = $props();
33
+
34
+ const gradientString = $derived(
35
+ `linear-gradient(to right, ${colors
36
+ .toSorted((a, b) => a.position - b.position)
37
+ .map(
38
+ ({ rgb, position }) =>
39
+ `rgb(${rgb.r * 255}, ${rgb.g * 255}, ${rgb.b * 255}) ${position * 100}%`
40
+ )
41
+ .join(', ')})`
42
+ );
43
+
44
+ let colorsRef = $state<(HTMLButtonElement | null)[]>(new Array(colors.length).fill(null));
45
+ let gradientRef = $state<HTMLDivElement | undefined>(undefined);
46
+ let isDragging = $state(new Array(colors.length).fill(false));
47
+
48
+ $effect(() => {
49
+ let gestures: DragGesture[] = [];
50
+
51
+ colorsRef.forEach((ref, i) => {
52
+ if (!ref) return;
53
+
54
+ gestures.push(
55
+ new DragGesture(
56
+ ref,
57
+ (state) => {
58
+ const { delta } = state;
59
+
60
+ const newPosition = delta[0] / (gradientRef?.clientWidth ?? 1);
61
+
62
+ colors[i].position += newPosition;
63
+ colors[i].position = Math.max(0, Math.min(1, colors[i].position));
64
+
65
+ if (Math.abs(state.offset[0]) > 10) {
66
+ isDragging[i] = state.active;
67
+ } else {
68
+ isDragging[i] = state.active;
69
+ }
70
+
71
+ if (state.tap) {
72
+ allOpen[i] = !allOpen[i];
73
+ } else {
74
+ onchange?.(colors);
75
+ }
76
+ },
77
+ {
78
+ preventDefault: true,
79
+ eventOptions: {
80
+ passive: false
81
+ }
82
+ }
83
+ )
84
+ );
85
+ });
86
+
87
+ return () => {
88
+ gestures.forEach((gesture) => gesture.destroy());
89
+ };
90
+ });
91
+
92
+ let allOpen = $state(new Array(colors.length).fill(false));
93
+
94
+ function getOpen(i: number) {
95
+ if (isDragging[i]) {
96
+ return false;
97
+ }
98
+ return allOpen[i];
99
+ }
100
+ </script>
101
+
102
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
103
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
104
+ <div
105
+ class={cn(
106
+ 'border-base-400 dark:border-base-600 relative w-full cursor-pointer touch-none rounded-2xl border',
107
+ size === 'sm' ? 'h-4' : 'h-8',
108
+ className
109
+ )}
110
+ style={'background: ' + gradientString}
111
+ bind:this={gradientRef}
112
+ onclick={(e) => {
113
+ // get target
114
+ const target = e.target as HTMLDivElement;
115
+ // if target is not the gradient itself return
116
+ if (target !== gradientRef) {
117
+ return;
118
+ }
119
+ let rect = gradientRef?.getBoundingClientRect();
120
+ // get the position of the click
121
+ const position = (e.clientX - rect.left) / (gradientRef?.clientWidth ?? 1);
122
+ // add a new color
123
+ colors.push({
124
+ rgb: { r: defaultNewColor.r, g: defaultNewColor.g, b: defaultNewColor.b },
125
+ position: position
126
+ });
127
+ colorsRef.push(null);
128
+ allOpen.push(true);
129
+ onchange?.(colors);
130
+ }}
131
+ onkeydown={(e) => {
132
+ if (e.key === '+') {
133
+ colors.push({
134
+ rgb: { r: defaultNewColor.r, g: defaultNewColor.g, b: defaultNewColor.b },
135
+ position: 0.5
136
+ });
137
+ colorsRef.push(null);
138
+ allOpen.push(true);
139
+ }
140
+ }}
141
+ >
142
+ {#each colors as color, i}
143
+ <Popover
144
+ bind:open={() => getOpen(i), (open) => {}}
145
+ side="bottom"
146
+ sideOffset={10}
147
+ onInteractOutside={() => {
148
+ allOpen[i] = false;
149
+ }}
150
+ onEscapeKeydown={() => {
151
+ allOpen[i] = false;
152
+ }}
153
+ onkeydown={(e) => {
154
+ if (e.key === 'Backspace' && colors.length > 2) {
155
+ colors.splice(i, 1);
156
+ onchange?.(colors);
157
+ }
158
+ }}
159
+ class="p-1 pl-2 pr-0"
160
+ >
161
+ {#snippet child({ props })}
162
+ <button
163
+ {...props}
164
+ class={cn(
165
+ 'absolute left-0 touch-none',
166
+ 'focus-visible:outline-base-900 dark:focus-visible:outline-base-100 cursor-pointer rounded-full focus-visible:outline-2 focus-visible:outline-offset-2',
167
+ size === 'sm' ? '-top-1.5' : '-top-4'
168
+ )}
169
+ style={`left: calc(${color.position * 100}% - 16px)`}
170
+ bind:this={colorsRef[i]}
171
+ tabindex={Math.floor(color.position * 100 + 10)}
172
+ onkeydown={(e) => {
173
+ if (e.key === 'Enter') {
174
+ allOpen[i] = !allOpen[i];
175
+ } else if (e.key === 'Backspace' && colors.length > 2) {
176
+ colors.splice(i, 1);
177
+ onchange?.(colors);
178
+ }
179
+ }}
180
+ >
181
+ <div
182
+ class={cn(
183
+ 'ring-base-400 dark:ring-base-500 focus-visible:outline-accent-500 shadow-base-900/50 z-10 rounded-full shadow-lg ring',
184
+ size === 'sm' ? 'size-6' : 'size-8'
185
+ )}
186
+ style={`background-color: rgb(${color.rgb.r * 255}, ${color.rgb.g * 255}, ${color.rgb.b * 255});`}
187
+ ></div>
188
+ <span class="sr-only">Color Picker</span>
189
+ </button>
190
+ {/snippet}
191
+ <div>
192
+ <ColorPicker
193
+ bind:rgb={colors[i].rgb}
194
+ onchange={() => {
195
+ onchange?.(colors);
196
+ }}
197
+ />
198
+
199
+ <div class="flex w-full justify-center">
200
+ {#if colors.length > 2}
201
+ <Button
202
+ variant="link"
203
+ class="mb-1 backdrop-blur-none"
204
+ onclick={() => {
205
+ colors.splice(i, 1);
206
+ onchange?.(colors);
207
+ }}
208
+ size="icon"
209
+ >
210
+ <svg
211
+ xmlns="http://www.w3.org/2000/svg"
212
+ viewBox="0 0 24 24"
213
+ fill="currentColor"
214
+ class="size-6"
215
+ >
216
+ <path
217
+ fill-rule="evenodd"
218
+ d="M16.5 4.478v.227a48.816 48.816 0 0 1 3.878.512.75.75 0 1 1-.256 1.478l-.209-.035-1.005 13.07a3 3 0 0 1-2.991 2.77H8.084a3 3 0 0 1-2.991-2.77L4.087 6.66l-.209.035a.75.75 0 0 1-.256-1.478A48.567 48.567 0 0 1 7.5 4.705v-.227c0-1.564 1.213-2.9 2.816-2.951a52.662 52.662 0 0 1 3.369 0c1.603.051 2.815 1.387 2.815 2.951Zm-6.136-1.452a51.196 51.196 0 0 1 3.273 0C14.39 3.05 15 3.684 15 4.478v.113a49.488 49.488 0 0 0-6 0v-.113c0-.794.609-1.428 1.364-1.452Zm-.355 5.945a.75.75 0 1 0-1.5.058l.347 9a.75.75 0 1 0 1.499-.058l-.346-9Zm5.48.058a.75.75 0 1 0-1.498-.058l-.347 9a.75.75 0 0 0 1.5.058l.345-9Z"
219
+ clip-rule="evenodd"
220
+ />
221
+ </svg>
222
+ Delete
223
+ </Button>
224
+ {/if}
225
+ </div>
226
+ </div>
227
+ </Popover>
228
+ {/each}
229
+ </div>
@@ -0,0 +1,16 @@
1
+ import { type RGB } from '../color-picker/base';
2
+ type ColorStop = {
3
+ rgb: RGB;
4
+ position: number;
5
+ };
6
+ type $$ComponentProps = {
7
+ rgb?: RGB;
8
+ colors?: ColorStop[];
9
+ class?: string;
10
+ defaultNewColor?: RGB;
11
+ size?: 'default' | 'sm';
12
+ onchange?: (colors: ColorStop[]) => void;
13
+ };
14
+ declare const ColorGradientPicker: import("svelte").Component<$$ComponentProps, {}, "colors">;
15
+ type ColorGradientPicker = ReturnType<typeof ColorGradientPicker>;
16
+ export default ColorGradientPicker;
@@ -0,0 +1 @@
1
+ export { default as ColorGradientPicker } from './ColorGradientPicker.svelte';
@@ -0,0 +1 @@
1
+ export { default as ColorGradientPicker } from './ColorGradientPicker.svelte';
@@ -0,0 +1,42 @@
1
+ from https://github.com/CaptainCodeman/svelte-color-select
2
+
3
+ MIT License
4
+
5
+ Copyright (c) 2022 Simon Green
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
24
+
25
+ Based on https://bottosson.github.io/posts/colorpicker/
26
+ Copyright (c) 2021 Björn Ottosson
27
+
28
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
29
+ this software and associated documentation files (the "Software"), to deal in
30
+ the Software without restriction, including without limitation the rights to
31
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
32
+ of the Software, and to permit persons to whom the Software is furnished to do
33
+ so, subject to the following conditions:
34
+ The above copyright notice and this permission notice shall be included in all
35
+ copies or substantial portions of the Software.
36
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
39
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
40
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
42
+ SOFTWARE.
@@ -0,0 +1,312 @@
1
+ <script lang="ts">
2
+ import { eps, picker_size, slider_width, border_size, gap_size } from './constants';
3
+ import { render_main_image, render_slider_image } from './render';
4
+ import type { RGB, OKlab, OKhsv, OKlch } from './color';
5
+ import {
6
+ okhsv_to_oklab,
7
+ okhsv_to_rgb,
8
+ okhsv_to_oklch,
9
+ rgb_to_hex,
10
+ oklab_to_okhsv,
11
+ rgb_to_okhsv,
12
+ oklab_to_rgb
13
+ } from './color';
14
+
15
+ import { cn } from '@foxui/core';
16
+
17
+ let {
18
+ rgb = $bindable(),
19
+ oklab = $bindable(),
20
+ okhsv = $bindable(),
21
+ class: className,
22
+ onchange,
23
+ quickSelects = $bindable([])
24
+ }: {
25
+ rgb?: RGB;
26
+ oklab?: OKlab;
27
+ okhsv?: OKhsv;
28
+ class?: string;
29
+ onchange?: (color: { hex: string; rgb: RGB; oklab: OKlab; okhsv: OKhsv; oklch: OKlch }) => void;
30
+ quickSelects?: {
31
+ label: string;
32
+ rgb?: RGB;
33
+ oklab?: OKlab;
34
+ okhsv?: OKhsv;
35
+ }[];
36
+ } = $props();
37
+
38
+ const width = picker_size + slider_width + gap_size + border_size * 2;
39
+ const height = picker_size + border_size * 2;
40
+
41
+ let color = $derived(convertToInternal(rgb, oklab, okhsv));
42
+ let uihsv = $derived(scale_to_ui(color));
43
+
44
+ function scale_to_ui(okhsv: OKhsv): OKhsv {
45
+ return {
46
+ h: clamp_ui(okhsv.h / 360),
47
+ s: clamp_ui(okhsv.s),
48
+ v: clamp_ui(1 - okhsv.v)
49
+ };
50
+ }
51
+
52
+ function clamp(x: number) {
53
+ return x < eps ? eps : x > 1 - eps ? 1 - eps : x;
54
+ }
55
+
56
+ function clamp_ui(value: number) {
57
+ return value < 0 ? 0 : value > 1 ? picker_size : value * picker_size;
58
+ }
59
+
60
+ function convertToInternal(
61
+ rgb: RGB | undefined,
62
+ oklab: OKlab | undefined,
63
+ okhsv: OKhsv | undefined
64
+ ) {
65
+ if (okhsv) {
66
+ return okhsv;
67
+ }
68
+
69
+ if (oklab) {
70
+ return oklab_to_okhsv(oklab);
71
+ }
72
+
73
+ if (rgb) {
74
+ return rgb_to_okhsv(rgb);
75
+ }
76
+
77
+ throw 'rgb, oklab, or okhsv required';
78
+ }
79
+
80
+ async function update_input() {
81
+ let new_rgb = okhsv_to_rgb(color);
82
+ let new_oklab = okhsv_to_oklab(color);
83
+ let new_oklch = okhsv_to_oklch(color);
84
+ let new_hex = rgb_to_hex(new_rgb);
85
+ let new_okhsv = color;
86
+
87
+ if (okhsv) {
88
+ okhsv = color;
89
+ }
90
+
91
+ if (oklab) {
92
+ oklab = new_oklab;
93
+ }
94
+
95
+ if (rgb) {
96
+ rgb = new_rgb;
97
+ }
98
+
99
+ onchange?.({
100
+ rgb: new_rgb,
101
+ oklab: new_oklab,
102
+ okhsv: new_okhsv,
103
+ oklch: new_oklch,
104
+ hex: new_hex
105
+ });
106
+ }
107
+
108
+ function update_sv(x: number, y: number) {
109
+ let new_s = clamp(x / picker_size);
110
+ let new_v = clamp(1 - y / picker_size);
111
+
112
+ color.s = new_s;
113
+ color.v = new_v;
114
+
115
+ update_input();
116
+ }
117
+
118
+ function update_h(x: number, y: number) {
119
+ let h = clamp(y / picker_size);
120
+ color.h = h * 360;
121
+ color.s = Math.max(color.s, 0.00001);
122
+ color.v = Math.max(color.v, 0.00001);
123
+
124
+ update_input();
125
+ }
126
+
127
+ function pointer(node: HTMLCanvasElement, fn: (x: number, y: number) => void) {
128
+ let active = false;
129
+
130
+ function update(event: PointerEvent) {
131
+ const x = event.offsetX;
132
+ const y = event.offsetY;
133
+ fn(x, y);
134
+ }
135
+
136
+ function onpointerdown(event: PointerEvent) {
137
+ event.stopPropagation();
138
+ node.setPointerCapture(event.pointerId);
139
+ update(event);
140
+ active = true;
141
+ }
142
+
143
+ function onpointermove(event: PointerEvent) {
144
+ event.stopPropagation();
145
+ if (active) {
146
+ update(event);
147
+ }
148
+ }
149
+
150
+ function onpointerend(event: PointerEvent) {
151
+ event.stopPropagation();
152
+ node.releasePointerCapture(event.pointerId);
153
+ active = false;
154
+ }
155
+
156
+ node.addEventListener('pointerdown', onpointerdown, { passive: true });
157
+ node.addEventListener('pointermove', onpointermove, { passive: true });
158
+ node.addEventListener('pointerup', onpointerend, { passive: true });
159
+ node.addEventListener('pointercancel', onpointerend, { passive: true });
160
+
161
+ return {
162
+ destroy() {
163
+ node.removeEventListener('pointerdown', onpointerdown);
164
+ node.removeEventListener('pointermove', onpointermove);
165
+ node.removeEventListener('pointerup', onpointerend);
166
+ node.removeEventListener('pointercancel', onpointerend);
167
+ }
168
+ };
169
+ }
170
+
171
+ function onKeydown(event: KeyboardEvent) {
172
+ const keyHandled = () => {
173
+ event.preventDefault();
174
+ event.stopPropagation();
175
+ };
176
+
177
+ const step = event.shiftKey ? 10 : 1;
178
+
179
+ switch (event.key) {
180
+ case 'ArrowUp':
181
+ if (event.altKey) {
182
+ update_h(0, Math.round(uihsv.h! - step));
183
+ } else {
184
+ update_sv(uihsv.s, Math.round(uihsv.v - step));
185
+ }
186
+ keyHandled();
187
+ break;
188
+
189
+ case 'ArrowDown':
190
+ if (event.altKey) {
191
+ update_h(0, Math.round(uihsv.h! + step));
192
+ } else {
193
+ update_sv(uihsv.s, Math.round(uihsv.v + step));
194
+ }
195
+ keyHandled();
196
+ break;
197
+
198
+ case 'ArrowLeft':
199
+ update_sv(Math.round(uihsv.s - step), uihsv.v);
200
+ keyHandled();
201
+ break;
202
+
203
+ case 'ArrowRight':
204
+ update_sv(Math.round(uihsv.s + step), uihsv.v);
205
+ keyHandled();
206
+ break;
207
+ }
208
+ }
209
+
210
+ function convertToRgb(
211
+ rgb: RGB | undefined,
212
+ oklab: OKlab | undefined,
213
+ okhsv: OKhsv | undefined
214
+ ): RGB {
215
+ if (okhsv) {
216
+ return okhsv_to_rgb(okhsv);
217
+ }
218
+
219
+ if (oklab) {
220
+ return oklab_to_rgb(oklab);
221
+ }
222
+
223
+ if (rgb) {
224
+ return rgb;
225
+ }
226
+
227
+ throw 'rgb, oklab, or okhsv required';
228
+ }
229
+
230
+ function getRgbString(rgb: RGB) {
231
+ return `rgb(${rgb.r * 255}, ${rgb.g * 255}, ${rgb.b * 255})`;
232
+ }
233
+ </script>
234
+
235
+ <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
236
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
237
+ <div
238
+ class={cn(
239
+ 'focus-visible:outline-base-900 dark:focus-visible:outline-base-100 relative rounded-xl border-none focus-visible:outline-2 focus-visible:outline-offset-2',
240
+ className
241
+ )}
242
+ tabindex="0"
243
+ style:width="{width}px"
244
+ style:height="{height}px"
245
+ onkeydown={onKeydown}
246
+ >
247
+ <canvas
248
+ id="okhsv_sv_canvas"
249
+ width={picker_size}
250
+ height={picker_size}
251
+ style:top="{border_size}px"
252
+ style:left="{border_size}px"
253
+ class="absolute touch-none rounded-xl"
254
+ use:pointer={update_sv}
255
+ use:render_main_image={color.h}
256
+ ></canvas>
257
+ <canvas
258
+ width={slider_width}
259
+ height={picker_size}
260
+ style:top="{border_size}px"
261
+ style:left="{picker_size + gap_size}px"
262
+ class="absolute touch-none rounded-xl"
263
+ use:pointer={update_h}
264
+ use:render_slider_image
265
+ ></canvas>
266
+
267
+ <svg {width} {height} class="pointer-events-none absolute touch-none">
268
+ <g transform="translate({border_size},{border_size})">
269
+ <g transform="translate({uihsv.s},{uihsv.v})">
270
+ <circle cx="0" cy="0" r="5" fill="none" stroke-width="1.75" class="stroke-base-50" />
271
+ <circle cx="0" cy="0" r="6" fill="none" stroke-width="1.25" class="stroke-base-950" />
272
+ </g>
273
+ </g>
274
+ <g transform="translate({picker_size + gap_size},{border_size})">
275
+ <g transform="translate(0,{uihsv.h})">
276
+ <polygon points="-7,-4 -1,0 -7,4" stroke-width="0.8" class="stroke-base-950 fill-base-50" />
277
+ <polygon
278
+ points="{slider_width + 7},-4 {slider_width + 1},0 {slider_width + 7},4"
279
+ stroke-width="0.8"
280
+ class="stroke-base-950 fill-base-50"
281
+ />
282
+ </g>
283
+ </g>
284
+ </svg>
285
+ </div>
286
+
287
+ {#if quickSelects.length > 0}
288
+ <div class="grid grid-cols-7 gap-2 px-2 pt-2 pb-3"
289
+ style:width="{width}px">
290
+ {#each quickSelects as quickSelect}
291
+ <button
292
+ class={cn(
293
+ 'focus-visible:outline-base-900 dark:focus-visible:outline-base-100 cursor-pointer rounded-full focus-visible:outline-2 focus-visible:outline-offset-2',
294
+ 'group'
295
+ )}
296
+ onclick={() => {
297
+ color = convertToInternal(quickSelect.rgb, quickSelect.oklab, quickSelect.okhsv);
298
+
299
+ update_input();
300
+ }}
301
+ >
302
+ <div
303
+ class="border-base-300 dark:border-base-700 focus-visible:outline-accent-500 z-10 size-7 rounded-full border group-hover:scale-105 group-active:scale-95 transition-all duration-100"
304
+ style="background-color: {getRgbString(
305
+ convertToRgb(quickSelect.rgb, quickSelect.oklab, quickSelect.okhsv)
306
+ )};"
307
+ ></div>
308
+ <span class="sr-only">Select {quickSelect.label}</span>
309
+ </button>
310
+ {/each}
311
+ </div>
312
+ {/if}
@@ -0,0 +1,23 @@
1
+ import type { RGB, OKlab, OKhsv, OKlch } from './color';
2
+ type $$ComponentProps = {
3
+ rgb?: RGB;
4
+ oklab?: OKlab;
5
+ okhsv?: OKhsv;
6
+ class?: string;
7
+ onchange?: (color: {
8
+ hex: string;
9
+ rgb: RGB;
10
+ oklab: OKlab;
11
+ okhsv: OKhsv;
12
+ oklch: OKlch;
13
+ }) => void;
14
+ quickSelects?: {
15
+ label: string;
16
+ rgb?: RGB;
17
+ oklab?: OKlab;
18
+ okhsv?: OKhsv;
19
+ }[];
20
+ };
21
+ declare const ColorPicker: import("svelte").Component<$$ComponentProps, {}, "rgb" | "oklab" | "okhsv" | "quickSelects">;
22
+ type ColorPicker = ReturnType<typeof ColorPicker>;
23
+ export default ColorPicker;