@tableslayer/ui 0.1.3 → 0.1.4

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 (205) hide show
  1. package/package.json +2 -13
  2. package/src/lib/components/Avatar/Avatar.svelte +82 -0
  3. package/src/lib/components/Avatar/AvatarFileInput.svelte +85 -0
  4. package/src/lib/components/Avatar/AvatarPopover.svelte +34 -0
  5. package/src/lib/components/Avatar/index.ts +4 -0
  6. package/src/lib/components/Avatar/types.ts +24 -0
  7. package/src/lib/components/BrushSizeSlider/BrushSizeSlider.svelte +174 -0
  8. package/src/lib/components/BrushSizeSlider/index.ts +1 -0
  9. package/src/lib/components/Button/Button.svelte +182 -0
  10. package/src/lib/components/Button/ConfirmActionButton.svelte +98 -0
  11. package/src/lib/components/Button/IconButton.svelte +121 -0
  12. package/src/lib/components/Button/RadioButton.svelte +93 -0
  13. package/src/lib/components/Button/index.ts +5 -0
  14. package/src/lib/components/Button/types.ts +54 -0
  15. package/src/lib/components/CardFan/CardFan.svelte +165 -0
  16. package/src/lib/components/CardFan/index.ts +2 -0
  17. package/src/lib/components/CardFan/types.ts +6 -0
  18. package/src/lib/components/CodeBlock/Code.svelte +7 -0
  19. package/src/lib/components/CodeBlock/CodeBlock.svelte +102 -0
  20. package/src/lib/components/CodeBlock/index.ts +3 -0
  21. package/src/lib/components/CodeBlock/types.ts +10 -0
  22. package/src/lib/components/ColorMode/ColorMode.svelte +8 -0
  23. package/src/lib/components/ColorMode/index.ts +2 -0
  24. package/src/lib/components/ColorMode/types.ts +12 -0
  25. package/src/lib/components/ColorPicker/ColorPicker.svelte +838 -0
  26. package/src/lib/components/ColorPicker/ColorPickerSwatch.svelte +32 -0
  27. package/src/lib/components/ColorPicker/index.ts +3 -0
  28. package/src/lib/components/ColorPicker/types.ts +51 -0
  29. package/src/lib/components/ContextMenu/ContextMenu.svelte +86 -0
  30. package/src/lib/components/ContextMenu/index.ts +2 -0
  31. package/src/lib/components/ContextMenu/types.ts +15 -0
  32. package/src/lib/components/DrawingSliders/DrawingSliders.svelte +379 -0
  33. package/src/lib/components/DrawingSliders/index.ts +1 -0
  34. package/src/lib/components/Editor/Editor.svelte +825 -0
  35. package/src/lib/components/Editor/index.ts +1 -0
  36. package/src/lib/components/FogSliders/FogSliders.svelte +33 -0
  37. package/src/lib/components/FogSliders/index.ts +1 -0
  38. package/src/lib/components/Hr/Hr.svelte +15 -0
  39. package/src/lib/components/Hr/index.ts +1 -0
  40. package/src/lib/components/Icon/Icon.svelte +6 -0
  41. package/src/lib/components/Icon/index.ts +2 -0
  42. package/src/lib/components/Icon/types.ts +20 -0
  43. package/src/lib/components/Input/DualInputSlider.svelte +126 -0
  44. package/src/lib/components/Input/FileInput.svelte +176 -0
  45. package/src/lib/components/Input/FormControl.svelte +150 -0
  46. package/src/lib/components/Input/FormError.svelte +37 -0
  47. package/src/lib/components/Input/Input.svelte +56 -0
  48. package/src/lib/components/Input/InputCheckbox.svelte +99 -0
  49. package/src/lib/components/Input/InputSlider.svelte +86 -0
  50. package/src/lib/components/Input/Label.svelte +19 -0
  51. package/src/lib/components/Input/index.ts +9 -0
  52. package/src/lib/components/Input/types.ts +39 -0
  53. package/src/lib/components/Link/Link.svelte +41 -0
  54. package/src/lib/components/Link/LinkBox.svelte +20 -0
  55. package/src/lib/components/Link/LinkOverlay.svelte +23 -0
  56. package/src/lib/components/Link/index.ts +4 -0
  57. package/src/lib/components/Link/types.ts +17 -0
  58. package/src/lib/components/Loading/Loader.svelte +60 -0
  59. package/src/lib/components/Loading/Skeleton.svelte +9 -0
  60. package/src/lib/components/Loading/index.ts +2 -0
  61. package/src/lib/components/Logo/Logo.svelte +16 -0
  62. package/src/lib/components/Logo/index.ts +1 -0
  63. package/src/lib/components/MarkerTooltip/MarkerTooltip.svelte +435 -0
  64. package/src/lib/components/MarkerTooltip/index.ts +1 -0
  65. package/src/lib/components/Menu/SelectorMenu.svelte +280 -0
  66. package/src/lib/components/Menu/index.ts +2 -0
  67. package/src/lib/components/Menu/types.ts +17 -0
  68. package/src/lib/components/MyCounterButton.svelte +11 -0
  69. package/src/lib/components/Panel/index.ts +2 -0
  70. package/src/lib/components/Panel/panel.svelte +18 -0
  71. package/src/lib/components/Panel/types.ts +8 -0
  72. package/src/lib/components/PersistButton/PersistButton.svelte +100 -0
  73. package/src/lib/components/PersistButton/index.ts +1 -0
  74. package/src/lib/components/Popover/Popover.svelte +81 -0
  75. package/src/lib/components/Popover/index.ts +2 -0
  76. package/src/lib/components/Popover/types.ts +19 -0
  77. package/src/lib/components/PropsTable/PropsTable.svelte +107 -0
  78. package/src/lib/components/RadialMenu/EffectPreview.svelte +36 -0
  79. package/src/lib/components/RadialMenu/EffectPreviewScene.svelte +194 -0
  80. package/src/lib/components/RadialMenu/RadialMenu.svelte +503 -0
  81. package/src/lib/components/RadialMenu/RadialMenuItem.svelte +176 -0
  82. package/src/lib/components/RadialMenu/index.ts +2 -0
  83. package/src/lib/components/RadialMenu/types.ts +35 -0
  84. package/src/lib/components/Select/Select.svelte +342 -0
  85. package/src/lib/components/Select/index.ts +2 -0
  86. package/src/lib/components/Select/types.ts +22 -0
  87. package/src/lib/components/Spacer/Spacer.svelte +14 -0
  88. package/src/lib/components/Spacer/index.ts +2 -0
  89. package/src/lib/components/Spacer/types.ts +5 -0
  90. package/src/lib/components/Stage/components/AnnotationLayer/AnnotationLayer.svelte +445 -0
  91. package/src/lib/components/Stage/components/AnnotationLayer/AnnotationMaterial.svelte +167 -0
  92. package/src/lib/components/Stage/components/AnnotationLayer/types.ts +196 -0
  93. package/src/lib/components/Stage/components/CursorLayer/CursorLayer.svelte +148 -0
  94. package/src/lib/components/Stage/components/CursorLayer/cursor.svg +26 -0
  95. package/src/lib/components/Stage/components/CursorLayer/index.ts +2 -0
  96. package/src/lib/components/Stage/components/CursorLayer/types.ts +23 -0
  97. package/src/lib/components/Stage/components/DrawingLayer/DrawingMaterial.svelte +364 -0
  98. package/src/lib/components/Stage/components/DrawingLayer/types.ts +65 -0
  99. package/src/lib/components/Stage/components/EdgeOverlayLayer/EdgeOverlayLayer.svelte +72 -0
  100. package/src/lib/components/Stage/components/EdgeOverlayLayer/types.ts +34 -0
  101. package/src/lib/components/Stage/components/FogLayer/FogLayer.svelte +75 -0
  102. package/src/lib/components/Stage/components/FogLayer/types.ts +51 -0
  103. package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarLayer.svelte +249 -0
  104. package/src/lib/components/Stage/components/FogOfWarLayer/FogOfWarMaterial.svelte +200 -0
  105. package/src/lib/components/Stage/components/FogOfWarLayer/types.ts +116 -0
  106. package/src/lib/components/Stage/components/GridLayer/GridLayer.svelte +20 -0
  107. package/src/lib/components/Stage/components/GridLayer/GridMaterial.svelte +69 -0
  108. package/src/lib/components/Stage/components/GridLayer/types.ts +79 -0
  109. package/src/lib/components/Stage/components/LayerInput/LayerInput.svelte +300 -0
  110. package/src/lib/components/Stage/components/MapLayer/MapLayer.svelte +196 -0
  111. package/src/lib/components/Stage/components/MapLayer/dataSources/GifDataSource.ts +265 -0
  112. package/src/lib/components/Stage/components/MapLayer/dataSources/IMapDataSource.ts +55 -0
  113. package/src/lib/components/Stage/components/MapLayer/dataSources/ImageDataSource.ts +87 -0
  114. package/src/lib/components/Stage/components/MapLayer/dataSources/VideoDataSource.ts +150 -0
  115. package/src/lib/components/Stage/components/MapLayer/dataSources/dataSourceFactory.ts +48 -0
  116. package/src/lib/components/Stage/components/MapLayer/dataSources/index.ts +16 -0
  117. package/src/lib/components/Stage/components/MapLayer/types.ts +58 -0
  118. package/src/lib/components/Stage/components/MarkerLayer/MarkerLayer.svelte +398 -0
  119. package/src/lib/components/Stage/components/MarkerLayer/MarkerToken.svelte +262 -0
  120. package/src/lib/components/Stage/components/MarkerLayer/types.ts +126 -0
  121. package/src/lib/components/Stage/components/MeasurementLayer/MeasurementLayer.svelte +364 -0
  122. package/src/lib/components/Stage/components/MeasurementLayer/MeasurementManager.svelte +473 -0
  123. package/src/lib/components/Stage/components/MeasurementLayer/measurements/BaseMeasurement.ts +427 -0
  124. package/src/lib/components/Stage/components/MeasurementLayer/measurements/BeamMeasurement.ts +105 -0
  125. package/src/lib/components/Stage/components/MeasurementLayer/measurements/CircleMeasurement.ts +98 -0
  126. package/src/lib/components/Stage/components/MeasurementLayer/measurements/ConeMeasurement.ts +163 -0
  127. package/src/lib/components/Stage/components/MeasurementLayer/measurements/LineMeasurement.ts +102 -0
  128. package/src/lib/components/Stage/components/MeasurementLayer/measurements/RectangleMeasurement.ts +120 -0
  129. package/src/lib/components/Stage/components/MeasurementLayer/measurements/index.ts +7 -0
  130. package/src/lib/components/Stage/components/MeasurementLayer/types.ts +94 -0
  131. package/src/lib/components/Stage/components/MeasurementLayer/utils/canvasDrawing.ts +357 -0
  132. package/src/lib/components/Stage/components/MeasurementLayer/utils/distanceCalculations.ts +170 -0
  133. package/src/lib/components/Stage/components/ParticleSystem/ParticleSystem.svelte +220 -0
  134. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/ash.png +0 -0
  135. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/leaves.png +0 -0
  136. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/rain.png +0 -0
  137. package/src/lib/components/Stage/components/ParticleSystem/particles/atlases/snow.png +0 -0
  138. package/src/lib/components/Stage/components/ParticleSystem/rng.js +20 -0
  139. package/src/lib/components/Stage/components/ParticleSystem/types.ts +95 -0
  140. package/src/lib/components/Stage/components/PerformanceDebugger/PerformanceDebugger.svelte +144 -0
  141. package/src/lib/components/Stage/components/PerformanceDebugger/index.ts +1 -0
  142. package/src/lib/components/Stage/components/PerformanceOverlay/PerformanceOverlay.svelte +208 -0
  143. package/src/lib/components/Stage/components/PerformanceOverlay/index.ts +1 -0
  144. package/src/lib/components/Stage/components/PointerInputManager/PointerInputManager.svelte +201 -0
  145. package/src/lib/components/Stage/components/Scene/Scene.svelte +651 -0
  146. package/src/lib/components/Stage/components/Scene/luts.ts +24 -0
  147. package/src/lib/components/Stage/components/Scene/types.ts +225 -0
  148. package/src/lib/components/Stage/components/Stage/Stage.svelte +332 -0
  149. package/src/lib/components/Stage/components/Stage/types.ts +136 -0
  150. package/src/lib/components/Stage/components/WeatherLayer/WeatherLayer.svelte +135 -0
  151. package/src/lib/components/Stage/components/WeatherLayer/presets/AshPreset.ts +71 -0
  152. package/src/lib/components/Stage/components/WeatherLayer/presets/LeavesPreset.ts +70 -0
  153. package/src/lib/components/Stage/components/WeatherLayer/presets/RainPreset.ts +68 -0
  154. package/src/lib/components/Stage/components/WeatherLayer/presets/SnowPreset.ts +70 -0
  155. package/src/lib/components/Stage/components/WeatherLayer/presets/index.ts +6 -0
  156. package/src/lib/components/Stage/components/WeatherLayer/types.ts +35 -0
  157. package/src/lib/components/Stage/helpers/clippingPlaneStore.svelte.ts +28 -0
  158. package/src/lib/components/Stage/helpers/debugState.svelte.ts +18 -0
  159. package/src/lib/components/Stage/helpers/grid.ts +548 -0
  160. package/src/lib/components/Stage/helpers/lazyBrush.ts +171 -0
  161. package/src/lib/components/Stage/helpers/performanceMetrics.svelte.ts +220 -0
  162. package/src/lib/components/Stage/helpers/utils.ts +21 -0
  163. package/src/lib/components/Stage/index.ts +49 -0
  164. package/src/lib/components/Stage/shaders/AnnotationEffects.frag +1070 -0
  165. package/src/lib/components/Stage/shaders/Annotations.frag +29 -0
  166. package/src/lib/components/Stage/shaders/Drawing.frag +83 -0
  167. package/src/lib/components/Stage/shaders/Drawing.vert +5 -0
  168. package/src/lib/components/Stage/shaders/Fog.frag +147 -0
  169. package/src/lib/components/Stage/shaders/FractalNoise.frag +96 -0
  170. package/src/lib/components/Stage/shaders/GridShader.frag +174 -0
  171. package/src/lib/components/Stage/shaders/Overlay.frag +23 -0
  172. package/src/lib/components/Stage/shaders/Overlay.vert +0 -0
  173. package/src/lib/components/Stage/shaders/Particles.frag +27 -0
  174. package/src/lib/components/Stage/shaders/Particles.vert +51 -0
  175. package/src/lib/components/Stage/shaders/ToolOutline.frag +59 -0
  176. package/src/lib/components/Stage/shaders/default.vert +8 -0
  177. package/src/lib/components/Stage/types.ts +4 -0
  178. package/src/lib/components/Table/Table.svelte +16 -0
  179. package/src/lib/components/Table/Td.svelte +17 -0
  180. package/src/lib/components/Table/Th.svelte +18 -0
  181. package/src/lib/components/Table/index.ts +4 -0
  182. package/src/lib/components/Table/types.ts +14 -0
  183. package/src/lib/components/Text/Text.svelte +23 -0
  184. package/src/lib/components/Text/index.ts +2 -0
  185. package/src/lib/components/Text/types.ts +12 -0
  186. package/src/lib/components/Title/Title.svelte +54 -0
  187. package/src/lib/components/Title/index.ts +2 -0
  188. package/src/lib/components/Title/types.ts +9 -0
  189. package/src/lib/components/Toast/Toast.svelte +155 -0
  190. package/src/lib/components/Toast/index.ts +5 -0
  191. package/src/lib/components/Toast/toastCookie.ts +24 -0
  192. package/src/lib/components/Toast/types.ts +6 -0
  193. package/src/lib/components/ToolTip/ToolTip.svelte +70 -0
  194. package/src/lib/components/ToolTip/index.ts +2 -0
  195. package/src/lib/components/ToolTip/types.ts +14 -0
  196. package/src/lib/components/index.ts +32 -0
  197. package/src/lib/components/types.ts +0 -0
  198. package/src/lib/index.ts +2 -0
  199. package/src/lib/styles/globals.css +108 -0
  200. package/src/lib/styles/normalize.css +9 -0
  201. package/src/lib/styles/reset.css +133 -0
  202. package/src/lib/styles/utilities.css +179 -0
  203. package/src/lib/styles/vars.css +1103 -0
  204. package/src/lib/types/awareness.ts +17 -0
  205. package/src/lib/utils/rle.ts +217 -0
