twintrinsic 0.0.1

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 (212) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +150 -0
  3. package/dist/App/App.svelte +54 -0
  4. package/dist/App/App.svelte.d.ts +65 -0
  5. package/dist/Section.svelte +25 -0
  6. package/dist/Section.svelte.d.ts +34 -0
  7. package/dist/actions/clickOutside.d.ts +9 -0
  8. package/dist/actions/clickOutside.js +19 -0
  9. package/dist/actions/index.d.ts +1 -0
  10. package/dist/actions/index.js +1 -0
  11. package/dist/components/Accordion/Accordion.svelte +75 -0
  12. package/dist/components/Accordion/Accordion.svelte.d.ts +39 -0
  13. package/dist/components/Accordion/AccordionItem.svelte +150 -0
  14. package/dist/components/Accordion/AccordionItem.svelte.d.ts +30 -0
  15. package/dist/components/App/App.story.md +8 -0
  16. package/dist/components/App/App.story.svelte +170 -0
  17. package/dist/components/App/App.story.svelte.d.ts +22 -0
  18. package/dist/components/App/App.svelte +77 -0
  19. package/dist/components/App/App.svelte.d.ts +66 -0
  20. package/dist/components/App/Split.svelte +346 -0
  21. package/dist/components/App/Split.svelte.d.ts +54 -0
  22. package/dist/components/App/index.d.ts +2 -0
  23. package/dist/components/App/index.js +3 -0
  24. package/dist/components/AppHeader/AppHeader.svelte +439 -0
  25. package/dist/components/AppHeader/AppHeader.svelte.d.ts +24 -0
  26. package/dist/components/Avatar/Avatar.svelte +300 -0
  27. package/dist/components/Avatar/Avatar.svelte.d.ts +48 -0
  28. package/dist/components/Avatar/AvatarGroup.svelte +185 -0
  29. package/dist/components/Avatar/AvatarGroup.svelte.d.ts +46 -0
  30. package/dist/components/Badge/Badge.svelte +186 -0
  31. package/dist/components/Badge/Badge.svelte.d.ts +51 -0
  32. package/dist/components/BottomBar/BottomBar.svelte +146 -0
  33. package/dist/components/BottomBar/BottomBar.svelte.d.ts +38 -0
  34. package/dist/components/Breadcrumb/Breadcrumb.svelte +77 -0
  35. package/dist/components/Breadcrumb/Breadcrumb.svelte.d.ts +42 -0
  36. package/dist/components/Breadcrumb/BreadcrumbItem.svelte +171 -0
  37. package/dist/components/Breadcrumb/BreadcrumbItem.svelte.d.ts +38 -0
  38. package/dist/components/Button/Button.svelte +252 -0
  39. package/dist/components/Button/Button.svelte.d.ts +80 -0
  40. package/dist/components/Button/ButtonGroup.svelte +127 -0
  41. package/dist/components/Button/ButtonGroup.svelte.d.ts +44 -0
  42. package/dist/components/Card/Card.svelte +152 -0
  43. package/dist/components/Card/Card.svelte.d.ts +55 -0
  44. package/dist/components/Carousel/Carousel.svelte +461 -0
  45. package/dist/components/Carousel/Carousel.svelte.d.ts +79 -0
  46. package/dist/components/Carousel/CarouselItem.svelte +149 -0
  47. package/dist/components/Carousel/CarouselItem.svelte.d.ts +35 -0
  48. package/dist/components/Chip/Chip.svelte +288 -0
  49. package/dist/components/Chip/Chip.svelte.d.ts +71 -0
  50. package/dist/components/Chip/ChipGroup.svelte +190 -0
  51. package/dist/components/Chip/ChipGroup.svelte.d.ts +71 -0
  52. package/dist/components/CodeBlock/CodeBlock.svelte +356 -0
  53. package/dist/components/CodeBlock/CodeBlock.svelte.d.ts +44 -0
  54. package/dist/components/CodeBlock/index.d.ts +1 -0
  55. package/dist/components/CodeBlock/index.js +1 -0
  56. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte +145 -0
  57. package/dist/components/CodeBlockSpeed/CodeBlockSpeed.svelte.d.ts +44 -0
  58. package/dist/components/CodeEditor/CodeEditor.svelte +229 -0
  59. package/dist/components/CodeEditor/CodeEditor.svelte.d.ts +23 -0
  60. package/dist/components/Combobox/Combobox.svelte +279 -0
  61. package/dist/components/Combobox/Combobox.svelte.d.ts +34 -0
  62. package/dist/components/Container/Container.svelte +45 -0
  63. package/dist/components/Container/Container.svelte.d.ts +36 -0
  64. package/dist/components/DataTable/DataTable.svelte +879 -0
  65. package/dist/components/DataTable/DataTable.svelte.d.ts +102 -0
  66. package/dist/components/Form/AutoComplete.svelte +357 -0
  67. package/dist/components/Form/AutoComplete.svelte.d.ts +73 -0
  68. package/dist/components/Form/Calendar.svelte +429 -0
  69. package/dist/components/Form/Calendar.svelte.d.ts +53 -0
  70. package/dist/components/Form/Checkbox.svelte +196 -0
  71. package/dist/components/Form/Checkbox.svelte.d.ts +50 -0
  72. package/dist/components/Form/ColorPicker.svelte +396 -0
  73. package/dist/components/Form/ColorPicker.svelte.d.ts +43 -0
  74. package/dist/components/Form/Combobox.svelte +645 -0
  75. package/dist/components/Form/Combobox.svelte.d.ts +93 -0
  76. package/dist/components/Form/Dropdown.svelte +773 -0
  77. package/dist/components/Form/Dropdown.svelte.d.ts +81 -0
  78. package/dist/components/Form/FileUpload.svelte +796 -0
  79. package/dist/components/Form/FileUpload.svelte.d.ts +78 -0
  80. package/dist/components/Form/FloatLabel.svelte +245 -0
  81. package/dist/components/Form/FloatLabel.svelte.d.ts +44 -0
  82. package/dist/components/Form/Form.svelte +281 -0
  83. package/dist/components/Form/Form.svelte.d.ts +54 -0
  84. package/dist/components/Form/FormField.svelte +218 -0
  85. package/dist/components/Form/FormField.svelte.d.ts +47 -0
  86. package/dist/components/Form/Input.svelte +340 -0
  87. package/dist/components/Form/Input.svelte.d.ts +79 -0
  88. package/dist/components/Form/InputSwitch.svelte +189 -0
  89. package/dist/components/Form/InputSwitch.svelte.d.ts +46 -0
  90. package/dist/components/Form/InvalidState.svelte +97 -0
  91. package/dist/components/Form/InvalidState.svelte.d.ts +37 -0
  92. package/dist/components/Form/Knob.svelte +537 -0
  93. package/dist/components/Form/Knob.svelte.d.ts +78 -0
  94. package/dist/components/Form/ListInput.svelte +469 -0
  95. package/dist/components/Form/ListInput.svelte.d.ts +70 -0
  96. package/dist/components/Form/Listbox.svelte +513 -0
  97. package/dist/components/Form/Listbox.svelte.d.ts +74 -0
  98. package/dist/components/Form/NumberInput.svelte +452 -0
  99. package/dist/components/Form/NumberInput.svelte.d.ts +82 -0
  100. package/dist/components/Form/Radio.svelte +192 -0
  101. package/dist/components/Form/Radio.svelte.d.ts +53 -0
  102. package/dist/components/Form/RadioGroup.svelte +155 -0
  103. package/dist/components/Form/RadioGroup.svelte.d.ts +48 -0
  104. package/dist/components/Form/Rating.svelte +380 -0
  105. package/dist/components/Form/Rating.svelte.d.ts +64 -0
  106. package/dist/components/Form/Select.svelte +436 -0
  107. package/dist/components/Form/Select.svelte.d.ts +49 -0
  108. package/dist/components/Form/SelectGroup.svelte +34 -0
  109. package/dist/components/Form/SelectGroup.svelte.d.ts +33 -0
  110. package/dist/components/Form/Slider.svelte +622 -0
  111. package/dist/components/Form/Slider.svelte.d.ts +73 -0
  112. package/dist/components/Form/Switch.svelte +192 -0
  113. package/dist/components/Form/Switch.svelte.d.ts +46 -0
  114. package/dist/components/Form/TextInput.svelte +274 -0
  115. package/dist/components/Form/TextInput.svelte.d.ts +74 -0
  116. package/dist/components/Form/Textarea.svelte +207 -0
  117. package/dist/components/Form/Textarea.svelte.d.ts +62 -0
  118. package/dist/components/Icon/Icon.svelte +140 -0
  119. package/dist/components/Icon/Icon.svelte.d.ts +25 -0
  120. package/dist/components/Icon/index.d.ts +1 -0
  121. package/dist/components/Icon/index.js +1 -0
  122. package/dist/components/Lazy/Lazy.svelte +158 -0
  123. package/dist/components/Lazy/Lazy.svelte.d.ts +42 -0
  124. package/dist/components/Masonry/Masonry.svelte +299 -0
  125. package/dist/components/Masonry/Masonry.svelte.d.ts +55 -0
  126. package/dist/components/Menu/Menu/Menu.svelte +65 -0
  127. package/dist/components/Menu/Menu/Menu.svelte.d.ts +17 -0
  128. package/dist/components/Menu/Menu/MenuItem.svelte +90 -0
  129. package/dist/components/Menu/Menu/MenuItem.svelte.d.ts +27 -0
  130. package/dist/components/Modal/Modal.svelte +334 -0
  131. package/dist/components/Modal/Modal.svelte.d.ts +55 -0
  132. package/dist/components/Panel/Card.svelte +141 -0
  133. package/dist/components/Panel/Card.svelte.d.ts +52 -0
  134. package/dist/components/Panel/Hero/Hero.story.md +9 -0
  135. package/dist/components/Panel/Hero/Hero.story.svelte +49 -0
  136. package/dist/components/Panel/Hero/Hero.story.svelte.d.ts +21 -0
  137. package/dist/components/Panel/Hero/Hero.svelte +24 -0
  138. package/dist/components/Panel/Hero/Hero.svelte.d.ts +32 -0
  139. package/dist/components/Panel/LazyPanel.svelte +110 -0
  140. package/dist/components/Panel/LazyPanel.svelte.d.ts +46 -0
  141. package/dist/components/Panel/Panel.svelte +205 -0
  142. package/dist/components/Panel/Panel.svelte.d.ts +23 -0
  143. package/dist/components/Progress/Progress.svelte +220 -0
  144. package/dist/components/Progress/Progress.svelte.d.ts +61 -0
  145. package/dist/components/Separator/Separator.svelte +109 -0
  146. package/dist/components/Separator/Separator.svelte.d.ts +35 -0
  147. package/dist/components/Sidebar/Sidebar.svelte +213 -0
  148. package/dist/components/Sidebar/Sidebar.svelte.d.ts +60 -0
  149. package/dist/components/Skeleton/Skeleton.svelte +170 -0
  150. package/dist/components/Skeleton/Skeleton.svelte.d.ts +48 -0
  151. package/dist/components/Stepper/Stepper.svelte +111 -0
  152. package/dist/components/Stepper/Stepper.svelte.d.ts +54 -0
  153. package/dist/components/Stepper/StepperStep.svelte +369 -0
  154. package/dist/components/Stepper/StepperStep.svelte.d.ts +63 -0
  155. package/dist/components/Table/Table.svelte +167 -0
  156. package/dist/components/Table/Table.svelte.d.ts +56 -0
  157. package/dist/components/Table/TableBody.svelte +41 -0
  158. package/dist/components/Table/TableBody.svelte.d.ts +33 -0
  159. package/dist/components/Table/TableCell.svelte +76 -0
  160. package/dist/components/Table/TableCell.svelte.d.ts +36 -0
  161. package/dist/components/Table/TableHead.svelte +41 -0
  162. package/dist/components/Table/TableHead.svelte.d.ts +32 -0
  163. package/dist/components/Table/TableHeader.svelte +148 -0
  164. package/dist/components/Table/TableHeader.svelte.d.ts +42 -0
  165. package/dist/components/Table/TableRow.svelte +99 -0
  166. package/dist/components/Table/TableRow.svelte.d.ts +40 -0
  167. package/dist/components/Tabs/Tab.svelte +145 -0
  168. package/dist/components/Tabs/Tab.svelte.d.ts +36 -0
  169. package/dist/components/Tabs/TabList.svelte +60 -0
  170. package/dist/components/Tabs/TabList.svelte.d.ts +32 -0
  171. package/dist/components/Tabs/TabPanel.svelte +118 -0
  172. package/dist/components/Tabs/TabPanel.svelte.d.ts +38 -0
  173. package/dist/components/Tabs/Tabs.svelte +287 -0
  174. package/dist/components/Tabs/Tabs.svelte.d.ts +50 -0
  175. package/dist/components/Tag/Tag.svelte +260 -0
  176. package/dist/components/Tag/Tag.svelte.d.ts +54 -0
  177. package/dist/components/Tag/TagGroup.svelte +147 -0
  178. package/dist/components/Tag/TagGroup.svelte.d.ts +62 -0
  179. package/dist/components/ThemeToggle/ThemeToggle.svelte +93 -0
  180. package/dist/components/ThemeToggle/ThemeToggle.svelte.d.ts +12 -0
  181. package/dist/components/Timeline/Timeline.svelte +144 -0
  182. package/dist/components/Timeline/Timeline.svelte.d.ts +48 -0
  183. package/dist/components/Timeline/TimelineItem.svelte +391 -0
  184. package/dist/components/Timeline/TimelineItem.svelte.d.ts +63 -0
  185. package/dist/components/Toast/Toast.svelte +313 -0
  186. package/dist/components/Toast/Toast.svelte.d.ts +44 -0
  187. package/dist/components/Toast/toastStore.d.ts +40 -0
  188. package/dist/components/Toast/toastStore.js +293 -0
  189. package/dist/components/Tooltip/Tooltip.svelte +282 -0
  190. package/dist/components/Tooltip/Tooltip.svelte.d.ts +55 -0
  191. package/dist/components/Tree/Tree.svelte +129 -0
  192. package/dist/components/Tree/Tree.svelte.d.ts +61 -0
  193. package/dist/components/Tree/TreeNode.svelte +332 -0
  194. package/dist/components/Tree/TreeNode.svelte.d.ts +55 -0
  195. package/dist/components/icons/TwintrinsicLogo.svelte +73 -0
  196. package/dist/components/icons/TwintrinsicLogo.svelte.d.ts +17 -0
  197. package/dist/components/icons/twintrinsic-source.svg +73 -0
  198. package/dist/components/icons/twintrinsic.svg +38 -0
  199. package/dist/docs/EventsTable.svelte +86 -0
  200. package/dist/docs/EventsTable.svelte.d.ts +27 -0
  201. package/dist/docs/PropsTable.svelte +103 -0
  202. package/dist/docs/PropsTable.svelte.d.ts +28 -0
  203. package/dist/docs/index.d.ts +2 -0
  204. package/dist/docs/index.js +2 -0
  205. package/dist/helpers/detectLanguage.d.ts +6 -0
  206. package/dist/helpers/detectLanguage.js +60 -0
  207. package/dist/helpers/index.d.ts +1 -0
  208. package/dist/helpers/index.js +1 -0
  209. package/dist/index.d.ts +86 -0
  210. package/dist/index.js +94 -0
  211. package/dist/twintrinsic.css +347 -0
  212. package/package.json +98 -0
