svelte-colourpicker 0.1.0 → 0.2.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.
@@ -1,346 +1,428 @@
1
- <svelte:options immutable />
2
-
3
- <script context="module">const hueColours = ["#ff0000", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff", "#ff0000"];
4
- const size = 230;
5
- const numberRegExp = "(\\d*\\.?\\d{1,3})";
6
- const delimiterRegExp = "[, ]+";
7
- const rgbRegExp = new RegExp(`rgb\\(\\s*${numberRegExp}${delimiterRegExp}${numberRegExp}${delimiterRegExp}${numberRegExp}\\s*\\)`, "i");
8
- const rgbaRegExp = new RegExp(`rgba\\(\\s*${numberRegExp}${delimiterRegExp}${numberRegExp}${delimiterRegExp}${numberRegExp}${delimiterRegExp}${numberRegExp}\\s*\\)`, "i");
9
- const hexRegExp = (n) => `([\\dA-F]{${n}})`;
10
- const hex3RegExp = new RegExp(`^#?${hexRegExp(1).repeat(3)}$`, "i");
11
- const hex6RegExp = new RegExp(`^#?${hexRegExp(2).repeat(3)}$`, "i");
12
- const hex8RegExp = new RegExp(`^#?${hexRegExp(2).repeat(4)}$`, "i");
13
- function colour(input) {
14
- let r, g, b, h, s, v, a, rgba, hex;
15
- if (typeof input == "string") {
16
- let result;
17
- if (result = rgbRegExp.exec(input)) {
18
- r = parseFloat(result[1]);
19
- g = parseFloat(result[2]);
20
- b = parseFloat(result[3]);
21
- a = 1;
22
- } else if (result = rgbaRegExp.exec(input)) {
23
- r = parseFloat(result[1]);
24
- g = parseFloat(result[2]);
25
- b = parseFloat(result[3]);
26
- a = parseFloat(result[4]);
27
- } else if (result = hex3RegExp.exec(input)) {
28
- r = parseInt(result[1].repeat(2), 16);
29
- g = parseInt(result[2].repeat(2), 16);
30
- b = parseInt(result[3].repeat(2), 16);
31
- a = 1;
32
- hex = `#${input}`;
33
- } else if (result = hex6RegExp.exec(input)) {
34
- r = parseInt(result[1], 16);
35
- g = parseInt(result[2], 16);
36
- b = parseInt(result[3], 16);
37
- a = 1;
38
- hex = `#${input}`;
39
- } else if (result = hex8RegExp.exec(input)) {
40
- r = parseInt(result[1], 16);
41
- g = parseInt(result[2], 16);
42
- b = parseInt(result[3], 16);
43
- a = parseInt(result[4], 16);
44
- hex = `#${input}`;
45
- } else {
46
- throw new Error(`Invalid input: "${input}"`);
47
- }
48
- const hsv = RGBToHSV(r, g, b);
49
- h = hsv.h;
50
- s = hsv.s;
51
- v = hsv.v;
52
- } else if ("r" in input) {
53
- r = input.r;
54
- g = input.g;
55
- b = input.b;
56
- const hsv = RGBToHSV(r, g, b);
57
- h = hsv.h;
58
- s = hsv.s;
59
- v = hsv.v;
60
- a = "a" in input ? input.a : 1;
61
- } else {
62
- h = input.h;
63
- s = input.s;
64
- v = input.v;
65
- const rgb = HSVToRGB(h, s, v);
66
- r = rgb.r;
67
- g = rgb.g;
68
- b = rgb.b;
69
- a = "a" in input ? input.a : 1;
70
- }
71
- rgba = `rgba(${r}, ${g}, ${b}, ${a})`;
72
- hex ??= `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}${a != 1 ? Math.round(a * 255).toString(16) : ""}`;
73
- return {
74
- r,
75
- g,
76
- b,
77
- h,
78
- s,
79
- v,
80
- a,
81
- rgba,
82
- hex
83
- };
84
- }
85
- function RGBToHSV(r, g, b) {
86
- let h, s, v;
87
- const r1 = r / 255;
88
- const g1 = g / 255;
89
- const b1 = b / 255;
90
- const max = Math.max(r1, g1, b1);
91
- const min = Math.min(r1, g1, b1);
92
- const difference = max - min;
93
- if (difference == 0) {
94
- h = 0;
95
- } else {
96
- switch (max) {
97
- case r1:
98
- h = (60 * ((g1 - b1) / difference) + 360) % 360;
99
- break;
100
- case g1:
101
- h = (60 * ((b1 - r1) / difference) + 120) % 360;
102
- break;
103
- case b1:
104
- h = (60 * ((r1 - g1) / difference) + 240) % 360;
105
- break;
106
- default:
107
- throw new Error("h did not match r1 | g1 | b1");
108
- }
109
- }
110
- s = max == 0 ? 0 : difference / max;
111
- v = max;
112
- return { h, s, v };
113
- }
114
- function HSVToRGB(h, s, v) {
115
- let r, g, b;
116
- const chroma = s * v;
117
- const x = chroma * (1 - Math.abs(h / 60 % 2 - 1));
118
- const m = v - chroma;
119
- let r1, g1, b1;
120
- if (h < 60) {
121
- r1 = chroma;
122
- g1 = x;
123
- b1 = 0;
124
- } else if (60 <= h && h < 120) {
125
- r1 = x;
126
- g1 = chroma;
127
- b1 = 0;
128
- } else if (h < 180) {
129
- r1 = 0;
130
- g1 = chroma;
131
- b1 = x;
132
- } else if (h < 240) {
133
- r1 = 0;
134
- g1 = x;
135
- b1 = chroma;
136
- } else if (h < 300) {
137
- r1 = x;
138
- g1 = 0;
139
- b1 = chroma;
140
- } else {
141
- r1 = chroma;
142
- g1 = 0;
143
- b1 = x;
144
- }
145
- r = Math.round((r1 + m) * 255);
146
- g = Math.round((g1 + m) * 255);
147
- b = Math.round((b1 + m) * 255);
148
- return { r, g, b };
149
- }
150
- function clamp(value, min, max) {
151
- if (value < min) {
152
- return min;
153
- }
154
- if (value > max) {
155
- return max;
156
- }
157
- return value;
158
- }
1
+ <script module lang="ts">
2
+ const hueColours = ["#ff0000", "#ffff00", "#00ff00", "#00ffff", "#0000ff", "#ff00ff", "#ff0000"]
3
+
4
+ const size = 230
5
+
6
+ type RGB = { r: number; g: number; b: number }
7
+ type RGBA = RGB & A
8
+
9
+ type HSV = { h: number; s: number; v: number }
10
+ type HSVA = HSV & A
11
+
12
+ type A = { a: number }
13
+
14
+ const numberRegExp = "(\\d*\\.?\\d{1,3})"
15
+ const delimiterRegExp = "[, ]+"
16
+
17
+ const rgbRegExp = new RegExp(`rgb\\(\\s*${numberRegExp}${delimiterRegExp}${numberRegExp}${delimiterRegExp}${numberRegExp}\\s*\\)`, "i")
18
+ const rgbaRegExp = new RegExp(`rgba\\(\\s*${numberRegExp}${delimiterRegExp}${numberRegExp}${delimiterRegExp}${numberRegExp}${delimiterRegExp}${numberRegExp}\\s*\\)`, "i")
19
+
20
+ const hexRegExp = (n: number) => `([\\dA-F]{${n}})`
21
+
22
+ const hex3RegExp = new RegExp(`^#?${hexRegExp(1).repeat(3)}$`, "i")
23
+ const hex6RegExp = new RegExp(`^#?${hexRegExp(2).repeat(3)}$`, "i")
24
+ const hex8RegExp = new RegExp(`^#?${hexRegExp(2).repeat(4)}$`, "i")
25
+
26
+ function colour(input: string | RGB | RGBA | HSV | HSVA) {
27
+ let r: number, g: number, b: number, h: number, s: number, v: number, a: number, rgba: string, hex: `#${string}`
28
+
29
+ if (typeof input == "string") {
30
+ let result: RegExpMatchArray | null
31
+
32
+ if ((result = rgbRegExp.exec(input))) {
33
+ r = parseFloat(result[1])
34
+ g = parseFloat(result[2])
35
+ b = parseFloat(result[3])
36
+ a = 1
37
+ } else if ((result = rgbaRegExp.exec(input))) {
38
+ r = parseFloat(result[1])
39
+ g = parseFloat(result[2])
40
+ b = parseFloat(result[3])
41
+ a = parseFloat(result[4])
42
+ } else if ((result = hex3RegExp.exec(input))) {
43
+ r = parseInt(result[1].repeat(2), 16)
44
+ g = parseInt(result[2].repeat(2), 16)
45
+ b = parseInt(result[3].repeat(2), 16)
46
+ a = 1
47
+ hex = `#${input}`
48
+ } else if ((result = hex6RegExp.exec(input))) {
49
+ r = parseInt(result[1], 16)
50
+ g = parseInt(result[2], 16)
51
+ b = parseInt(result[3], 16)
52
+ a = 1
53
+ hex = `#${input}`
54
+ } else if ((result = hex8RegExp.exec(input))) {
55
+ r = parseInt(result[1], 16)
56
+ g = parseInt(result[2], 16)
57
+ b = parseInt(result[3], 16)
58
+ a = parseInt(result[4], 16)
59
+ hex = `#${input}`
60
+ } else {
61
+ throw new Error(`Invalid input: "${input}"`)
62
+ }
63
+
64
+ const hsv = RGBToHSV(r, g, b)
65
+
66
+ h = hsv.h
67
+ s = hsv.s
68
+ v = hsv.v
69
+ } else if ("r" in input) {
70
+ r = input.r
71
+ g = input.g
72
+ b = input.b
73
+
74
+ const hsv = RGBToHSV(r, g, b)
75
+
76
+ h = hsv.h
77
+ s = hsv.s
78
+ v = hsv.v
79
+
80
+ a = "a" in input ? input.a : 1
81
+ } else {
82
+ h = input.h
83
+ s = input.s
84
+ v = input.v
85
+
86
+ const rgb = HSVToRGB(h, s, v)
87
+
88
+ r = rgb.r
89
+ g = rgb.g
90
+ b = rgb.b
91
+
92
+ a = "a" in input ? input.a : 1
93
+ }
94
+
95
+ rgba = `rgba(${r}, ${g}, ${b}, ${a})`
96
+ hex ??= `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}${a != 1 ? Math.round(a * 255).toString(16) : ""}`
97
+
98
+ return {
99
+ r,
100
+ g,
101
+ b,
102
+ h,
103
+ s,
104
+ v,
105
+ a,
106
+ rgba,
107
+ hex
108
+ }
109
+ }
110
+
111
+ /**
112
+ * https://www.rapidtables.com/convert/color/rgb-to-hsv.html
113
+ * https://www.geeksforgeeks.org/program-change-rgb-color-model-hsv-color-model/
114
+ */
115
+ function RGBToHSV(r: number, g: number, b: number): HSV {
116
+ let h: number, s: number, v: number
117
+
118
+ const r1 = r / 255
119
+ const g1 = g / 255
120
+ const b1 = b / 255
121
+
122
+ const max = Math.max(r1, g1, b1)
123
+ const min = Math.min(r1, g1, b1)
124
+
125
+ const difference = max - min
126
+
127
+ if (difference == 0) {
128
+ h = 0
129
+ } else {
130
+ switch (max) {
131
+ case r1:
132
+ h = (60 * ((g1 - b1) / difference) + 360) % 360
133
+ break
134
+ case g1:
135
+ h = (60 * ((b1 - r1) / difference) + 120) % 360
136
+ break
137
+ case b1:
138
+ h = (60 * ((r1 - g1) / difference) + 240) % 360
139
+ break
140
+ default:
141
+ throw new Error("h did not match r1 | g1 | b1")
142
+ }
143
+ }
144
+
145
+ s = max == 0 ? 0 : difference / max
146
+ v = max
147
+
148
+ return { h, s, v }
149
+ }
150
+
151
+ /**
152
+ * https://www.rapidtables.com/convert/color/hsv-to-rgb.html
153
+ */
154
+ function HSVToRGB(h: number, s: number, v: number): RGB {
155
+ let r: number, g: number, b: number
156
+
157
+ const chroma = s * v
158
+ const x = chroma * (1 - Math.abs(((h / 60) % 2) - 1))
159
+ const m = v - chroma
160
+
161
+ let r1: number, g1: number, b1: number
162
+
163
+ if (h < 60) {
164
+ r1 = chroma
165
+ g1 = x
166
+ b1 = 0
167
+ } else if (60 <= h && h < 120) {
168
+ r1 = x
169
+ g1 = chroma
170
+ b1 = 0
171
+ } else if (h < 180) {
172
+ r1 = 0
173
+ g1 = chroma
174
+ b1 = x
175
+ } else if (h < 240) {
176
+ r1 = 0
177
+ g1 = x
178
+ b1 = chroma
179
+ } else if (h < 300) {
180
+ r1 = x
181
+ g1 = 0
182
+ b1 = chroma
183
+ } /* if (h < 360) */ else {
184
+ r1 = chroma
185
+ g1 = 0
186
+ b1 = x
187
+ }
188
+
189
+ r = Math.round((r1 + m) * 255)
190
+ g = Math.round((g1 + m) * 255)
191
+ b = Math.round((b1 + m) * 255)
192
+
193
+ return { r, g, b }
194
+ }
195
+
196
+ function clamp(value: number, min: number, max: number): number {
197
+ if (value < min) {
198
+ return min
199
+ }
200
+ if (value > max) {
201
+ return max
202
+ }
203
+ return value
204
+ }
159
205
  </script>
160
206
 
161
- <script>import { createEventDispatcher, tick } from "svelte";
162
- import { fade, fly } from "svelte/transition";
163
- export let value = "rgba(255, 0, 0, 1)";
164
- const dispatch = createEventDispatcher();
165
- let c = colour(value);
166
- $:
167
- c && onColourChanged();
168
- function onColourChanged() {
169
- value = c.rgba;
170
- dispatch("change", { rgb: { r: c.r, b: c.b, g: c.g }, hsv: { h: c.h, s: c.s, v: c.v }, hex: c.hex, a: c.a });
171
- }
172
- $:
173
- value && onValueChanged();
174
- function onValueChanged() {
175
- if (value != c.rgba) {
176
- c = colour(value);
177
- }
178
- }
179
- let open = false;
180
- let dragging = false;
181
- async function openPicker() {
182
- open = true;
183
- await tick();
184
- drawColourCanvas();
185
- drawHueSlider();
186
- drawOpacitySlider();
187
- }
188
- async function tryClosePicker() {
189
- await tick();
190
- if (!dragging) {
191
- open = false;
192
- }
193
- }
194
- let colourCanvas;
195
- let hueSlider;
196
- let opacitySlider;
197
- function drawColourCanvas() {
198
- const context = colourCanvas.getContext("2d");
199
- context.clearRect(0, 0, colourCanvas.width, colourCanvas.height);
200
- const horizontalGradient = context.createLinearGradient(0, 0, colourCanvas.width, 0);
201
- horizontalGradient.addColorStop(0, "#ffffff");
202
- horizontalGradient.addColorStop(1, colour({ h: c.h, s: 1, v: 1 }).rgba);
203
- context.fillStyle = horizontalGradient;
204
- context.fillRect(0, 0, colourCanvas.width, colourCanvas.height);
205
- const verticalGradient = context.createLinearGradient(0, 0, 0, colourCanvas.height);
206
- verticalGradient.addColorStop(0, "transparent");
207
- verticalGradient.addColorStop(1, "#000000");
208
- context.fillStyle = verticalGradient;
209
- context.fillRect(0, 0, colourCanvas.width, colourCanvas.height);
210
- }
211
- function drawHueSlider() {
212
- const context = hueSlider.getContext("2d");
213
- context.clearRect(0, 0, hueSlider.width, hueSlider.height);
214
- const gradient = context.createLinearGradient(0, 0, 0, hueSlider.height);
215
- for (let i = 0, offset = 0, step = 1 / (hueColours.length - 1); i < hueColours.length; i++, offset += step) {
216
- gradient.addColorStop(offset, hueColours[i]);
217
- }
218
- context.fillStyle = gradient;
219
- context.fillRect(0, 0, hueSlider.width, hueSlider.height);
220
- }
221
- function drawOpacitySlider() {
222
- const context = opacitySlider.getContext("2d");
223
- context.clearRect(0, 0, opacitySlider.width, opacitySlider.height);
224
- const gradient = context.createLinearGradient(0, 0, 0, opacitySlider.height);
225
- gradient.addColorStop(0, "transparent");
226
- gradient.addColorStop(1, colour({ h: c.h, s: c.s, v: c.v, a: 1 }).rgba);
227
- context.fillStyle = gradient;
228
- context.fillRect(0, 0, opacitySlider.width, opacitySlider.height);
229
- }
230
- const initListener = (fn) => {
231
- const fn_preventDefault = (e) => {
232
- e.preventDefault();
233
- fn(e);
234
- };
235
- return function(e) {
236
- if (e.button != 0) {
237
- return;
238
- }
239
- dragging = true;
240
- fn(e);
241
- addEventListener("mousemove", fn_preventDefault);
242
- addEventListener("mouseup", () => {
243
- removeEventListener("mousemove", fn_preventDefault);
244
- dragging = false;
245
- });
246
- };
247
- };
248
- function colourCanvasMove(e) {
249
- const rect = colourCanvas.getBoundingClientRect();
250
- c = colour({ h: c.h, s: clamp(e.pageX - rect.left, 0, size) / size, v: (size - clamp(e.pageY - rect.top, 0, size)) / size, a: c.a });
251
- drawOpacitySlider();
252
- }
253
- function hueSliderMove(e) {
254
- c = colour({ h: clamp(e.pageY - hueSlider.getBoundingClientRect().top, 0, size) / size * 360, s: c.s, v: c.v, a: c.a });
255
- drawColourCanvas();
256
- drawOpacitySlider();
257
- }
258
- function opacitySliderMove(e) {
259
- c = colour({ h: c.h, s: c.s, v: c.v, a: parseFloat((clamp(e.pageY - opacitySlider.getBoundingClientRect().top, 0, size) / size).toFixed(2)) });
260
- }
261
- function hexInputChange(e) {
262
- let value2 = e.currentTarget.value;
263
- if (value2.startsWith("#")) {
264
- value2 = value2.substring(1);
265
- }
266
- if ([3, 6, 8].includes(value2.length)) {
267
- c = colour(value2);
268
- }
269
- }
270
- function redInputChange(e) {
271
- const value2 = parseFloat(e.currentTarget.value);
272
- if (value2 >= 0 && value2 <= 255) {
273
- c = colour({ r: value2, g: c.g, b: c.b, a: c.a });
274
- drawColourCanvas();
275
- drawOpacitySlider();
276
- }
277
- }
278
- function greenInputChange(e) {
279
- const value2 = parseFloat(e.currentTarget.value);
280
- if (value2 >= 0 && value2 <= 255) {
281
- c = colour({ r: c.r, g: value2, b: c.b, a: c.a });
282
- drawColourCanvas();
283
- drawOpacitySlider();
284
- }
285
- }
286
- function blueInputChange(e) {
287
- const value2 = parseFloat(e.currentTarget.value);
288
- if (value2 >= 0 && value2 <= 255) {
289
- c = colour({ r: c.r, g: c.g, b: value2, a: c.a });
290
- drawColourCanvas();
291
- drawOpacitySlider();
292
- }
293
- }
294
- function alphaInputChange(e) {
295
- const value2 = parseFloat(e.currentTarget.value);
296
- if (value2 >= 0 && value2 <= 1) {
297
- c = colour({ h: c.h, s: c.s, v: c.v, a: value2 });
298
- }
299
- }
207
+ <script lang="ts">
208
+ import { tick } from "svelte"
209
+ import { fade, fly } from "svelte/transition"
210
+
211
+ interface Props {
212
+ value?: string
213
+ onchange?: (event: CustomEvent<{ rgb: RGB; hsv: HSV; hex: `#${string}` } & A>) => any
214
+ }
215
+
216
+ let { value = $bindable("rgba(255, 0, 0, 1)"), onchange }: Props = $props()
217
+
218
+ let c = $state.raw(colour(value))
219
+
220
+ $effect(() => {
221
+ value = c.rgba
222
+ onchange?.(new CustomEvent("change", { detail: { rgb: { r: c.r, b: c.b, g: c.g }, hsv: { h: c.h, s: c.s, v: c.v }, hex: c.hex, a: c.a } }))
223
+ })
224
+
225
+ $effect(() => {
226
+ if (value != c.rgba) {
227
+ c = colour(value)
228
+ }
229
+ })
230
+
231
+ let open = $state(false)
232
+ let dragging = $state(false)
233
+
234
+ async function openPicker(event: Event) {
235
+ event.stopPropagation()
236
+ open = true
237
+ await tick()
238
+ drawColourCanvas()
239
+ drawHueSlider()
240
+ drawOpacitySlider()
241
+ }
242
+
243
+ async function tryClosePicker() {
244
+ await tick()
245
+ if (!dragging) {
246
+ open = false
247
+ }
248
+ }
249
+
250
+ let colourCanvas: HTMLCanvasElement = $state()!
251
+ let hueSlider: HTMLCanvasElement = $state()!
252
+ let opacitySlider: HTMLCanvasElement = $state()!
253
+
254
+ // #region Draw Canvas
255
+
256
+ function drawColourCanvas() {
257
+ const context = colourCanvas.getContext("2d")!
258
+
259
+ context.clearRect(0, 0, colourCanvas.width, colourCanvas.height)
260
+
261
+ const horizontalGradient = context.createLinearGradient(0, 0, colourCanvas.width, 0)
262
+ horizontalGradient.addColorStop(0, "#ffffff")
263
+ horizontalGradient.addColorStop(1, colour({ h: c.h, s: 1, v: 1 }).rgba)
264
+ context.fillStyle = horizontalGradient
265
+ context.fillRect(0, 0, colourCanvas.width, colourCanvas.height)
266
+
267
+ const verticalGradient = context.createLinearGradient(0, 0, 0, colourCanvas.height)
268
+ verticalGradient.addColorStop(0, "transparent")
269
+ verticalGradient.addColorStop(1, "#000000")
270
+ context.fillStyle = verticalGradient
271
+ context.fillRect(0, 0, colourCanvas.width, colourCanvas.height)
272
+ }
273
+
274
+ function drawHueSlider() {
275
+ const context = hueSlider.getContext("2d")!
276
+ context.clearRect(0, 0, hueSlider.width, hueSlider.height)
277
+ const gradient = context.createLinearGradient(0, 0, 0, hueSlider.height)
278
+ for (let i = 0, offset = 0, step = 1 / (hueColours.length - 1); i < hueColours.length; i++, offset += step) {
279
+ gradient.addColorStop(offset, hueColours[i])
280
+ }
281
+ context.fillStyle = gradient
282
+ context.fillRect(0, 0, hueSlider.width, hueSlider.height)
283
+ }
284
+
285
+ function drawOpacitySlider() {
286
+ const context = opacitySlider.getContext("2d")!
287
+ context.clearRect(0, 0, opacitySlider.width, opacitySlider.height)
288
+
289
+ const gradient = context.createLinearGradient(0, 0, 0, opacitySlider.height)
290
+ gradient.addColorStop(0, "transparent")
291
+ gradient.addColorStop(1, colour({ h: c.h, s: c.s, v: c.v, a: 1 }).rgba)
292
+ context.fillStyle = gradient
293
+ context.fillRect(0, 0, opacitySlider.width, opacitySlider.height)
294
+ }
295
+
296
+ // #endregion
297
+
298
+ // #region Events
299
+
300
+ const initListener = (fn: (e: MouseEvent) => void): ((e: MouseEvent) => void) => {
301
+ const fn_preventDefault = (e: MouseEvent) => {
302
+ e.preventDefault()
303
+ fn(e)
304
+ }
305
+ return function (e: MouseEvent) {
306
+ if (e.button != 0) {
307
+ return
308
+ }
309
+ dragging = true
310
+ fn(e)
311
+ addEventListener("mousemove", fn_preventDefault)
312
+ addEventListener("mouseup", () => {
313
+ removeEventListener("mousemove", fn_preventDefault)
314
+ dragging = false
315
+ })
316
+ }
317
+ }
318
+
319
+ function colourCanvasMove(e: MouseEvent): void {
320
+ const rect = colourCanvas.getBoundingClientRect()
321
+ c = colour({ h: c.h, s: clamp(e.pageX - rect.left, 0, size) / size, v: (size - clamp(e.pageY - rect.top, 0, size)) / size, a: c.a })
322
+ drawOpacitySlider()
323
+ }
324
+
325
+ function hueSliderMove(e: MouseEvent): void {
326
+ c = colour({ h: (clamp(e.pageY - hueSlider.getBoundingClientRect().top, 0, size) / size) * 360, s: c.s, v: c.v, a: c.a })
327
+ drawColourCanvas()
328
+ drawOpacitySlider()
329
+ }
330
+
331
+ function opacitySliderMove(e: MouseEvent): void {
332
+ c = colour({ h: c.h, s: c.s, v: c.v, a: parseFloat((clamp(e.pageY - opacitySlider.getBoundingClientRect().top, 0, size) / size).toFixed(2)) })
333
+ }
334
+
335
+ function hexInputChange(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
336
+ let value = e.currentTarget.value
337
+ if (value.startsWith("#")) {
338
+ value = value.substring(1)
339
+ }
340
+ if ([3, 6, 8].includes(value.length)) {
341
+ c = colour(value)
342
+ }
343
+ }
344
+
345
+ function redInputChange(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
346
+ const value = parseFloat(e.currentTarget.value)
347
+ if (value >= 0 && value <= 255) {
348
+ c = colour({ r: value, g: c.g, b: c.b, a: c.a })
349
+ drawColourCanvas()
350
+ drawOpacitySlider()
351
+ }
352
+ }
353
+
354
+ function greenInputChange(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
355
+ const value = parseFloat(e.currentTarget.value)
356
+ if (value >= 0 && value <= 255) {
357
+ c = colour({ r: c.r, g: value, b: c.b, a: c.a })
358
+ drawColourCanvas()
359
+ drawOpacitySlider()
360
+ }
361
+ }
362
+
363
+ function blueInputChange(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
364
+ const value = parseFloat(e.currentTarget.value)
365
+ if (value >= 0 && value <= 255) {
366
+ c = colour({ r: c.r, g: c.g, b: value, a: c.a })
367
+ drawColourCanvas()
368
+ drawOpacitySlider()
369
+ }
370
+ }
371
+
372
+ function alphaInputChange(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
373
+ const value = parseFloat(e.currentTarget.value)
374
+ if (value >= 0 && value <= 1) {
375
+ c = colour({ h: c.h, s: c.s, v: c.v, a: value })
376
+ }
377
+ }
378
+
379
+ // #endregion
300
380
  </script>
301
381
 
302
- <svelte:window on:mouseup={open && !dragging ? tryClosePicker : null} />
382
+ <svelte:window onmouseup={open && !dragging ? tryClosePicker : null} />
303
383
 
304
384
  <div class="container">
305
385
  <div class="colour-input-container transparent">
306
- <div class="colour-input" style:--value={value} on:click|stopPropagation={openPicker} on:keypress|stopPropagation={openPicker} />
386
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
387
+ <div class="colour-input" style:--value={value} onclick={openPicker} onkeypress={openPicker}></div>
307
388
  </div>
308
389
  {#if open}
390
+ <!-- svelte-ignore a11y_no_static_element_interactions -->
309
391
  <div
310
392
  class="colour-picker-container"
311
393
  in:fly={{ y: 10, duration: 150 }}
312
394
  out:fade={{ duration: 50 }}
313
- on:click|stopPropagation
314
- on:keypress|stopPropagation
315
- on:mouseup={(e) => !dragging && e.stopPropagation()}
395
+ onclick={(event) => event.stopPropagation()}
396
+ onkeypress={(event) => event.stopPropagation()}
397
+ onmouseup={(e) => !dragging && e.stopPropagation()}
316
398
  >
317
399
  <div class="colour-picker-arrow">
318
400
  <div class="colour-picker">
319
401
  <div class="main">
320
402
  <div class="canvas-container">
321
- <canvas bind:this={colourCanvas} width={size} height={size} on:mousedown={initListener(colourCanvasMove)} />
403
+ <canvas bind:this={colourCanvas} width={size} height={size} onmousedown={initListener(colourCanvasMove)}></canvas>
322
404
  <div
323
405
  class="pointer pointer-both"
324
406
  style:--pointer-bg={colour({ h: c.h, s: c.s, v: c.v, a: 1 }).rgba}
325
407
  style:--x={`${c.s * size}px`}
326
408
  style:--y={`${size - c.v * size}px`}
327
- />
409
+ ></div>
328
410
  </div>
329
411
  <div class="canvas-container">
330
- <canvas bind:this={hueSlider} width={18} height={size} on:mousedown={initListener(hueSliderMove)} />
331
- <div class="pointer pointer-vertical" style:--pointer-bg={colour({ h: c.h, s: 1, v: 1 }).rgba} style:--y={`${(c.h / 360) * size}px`} />
412
+ <canvas bind:this={hueSlider} width={18} height={size} onmousedown={initListener(hueSliderMove)}></canvas>
413
+ <div class="pointer pointer-vertical" style:--pointer-bg={colour({ h: c.h, s: 1, v: 1 }).rgba} style:--y={`${(c.h / 360) * size}px`}></div>
332
414
  </div>
333
415
  <div class="canvas-container transparent">
334
- <canvas bind:this={opacitySlider} width={18} height={size} on:mousedown={initListener(opacitySliderMove)} />
335
- <div class="pointer pointer-vertical" style:--pointer-bg={c.rgba} style:--y={`${c.a * size}px`} />
416
+ <canvas bind:this={opacitySlider} width={18} height={size} onmousedown={initListener(opacitySliderMove)}></canvas>
417
+ <div class="pointer pointer-vertical" style:--pointer-bg={c.rgba} style:--y={`${c.a * size}px`}></div>
336
418
  </div>
337
419
  </div>
338
420
  <div class="inputs-container">
339
- <input type="text" maxlength={7} id="hex" value={c.hex} on:input={hexInputChange} />
340
- <input type="number" autocomplete="off" min={0} max={255} id="r" value={c.r} on:input={redInputChange} />
341
- <input type="number" autocomplete="off" min={0} max={255} id="g" value={c.g} on:input={greenInputChange} />
342
- <input type="number" autocomplete="off" min={0} max={255} id="b" value={c.b} on:input={blueInputChange} />
343
- <input type="number" autocomplete="off" min={0} max={255} id="a" value={c.a} on:input={alphaInputChange} />
421
+ <input type="text" maxlength={7} id="hex" value={c.hex} oninput={hexInputChange} />
422
+ <input type="number" autocomplete="off" min={0} max={255} id="r" value={c.r} oninput={redInputChange} />
423
+ <input type="number" autocomplete="off" min={0} max={255} id="g" value={c.g} oninput={greenInputChange} />
424
+ <input type="number" autocomplete="off" min={0} max={255} id="b" value={c.b} oninput={blueInputChange} />
425
+ <input type="number" autocomplete="off" min={0} max={255} id="a" value={c.a} oninput={alphaInputChange} />
344
426
  <label for="hex">HEX</label>
345
427
  <label for="r">R</label>
346
428
  <label for="g">G</label>
@@ -1,4 +1,3 @@
1
- import { SvelteComponentTyped } from "svelte";
2
1
  type RGB = {
3
2
  r: number;
4
3
  g: number;
@@ -12,26 +11,14 @@ type HSV = {
12
11
  type A = {
13
12
  a: number;
14
13
  };
15
- declare const __propDef: {
16
- props: {
17
- value?: string | undefined;
18
- };
19
- events: {
20
- click: MouseEvent;
21
- keypress: KeyboardEvent;
22
- change: CustomEvent<{
23
- rgb: RGB;
24
- hsv: HSV;
25
- hex: `#${string}`;
26
- } & A>;
27
- } & {
28
- [evt: string]: CustomEvent<any>;
29
- };
30
- slots: {};
31
- };
32
- export type ColourPickerProps = typeof __propDef.props;
33
- export type ColourPickerEvents = typeof __propDef.events;
34
- export type ColourPickerSlots = typeof __propDef.slots;
35
- export default class ColourPicker extends SvelteComponentTyped<ColourPickerProps, ColourPickerEvents, ColourPickerSlots> {
14
+ interface Props {
15
+ value?: string;
16
+ onchange?: (event: CustomEvent<{
17
+ rgb: RGB;
18
+ hsv: HSV;
19
+ hex: `#${string}`;
20
+ } & A>) => any;
36
21
  }
37
- export {};
22
+ declare const ColourPicker: import("svelte").Component<Props, {}, "value">;
23
+ type ColourPicker = ReturnType<typeof ColourPicker>;
24
+ export default ColourPicker;
package/dist/index.js CHANGED
@@ -1,3 +1,2 @@
1
- // Reexport your entry components here
2
1
  import ColourPicker from "./ColourPicker.svelte";
3
2
  export { ColourPicker };
package/package.json CHANGED
@@ -1,56 +1,56 @@
1
- {
2
- "name": "svelte-colourpicker",
3
- "version": "0.1.0",
4
- "description": "Svelte colour picker component",
5
- "keywords": [
6
- "color",
7
- "colour",
8
- "picker",
9
- "svelte"
10
- ],
11
- "homepage": "https://github.com/cooolbros/svelte-colourpicker#readme",
12
- "license": "MIT",
13
- "repository": {
14
- "type": "git",
15
- "url": "https://github.com/cooolbros/svelte-colourpicker.git"
16
- },
17
- "scripts": {
18
- "dev": "vite dev",
19
- "build": "vite build && npm run package",
20
- "preview": "vite preview",
21
- "package": "svelte-kit sync && svelte-package && publint",
22
- "prepublishOnly": "npm run package",
23
- "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
24
- "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
25
- "lint": "prettier --plugin-search-dir . --check .",
26
- "format": "prettier --plugin-search-dir . --write ."
27
- },
28
- "exports": {
29
- ".": {
30
- "types": "./dist/index.d.ts",
31
- "svelte": "./dist/index.js"
32
- }
33
- },
34
- "files": [
35
- "dist"
36
- ],
37
- "peerDependencies": {
38
- "svelte": "^3.55.1"
39
- },
40
- "devDependencies": {
41
- "@sveltejs/adapter-auto": "^2.1.0",
42
- "@sveltejs/kit": "^1.19.0",
43
- "@sveltejs/package": "^2.0.2",
44
- "prettier": "^2.8.8",
45
- "prettier-plugin-svelte": "^2.10.1",
46
- "publint": "^0.1.12",
47
- "svelte": "^3.59.1",
48
- "svelte-check": "^3.4.3",
49
- "tslib": "^2.5.2",
50
- "typescript": "^5.0.4",
51
- "vite": "^4.3.9"
52
- },
53
- "svelte": "./dist/index.js",
54
- "types": "./dist/index.d.ts",
55
- "type": "module"
56
- }
1
+ {
2
+ "name": "svelte-colourpicker",
3
+ "version": "0.2.0",
4
+ "description": "Svelte colour picker component",
5
+ "keywords": [
6
+ "color",
7
+ "colour",
8
+ "picker",
9
+ "svelte"
10
+ ],
11
+ "homepage": "https://github.com/cooolbros/svelte-colourpicker#readme",
12
+ "license": "MIT",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/cooolbros/svelte-colourpicker.git"
16
+ },
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.ts",
20
+ "svelte": "./dist/index.js"
21
+ }
22
+ },
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "peerDependencies": {
27
+ "svelte": "^5.0.0"
28
+ },
29
+ "devDependencies": {
30
+ "@sveltejs/adapter-auto": "^7.0.1",
31
+ "@sveltejs/kit": "^2.63.0",
32
+ "@sveltejs/package": "^2.5.8",
33
+ "@sveltejs/vite-plugin-svelte": "^7.1.2",
34
+ "prettier": "^3.8.3",
35
+ "prettier-plugin-svelte": "^4.1.0",
36
+ "publint": "^0.3.21",
37
+ "svelte": "^5.56.2",
38
+ "svelte-check": "^4.6.0",
39
+ "tslib": "^2.5.2",
40
+ "typescript": "^6.0.3",
41
+ "vite": "^8.0.16"
42
+ },
43
+ "svelte": "./dist/index.js",
44
+ "types": "./dist/index.d.ts",
45
+ "type": "module",
46
+ "scripts": {
47
+ "dev": "vite dev",
48
+ "build": "vite build && pnpm package",
49
+ "preview": "vite preview",
50
+ "package": "svelte-kit sync && svelte-package && publint",
51
+ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
52
+ "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
53
+ "lint": "prettier --plugin-search-dir . --check .",
54
+ "format": "prettier --plugin-search-dir . --write ."
55
+ }
56
+ }