@@ -0,0 +1,838 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import { Input, Select } from '../'; // Adjust the import path based on your project structure
4
+ import type { ColorState, ColorPickerFormats, ColorPickerProps } from './types';
5
+ import chroma from 'chroma-js';
6
+
7
+ // Bindable props with correct syntax and typings
8
+ let {
9
+ hex = $bindable<string>(''),
10
+ rgba = $bindable(),
11
+ hsva = $bindable(),
12
+ hsla = $bindable(),
13
+ showInputs = false,
14
+ showOpacity = true,
15
+ onUpdate = () => {},
16
+ id,
17
+ ...restProps
18
+ }: ColorPickerProps = $props();
19
+
20
+ const color = $state<ColorState>({
21
+ hue: 0,
22
+ saturation: 100,
23
+ value: 100,
24
+ opacity: 100,
25
+ isSelecting: false,
26
+ isAdjustingSV: false,
27
+ isAdjustingHue: false
28
+ });
29
+
30
+ let lastValidHue = color.hue;
31
+
32
+ let canvasElement: HTMLCanvasElement;
33
+ let colorInputFocused = false;
34
+ let selectedFormat = $state<ColorPickerFormats>('hex');
35
+
36
+ // Input fields for different color modes
37
+ let hexInput = $state('');
38
+ let rgbInputs = $state({ r: '', g: '', b: '', a: '' });
39
+ let hslInputs = $state({ h: '', s: '', l: '', a: '' });
40
+ let hsvInputs = $state({ h: '', s: '', v: '', a: '' });
41
+
42
+ // Helper Functions using chroma-js
43
+ const toHex = (color: ColorState): string => {
44
+ const alpha = color.opacity / 100;
45
+ const chromaColor = chroma.hsv(color.hue, color.saturation / 100, color.value / 100).alpha(alpha);
46
+ if (showOpacity) {
47
+ return chromaColor.hex();
48
+ } else {
49
+ return chromaColor.hex().slice(0, 7); // Return only 6-digit hex without alpha
50
+ }
51
+ };
52
+
53
+ const drawSaturationValueGradient = (): void => {
54
+ if (!canvasElement) return;
55
+ const ctx = canvasElement.getContext('2d');
56
+ if (!ctx) return;
57
+ const width = canvasElement.width;
58
+ const height = canvasElement.height;
59
+
60
+ // Clear the canvas
61
+ ctx.clearRect(0, 0, width, height);
62
+
63
+ const saturationGradient = ctx.createLinearGradient(0, 0, width, 0);
64
+ saturationGradient.addColorStop(0, 'white');
65
+ saturationGradient.addColorStop(1, `hsl(${displayHue()}, 100%, 50%)`);
66
+ ctx.fillStyle = saturationGradient;
67
+ ctx.fillRect(0, 0, width, height);
68
+
69
+ const valueGradient = ctx.createLinearGradient(0, 0, 0, height);
70
+ valueGradient.addColorStop(0, 'rgba(0,0,0,0)');
71
+ valueGradient.addColorStop(1, 'rgba(0,0,0,1)');
72
+ ctx.fillStyle = valueGradient;
73
+ ctx.fillRect(0, 0, width, height);
74
+ };
75
+
76
+ let saturationBoxRect: DOMRect;
77
+
78
+ const updateSaturationValue = (e: MouseEvent): void => {
79
+ if (!saturationBoxRect) return;
80
+ const rect = saturationBoxRect;
81
+ const x = e.clientX - rect.left;
82
+ const y = e.clientY - rect.top;
83
+ color.saturation = Math.max(0, Math.min(100, (x / rect.width) * 100));
84
+ color.value = Math.max(0, Math.min(100, 100 - (y / rect.height) * 100));
85
+ };
86
+
87
+ const startSelection = (e: MouseEvent): void => {
88
+ e.preventDefault();
89
+ saturationBoxRect = canvasElement.getBoundingClientRect();
90
+ color.isSelecting = true;
91
+ color.isAdjustingSV = true;
92
+ updateSaturationValue(e);
93
+ };
94
+
95
+ const endSelection = (): void => {
96
+ color.isSelecting = false;
97
+ color.isAdjustingSV = false;
98
+ };
99
+
100
+ const handleMouseMove = (e: MouseEvent): void => {
101
+ if (color.isSelecting) {
102
+ updateSaturationValue(e);
103
+ }
104
+ };
105
+
106
+ const handleMouseUp = (): void => {
107
+ if (color.isSelecting) {
108
+ endSelection();
109
+ }
110
+ };
111
+
112
+ // Touch event handlers
113
+ const updateSaturationValueFromTouch = (e: TouchEvent): void => {
114
+ if (!saturationBoxRect) return;
115
+ const rect = saturationBoxRect;
116
+ const touch = e.touches[0] || e.changedTouches[0];
117
+ const x = touch.clientX - rect.left;
118
+ const y = touch.clientY - rect.top;
119
+ color.saturation = Math.max(0, Math.min(100, (x / rect.width) * 100));
120
+ color.value = Math.max(0, Math.min(100, 100 - (y / rect.height) * 100));
121
+ };
122
+
123
+ const startTouchSelection = (e: TouchEvent): void => {
124
+ e.preventDefault();
125
+ saturationBoxRect = canvasElement.getBoundingClientRect();
126
+ color.isSelecting = true;
127
+ color.isAdjustingSV = true;
128
+ updateSaturationValueFromTouch(e);
129
+ };
130
+
131
+ const handleTouchMove = (e: TouchEvent): void => {
132
+ if (color.isSelecting) {
133
+ e.preventDefault();
134
+ updateSaturationValueFromTouch(e);
135
+ }
136
+ };
137
+
138
+ const handleTouchEnd = (): void => {
139
+ if (color.isSelecting) {
140
+ endSelection();
141
+ }
142
+ };
143
+
144
+ const getOpacityGradient = (): string => {
145
+ const chromaColor = chroma.hsv(displayHue(), color.saturation / 100, color.value / 100);
146
+ const [r, g, b] = chromaColor.rgb();
147
+ const rgba0 = `rgba(${r}, ${g}, ${b}, 0)`;
148
+ const rgba1 = `rgba(${r}, ${g}, ${b}, 1)`;
149
+ return `linear-gradient(to right, ${rgba0}, ${rgba1})`;
150
+ };
151
+
152
+ // Synchronize internal color state with bindable props
153
+ let updatingFromProps = false;
154
+
155
+ // Update internal color state when bindable props change
156
+ $effect(() => {
157
+ if (colorInputFocused || updatingFromProps) return;
158
+
159
+ updatingFromProps = true;
160
+
161
+ if (!color.isAdjustingSV) {
162
+ if (hex && hex.trim()) {
163
+ try {
164
+ const chromaColor = chroma(hex.trim());
165
+ const [h, s, v] = chromaColor.hsv();
166
+ const alpha = chromaColor.alpha();
167
+ if (!isNaN(h)) {
168
+ color.hue = h;
169
+ lastValidHue = h;
170
+ } else {
171
+ color.hue = lastValidHue;
172
+ }
173
+ color.saturation = s * 100;
174
+ color.value = v * 100;
175
+ color.opacity = alpha * 100;
176
+ } catch (error) {
177
+ console.log(error);
178
+ // Invalid hex code
179
+ }
180
+ } else if (rgba) {
181
+ const { r, g, b, a } = rgba;
182
+ if (typeof r === 'number' && typeof g === 'number' && typeof b === 'number') {
183
+ const chromaColor = chroma.rgb(r, g, b).alpha(a);
184
+ const [h, s, v] = chromaColor.hsv();
185
+ if (!isNaN(h)) {
186
+ color.hue = h;
187
+ lastValidHue = h;
188
+ } else {
189
+ color.hue = lastValidHue;
190
+ }
191
+ color.saturation = s * 100;
192
+ color.value = v * 100;
193
+ color.opacity = chromaColor.alpha() * 100;
194
+ }
195
+ } else if (hsva) {
196
+ const { h, s, v, a } = hsva;
197
+ if (!isNaN(h)) {
198
+ color.hue = h;
199
+ lastValidHue = h;
200
+ } else {
201
+ color.hue = lastValidHue;
202
+ }
203
+ color.saturation = s;
204
+ color.value = v;
205
+ color.opacity = a * 100;
206
+ } else if (hsla) {
207
+ const { h, s, l, a } = hsla;
208
+ const chromaColor = chroma.hsl(h, s / 100, l / 100).alpha(a);
209
+ const [h2, s2, v] = chromaColor.hsv();
210
+ if (!isNaN(h2)) {
211
+ color.hue = h2;
212
+ lastValidHue = h2;
213
+ } else {
214
+ color.hue = lastValidHue;
215
+ }
216
+ color.saturation = s2 * 100;
217
+ color.value = v * 100;
218
+ color.opacity = chromaColor.alpha() * 100;
219
+ }
220
+ }
221
+
222
+ updatingFromProps = false;
223
+ });
224
+
225
+ // Update bindable props when internal color state changes
226
+ let previousColor = { ...color };
227
+
228
+ $effect(() => {
229
+ if (updatingFromProps) return;
230
+
231
+ const hueChanged = color.hue !== previousColor.hue && !isNaN(color.hue);
232
+ const satChanged = color.saturation !== previousColor.saturation;
233
+ const valChanged = color.value !== previousColor.value;
234
+ const opacityChanged = color.opacity !== previousColor.opacity;
235
+
236
+ if (hueChanged || satChanged || valChanged || opacityChanged) {
237
+ if (!color.isAdjustingSV) {
238
+ if (hueChanged) {
239
+ lastValidHue = color.hue;
240
+ }
241
+ } else {
242
+ // When adjusting SV, keep the hue constant
243
+ color.hue = lastValidHue;
244
+ }
245
+
246
+ // Compute color representations using chroma-js
247
+ const opacity = showOpacity ? color.opacity / 100 : 1;
248
+ const chromaColor = chroma.hsv(displayHue(), color.saturation / 100, color.value / 100).alpha(opacity);
249
+
250
+ const [r, g, b] = chromaColor.rgb();
251
+ let newHex;
252
+ if (showOpacity) {
253
+ newHex = chromaColor.hex();
254
+ } else {
255
+ newHex = chromaColor.hex().slice(0, 7); // Return only 6-digit hex without alpha
256
+ }
257
+ const newRgba = { r, g, b, a: chromaColor.alpha() };
258
+ const [hHSL, sHSL, lHSL] = chromaColor.hsl();
259
+ const newHsla = { h: hHSL, s: sHSL * 100, l: lHSL * 100, a: chromaColor.alpha() };
260
+ const [hHSV, sHSV, vHSV] = chromaColor.hsv();
261
+ const newHsva = { h: hHSV, s: sHSV * 100, v: vHSV * 100, a: chromaColor.alpha() };
262
+
263
+ // Update bindable props
264
+ hex = newHex;
265
+ rgba = newRgba;
266
+ hsla = newHsla;
267
+ hsva = newHsva;
268
+
269
+ // Call onUpdate
270
+ onUpdate({ hex: newHex, rgba: newRgba, hsva: newHsva, hsla: newHsla });
271
+
272
+ previousColor = { ...color };
273
+ }
274
+ });
275
+
276
+ // Function to update color inputs based on selectedFormat and color state
277
+ const updateColorInputs = () => {
278
+ const chromaColor = chroma.hsv(color.hue, color.saturation / 100, color.value / 100).alpha(color.opacity / 100);
279
+
280
+ switch (selectedFormat) {
281
+ case 'hex': {
282
+ hexInput = toHex(color);
283
+ break;
284
+ }
285
+ case 'rgb': {
286
+ const [r, g, b] = chromaColor.rgb();
287
+ rgbInputs = {
288
+ r: r.toString(),
289
+ g: g.toString(),
290
+ b: b.toString(),
291
+ a: chromaColor.alpha().toFixed(2)
292
+ };
293
+ break;
294
+ }
295
+ case 'hsl': {
296
+ const [h, s, l] = chromaColor.hsl();
297
+ hslInputs = {
298
+ h: Math.round(h).toString(),
299
+ s: Math.round(s * 100).toString(),
300
+ l: Math.round(l * 100).toString(),
301
+ a: chromaColor.alpha().toFixed(2)
302
+ };
303
+ break;
304
+ }
305
+ case 'hsv': {
306
+ const [h, s, v] = chromaColor.hsv();
307
+ hsvInputs = {
308
+ h: Math.round(h).toString(),
309
+ s: Math.round(s * 100).toString(),
310
+ v: Math.round(v * 100).toString(),
311
+ a: chromaColor.alpha().toFixed(2)
312
+ };
313
+ break;
314
+ }
315
+ }
316
+ };
317
+
318
+ // Function to parse inputs and update color state
319
+ const parseColorInputs = () => {
320
+ try {
321
+ switch (selectedFormat) {
322
+ case 'hex': {
323
+ const hexValue = hexInput.trim();
324
+ // Regular expression for either 6-digit or 8-digit hex
325
+ if (/^#?([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(hexValue)) {
326
+ const chromaColor = chroma(hexValue);
327
+ const [h, s, v] = chromaColor.hsv();
328
+ if (!isNaN(h)) {
329
+ color.hue = h;
330
+ lastValidHue = h;
331
+ } else {
332
+ color.hue = lastValidHue;
333
+ }
334
+ color.saturation = s * 100;
335
+ color.value = v * 100;
336
+ // Only update opacity if we're showing the opacity slider
337
+ if (showOpacity) {
338
+ color.opacity = chromaColor.alpha() * 100;
339
+ } else {
340
+ color.opacity = 100; // Always full opacity when showOpacity is false
341
+ }
342
+ } else {
343
+ console.error('Invalid hex code');
344
+ }
345
+ break;
346
+ }
347
+ case 'rgb': {
348
+ const r = parseInt(rgbInputs.r);
349
+ const g = parseInt(rgbInputs.g);
350
+ const b = parseInt(rgbInputs.b);
351
+ const a = parseFloat(rgbInputs.a);
352
+ if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
353
+ const chromaColor = chroma.rgb(r, g, b).alpha(a);
354
+ const [h, s, v] = chromaColor.hsv();
355
+ if (!isNaN(h)) {
356
+ color.hue = h;
357
+ lastValidHue = h;
358
+ } else {
359
+ color.hue = lastValidHue;
360
+ }
361
+ color.saturation = s * 100;
362
+ color.value = v * 100;
363
+ if (!isNaN(a)) {
364
+ color.opacity = a * 100;
365
+ }
366
+ }
367
+ break;
368
+ }
369
+ case 'hsl': {
370
+ const h = parseFloat(hslInputs.h);
371
+ const s = parseFloat(hslInputs.s);
372
+ const l = parseFloat(hslInputs.l);
373
+ const a = parseFloat(hslInputs.a);
374
+ if (!isNaN(h) && !isNaN(s) && !isNaN(l)) {
375
+ const chromaColor = chroma.hsl(h, s / 100, l / 100).alpha(a);
376
+ const [h2, s2, v] = chromaColor.hsv();
377
+ if (!isNaN(h2)) {
378
+ color.hue = h2;
379
+ lastValidHue = h2;
380
+ } else {
381
+ color.hue = lastValidHue;
382
+ }
383
+ color.saturation = s2 * 100;
384
+ color.value = v * 100;
385
+ if (!isNaN(a)) {
386
+ color.opacity = a * 100;
387
+ }
388
+ }
389
+ break;
390
+ }
391
+ case 'hsv': {
392
+ const h = parseFloat(hsvInputs.h);
393
+ const s = parseFloat(hsvInputs.s);
394
+ const v = parseFloat(hsvInputs.v);
395
+ const a = parseFloat(hsvInputs.a);
396
+ if (!isNaN(h) && !isNaN(s) && !isNaN(v)) {
397
+ if (!isNaN(h)) {
398
+ color.hue = h;
399
+ lastValidHue = h;
400
+ } else {
401
+ color.hue = lastValidHue;
402
+ }
403
+ color.saturation = s;
404
+ color.value = v;
405
+ if (!isNaN(a)) {
406
+ color.opacity = a * 100;
407
+ }
408
+ }
409
+ break;
410
+ }
411
+ }
412
+ } catch (error) {
413
+ // Invalid input; reset inputs to current color
414
+ console.error(error);
415
+ updateColorInputs();
416
+ }
417
+ };
418
+
419
+ // Update inputs when color changes, but not if input is focused
420
+ $effect(() => {
421
+ if (!colorInputFocused) {
422
+ updateColorInputs();
423
+ }
424
+ drawSaturationValueGradient();
425
+ });
426
+
427
+ // Handle input changes on blur (when input loses focus)
428
+ const handleInputsBlur = (): void => {
429
+ colorInputFocused = false;
430
+ parseColorInputs();
431
+ updateColorInputs();
432
+ };
433
+
434
+ // Update inputs when format changes
435
+ $effect(() => {
436
+ updateColorInputs();
437
+ });
438
+
439
+ // Keyboard event handler for adjusting saturation and value
440
+ const handleKeyDown = (e: KeyboardEvent): void => {
441
+ const step = 2; // Adjust this step size as needed for fine control
442
+
443
+ // Handle the arrow keys for movement
444
+ switch (e.key) {
445
+ case 'ArrowUp':
446
+ color.value = Math.min(100, color.value + step);
447
+ e.preventDefault(); // Prevent default scrolling
448
+ break;
449
+ case 'ArrowDown':
450
+ color.value = Math.max(0, color.value - step);
451
+ e.preventDefault(); // Prevent default scrolling
452
+ break;
453
+ case 'ArrowLeft':
454
+ color.saturation = Math.max(0, color.saturation - step);
455
+ e.preventDefault(); // Prevent default scrolling
456
+ break;
457
+ case 'ArrowRight':
458
+ color.saturation = Math.min(100, color.saturation + step);
459
+ e.preventDefault(); // Prevent default scrolling
460
+ break;
461
+ case 'Tab':
462
+ // Allow default behavior for Tab key to prevent focus lock
463
+ return;
464
+ }
465
+ };
466
+
467
+ // Function to start focusing and adjusting the saturation/value box
468
+ const startSaturationAdjustment = () => {
469
+ color.isAdjustingSV = true;
470
+ };
471
+
472
+ // Function to end focusing and adjusting the saturation/value box
473
+ const endSaturationAdjustment = () => {
474
+ color.isAdjustingSV = false;
475
+ };
476
+
477
+ // onMount
478
+ onMount(() => {
479
+ if (canvasElement) {
480
+ drawSaturationValueGradient();
481
+ }
482
+ // Initialize inputs
483
+ updateColorInputs();
484
+ });
485
+
486
+ const displayHue = () => (isNaN(color.hue) ? lastValidHue : color.hue);
487
+
488
+ // @ts-expect-error Can't figure out how to type this to FormatOption
489
+ const handleFormatChange = (selected) => {
490
+ selectedFormat = selected[0];
491
+ updateColorInputs();
492
+ };
493
+
494
+ const formatOptions = [
495
+ { value: 'hex', label: 'HEX' },
496
+ { value: 'rgb', label: 'RGB' },
497
+ { value: 'hsl', label: 'HSL' },
498
+ { value: 'hsv', label: 'HSV' }
499
+ ];
500
+ </script>
501
+
502
+ <svelte:window
503
+ onmousemove={handleMouseMove}
504
+ onmouseup={handleMouseUp}
505
+ ontouchmove={handleTouchMove}
506
+ ontouchend={handleTouchEnd}
507
+ />
508
+
509
+ <div class="colorPicker" {...restProps}>
510
+ <!-- Saturation/Value Selector -->
511
+ <div class="colorPicker__box">
512
+ <canvas
513
+ class="colorPicker__canvas"
514
+ bind:this={canvasElement}
515
+ width="200"
516
+ height="200"
517
+ tabindex="0"
518
+ onmousedown={startSelection}
519
+ ontouchstart={startTouchSelection}
520
+ onfocus={startSaturationAdjustment}
521
+ onblur={endSaturationAdjustment}
522
+ onkeydown={handleKeyDown}
523
+ {id}
524
+ ></canvas>
525
+ <!-- Selection Indicator -->
526
+ <div
527
+ class="colorPicker__boxIndicator"
528
+ style="top: {100 - color.value}%; left: {color.saturation}%; background-color: {toHex(color)};"
529
+ ></div>
530
+ </div>
531
+
532
+ <!-- Hue Slider -->
533
+ <div class="colorPicker__sliderWrapper colorPicker__sliderWrapper--hue">
534
+ <input
535
+ type="range"
536
+ min="0"
537
+ max="360"
538
+ step="1"
539
+ bind:value={color.hue}
540
+ onmousedown={() => (color.isAdjustingHue = true)}
541
+ onmouseup={() => (color.isAdjustingHue = false)}
542
+ ontouchstart={() => (color.isAdjustingHue = true)}
543
+ ontouchend={() => (color.isAdjustingHue = false)}
544
+ oninput={() => {
545
+ color.isAdjustingHue = true;
546
+ lastValidHue = color.hue;
547
+ }}
548
+ onchange={() => (color.isAdjustingHue = false)}
549
+ aria-label="Hue Selector"
550
+ style="--thumbBG: hsl({displayHue()}, 100%, 50%)"
551
+ class="colorPicker__slider"
552
+ />
553
+ </div>
554
+
555
+ {#if showOpacity}
556
+ <div
557
+ class="colorPicker__sliderWrapper colorPicker__sliderWrapper--opacity"
558
+ style="--opacityGradient: {getOpacityGradient()};"
559
+ >
560
+ <input
561
+ type="range"
562
+ min="0"
563
+ max="100"
564
+ step="1"
565
+ bind:value={color.opacity}
566
+ aria-label="Opacity Slider"
567
+ class="colorPicker__slider"
568
+ style="--thumbBG: {toHex(color)};"
569
+ />
570
+ </div>
571
+ {/if}
572
+
573
+ {#if showInputs}
574
+ <div class="colorPicker__inputs colorPicker__inputs--{selectedFormat}">
575
+ <Select selected={[formatOptions[0].value]} options={formatOptions} onSelectedChange={handleFormatChange} />
576
+
577
+ <!-- Inputs based on selected format -->
578
+ {#if selectedFormat === 'hex'}
579
+ <Input
580
+ type="text"
581
+ bind:value={hexInput}
582
+ aria-label="Hex Color Input"
583
+ onfocus={() => (colorInputFocused = true)}
584
+ onblur={handleInputsBlur}
585
+ />
586
+ {:else if selectedFormat === 'rgb'}
587
+ <Input
588
+ type="number"
589
+ min="0"
590
+ max="255"
591
+ bind:value={rgbInputs.r}
592
+ aria-label="Red"
593
+ placeholder="R"
594
+ onfocus={() => (colorInputFocused = true)}
595
+ onblur={handleInputsBlur}
596
+ />
597
+ <Input
598
+ type="number"
599
+ min="0"
600
+ max="255"
601
+ bind:value={rgbInputs.g}
602
+ aria-label="Green"
603
+ placeholder="G"
604
+ onfocus={() => (colorInputFocused = true)}
605
+ onblur={handleInputsBlur}
606
+ />
607
+ <Input
608
+ type="number"
609
+ min="0"
610
+ max="255"
611
+ bind:value={rgbInputs.b}
612
+ aria-label="Blue"
613
+ placeholder="B"
614
+ onfocus={() => (colorInputFocused = true)}
615
+ onblur={handleInputsBlur}
616
+ />
617
+ {#if showOpacity}
618
+ <Input
619
+ type="number"
620
+ min="0"
621
+ max="1"
622
+ step="0.01"
623
+ bind:value={rgbInputs.a}
624
+ aria-label="Alpha"
625
+ placeholder="A"
626
+ onfocus={() => (colorInputFocused = true)}
627
+ onblur={handleInputsBlur}
628
+ />
629
+ {/if}
630
+ {:else if selectedFormat === 'hsl'}
631
+ <Input
632
+ type="number"
633
+ min="0"
634
+ max="360"
635
+ bind:value={hslInputs.h}
636
+ aria-label="Hue"
637
+ placeholder="H"
638
+ onfocus={() => (colorInputFocused = true)}
639
+ onblur={handleInputsBlur}
640
+ />
641
+ <Input
642
+ type="number"
643
+ min="0"
644
+ max="100"
645
+ bind:value={hslInputs.s}
646
+ aria-label="Saturation"
647
+ placeholder="S"
648
+ onfocus={() => (colorInputFocused = true)}
649
+ onblur={handleInputsBlur}
650
+ />
651
+ <Input
652
+ type="number"
653
+ min="0"
654
+ max="100"
655
+ bind:value={hslInputs.l}
656
+ aria-label="Lightness"
657
+ placeholder="L"
658
+ onfocus={() => (colorInputFocused = true)}
659
+ onblur={handleInputsBlur}
660
+ />
661
+ {#if showOpacity}
662
+ <Input
663
+ type="number"
664
+ min="0"
665
+ max="1"
666
+ step="0.01"
667
+ bind:value={hslInputs.a}
668
+ aria-label="Alpha"
669
+ placeholder="A"
670
+ onfocus={() => (colorInputFocused = true)}
671
+ onblur={handleInputsBlur}
672
+ />
673
+ {/if}
674
+ {:else if selectedFormat === 'hsv'}
675
+ <Input
676
+ type="number"
677
+ min="0"
678
+ max="360"
679
+ bind:value={hsvInputs.h}
680
+ aria-label="Hue"
681
+ placeholder="H"
682
+ onfocus={() => (colorInputFocused = true)}
683
+ onblur={handleInputsBlur}
684
+ />
685
+ <Input
686
+ type="number"
687
+ min="0"
688
+ max="100"
689
+ bind:value={hsvInputs.s}
690
+ aria-label="Saturation"
691
+ placeholder="S"
692
+ onfocus={() => (colorInputFocused = true)}
693
+ onblur={handleInputsBlur}
694
+ />
695
+ <Input
696
+ type="number"
697
+ min="0"
698
+ max="100"
699
+ bind:value={hsvInputs.v}
700
+ aria-label="Value"
701
+ placeholder="V"
702
+ onfocus={() => (colorInputFocused = true)}
703
+ onblur={handleInputsBlur}
704
+ />
705
+ {#if showOpacity}
706
+ <Input
707
+ type="number"
708
+ min="0"
709
+ max="1"
710
+ step="0.01"
711
+ bind:value={hsvInputs.a}
712
+ aria-label="Alpha"
713
+ placeholder="A"
714
+ onfocus={() => (colorInputFocused = true)}
715
+ onblur={handleInputsBlur}
716
+ />
717
+ {/if}
718
+ {/if}
719
+ </div>
720
+ {/if}
721
+ </div>
722
+
723
+ <style>
724
+ .colorPicker {
725
+ display: flex;
726
+ flex-direction: column;
727
+ gap: 0.5rem;
728
+ width: 100%;
729
+ max-width: 25rem;
730
+ }
731
+
732
+ .colorPicker__box {
733
+ width: 100%;
734
+ height: 8rem;
735
+ position: relative;
736
+ cursor: crosshair;
737
+ }
738
+
739
+ .colorPicker__canvas {
740
+ width: 100%;
741
+ height: 100%;
742
+ touch-action: none;
743
+ }
744
+
745
+ .colorPicker__boxIndicator {
746
+ position: absolute;
747
+ width: 0.75rem;
748
+ height: 0.75rem;
749
+ border-radius: 50%;
750
+ border: 2px solid white;
751
+ transform: translate(-50%, -50%);
752
+ pointer-events: none;
753
+ }
754
+
755
+ .colorPicker__sliderWrapper {
756
+ position: relative;
757
+ width: 100%;
758
+ height: 1rem;
759
+ }
760
+
761
+ .colorPicker__sliderWrapper::before {
762
+ content: '';
763
+ position: absolute;
764
+ top: 50%;
765
+ left: 0;
766
+ right: 0;
767
+ height: 0.25rem;
768
+ transform: translateY(-50%);
769
+ border-radius: 0.125rem;
770
+ pointer-events: none;
771
+ }
772
+
773
+ .colorPicker__sliderWrapper--hue::before {
774
+ background: linear-gradient(to right, red, yellow, lime, cyan, blue, magenta, red);
775
+ }
776
+
777
+ .colorPicker__sliderWrapper--opacity::before {
778
+ background: var(--opacityGradient);
779
+ background-size: cover;
780
+ }
781
+
782
+ .colorPicker__slider {
783
+ -webkit-appearance: none;
784
+ appearance: none;
785
+ width: 100%;
786
+ height: 100%;
787
+ cursor: pointer;
788
+ background: transparent;
789
+ position: relative;
790
+ z-index: 1;
791
+ }
792
+
793
+ .colorPicker__slider::-webkit-slider-thumb {
794
+ -webkit-appearance: none;
795
+ appearance: none;
796
+ width: 0.5rem;
797
+ height: 0.5rem;
798
+ border-radius: 50%;
799
+ border: 2px solid white;
800
+ background-color: var(--thumbBG);
801
+ }
802
+
803
+ .colorPicker__slider::-moz-range-thumb {
804
+ width: 0.5rem;
805
+ height: 0.5rem;
806
+ border-radius: 50%;
807
+ border: 2px solid white;
808
+ background-color: var(--thumbBG);
809
+ }
810
+
811
+ .colorPicker__inputs {
812
+ display: grid;
813
+ grid-template-columns: repeat(5, minmax(0, 1fr));
814
+ gap: 0.5rem;
815
+ }
816
+ .colorPicker__inputs--hex {
817
+ grid-template-columns: 1fr 2fr;
818
+ }
819
+
820
+ @media (pointer: coarse) {
821
+ .colorPicker__slider {
822
+ height: 1rem;
823
+ border-radius: var(--radius-2);
824
+ }
825
+
826
+ .colorPicker__sliderWrapper::before {
827
+ height: 1rem;
828
+ }
829
+ .colorPicker__slider::-webkit-slider-thumb {
830
+ width: 1rem;
831
+ height: 1rem;
832
+ }
833
+ .colorPicker__slider::-moz-range-thumb {
834
+ width: 1rem;
835
+ height: 1rem;
836
+ }
837
+ }
838
+ </style>