@@ -0,0 +1,537 @@
1
+ <!--
2
+ @component
3
+ Knob - A circular progress slider component for selecting numeric values.
4
+ Provides an intuitive circular interface with drag interaction and keyboard controls.
5
+
6
+ Usage:
7
+ ```svelte
8
+ <Knob
9
+ name="volume"
10
+ value={50}
11
+ min={0}
12
+ max={100}
13
+ />
14
+
15
+ <Knob
16
+ name="progress"
17
+ value={75}
18
+ showValue
19
+ valueTemplate="{value}%"
20
+ size="lg"
21
+ />
22
+
23
+ <FormField label="Temperature">
24
+ <Knob
25
+ name="temperature"
26
+ value={22}
27
+ min={15}
28
+ max={30}
29
+ step={0.5}
30
+ color="var(--color-primary-500)"
31
+ />
32
+ </FormField>
33
+ ```
34
+ -->
35
+ <script>
36
+ import { getContext, onMount } from "svelte"
37
+
38
+ const {
39
+ /** @type {string} - Additional CSS classes */
40
+ class: className = "",
41
+
42
+ /** @type {string} - HTML id for accessibility */
43
+ id = crypto.randomUUID(),
44
+
45
+ /** @type {string} - Input name */
46
+ name,
47
+
48
+ /** @type {number} - Current value */
49
+ value = 0,
50
+
51
+ /** @type {number} - Minimum value */
52
+ min = 0,
53
+
54
+ /** @type {number} - Maximum value */
55
+ max = 100,
56
+
57
+ /** @type {number} - Step increment */
58
+ step = 1,
59
+
60
+ /** @type {string} - Size of the knob (sm, md, lg, xl) */
61
+ size = "md",
62
+
63
+ /** @type {boolean} - Whether the knob is disabled */
64
+ disabled = false,
65
+
66
+ /** @type {boolean} - Whether to show the current value */
67
+ showValue = false,
68
+
69
+ /** @type {string} - Template for displaying the value, use {value} as placeholder */
70
+ valueTemplate = "{value}",
71
+
72
+ /** @type {string} - Color of the progress arc */
73
+ color,
74
+
75
+ /** @type {number} - Thickness of the progress arc (1-10) */
76
+ thickness = 4,
77
+
78
+ /** @type {boolean} - Whether to show tick marks */
79
+ showTicks = false,
80
+
81
+ /** @type {(event: CustomEvent) => void} - Input event handler */
82
+ oninput,
83
+ /** @type {(event: CustomEvent) => void} - Change event handler */
84
+ onchange,
85
+
86
+ /** @type {number} - Number of tick marks to display */
87
+ tickCount = 10,
88
+
89
+ /** @type {string} - ARIA label for accessibility */
90
+ ariaLabel,
91
+ } = $props()
92
+
93
+ // Get form context if available
94
+ const formContext = getContext("form")
95
+
96
+ // Component state
97
+ let currentValue = $state(value)
98
+ let isDragging = $state(false)
99
+ let knobElement = $state()
100
+ let radius = $state(0)
101
+ let center = $state({ x: 0, y: 0 })
102
+
103
+ // Register with form if available
104
+ let fieldApi = $state()
105
+
106
+ $effect(() => {
107
+ if (formContext && name) {
108
+ fieldApi = formContext.registerField(name, value)
109
+ }
110
+ })
111
+
112
+ // Update value when form field changes
113
+ $effect(() => {
114
+ if (fieldApi) {
115
+ const formValue = fieldApi.getValue()
116
+ if (formValue !== undefined && formValue !== currentValue) {
117
+ currentValue = formValue
118
+ }
119
+ }
120
+ })
121
+
122
+ // Update internal value when prop changes
123
+ $effect(() => {
124
+ currentValue = value
125
+ })
126
+
127
+ /**
128
+ * Constrains a value to min/max bounds and applies step
129
+ * @param {number} val - Value to constrain
130
+ * @returns {number} - Constrained value
131
+ */
132
+ function constrainValue(val) {
133
+ // Apply min/max constraints
134
+ let constrained = Math.max(min, Math.min(max, val))
135
+
136
+ // Apply step
137
+ if (step !== 0) {
138
+ constrained = Math.round((constrained - min) / step) * step + min
139
+ }
140
+
141
+ return constrained
142
+ }
143
+
144
+ /**
145
+ * Converts a value to a percentage (0-100)
146
+ * @param {number} val - Value to convert
147
+ * @returns {number} - Percentage
148
+ */
149
+ function valueToPercentage(val) {
150
+ return ((val - min) / (max - min)) * 100
151
+ }
152
+
153
+ /**
154
+ * Converts a percentage to a value
155
+ * @param {number} percentage - Percentage (0-100)
156
+ * @returns {number} - Value
157
+ */
158
+ function percentageToValue(percentage) {
159
+ return (percentage / 100) * (max - min) + min
160
+ }
161
+
162
+ /**
163
+ * Formats the current value using the template
164
+ * @returns {string} - Formatted value
165
+ */
166
+ function formatValue() {
167
+ return valueTemplate.replace("{value}", currentValue.toString())
168
+ }
169
+
170
+ /**
171
+ * Calculates the SVG path for the progress arc
172
+ * @param {number} percentage - Percentage (0-100)
173
+ * @returns {string} - SVG path
174
+ */
175
+ function calculateArc(percentage) {
176
+ const r = 50 - thickness / 2 // Adjust radius for thickness
177
+ const circumference = 2 * Math.PI * r
178
+ const arcLength = (percentage / 100) * circumference
179
+ const dashArray = `${arcLength} ${circumference}`
180
+
181
+ return dashArray
182
+ }
183
+
184
+ /**
185
+ * Calculates tick positions
186
+ * @returns {Array} - Array of tick positions
187
+ */
188
+ function calculateTicks() {
189
+ const ticks = []
190
+ const r = 50 - thickness / 2 // Adjust radius for thickness
191
+
192
+ for (let i = 0; i < tickCount; i++) {
193
+ const percentage = (i / (tickCount - 1)) * 100
194
+ const angle = (percentage / 100) * 360 - 90 // -90 to start at top
195
+ const angleRad = (angle * Math.PI) / 180
196
+
197
+ const x1 = 50 + (r - 2) * Math.cos(angleRad)
198
+ const y1 = 50 + (r - 2) * Math.sin(angleRad)
199
+ const x2 = 50 + (r + 2) * Math.cos(angleRad)
200
+ const y2 = 50 + (r + 2) * Math.sin(angleRad)
201
+
202
+ ticks.push({ x1, y1, x2, y2 })
203
+ }
204
+
205
+ return ticks
206
+ }
207
+
208
+ /**
209
+ * Calculates the angle from center to a point
210
+ * @param {number} x - X coordinate
211
+ * @param {number} y - Y coordinate
212
+ * @returns {number} - Angle in degrees (0-360)
213
+ */
214
+ function calculateAngle(x, y) {
215
+ const dx = x - center.x
216
+ const dy = y - center.y
217
+
218
+ // Calculate angle in radians
219
+ let angle = Math.atan2(dy, dx)
220
+
221
+ // Convert to degrees and adjust to start from top (0 degrees)
222
+ angle = (angle * 180) / Math.PI + 90
223
+
224
+ // Ensure angle is between 0-360
225
+ if (angle < 0) angle += 360
226
+
227
+ return angle
228
+ }
229
+
230
+ /**
231
+ * Converts an angle to a percentage
232
+ * @param {number} angle - Angle in degrees (0-360)
233
+ * @returns {number} - Percentage (0-100)
234
+ */
235
+ function angleToPercentage(angle) {
236
+ return (angle / 360) * 100
237
+ }
238
+
239
+ /**
240
+ * Updates the value based on mouse/touch position
241
+ * @param {MouseEvent|TouchEvent} event - Mouse or touch event
242
+ */
243
+ function updateValueFromEvent(event) {
244
+ if (disabled) return
245
+
246
+ // Get coordinates
247
+ const clientX = event.type.includes("touch") ? event.touches[0].clientX : event.clientX
248
+ const clientY = event.type.includes("touch") ? event.touches[0].clientY : event.clientY
249
+
250
+ // Get element position
251
+ const rect = knobElement.getBoundingClientRect()
252
+ const x = clientX - rect.left
253
+ const y = clientY - rect.top
254
+
255
+ // Calculate angle and percentage
256
+ const angle = calculateAngle(x, y)
257
+ const percentage = angleToPercentage(angle)
258
+
259
+ // Update value
260
+ const newValue = constrainValue(percentageToValue(percentage))
261
+ currentValue = newValue
262
+
263
+ // Update form field if available
264
+ if (fieldApi) {
265
+ fieldApi.setValue(newValue)
266
+ }
267
+
268
+ oninput?.(new CustomEvent("input", { detail: { value: newValue } }))
269
+ }
270
+
271
+ /**
272
+ * Starts dragging
273
+ * @param {MouseEvent|TouchEvent} event - Mouse or touch event
274
+ */
275
+ function startDrag(event) {
276
+ if (disabled) return
277
+
278
+ isDragging = true
279
+ updateValueFromEvent(event)
280
+
281
+ // Add event listeners for drag
282
+ if (event.type === "mousedown") {
283
+ document.addEventListener("mousemove", updateValueFromEvent)
284
+ document.addEventListener("mouseup", stopDrag)
285
+ } else if (event.type === "touchstart") {
286
+ document.addEventListener("touchmove", updateValueFromEvent)
287
+ document.addEventListener("touchend", stopDrag)
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Stops dragging
293
+ */
294
+ function stopDrag() {
295
+ if (!isDragging) return
296
+
297
+ isDragging = false
298
+
299
+ // Remove event listeners
300
+ document.removeEventListener("mousemove", updateValueFromEvent)
301
+ document.removeEventListener("mouseup", stopDrag)
302
+ document.removeEventListener("touchmove", updateValueFromEvent)
303
+ document.removeEventListener("touchend", stopDrag)
304
+
305
+ onchange?.(new CustomEvent("change", { detail: { value: currentValue } }))
306
+ }
307
+
308
+ /**
309
+ * Handles keyboard navigation
310
+ * @param {KeyboardEvent} event - Keydown event
311
+ */
312
+ function handleKeydown(event) {
313
+ if (disabled) return
314
+
315
+ let newValue = currentValue
316
+
317
+ switch (event.key) {
318
+ case "ArrowUp":
319
+ case "ArrowRight":
320
+ event.preventDefault()
321
+ newValue = constrainValue(currentValue + step)
322
+ break
323
+
324
+ case "ArrowDown":
325
+ case "ArrowLeft":
326
+ event.preventDefault()
327
+ newValue = constrainValue(currentValue - step)
328
+ break
329
+
330
+ case "Home":
331
+ event.preventDefault()
332
+ newValue = min
333
+ break
334
+
335
+ case "End":
336
+ event.preventDefault()
337
+ newValue = max
338
+ break
339
+
340
+ case "PageUp":
341
+ event.preventDefault()
342
+ newValue = constrainValue(currentValue + step * 10)
343
+ break
344
+
345
+ case "PageDown":
346
+ event.preventDefault()
347
+ newValue = constrainValue(currentValue - step * 10)
348
+ break
349
+ }
350
+
351
+ if (newValue !== currentValue) {
352
+ currentValue = newValue
353
+
354
+ // Update form field if available
355
+ if (fieldApi) {
356
+ fieldApi.setValue(newValue)
357
+ }
358
+
359
+ dispatch("input", { value: newValue })
360
+ dispatch("change", { value: newValue })
361
+ }
362
+ }
363
+
364
+ // Initialize component
365
+ onMount(() => {
366
+ if (knobElement) {
367
+ const rect = knobElement.getBoundingClientRect()
368
+ radius = rect.width / 2
369
+ center = { x: radius, y: radius }
370
+ }
371
+
372
+ return () => {
373
+ // Clean up event listeners
374
+ document.removeEventListener("mousemove", updateValueFromEvent)
375
+ document.removeEventListener("mouseup", stopDrag)
376
+ document.removeEventListener("touchmove", updateValueFromEvent)
377
+ document.removeEventListener("touchend", stopDrag)
378
+ }
379
+ })
380
+
381
+ // Computed values
382
+ const percentage = $derived(valueToPercentage(currentValue))
383
+ const arcDashArray = $derived(calculateArc(percentage))
384
+ const ticks = $derived(showTicks ? calculateTicks() : [])
385
+
386
+ // Determine size classes
387
+ const sizeClasses = $derived(
388
+ {
389
+ sm: "w-16 h-16",
390
+ md: "w-24 h-24",
391
+ lg: "w-32 h-32",
392
+ xl: "w-40 h-40",
393
+ }[size] || "w-24 h-24"
394
+ )
395
+
396
+ // Determine font size classes
397
+ const fontSizeClasses = $derived(
398
+ {
399
+ sm: "text-xs",
400
+ md: "text-sm",
401
+ lg: "text-base",
402
+ xl: "text-lg",
403
+ }[size] || "text-sm"
404
+ )
405
+ </script>
406
+
407
+ <div
408
+ {id}
409
+ class="knob {sizeClasses} {className}"
410
+ class:disabled
411
+ tabindex={disabled ? undefined : 0}
412
+ role="slider"
413
+ aria-valuemin={min}
414
+ aria-valuemax={max}
415
+ aria-valuenow={currentValue}
416
+ aria-label={ariaLabel || name}
417
+ onkeydown={handleKeydown}
418
+ bind:this={knobElement}
419
+ >
420
+ <svg
421
+ viewBox="0 0 100 100"
422
+ class="knob-svg"
423
+ onmousedown={startDrag}
424
+ ontouchstart={startDrag}
425
+ >
426
+ <!-- Background circle -->
427
+ <circle
428
+ cx="50"
429
+ cy="50"
430
+ r={50 - thickness / 2}
431
+ class="knob-track"
432
+ stroke-width={thickness}
433
+ />
434
+
435
+ <!-- Progress arc -->
436
+ <circle
437
+ cx="50"
438
+ cy="50"
439
+ r={50 - thickness / 2}
440
+ class="knob-progress"
441
+ stroke-width={thickness}
442
+ stroke-dasharray={arcDashArray}
443
+ style={color ? `stroke: ${color}` : ''}
444
+ transform="rotate(-90 50 50)"
445
+ />
446
+
447
+ <!-- Tick marks -->
448
+ {#if showTicks}
449
+ {#each ticks as tick}
450
+ <line
451
+ x1={tick.x1}
452
+ y1={tick.y1}
453
+ x2={tick.x2}
454
+ y2={tick.y2}
455
+ class="knob-tick"
456
+ />
457
+ {/each}
458
+ {/if}
459
+
460
+ <!-- Indicator dot -->
461
+ {#if !showValue}
462
+ <circle
463
+ cx={50 + (50 - thickness / 2 - 3) * Math.cos((percentage / 100 * 360 - 90) * Math.PI / 180)}
464
+ cy={50 + (50 - thickness / 2 - 3) * Math.sin((percentage / 100 * 360 - 90) * Math.PI / 180)}
465
+ r="3"
466
+ class="knob-indicator"
467
+ style={color ? `fill: ${color}` : ''}
468
+ />
469
+ {/if}
470
+
471
+ <!-- Value text -->
472
+ {#if showValue}
473
+ <text
474
+ x="50"
475
+ y="50"
476
+ text-anchor="middle"
477
+ dominant-baseline="middle"
478
+ class="knob-value {fontSizeClasses}"
479
+ >
480
+ {formatValue()}
481
+ </text>
482
+ {/if}
483
+ </svg>
484
+
485
+ <!-- Hidden input for form submission -->
486
+ <input
487
+ type="hidden"
488
+ {name}
489
+ value={currentValue}
490
+ {disabled}
491
+ />
492
+ </div>
493
+
494
+ <style>
495
+ @reference "../../twintrinsic.css";
496
+
497
+ .knob {
498
+ @apply relative inline-block;
499
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 rounded-full;
500
+ }
501
+
502
+ .knob.disabled {
503
+ @apply opacity-50 cursor-not-allowed;
504
+ }
505
+
506
+ .knob-svg {
507
+ @apply w-full h-full cursor-pointer;
508
+ }
509
+
510
+ .knob.disabled .knob-svg {
511
+ @apply cursor-not-allowed;
512
+ }
513
+
514
+ .knob-track {
515
+ @apply fill-none stroke-border dark:stroke-border;
516
+ }
517
+
518
+ .knob-progress {
519
+ @apply fill-none stroke-primary-500 dark:stroke-primary-400;
520
+ stroke-linecap: round;
521
+ transform-origin: center;
522
+ stroke-dashoffset: 0;
523
+ }
524
+
525
+ .knob-indicator {
526
+ @apply fill-primary-500 dark:fill-primary-400;
527
+ }
528
+
529
+ .knob-tick {
530
+ @apply stroke-border dark:stroke-border;
531
+ stroke-width: 1;
532
+ }
533
+
534
+ .knob-value {
535
+ @apply fill-text dark:fill-text font-medium;
536
+ }
537
+ </style>
@@ -0,0 +1,78 @@
1
+ export default Knob;
2
+ type Knob = {
3
+ $on?(type: string, callback: (e: any) => void): () => void;
4
+ $set?(props: Partial<$$ComponentProps>): void;
5
+ };
6
+ /**
7
+ * Knob - A circular progress slider component for selecting numeric values.
8
+ * Provides an intuitive circular interface with drag interaction and keyboard controls.
9
+ *
10
+ * Usage:
11
+ * ```svelte
12
+ * <Knob
13
+ * name="volume"
14
+ * value={50}
15
+ * min={0}
16
+ * max={100}
17
+ * />
18
+ *
19
+ * <Knob
20
+ * name="progress"
21
+ * value={75}
22
+ * showValue
23
+ * valueTemplate="{value}%"
24
+ * size="lg"
25
+ * />
26
+ *
27
+ * <FormField label="Temperature">
28
+ * <Knob
29
+ * name="temperature"
30
+ * value={22}
31
+ * min={15}
32
+ * max={30}
33
+ * step={0.5}
34
+ * color="var(--color-primary-500)"
35
+ * />
36
+ * </FormField>
37
+ * ```
38
+ */
39
+ declare const Knob: import("svelte").Component<{
40
+ class?: string;
41
+ id?: any;
42
+ name: any;
43
+ value?: number;
44
+ min?: number;
45
+ max?: number;
46
+ step?: number;
47
+ size?: string;
48
+ disabled?: boolean;
49
+ showValue?: boolean;
50
+ valueTemplate?: string;
51
+ color: any;
52
+ thickness?: number;
53
+ showTicks?: boolean;
54
+ oninput: any;
55
+ onchange: any;
56
+ tickCount?: number;
57
+ ariaLabel: any;
58
+ }, {}, "">;
59
+ type $$ComponentProps = {
60
+ class?: string;
61
+ id?: any;
62
+ name: any;
63
+ value?: number;
64
+ min?: number;
65
+ max?: number;
66
+ step?: number;
67
+ size?: string;
68
+ disabled?: boolean;
69
+ showValue?: boolean;
70
+ valueTemplate?: string;
71
+ color: any;
72
+ thickness?: number;
73
+ showTicks?: boolean;
74
+ oninput: any;
75
+ onchange: any;
76
+ tickCount?: number;
77
+ ariaLabel: any;
78
+ };