@ixfx/components 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/bundle/index.d.ts +1086 -603
  2. package/bundle/index.d.ts.map +1 -1
  3. package/bundle/index.js +16527 -14258
  4. package/bundle/index.js.map +1 -1
  5. package/bundle/style.css +14 -4
  6. package/dist/ac-text.d.ts.map +1 -1
  7. package/dist/ac-text.js +11 -6
  8. package/dist/ac-text.js.map +1 -1
  9. package/dist/{button-DMt5XWUK.js → button-C3fLMKUv.js} +4 -4
  10. package/dist/{button-DMt5XWUK.js.map → button-C3fLMKUv.js.map} +1 -1
  11. package/dist/button.d.ts.map +1 -1
  12. package/dist/button.js +1 -1
  13. package/dist/checkbox.d.ts +2 -2
  14. package/dist/checkbox.d.ts.map +1 -1
  15. package/dist/checkbox.js +14 -8
  16. package/dist/checkbox.js.map +1 -1
  17. package/dist/{colour-C3MQIjFJ-BLXg8W3U.js → colour-C3MQIjFJ-vRCdr8W-.js} +146 -552
  18. package/dist/colour-C3MQIjFJ-vRCdr8W-.js.map +1 -0
  19. package/dist/{colour-picker-K6zu82lm.js → colour-picker-0sgAha1T.js} +14 -9
  20. package/dist/colour-picker-0sgAha1T.js.map +1 -0
  21. package/dist/colour-picker.d.ts +1 -1
  22. package/dist/colour-picker.js +1 -1
  23. package/dist/crumbs.d.ts +1 -1
  24. package/dist/crumbs.d.ts.map +1 -1
  25. package/dist/crumbs.js +11 -6
  26. package/dist/crumbs.js.map +1 -1
  27. package/dist/data-display.d.ts +9 -4
  28. package/dist/data-display.d.ts.map +1 -1
  29. package/dist/data-display.js +270 -43
  30. package/dist/data-display.js.map +1 -1
  31. package/dist/data-provider-DgI909FP.js +23 -0
  32. package/dist/data-provider-DgI909FP.js.map +1 -0
  33. package/dist/{decorate-D7rC1gLP.js → decorate-DdjvVHS-.js} +2 -2
  34. package/dist/{decorate-D7rC1gLP.js.map → decorate-DdjvVHS-.js.map} +1 -1
  35. package/dist/{defaults-C7elhuyJ.js → defaults-C3b9OWJD.js} +2 -2
  36. package/dist/{defaults-C7elhuyJ.js.map → defaults-C3b9OWJD.js.map} +1 -1
  37. package/dist/{dist-TAGYwaju.js → dist-CA13FvMn.js} +7 -66
  38. package/dist/dist-CA13FvMn.js.map +1 -0
  39. package/dist/editable-label.d.ts.map +1 -1
  40. package/dist/editable-label.js +3 -2
  41. package/dist/editable-label.js.map +1 -1
  42. package/dist/fallbacks-pNMOMx38.js +168 -0
  43. package/dist/fallbacks-pNMOMx38.js.map +1 -0
  44. package/dist/{hex-editor-DlfoA65y.d.ts → hex-editor-CMIxtNgT.d.ts} +1 -1
  45. package/dist/hex-editor-CMIxtNgT.d.ts.map +1 -0
  46. package/dist/{hex-editor-CsN_ySkH.js → hex-editor-WZrjRtvL.js} +3 -3
  47. package/dist/{hex-editor-CsN_ySkH.js.map → hex-editor-WZrjRtvL.js.map} +1 -1
  48. package/dist/hex.d.ts +1 -1
  49. package/dist/hex.js +1 -1
  50. package/dist/{highlight-TPbSHruI.js → highlight-B-eQrhhb.js} +1 -1
  51. package/dist/{highlight-TPbSHruI.js.map → highlight-B-eQrhhb.js.map} +1 -1
  52. package/dist/{icon-CeTDJhC7.d.ts → icon-Bd5BiU4b.d.ts} +2 -2
  53. package/dist/icon-Bd5BiU4b.d.ts.map +1 -0
  54. package/dist/{icon-9NWTXtE1.js → icon-Ndo40kNO.js} +6 -5
  55. package/dist/icon-Ndo40kNO.js.map +1 -0
  56. package/dist/icons.d.ts +2 -2
  57. package/dist/icons.js +3 -3
  58. package/dist/{incr-search-DSKKsk6w.js → incr-search-CeERfijK.js} +2 -2
  59. package/dist/{incr-search-DSKKsk6w.js.map → incr-search-CeERfijK.js.map} +1 -1
  60. package/dist/incr-search.d.ts +1 -1
  61. package/dist/incr-search.js +2 -2
  62. package/dist/{index-hXFn14tM.d.ts → index-BMjri2-S.d.ts} +2 -2
  63. package/dist/{index-hXFn14tM.d.ts.map → index-BMjri2-S.d.ts.map} +1 -1
  64. package/dist/{index-DSlPeWLK.d.ts → index-CWpNcQKE.d.ts} +5 -5
  65. package/dist/index-CWpNcQKE.d.ts.map +1 -0
  66. package/dist/{index--hrxPBkQ.d.ts → index-CowKi2mo.d.ts} +2 -1
  67. package/dist/index-CowKi2mo.d.ts.map +1 -0
  68. package/dist/{index-B1hNR7Z9.d.ts → index-D87kRcEs.d.ts} +1 -1
  69. package/dist/{index-B1hNR7Z9.d.ts.map → index-D87kRcEs.d.ts.map} +1 -1
  70. package/dist/{index-CnL9krsH.d.ts → index-DgXpCg_G.d.ts} +32 -26
  71. package/dist/index-DgXpCg_G.d.ts.map +1 -0
  72. package/dist/{index-DrQjA8Gs.d.ts → index-s_IhSnwc.d.ts} +2 -2
  73. package/dist/index-s_IhSnwc.d.ts.map +1 -0
  74. package/dist/index.d.ts +432 -121
  75. package/dist/index.d.ts.map +1 -1
  76. package/dist/index.js +2370 -1707
  77. package/dist/index.js.map +1 -1
  78. package/dist/interaction-D6efrBLo.js +1 -0
  79. package/dist/{keyboard-De_vOHLg.js → keyboard-CimiEskD.js} +1 -1
  80. package/dist/{keyboard-De_vOHLg.js.map → keyboard-CimiEskD.js.map} +1 -1
  81. package/dist/{labelled-input-base-CqPL5rKm.js → labelled-input-base-9K_w3oLj.js} +2 -2
  82. package/dist/{labelled-input-base-CqPL5rKm.js.map → labelled-input-base-9K_w3oLj.js.map} +1 -1
  83. package/dist/{labelled-input-base-D-Y7agky.d.ts → labelled-input-base-ORb1rGq6.d.ts} +1 -1
  84. package/dist/labelled-input-base-ORb1rGq6.d.ts.map +1 -0
  85. package/dist/labelled-radial-input.d.ts +2 -2
  86. package/dist/labelled-radial-input.d.ts.map +1 -1
  87. package/dist/labelled-radial-input.js +7 -6
  88. package/dist/labelled-radial-input.js.map +1 -1
  89. package/dist/labelled-range-input.d.ts +1 -1
  90. package/dist/labelled-range-input.js +2 -2
  91. package/dist/led.d.ts +1 -1
  92. package/dist/led.d.ts.map +1 -1
  93. package/dist/led.js +2 -2
  94. package/dist/led.js.map +1 -1
  95. package/dist/{menu-item-CKRfP0pN.js → menu-item-BrIuORYD.js} +20 -10
  96. package/dist/menu-item-BrIuORYD.js.map +1 -0
  97. package/dist/{menu-item-ClQF2DKN.d.ts → menu-item-COx-Azgj.d.ts} +1 -1
  98. package/dist/menu-item-COx-Azgj.d.ts.map +1 -0
  99. package/dist/{menu-DJJuCm-5.js → menu-vjoiHZ13.js} +30 -17
  100. package/dist/menu-vjoiHZ13.js.map +1 -0
  101. package/dist/menu.d.ts +2 -2
  102. package/dist/menu.js +2 -2
  103. package/dist/miller.d.ts +2 -2
  104. package/dist/miller.d.ts.map +1 -1
  105. package/dist/miller.js +18 -11
  106. package/dist/miller.js.map +1 -1
  107. package/dist/narrowed-text.d.ts.map +1 -1
  108. package/dist/narrowed-text.js +7 -5
  109. package/dist/narrowed-text.js.map +1 -1
  110. package/dist/panel.d.ts +1 -1
  111. package/dist/panel.d.ts.map +1 -1
  112. package/dist/panel.js +5 -4
  113. package/dist/panel.js.map +1 -1
  114. package/dist/plots.d.ts +2 -2
  115. package/dist/plots.js +2 -2
  116. package/dist/polar-pad.d.ts +1 -1
  117. package/dist/polar-pad.d.ts.map +1 -1
  118. package/dist/polar-pad.js +10 -4
  119. package/dist/polar-pad.js.map +1 -1
  120. package/dist/{popup-ImtS9KnS.js → popup-DiWMxsWj.js} +1 -1
  121. package/dist/{popup-ImtS9KnS.js.map → popup-DiWMxsWj.js.map} +1 -1
  122. package/dist/prominence-DlFShic3.js +104 -0
  123. package/dist/prominence-DlFShic3.js.map +1 -0
  124. package/dist/{radial-input-LUSBEycd.d.ts → radial-input-CpQPs1Mp.d.ts} +1 -1
  125. package/dist/radial-input-CpQPs1Mp.d.ts.map +1 -0
  126. package/dist/{radial-input-DZA6WaFu.js → radial-input-DZ7eQCcM.js} +11 -6
  127. package/dist/radial-input-DZ7eQCcM.js.map +1 -0
  128. package/dist/radial-input.d.ts +1 -1
  129. package/dist/radial-input.js +1 -1
  130. package/dist/range-input.d.ts +1 -1
  131. package/dist/range-input.d.ts.map +1 -1
  132. package/dist/range-input.js +9 -4
  133. package/dist/range-input.js.map +1 -1
  134. package/dist/range.d.ts +1 -1
  135. package/dist/range.d.ts.map +1 -1
  136. package/dist/range.js +5 -3
  137. package/dist/range.js.map +1 -1
  138. package/dist/{registry-Q2gYQCHM.js → registry-CkW7q09Q.js} +1 -1
  139. package/dist/{registry-Q2gYQCHM.js.map → registry-CkW7q09Q.js.map} +1 -1
  140. package/dist/selecthorizontal.d.ts +1 -1
  141. package/dist/selecthorizontal.d.ts.map +1 -1
  142. package/dist/selecthorizontal.js +4 -3
  143. package/dist/selecthorizontal.js.map +1 -1
  144. package/dist/snap-container.d.ts.map +1 -1
  145. package/dist/snap-container.js +1 -1
  146. package/dist/split-layout.d.ts +1 -1
  147. package/dist/split-layout.d.ts.map +1 -1
  148. package/dist/split-layout.js +6 -5
  149. package/dist/split-layout.js.map +1 -1
  150. package/dist/style.css +14 -4
  151. package/dist/swipe.d.ts.map +1 -1
  152. package/dist/swipe.js +9 -4
  153. package/dist/swipe.js.map +1 -1
  154. package/dist/{tab-list-BnqISNvO.d.ts → tab-list-DLzb-m-I.d.ts} +2 -2
  155. package/dist/tab-list-DLzb-m-I.d.ts.map +1 -0
  156. package/dist/tabs.d.ts +1 -1
  157. package/dist/tabs.js +10 -4
  158. package/dist/tabs.js.map +1 -1
  159. package/dist/{tickled-controller-Cq1Va0rR.d.ts → tickled-controller-BjYCzGrU.d.ts} +2 -2
  160. package/dist/tickled-controller-BjYCzGrU.d.ts.map +1 -0
  161. package/dist/{tickled-styles-Bg3QbcrD.js → tickled-styles-fNDdqf6l.js} +1 -17
  162. package/dist/{tickled-styles-Bg3QbcrD.js.map → tickled-styles-fNDdqf6l.js.map} +1 -1
  163. package/dist/{timeline-Cv75Th2U.js → timeline-2OFtN4Ta.js} +1437 -36
  164. package/dist/timeline-2OFtN4Ta.js.map +1 -0
  165. package/dist/timeline.d.ts +1 -1
  166. package/dist/timeline.js +1 -1
  167. package/dist/{tooltip-DMxmBNky.js → tooltip-CJt3WLmN.js} +1 -1
  168. package/dist/{tooltip-DMxmBNky.js.map → tooltip-CJt3WLmN.js.map} +1 -1
  169. package/dist/{tooltip-CwGfb4lp.d.ts → tooltip-RGVIwHrZ.d.ts} +1 -1
  170. package/dist/{tooltip-CwGfb4lp.d.ts.map → tooltip-RGVIwHrZ.d.ts.map} +1 -1
  171. package/dist/{tree-CvvKbO-f.js → tree-CJWpMUvD.js} +113 -103
  172. package/dist/tree-CJWpMUvD.js.map +1 -0
  173. package/dist/{tree-component-D8Bg48tt.d.ts → tree-component-BcO26Xvt.d.ts} +65 -3
  174. package/dist/tree-component-BcO26Xvt.d.ts.map +1 -0
  175. package/dist/tree.d.ts +15 -15
  176. package/dist/tree.d.ts.map +1 -1
  177. package/dist/tree.js +1 -1
  178. package/dist/{types-BKaqnpwx.d.ts → types-DSfbtHs3.d.ts} +1 -1
  179. package/dist/types-DSfbtHs3.d.ts.map +1 -0
  180. package/dist/{xy-axis-DL0tfJDr.js → xy-axis-CK64haaK.js} +1831 -359
  181. package/dist/xy-axis-CK64haaK.js.map +1 -0
  182. package/dist/{xy-axis-D9uwpGTT.d.ts → xy-axis-McdUpcYr.d.ts} +102 -3
  183. package/dist/xy-axis-McdUpcYr.d.ts.map +1 -0
  184. package/dist/xy-pad.d.ts +1 -1
  185. package/dist/xy-pad.d.ts.map +1 -1
  186. package/dist/xy-pad.js +11 -6
  187. package/dist/xy-pad.js.map +1 -1
  188. package/docs-user/README.md +45 -0
  189. package/docs-user/ac-text.md +67 -0
  190. package/docs-user/ac-token.md +187 -0
  191. package/docs-user/button.md +355 -0
  192. package/docs-user/checkbox.md +62 -0
  193. package/docs-user/colour-picker.md +237 -0
  194. package/docs-user/crumbs.md +66 -0
  195. package/docs-user/data-display.md +98 -0
  196. package/docs-user/editable-label.md +244 -0
  197. package/docs-user/grouped-item-lister.md +424 -0
  198. package/docs-user/icons.md +191 -0
  199. package/docs-user/incr-search.md +94 -0
  200. package/docs-user/labelled-radial-input.md +204 -0
  201. package/docs-user/labelled-range-input.md +194 -0
  202. package/docs-user/led.md +40 -0
  203. package/docs-user/menu.md +319 -0
  204. package/docs-user/miller.md +453 -0
  205. package/docs-user/narrowed-text.md +72 -0
  206. package/docs-user/panel.md +250 -0
  207. package/docs-user/plots.md +361 -0
  208. package/docs-user/polar-pad.md +228 -0
  209. package/docs-user/radial-input.md +559 -0
  210. package/docs-user/range-input.md +264 -0
  211. package/docs-user/range.md +12 -0
  212. package/docs-user/selecthorizontal.md +32 -0
  213. package/docs-user/snap-container.md +132 -0
  214. package/docs-user/split-layout.md +232 -0
  215. package/docs-user/swipe.md +44 -0
  216. package/docs-user/tabs.md +217 -0
  217. package/docs-user/timeline.md +116 -0
  218. package/docs-user/transitory-label.md +120 -0
  219. package/docs-user/tree.md +592 -0
  220. package/docs-user/user-catalog.md +165 -0
  221. package/docs-user/user-theming.md +379 -0
  222. package/docs-user/util.md +122 -0
  223. package/docs-user/vertical-list.md +351 -0
  224. package/docs-user/xy-pad.md +94 -0
  225. package/package.json +19 -18
  226. package/dist/colour-C3MQIjFJ-BLXg8W3U.js.map +0 -1
  227. package/dist/colour-picker-K6zu82lm.js.map +0 -1
  228. package/dist/dist-BX0OVIm7.js +0 -1290
  229. package/dist/dist-BX0OVIm7.js.map +0 -1
  230. package/dist/dist-TAGYwaju.js.map +0 -1
  231. package/dist/hex-editor-DlfoA65y.d.ts.map +0 -1
  232. package/dist/icon-9NWTXtE1.js.map +0 -1
  233. package/dist/icon-CeTDJhC7.d.ts.map +0 -1
  234. package/dist/index--hrxPBkQ.d.ts.map +0 -1
  235. package/dist/index-CnL9krsH.d.ts.map +0 -1
  236. package/dist/index-DSlPeWLK.d.ts.map +0 -1
  237. package/dist/index-DrQjA8Gs.d.ts.map +0 -1
  238. package/dist/interaction-DG7X1rhI.js +0 -1
  239. package/dist/labelled-input-base-D-Y7agky.d.ts.map +0 -1
  240. package/dist/menu-DJJuCm-5.js.map +0 -1
  241. package/dist/menu-item-CKRfP0pN.js.map +0 -1
  242. package/dist/menu-item-ClQF2DKN.d.ts.map +0 -1
  243. package/dist/prominence--ic9uWBl.js +0 -98
  244. package/dist/prominence--ic9uWBl.js.map +0 -1
  245. package/dist/radial-input-DZA6WaFu.js.map +0 -1
  246. package/dist/radial-input-LUSBEycd.d.ts.map +0 -1
  247. package/dist/tab-list-BnqISNvO.d.ts.map +0 -1
  248. package/dist/tickled-controller-Cq1Va0rR.d.ts.map +0 -1
  249. package/dist/timeline-Cv75Th2U.js.map +0 -1
  250. package/dist/tree-CvvKbO-f.js.map +0 -1
  251. package/dist/tree-component-D8Bg48tt.d.ts.map +0 -1
  252. package/dist/types-BKaqnpwx.d.ts.map +0 -1
  253. package/dist/xy-axis-D9uwpGTT.d.ts.map +0 -1
  254. package/dist/xy-axis-DL0tfJDr.js.map +0 -1
  255. /package/bundle/{chunk-pbuEa-1d.js → chunk-D7D4PA-g.js} +0 -0
  256. /package/dist/{chunk-pbuEa-1d.js → chunk-D7D4PA-g.js} +0 -0
@@ -0,0 +1,559 @@
1
+ # ixfx-radial-input
2
+
3
+ A rotary dial input component inspired by music production interfaces (Ableton Live, synthesiser knobs). Renders as a flattened arc SVG and responds to drag and keyboard input.
4
+
5
+ ---
6
+
7
+ ## Basic Usage
8
+
9
+ ```html
10
+ <ixfx-radial-input min="0" max="1" value="0.5"></ixfx-radial-input>
11
+ ```
12
+
13
+ The component defaults to `100px` wide. Set width (and optionally height) via CSS:
14
+
15
+ ```html
16
+ <ixfx-radial-input min="0" max="100" value="50" style="width: 120px;"></ixfx-radial-input>
17
+ ```
18
+
19
+ To fill a container:
20
+
21
+ ```html
22
+ <div style="width: 80px; height: 80px;">
23
+ <ixfx-radial-input min="0" max="1" value="0.5" style="width: 100%; height: 100%;"></ixfx-radial-input>
24
+ </div>
25
+ ```
26
+
27
+ ---
28
+
29
+ ## Attributes & Properties
30
+
31
+ | Attribute / Property | Type | Default | Description |
32
+ |---|---|---|---|
33
+ | `value` | `number` | `0` | Current value, within `min`…`max` |
34
+ | `min` | `number` | `0` | Minimum value |
35
+ | `max` | `number` | `100` | Maximum value |
36
+ | `step` | `number` | `undefined` | Keyboard step size. If unset, defaults to 20% of range |
37
+ | `stops` | `number` | `undefined` | Quantise to N discrete positions. Also sets keyboard step size |
38
+ | `value-mode` | `"range"` \| `"bipolar"` \| `"unipolar"` | `"range"` | Controls how the value arc is drawn (see [Value Modes](#value-modes)) |
39
+ | `marker-style` | `"needle"` \| `"dot"` \| `"none"` | `"needle"` | Visual style of the value indicator |
40
+ | `flatten` | `number` | `0.3` | Proportion of circle to hide at the top (0 = full circle, 0.5 = semicircle, 1 = nothing). Controls vertical footprint |
41
+ | `radius` | `number` | `45` | Dial radius in viewBox units (10–50). Clamped automatically |
42
+ | `wedge` | `boolean` | `false` | Enable a filled pie-slice behind the arc |
43
+ | `disabled` | `boolean` | `false` | Disables all interaction. Reflected as attribute |
44
+
45
+ ### Sizing
46
+
47
+ Size via CSS `width`/`height`. The component's height adapts automatically to the `flatten` value to minimise whitespace — it tracks how much of the circle is visible and subtracts the hidden portion. You can override the height directly.
48
+
49
+ ```html
50
+ <!-- Small -->
51
+ <ixfx-radial-input style="width: 60px;" min="0" max="1" value="0.4"></ixfx-radial-input>
52
+
53
+ <!-- Large -->
54
+ <ixfx-radial-input style="width: 200px;" min="0" max="1" value="0.4"></ixfx-radial-input>
55
+ ```
56
+
57
+ ### Disabled state
58
+
59
+ ```html
60
+ <ixfx-radial-input disabled min="0" max="1" value="0.7"></ixfx-radial-input>
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Interaction
66
+
67
+ **Mouse / touch:** drag vertically. Dragging upward increases the value. Full min-to-max range spans `2×` the component's rendered height.
68
+
69
+ **Shift + drag:** fine control — sensitivity reduced to 1/10th (`KEY_STEP_DIVISOR = 10`).
70
+
71
+ **Keyboard** (when focused):
72
+
73
+ | Key | Action |
74
+ |---|---|
75
+ | `ArrowUp` / `ArrowRight` | Increase by step |
76
+ | `ArrowDown` / `ArrowLeft` | Decrease by step |
77
+ | `Home` | Set to minimum |
78
+ | `End` | Set to maximum |
79
+ | `Shift` + arrows | Fine step (÷10) |
80
+
81
+ Step size priority: `stops` > explicit `step` > 20% of range.
82
+
83
+ ---
84
+
85
+ ## Value Modes
86
+
87
+ ### `range` (default)
88
+
89
+ Arc sweeps from the minimum position clockwise to the current value. Classic knob/potentiometer behaviour.
90
+
91
+ ```html
92
+ <ixfx-radial-input value-mode="range" min="0" max="1" value="0.7"></ixfx-radial-input>
93
+ ```
94
+
95
+ ### `bipolar`
96
+
97
+ Arc pivots from the top-centre (midpoint). Values below the midpoint of `min`…`max` sweep left; values above sweep right. A midpoint marker is drawn at the top. Suited for pan, pitch, or any centred parameter.
98
+
99
+ ```html
100
+ <ixfx-radial-input value-mode="bipolar" min="-1" max="1" value="0.5"></ixfx-radial-input>
101
+ ```
102
+
103
+ ### `unipolar`
104
+
105
+ Arc spreads symmetrically from the top-centre in both directions. Suited for width or intensity parameters where the visual weight is centred. A midpoint marker is drawn at the top.
106
+
107
+ ```html
108
+ <ixfx-radial-input value-mode="unipolar" min="0" max="1" value="0.6"></ixfx-radial-input>
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Stepping & Quantisation
114
+
115
+ `step` controls keyboard navigation step size:
116
+
117
+ ```html
118
+ <ixfx-radial-input min="0" max="1" value="0.5" step="0.2"></ixfx-radial-input>
119
+ ```
120
+
121
+ `stops` quantises the value to N equally-spaced positions. Drag and keyboard input both snap:
122
+
123
+ ```html
124
+ <!-- 5 discrete stops: 0, 0.25, 0.5, 0.75, 1.0 -->
125
+ <ixfx-radial-input min="0" max="1" value="0.5" stops="5"></ixfx-radial-input>
126
+
127
+ <!-- 10 stops across 0-100: snaps to 0, 10, 20 … 100 -->
128
+ <ixfx-radial-input min="0" max="100" value="50" stops="10"></ixfx-radial-input>
129
+ ```
130
+
131
+ When `stops` is set it also governs the keyboard step (one stop per keypress).
132
+
133
+ ---
134
+
135
+ ## Events
136
+
137
+ Both events bubble and are `composed` (cross shadow-DOM boundaries).
138
+
139
+ ### `input`
140
+
141
+ Fired continuously during user interaction (every pointer/key event that changes the value). Use for live updates.
142
+
143
+ ```ts
144
+ import type { RadialInputInputEvent } from 'ixfx-radial-input/types';
145
+
146
+ dial.addEventListener('input', (e: RadialInputInputEvent) => {
147
+ const { value, valueRelative } = e.detail;
148
+ // value: number in min..max range
149
+ // valueRelative: number 0..1
150
+ });
151
+ ```
152
+
153
+ ### `change`
154
+
155
+ Fired after the user finishes interacting (debounced 150 ms). Equivalent to the native `<input type="range">` change semantics. Also fires immediately for programmatic value changes.
156
+
157
+ ```ts
158
+ import type { RadialInputChangeEvent } from 'ixfx-radial-input/types';
159
+
160
+ dial.addEventListener('change', (e: RadialInputChangeEvent) => {
161
+ const { value, valueRelative, previousValue } = e.detail;
162
+ });
163
+ ```
164
+
165
+ **Note:** `input` fires only for user-initiated changes. Programmatic `dial.value = x` triggers only `change`.
166
+
167
+ ---
168
+
169
+ ## CSS Custom Properties
170
+
171
+ ### Dial outline
172
+
173
+ | Variable | Default | Description |
174
+ |---|---|---|
175
+ | `--dial-border-color` | `var(--surface-4)` | Colour of the track border arc |
176
+ | `--dial-border-width` | `2` | Stroke width of the border arc (SVG units) |
177
+ | `--dial-fill` | `transparent` | Fill colour of the dial face |
178
+
179
+ ### Lanes
180
+
181
+ Lanes are arcs drawn around the dial. The built-in lanes are `track` (the grey background arc) and `value` (the accent-coloured progress arc).
182
+
183
+ | Variable | Default | Description |
184
+ |---|---|---|
185
+ | `--lane-track-color` | `var(--surface-4)` | Colour of the `track` lane |
186
+ | `--lane-value-color` | `var(--accent)` | Colour of the `value` lane |
187
+ | `--lane-color` | `var(--accent)` | Fallback colour for custom lanes |
188
+ | `--lane-<key>-color` | — | Per-lane colour override (e.g. `--lane-sweep-color`) |
189
+ | `--lane-<key>-width` | — | Per-lane width override |
190
+ | `--lane-dasharray` | `none` | SVG `stroke-dasharray` applied to all lanes |
191
+ | `--lane-dashoffset` | `0` | SVG `stroke-dashoffset` — animate for a marching-ants effect |
192
+
193
+ Dashed lanes:
194
+
195
+ ```html
196
+ <!-- 8px dashes, 4px gaps -->
197
+ <ixfx-radial-input style="--lane-dasharray: 8 4;" min="0" max="1" value="0.6"></ixfx-radial-input>
198
+
199
+ <!-- Dotted -->
200
+ <ixfx-radial-input style="--lane-dasharray: 2 4;" min="0" max="1" value="0.6"></ixfx-radial-input>
201
+ ```
202
+
203
+ ### Marker (value indicator)
204
+
205
+ | Variable | Default | Description |
206
+ |---|---|---|
207
+ | `--marker-color` | `var(--accent)` | Colour of the needle line or dot fill |
208
+ | `--marker-thickness` | `0.15` | Relative thickness (fraction of radius) |
209
+ | `--marker-length` | `0.8` | Needle length as fraction of radius |
210
+ | `--marker-offset` | `0.2` | Needle start offset from centre (needle), or radial position multiplier (dot) |
211
+ | `--marker-border-width` | `0` | Border halo thickness around needle/dot |
212
+ | `--marker-border-color` | `transparent` | Border halo colour |
213
+ | `--marker-dasharray` | `none` | Dashed needle pattern |
214
+ | `--marker-dashoffset` | `0` | Dashed needle offset |
215
+
216
+ ### Midpoint marker (bipolar/unipolar modes)
217
+
218
+ | Variable | Default | Description |
219
+ |---|---|---|
220
+ | `--midpoint-color` | `var(--surface-muted-text)` | Colour of the top-centre tick/dot |
221
+ | `--midpoint-thickness` | `1px` (needle) / `0.08` (dot) | Tick line width or dot size |
222
+ | `--midpoint-border-color` | `transparent` | Dot border colour (dot style only) |
223
+ | `--midpoint-border-width` | `0` | Dot border width (dot style only) |
224
+ | `--midpoint-length` | `0.15` | Tick line length as fraction of radius (needle style) |
225
+ | `--midpoint-offset` | `0.85` | Tick start offset from centre (needle style) |
226
+
227
+ ### Wedge
228
+
229
+ | Variable | Default | Description |
230
+ |---|---|---|
231
+ | `--wedge-fill` | `var(--accent)` | Wedge fill colour |
232
+ | `--wedge-fill-opacity` | `0.25` | Wedge fill opacity |
233
+ | `--wedge-border-color` | `var(--accent)` | Wedge border/stroke colour |
234
+ | `--wedge-border-width` | `0` | Wedge border stroke width |
235
+ | `--wedge-border-opacity` | `1` | Wedge border opacity |
236
+
237
+ ### Dynamic value variable
238
+
239
+ The component sets `--value-relative` (a number 0…1) on the host element as the value changes. Use it inside `calc()` to create value-reactive styles entirely in CSS:
240
+
241
+ ```html
242
+ <!-- Needle colour shifts from purple to pink as value increases -->
243
+ <ixfx-radial-input
244
+ min="0" max="1" value="0.6"
245
+ style="--marker-color: hsl(calc((1 - var(--value-relative)) * 280), 80%, 55%);"
246
+ ></ixfx-radial-input>
247
+
248
+ <!-- Wedge hue follows value -->
249
+ <ixfx-radial-input
250
+ wedge min="0" max="1" value="0.5"
251
+ style="--wedge-fill: hsl(calc(var(--value-relative) * 280), 80%, 50%); --wedge-fill-opacity: 0.4;"
252
+ ></ixfx-radial-input>
253
+
254
+ <!-- Border thickens and changes colour -->
255
+ <ixfx-radial-input
256
+ min="0" max="1" value="0.5"
257
+ style="
258
+ --dial-border-width: calc(1 + var(--value-relative) * 5);
259
+ --dial-border-color: hsl(calc(var(--value-relative) * 280), 80%, 55%);
260
+ "
261
+ ></ixfx-radial-input>
262
+
263
+ <!-- Needle width grows with value (1px thin → 9px thick) -->
264
+ <ixfx-radial-input
265
+ marker-style="needle" min="0" max="1" value="0.5"
266
+ style="--marker-thickness: calc(0.01 + var(--value-relative) * 0.2);"
267
+ ></ixfx-radial-input>
268
+ ```
269
+
270
+ ---
271
+
272
+ ## Marker Styles
273
+
274
+ ### `needle` (default)
275
+
276
+ A line from `--marker-offset` outward to `--marker-length` (both as fractions of radius).
277
+
278
+ ```html
279
+ <ixfx-radial-input marker-style="needle" min="0" max="1" value="0.6"></ixfx-radial-input>
280
+ ```
281
+
282
+ Thin minimalist look:
283
+
284
+ ```html
285
+ <ixfx-radial-input
286
+ marker-style="needle" min="0" max="1" value="0.6"
287
+ style="--marker-thickness: 0.05; --marker-color: var(--surface-muted-text); --marker-length: 0.9; --marker-offset: 0.5;"
288
+ ></ixfx-radial-input>
289
+ ```
290
+
291
+ With a border halo (useful for contrast on filled dials):
292
+
293
+ ```html
294
+ <ixfx-radial-input
295
+ marker-style="needle" min="0" max="1" value="0.6"
296
+ style="--marker-thickness: 0.1; --marker-border-width: 0.02; --marker-border-color: white;"
297
+ ></ixfx-radial-input>
298
+ ```
299
+
300
+ ### `dot`
301
+
302
+ A filled circle placed at the perimeter at the value's angular position. `--marker-offset` moves it radially (1.0 = on the arc, <1.0 = toward centre, >1.0 = outside arc).
303
+
304
+ ```html
305
+ <!-- Dot on the arc -->
306
+ <ixfx-radial-input marker-style="dot" min="0" max="1" value="0.6" style="--marker-offset: 1;"></ixfx-radial-input>
307
+
308
+ <!-- Dot inside perimeter -->
309
+ <ixfx-radial-input marker-style="dot" min="0" max="1" value="0.6" style="--marker-offset: 0.5;"></ixfx-radial-input>
310
+ ```
311
+
312
+ Dot with dynamic border:
313
+
314
+ ```html
315
+ <ixfx-radial-input
316
+ marker-style="dot" min="0" max="1" value="0.6"
317
+ style="
318
+ --marker-offset: 0.8;
319
+ --marker-thickness: 0.12;
320
+ --marker-color: var(--surface-1);
321
+ --marker-border-width: 0.05;
322
+ --marker-border-color: hsl(calc(var(--value-relative) * 280), 80%, 60%);
323
+ "
324
+ ></ixfx-radial-input>
325
+ ```
326
+
327
+ ### `none`
328
+
329
+ No marker rendered. Useful when the wedge or lanes carry the visual.
330
+
331
+ ---
332
+
333
+ ## Wedge Visualisation
334
+
335
+ The `wedge` attribute renders a filled pie-slice sector behind the lane arcs. It follows the active `value-mode`.
336
+
337
+ ```html
338
+ <!-- Basic -->
339
+ <ixfx-radial-input wedge min="0" max="1" value="0.5"></ixfx-radial-input>
340
+
341
+ <!-- With border -->
342
+ <ixfx-radial-input wedge min="0" max="1" value="0.5"
343
+ style="--wedge-fill-opacity: 0.2; --wedge-border-width: 1;">
344
+ </ixfx-radial-input>
345
+
346
+ <!-- Temperature gauge (blue→red) -->
347
+ <ixfx-radial-input wedge min="0" max="1" value="0.3"
348
+ style="--wedge-fill: hsl(calc((1 - var(--value-relative)) * 240), 80%, 50%); --wedge-fill-opacity: 0.4;">
349
+ </ixfx-radial-input>
350
+
351
+ <!-- Bipolar wedge -->
352
+ <ixfx-radial-input wedge value-mode="bipolar" min="-1" max="1" value="0.7"
353
+ style="--wedge-fill: hsl(120, 70%, 50%); --wedge-fill-opacity: 0.35;">
354
+ </ixfx-radial-input>
355
+ ```
356
+
357
+ ---
358
+
359
+ ## Flatten
360
+
361
+ `flatten` hides a fraction of the top of the circle, converting the full-circle dial into a horseshoe shape. The component height adjusts automatically to remove the dead space.
362
+
363
+ | Value | Arc visible | Degrees |
364
+ |---|---|---|
365
+ | `0` | 100% | 360° full circle |
366
+ | `0.3` (default) | 70% | 252° |
367
+ | `0.5` | 50% | 180° semicircle |
368
+ | `0.7` | 30% | 108° |
369
+
370
+ ```html
371
+ <ixfx-radial-input flatten="0.5" min="0" max="1" value="0.75"></ixfx-radial-input>
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Data Lanes (programmatic API)
377
+
378
+ Lanes are additional arcs drawn around the dial. The two built-in lanes are `track` (grey background) and `value` (accent progress arc). You can add custom lanes at runtime.
379
+
380
+ ### Lane types
381
+
382
+ | Type | Behaviour |
383
+ |---|---|
384
+ | `value` | Arc sweeps from origin to `lane.value` (same logic as the main value arc) |
385
+ | `fixed` | Always shows the full track arc — useful for static reference rings |
386
+ | `anchored` | Arc is centred on the current dial value; `lane.value` controls its width (0..1 of total arc) |
387
+
388
+ ### API
389
+
390
+ ```ts
391
+ import type { RadialInputElement } from 'ixfx-radial-input';
392
+
393
+ const dial = document.querySelector('ixfx-radial-input') as RadialInputElement;
394
+
395
+ // Wait for the element to be ready
396
+ await customElements.whenDefined('ixfx-radial-input');
397
+ await dial.updateComplete;
398
+
399
+ // Add a fixed reference ring at a different radial offset
400
+ dial.addLane('target', {
401
+ type: 'fixed',
402
+ offset: 0.2, // 20% closer to centre
403
+ width: 0.05, // thin line
404
+ });
405
+
406
+ // Add an animated value lane (e.g. a running average)
407
+ dial.addLane('average', {
408
+ type: 'value',
409
+ value: 0.4, // initial 0..1 position
410
+ });
411
+
412
+ // Add an anchored tolerance band centred on the current value
413
+ dial.addLane('tolerance', {
414
+ type: 'anchored',
415
+ value: 0.15, // band spans ±15% of total arc width
416
+ });
417
+
418
+ // Update a lane's value (0..1)
419
+ dial.setLaneValue('average', 0.6);
420
+
421
+ // Remove a lane
422
+ dial.removeLane('target');
423
+
424
+ // Read lane state
425
+ const lane = dial.getLane('average');
426
+ const allLanes = dial.getLanes(); // ReadonlyMap<string, LaneData>
427
+ ```
428
+
429
+ Style lanes via CSS variables using the lane key:
430
+
431
+ ```css
432
+ ixfx-radial-input {
433
+ --lane-average-color: hsl(120, 70%, 50%);
434
+ --lane-tolerance-color: hsl(180, 80%, 50%);
435
+ --lane-sweep-width: 0.06;
436
+ }
437
+ ```
438
+
439
+ ### Live lane animation example
440
+
441
+ From the demo — a sweep lane that oscillates between 0 and the current dial value:
442
+
443
+ ```ts
444
+ let sweepPhase = 0;
445
+ setInterval(() => {
446
+ sweepPhase += 0.08;
447
+ const sineValue = (Math.sin(sweepPhase) + 1) / 2;
448
+ const currentValue = (dial.value - dial.min) / (dial.max - dial.min);
449
+ dial.setLaneValue('sweep', sineValue * currentValue);
450
+ }, 50);
451
+ ```
452
+
453
+ ---
454
+
455
+ ## Tooltip Integration
456
+
457
+ Set `data-tooltip` and `data-tooltip-live` to wire up a live tooltip that updates as the user drags:
458
+
459
+ ```html
460
+ <ixfx-radial-input
461
+ min="0" max="1" value="0.5"
462
+ data-tooltip="50%"
463
+ data-tooltip-anchor="top"
464
+ data-tooltip-live="input"
465
+ ></ixfx-radial-input>
466
+ ```
467
+
468
+ When `data-tooltip-live` is present the component automatically updates `data-tooltip` to the current percentage (e.g. `"63%"`) on every `input` event.
469
+
470
+ ---
471
+
472
+ ## CSS Parts
473
+
474
+ Lane arcs are exposed as CSS shadow parts for styling from outside the shadow DOM:
475
+
476
+ ```css
477
+ ixfx-radial-input::part(lane) { … } /* all lanes */
478
+ ixfx-radial-input::part(lane-track) { … } /* track lane specifically */
479
+ ixfx-radial-input::part(lane-value) { … } /* value lane specifically */
480
+ ixfx-radial-input::part(lane-sweep) { … } /* custom "sweep" lane */
481
+ ixfx-radial-input::part(value-marker) { … } /* needle or dot group */
482
+ ixfx-radial-input::part(midpoint-marker) { … } /* bipolar/unipolar centre tick */
483
+ ```
484
+
485
+ ---
486
+
487
+ ## Styling Recipes
488
+
489
+ ### Purple theme
490
+
491
+ ```css
492
+ #my-dial {
493
+ --lane-value-color: hsl(280, 100%, 60%);
494
+ --lane-track-color: hsl(280, 30%, 30%);
495
+ --marker-thickness: 0.3;
496
+ }
497
+ ```
498
+
499
+ ### Animated marching-ants track
500
+
501
+ ```css
502
+ @keyframes march {
503
+ to { --lane-dashoffset: -30; }
504
+ }
505
+ #my-dial {
506
+ --lane-dasharray: 20 10;
507
+ animation: march 1s linear infinite;
508
+ }
509
+ ```
510
+
511
+ ---
512
+
513
+ ## Implementation Notes
514
+
515
+ These are lessons learned from building the component — useful context when building a similar thing from scratch.
516
+
517
+ ### SVG in a fixed 100×100 viewBox
518
+
519
+ All geometry is computed in a fixed `100×100` viewBox coordinate space with the circle centred at `{x: 50, y: 50}`. `radius` is a property clamped to 10–50 to prevent the dial from clipping the viewBox edges. This makes every calculation coordinate-system-agnostic — the SVG `viewBox` attribute takes care of scaling to the actual DOM pixels.
520
+
521
+ ### Flatten changes the viewBox height
522
+
523
+ A flat-bottomed dial is achieved by cutting off the top portion of the arc. The full circle sits in a `100×100` box, but showing only the bottom `(1 - flatten) × 100` rows means the top of the box is empty space. Rather than cropping with `overflow: hidden`, the component dynamically narrows the SVG `viewBox` height (and sets a matching `--dial-height` CSS variable on the host) after first render and whenever `flatten` changes. This removes the dead space and makes the component's physical footprint match its visual content without requiring the consumer to manually set a height.
524
+
525
+ ### Pointer capture, not window listeners… except for edge cases
526
+
527
+ The intended approach is `svg.setPointerCapture(event.pointerId)` on `pointerdown` so that `pointermove` events continue to reach the element even when the pointer leaves it. However, `pointermove` and `pointerup` are also attached to `window` as a belt-and-suspenders fallback, and removed again in `pointerup`/`pointercancel`. This handles edge cases where pointer capture is not available or fails silently (e.g. in some sandboxed environments).
528
+
529
+ ### Drag sensitivity tied to rendered height
530
+
531
+ Full min-to-max range = `2 × clientHeight` of drag distance. This means a 100 px dial needs 200 px of vertical drag to sweep its full range — comfortable but precise. The sensitivity scales automatically as the component is resized. `Shift` divides sensitivity by 10 (`KEY_STEP_DIVISOR`), matching the fine-control convention used across other components in this library.
532
+
533
+ ### Value stored in domain units; relative value (0..1) computed on demand
534
+
535
+ `this.value` is always in `min`…`max` domain units. Relative values are computed when needed with `scale(value, min, max, 0, 1)`. This avoids double-state and keeps the public API matching `<input type="range">` conventions.
536
+
537
+ ### `input` vs `change` event semantics mirror native inputs
538
+
539
+ `input` fires on every user-driven value change (live, high frequency). `change` is debounced (150 ms) and fires once after the user stops interacting — identical to native `<input>` semantics. Programmatic writes bypass the `input` event entirely and fire `change` immediately (not debounced). This distinction matters to consumers: bind live UI feedback to `input`, persist/process to `change`.
540
+
541
+ ### Lane rendering uses `part` + per-lane CSS variable patterns
542
+
543
+ Each lane path has `part="lane lane-<key>"` and reads its colour from `var(--lane-<key>-color, var(--lane-color))`. This two-tier fallback means a single `--lane-color` styles all custom lanes, while individual lanes can still be overridden by name. The same pattern is used for widths. The SVG `stroke-width` is expressed as a `calc()` over `--lane-<key>-width * radius%` so lane thickness scales proportionally with dial size.
544
+
545
+ ### `--value-relative` as a CSS custom property bridge
546
+
547
+ Because CSS custom properties are live (they cascade and recompute continuously), updating `--value-relative` on the host element during every render cycle lets any CSS expression that references `var(--value-relative)` update purely in the browser's style engine — no JS event listener needed by the consumer. The component also has a `#getCssVar()` helper that evaluates `calc(…var(--value-relative)…)` expressions when reading back CSS variables (e.g. for `--marker-thickness`), so CSS-driven geometry works even for SVG attributes that must be set as numbers.
548
+
549
+ ### Quantisation happens at the set-value boundary
550
+
551
+ `#quantise()` is called in `#setValueFromRelative()`, the single internal entry point for all value changes. Snapping to stops is therefore universal — drag, keyboard, and programmatic writes all go through the same path.
552
+
553
+ ### `willUpdate` initialises the value lane before the first render
554
+
555
+ The `value` lane's internal `value` field is a 0..1 relative value, not a domain value. It must be in sync before the first paint. This is done in `willUpdate()` (not the constructor) because `@property` values set from HTML attributes are not yet parsed at construction time.
556
+
557
+ ### `disconnectedCallback` cleans up timers and global listeners
558
+
559
+ The debounce timer and window pointer listeners are both cleaned up in `disconnectedCallback`. The pointer cleanup is achieved by calling `#onPointerUp()` directly, which also resets `#dragging`. Forgetting this causes a memory leak and a stuck-dragging bug if an element is removed mid-drag.