@5voltfx/design-system 0.1.1 → 0.3.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 (225) hide show
  1. package/dist/chunk-2KHDM6UP.js +58 -0
  2. package/dist/chunk-2KHDM6UP.js.map +1 -0
  3. package/dist/chunk-45346SXP.js +65 -0
  4. package/dist/chunk-45346SXP.js.map +1 -0
  5. package/dist/chunk-BAISLPWP.js +14 -0
  6. package/dist/chunk-BAISLPWP.js.map +1 -0
  7. package/dist/chunk-CT2TMR44.js +1 -0
  8. package/dist/chunk-CT2TMR44.js.map +1 -0
  9. package/dist/chunk-DD5VKG3L.js +22 -0
  10. package/dist/chunk-DD5VKG3L.js.map +1 -0
  11. package/dist/chunk-HLP2MPQ6.js +110 -0
  12. package/dist/chunk-HLP2MPQ6.js.map +1 -0
  13. package/dist/chunk-HPOL5NKF.js +22 -0
  14. package/dist/chunk-HPOL5NKF.js.map +1 -0
  15. package/dist/chunk-I3NS5MAC.js +23 -0
  16. package/dist/chunk-I3NS5MAC.js.map +1 -0
  17. package/dist/chunk-LPEUQ2DE.js +48 -0
  18. package/dist/chunk-LPEUQ2DE.js.map +1 -0
  19. package/dist/chunk-LY5MPGW2.js +129 -0
  20. package/dist/chunk-LY5MPGW2.js.map +1 -0
  21. package/dist/chunk-NJ2SBQYA.js +1208 -0
  22. package/dist/chunk-NJ2SBQYA.js.map +1 -0
  23. package/dist/chunk-R42A3ZCY.js +16 -0
  24. package/dist/chunk-R42A3ZCY.js.map +1 -0
  25. package/dist/chunk-UZW4E2UH.js +206 -0
  26. package/dist/chunk-UZW4E2UH.js.map +1 -0
  27. package/dist/chunk-VRGTUKSJ.js +20 -0
  28. package/dist/chunk-VRGTUKSJ.js.map +1 -0
  29. package/dist/chunk-YLUQANAI.js +1023 -0
  30. package/dist/chunk-YLUQANAI.js.map +1 -0
  31. package/dist/chunk-ZVUDPTFZ.js +76 -0
  32. package/dist/chunk-ZVUDPTFZ.js.map +1 -0
  33. package/dist/components/Accordion/Accordion.d.ts +29 -0
  34. package/dist/components/Accordion/Accordion.d.ts.map +1 -0
  35. package/dist/components/Accordion/index.css +58 -0
  36. package/dist/components/Accordion/index.css.map +1 -0
  37. package/dist/components/Accordion/index.d.ts +2 -0
  38. package/dist/components/Accordion/index.d.ts.map +1 -0
  39. package/dist/components/Accordion/index.js +7 -0
  40. package/dist/components/Accordion/index.js.map +1 -0
  41. package/dist/components/Button/Button.d.ts +8 -0
  42. package/dist/components/Button/Button.d.ts.map +1 -0
  43. package/dist/components/Button/index.d.ts +2 -0
  44. package/dist/components/Button/index.d.ts.map +1 -0
  45. package/dist/components/Card/Card.d.ts +6 -0
  46. package/dist/components/Card/Card.d.ts.map +1 -0
  47. package/dist/components/Card/index.d.ts +2 -0
  48. package/dist/components/Card/index.d.ts.map +1 -0
  49. package/dist/components/Checkbox/Checkbox.d.ts +15 -0
  50. package/dist/components/Checkbox/Checkbox.d.ts.map +1 -0
  51. package/dist/components/Checkbox/index.css +57 -0
  52. package/dist/components/Checkbox/index.css.map +1 -0
  53. package/dist/components/Checkbox/index.d.ts +2 -0
  54. package/dist/components/Checkbox/index.d.ts.map +1 -0
  55. package/dist/components/Checkbox/index.js +7 -0
  56. package/dist/components/Checkbox/index.js.map +1 -0
  57. package/dist/components/DialNav/DialNav.d.ts +48 -0
  58. package/dist/components/DialNav/DialNav.d.ts.map +1 -0
  59. package/dist/components/DialNav/geometry.d.ts +159 -0
  60. package/dist/components/DialNav/geometry.d.ts.map +1 -0
  61. package/dist/components/DialNav/index.css +321 -0
  62. package/dist/components/DialNav/index.css.map +1 -0
  63. package/dist/components/DialNav/index.d.ts +2 -0
  64. package/dist/components/DialNav/index.d.ts.map +1 -0
  65. package/dist/components/DialNav/index.js +8 -0
  66. package/dist/components/DialNav/index.js.map +1 -0
  67. package/dist/components/DialNav/variants/Concentric.d.ts +10 -0
  68. package/dist/components/DialNav/variants/Concentric.d.ts.map +1 -0
  69. package/dist/components/DialNav/variants/RatchetFan.d.ts +12 -0
  70. package/dist/components/DialNav/variants/RatchetFan.d.ts.map +1 -0
  71. package/dist/components/DialNav/variants/Volvelle.d.ts +12 -0
  72. package/dist/components/DialNav/variants/Volvelle.d.ts.map +1 -0
  73. package/dist/components/Footer/Footer.d.ts +15 -0
  74. package/dist/components/Footer/Footer.d.ts.map +1 -0
  75. package/dist/components/Footer/index.d.ts +2 -0
  76. package/dist/components/Footer/index.d.ts.map +1 -0
  77. package/dist/components/FormField/FormField.d.ts +27 -0
  78. package/dist/components/FormField/FormField.d.ts.map +1 -0
  79. package/dist/components/FormField/index.css +31 -0
  80. package/dist/components/FormField/index.css.map +1 -0
  81. package/dist/components/FormField/index.d.ts +2 -0
  82. package/dist/components/FormField/index.d.ts.map +1 -0
  83. package/dist/components/FormField/index.js +8 -0
  84. package/dist/components/FormField/index.js.map +1 -0
  85. package/dist/components/Header/Header.d.ts +15 -0
  86. package/dist/components/Header/Header.d.ts.map +1 -0
  87. package/dist/components/Header/index.d.ts +2 -0
  88. package/dist/components/Header/index.d.ts.map +1 -0
  89. package/dist/components/Input/Input.d.ts +16 -0
  90. package/dist/components/Input/Input.d.ts.map +1 -0
  91. package/dist/components/Input/index.css +45 -0
  92. package/dist/components/Input/index.css.map +1 -0
  93. package/dist/components/Input/index.d.ts +2 -0
  94. package/dist/components/Input/index.d.ts.map +1 -0
  95. package/dist/components/Input/index.js +7 -0
  96. package/dist/components/Input/index.js.map +1 -0
  97. package/dist/components/Label/Label.d.ts +15 -0
  98. package/dist/components/Label/Label.d.ts.map +1 -0
  99. package/dist/components/Label/index.css +14 -0
  100. package/dist/components/Label/index.css.map +1 -0
  101. package/dist/components/Label/index.d.ts +2 -0
  102. package/dist/components/Label/index.d.ts.map +1 -0
  103. package/dist/components/Label/index.js +8 -0
  104. package/dist/components/Label/index.js.map +1 -0
  105. package/dist/components/Layout/Container.d.ts +6 -0
  106. package/dist/components/Layout/Container.d.ts.map +1 -0
  107. package/dist/components/Layout/Grid.d.ts +9 -0
  108. package/dist/components/Layout/Grid.d.ts.map +1 -0
  109. package/dist/components/Layout/Stack.d.ts +10 -0
  110. package/dist/components/Layout/Stack.d.ts.map +1 -0
  111. package/dist/components/Layout/index.d.ts +4 -0
  112. package/dist/components/Layout/index.d.ts.map +1 -0
  113. package/dist/components/Menu/Menu.d.ts +30 -0
  114. package/dist/components/Menu/Menu.d.ts.map +1 -0
  115. package/dist/components/Menu/index.css +77 -0
  116. package/dist/components/Menu/index.css.map +1 -0
  117. package/dist/components/Menu/index.d.ts +2 -0
  118. package/dist/components/Menu/index.d.ts.map +1 -0
  119. package/dist/components/Menu/index.js +7 -0
  120. package/dist/components/Menu/index.js.map +1 -0
  121. package/dist/components/Modal/Modal.d.ts +24 -0
  122. package/dist/components/Modal/Modal.d.ts.map +1 -0
  123. package/dist/components/Modal/index.css +101 -0
  124. package/dist/components/Modal/index.css.map +1 -0
  125. package/dist/components/Modal/index.d.ts +2 -0
  126. package/dist/components/Modal/index.d.ts.map +1 -0
  127. package/dist/components/Modal/index.js +7 -0
  128. package/dist/components/Modal/index.js.map +1 -0
  129. package/dist/components/Nav/Nav.d.ts +10 -0
  130. package/dist/components/Nav/Nav.d.ts.map +1 -0
  131. package/dist/components/Nav/index.d.ts +2 -0
  132. package/dist/components/Nav/index.d.ts.map +1 -0
  133. package/dist/components/Radio/Radio.d.ts +17 -0
  134. package/dist/components/Radio/Radio.d.ts.map +1 -0
  135. package/dist/components/Radio/RadioGroup.d.ts +25 -0
  136. package/dist/components/Radio/RadioGroup.d.ts.map +1 -0
  137. package/dist/components/Radio/RadioGroupContext.d.ts +7 -0
  138. package/dist/components/Radio/RadioGroupContext.d.ts.map +1 -0
  139. package/dist/components/Radio/index.css +57 -0
  140. package/dist/components/Radio/index.css.map +1 -0
  141. package/dist/components/Radio/index.d.ts +3 -0
  142. package/dist/components/Radio/index.d.ts.map +1 -0
  143. package/dist/components/Radio/index.js +9 -0
  144. package/dist/components/Radio/index.js.map +1 -0
  145. package/dist/components/Select/Select.d.ts +18 -0
  146. package/dist/components/Select/Select.d.ts.map +1 -0
  147. package/dist/components/Select/index.css +52 -0
  148. package/dist/components/Select/index.css.map +1 -0
  149. package/dist/components/Select/index.d.ts +2 -0
  150. package/dist/components/Select/index.d.ts.map +1 -0
  151. package/dist/components/Select/index.js +7 -0
  152. package/dist/components/Select/index.js.map +1 -0
  153. package/dist/components/SlideRuleNav/SlideRuleNav.d.ts +54 -0
  154. package/dist/components/SlideRuleNav/SlideRuleNav.d.ts.map +1 -0
  155. package/dist/components/SlideRuleNav/geometry.d.ts +168 -0
  156. package/dist/components/SlideRuleNav/geometry.d.ts.map +1 -0
  157. package/dist/components/SlideRuleNav/index.css +196 -0
  158. package/dist/components/SlideRuleNav/index.css.map +1 -0
  159. package/dist/components/SlideRuleNav/index.d.ts +2 -0
  160. package/dist/components/SlideRuleNav/index.d.ts.map +1 -0
  161. package/dist/components/SlideRuleNav/index.js +8 -0
  162. package/dist/components/SlideRuleNav/index.js.map +1 -0
  163. package/dist/components/SlideRuleNav/variants/CursorRule.d.ts +11 -0
  164. package/dist/components/SlideRuleNav/variants/CursorRule.d.ts.map +1 -0
  165. package/dist/components/SlideRuleNav/variants/SlideStator.d.ts +13 -0
  166. package/dist/components/SlideRuleNav/variants/SlideStator.d.ts.map +1 -0
  167. package/dist/components/SlideRuleNav/variants/VernierCarriage.d.ts +13 -0
  168. package/dist/components/SlideRuleNav/variants/VernierCarriage.d.ts.map +1 -0
  169. package/dist/components/Switch/Switch.d.ts +15 -0
  170. package/dist/components/Switch/Switch.d.ts.map +1 -0
  171. package/dist/components/Switch/index.css +49 -0
  172. package/dist/components/Switch/index.css.map +1 -0
  173. package/dist/components/Switch/index.d.ts +2 -0
  174. package/dist/components/Switch/index.d.ts.map +1 -0
  175. package/dist/components/Switch/index.js +7 -0
  176. package/dist/components/Switch/index.js.map +1 -0
  177. package/dist/components/Tabs/Tabs.d.ts +34 -0
  178. package/dist/components/Tabs/Tabs.d.ts.map +1 -0
  179. package/dist/components/Tabs/index.css +54 -0
  180. package/dist/components/Tabs/index.css.map +1 -0
  181. package/dist/components/Tabs/index.d.ts +2 -0
  182. package/dist/components/Tabs/index.d.ts.map +1 -0
  183. package/dist/components/Tabs/index.js +7 -0
  184. package/dist/components/Tabs/index.js.map +1 -0
  185. package/dist/components/Textarea/Textarea.d.ts +16 -0
  186. package/dist/components/Textarea/Textarea.d.ts.map +1 -0
  187. package/dist/components/Textarea/index.css +36 -0
  188. package/dist/components/Textarea/index.css.map +1 -0
  189. package/dist/components/Textarea/index.d.ts +2 -0
  190. package/dist/components/Textarea/index.d.ts.map +1 -0
  191. package/dist/components/Textarea/index.js +7 -0
  192. package/dist/components/Textarea/index.js.map +1 -0
  193. package/dist/components/ThemeSwitcher/ThemeSwitcher.d.ts +4 -0
  194. package/dist/components/ThemeSwitcher/ThemeSwitcher.d.ts.map +1 -0
  195. package/dist/components/ThemeSwitcher/index.d.ts +2 -0
  196. package/dist/components/ThemeSwitcher/index.d.ts.map +1 -0
  197. package/dist/components/Typography/Heading.d.ts +9 -0
  198. package/dist/components/Typography/Heading.d.ts.map +1 -0
  199. package/dist/components/Typography/Text.d.ts +9 -0
  200. package/dist/components/Typography/Text.d.ts.map +1 -0
  201. package/dist/components/Typography/index.d.ts +3 -0
  202. package/dist/components/Typography/index.d.ts.map +1 -0
  203. package/dist/components/_shared/useDetentTravel.d.ts +87 -0
  204. package/dist/components/_shared/useDetentTravel.d.ts.map +1 -0
  205. package/dist/components/_shared/usePointerDrag.d.ts +24 -0
  206. package/dist/components/_shared/usePointerDrag.d.ts.map +1 -0
  207. package/dist/components/_shared/useReducedMotion.d.ts +7 -0
  208. package/dist/components/_shared/useReducedMotion.d.ts.map +1 -0
  209. package/dist/components/_shared/validateStationCount.d.ts +13 -0
  210. package/dist/components/_shared/validateStationCount.d.ts.map +1 -0
  211. package/dist/index.css +1175 -0
  212. package/dist/index.css.map +1 -1
  213. package/dist/index.d.ts +26 -0
  214. package/dist/index.d.ts.map +1 -0
  215. package/dist/index.js +70 -10
  216. package/dist/index.js.map +1 -1
  217. package/dist/theme/ThemeContext.d.ts +2 -0
  218. package/dist/theme/ThemeContext.d.ts.map +1 -0
  219. package/dist/theme/ThemeProvider.d.ts +12 -0
  220. package/dist/theme/ThemeProvider.d.ts.map +1 -0
  221. package/dist/theme/themeConstants.d.ts +8 -0
  222. package/dist/theme/themeConstants.d.ts.map +1 -0
  223. package/dist/theme/useTheme.d.ts +2 -0
  224. package/dist/theme/useTheme.d.ts.map +1 -0
  225. package/package.json +21 -5
@@ -0,0 +1,1208 @@
1
+ import {
2
+ useDetentTravel,
3
+ usePointerDrag,
4
+ useReducedMotion,
5
+ validateStationCount
6
+ } from "./chunk-UZW4E2UH.js";
7
+
8
+ // src/components/DialNav/DialNav.jsx
9
+ import { useCallback, useEffect, useMemo as useMemo4, useRef as useRef4, useState } from "react";
10
+ import { NavLink } from "react-router-dom";
11
+
12
+ // src/components/DialNav/geometry.js
13
+ var DIAL = Object.freeze({
14
+ /** C-I — a disc that swings out of the edge and spins to an index. */
15
+ volvelle: Object.freeze({
16
+ viewBox: Object.freeze([440, 420]),
17
+ cx: 210,
18
+ cy: 210,
19
+ /** Disc face, and the hairline circle just inside it. */
20
+ r: 200,
21
+ rInner: 193,
22
+ /** Decorative rim ticks run inward from rInner. */
23
+ rRing: 179,
24
+ /** Station band: quarter-detent ticks run inward from rBand. */
25
+ rBand: 177,
26
+ /** Radial centre of a station label — the pivot its flip turns about. */
27
+ labelR: 129,
28
+ /** Logarithmic ring at the hub, for the same reason the original had one. */
29
+ rLogInner: 62,
30
+ rLogOuter: 76,
31
+ rLogPlate: 78,
32
+ rLogNumeral: 52,
33
+ rHub: 34,
34
+ /** Total sweep the stations are spread across. */
35
+ arc: 216,
36
+ /** Fixed cursor arm, due east. */
37
+ arm: Object.freeze({ x: 38, w: 166, h: 38, hairline: 208, index: 218 }),
38
+ /**
39
+ * Half the width of the widest station label, including its padding.
40
+ * Labels are HTML, so this cannot be derived from the SVG — it is
41
+ * measured, and it is what the extraction distance has to clear.
42
+ */
43
+ labelClearance: 60,
44
+ rest: -376,
45
+ /*
46
+ * Far enough out that the hub sits at labelR + labelClearance from the
47
+ * stage edge, so a label swung to due west still fits. The artboards used
48
+ * -110, but their labels were SVG text with no padding; HTML labels run
49
+ * off the edge at that distance.
50
+ */
51
+ out: -10,
52
+ settleMs: 520
53
+ }),
54
+ /** C-II — two concentric rings and a swept arm, after the E6B. */
55
+ concentric: Object.freeze({
56
+ /* Wider than tall: the fixed index chevron reaches cx + 232. */
57
+ viewBox: Object.freeze([460, 440]),
58
+ cx: 220,
59
+ cy: 220,
60
+ r: 208,
61
+ /** Outer ring: sections, spread over a full turn. */
62
+ rOuter: 200,
63
+ rOuterInner: 146,
64
+ labelR: 164,
65
+ /* Measured: the widest section label renders 85px across at this ring's
66
+ reduced size, so half of it is 43. */
67
+ labelClearance: 44,
68
+ /** Inner ring: the pages of whichever section is indexed. */
69
+ rInnerPlate: 132,
70
+ rHubPlate: 58,
71
+ innerLabelR: 122,
72
+ innerArc: 135,
73
+ innerCount: 4,
74
+ arm: Object.freeze({ x: 50, w: 106, h: 30, tip: 156, point: 172 }),
75
+ index: Object.freeze({ from: 198, to: 226, chevron: 232, back: 214 }),
76
+ rest: -396,
77
+ /* cx - labelR - labelClearance leaves 12px; -6 keeps 6px of margin for a
78
+ section label swung due west by the ring's full turn. */
79
+ out: -6,
80
+ settleMs: 480,
81
+ innerSettleMs: 380
82
+ }),
83
+ /** C-III — a quadrant that blooms out of the edge on a ratchet. */
84
+ fan: Object.freeze({
85
+ viewBox: Object.freeze([400, 420]),
86
+ /** The pivot sits hard against the left edge. */
87
+ cx: 46,
88
+ cy: 210,
89
+ /** Spoke length. With halfAngle 80°, ro·sin(80°) ≈ 193 — the most a
90
+ 420-tall viewBox will take. Do not raise without re-measuring. */
91
+ r: 192,
92
+ labelR: 130,
93
+ labelClearance: 60,
94
+ arc: 150,
95
+ wedge: Object.freeze({ halfAngle: 80, ri: 64, ro: 196 }),
96
+ hub: Object.freeze({ outer: 64, inner: 52 }),
97
+ /** Ratchet teeth ride just outside the spoke ends. */
98
+ tooth: Object.freeze({ from: 3, to: 10, lead: 3.4 }),
99
+ /** How far the clip is held closed at rest, hiding all but the hub. */
100
+ restClip: 328,
101
+ bloomStaggerMs: 34,
102
+ settleMs: 440
103
+ })
104
+ });
105
+ var polar = (cx, cy, r, deg) => [
106
+ cx + r * Math.cos(deg * Math.PI / 180),
107
+ cy + r * Math.sin(deg * Math.PI / 180)
108
+ ];
109
+ function uprightFlip(absDeg) {
110
+ const a = (absDeg % 360 + 360) % 360;
111
+ return a > 90 && a < 270;
112
+ }
113
+ var pitchFor = (spec, count) => spec.arc / Math.max(1, count - 1);
114
+ var startAngle = (spec) => -spec.arc / 2;
115
+ var stationAngle = (spec, count, pos) => startAngle(spec) + pos * pitchFor(spec, count);
116
+ var rotationFor = (spec, count, pos) => -stationAngle(spec, count, pos);
117
+ function angleDelta(to, from) {
118
+ let d = to - from;
119
+ while (d > 180) d -= 360;
120
+ while (d <= -180) d += 360;
121
+ return d;
122
+ }
123
+ function logRingTicks() {
124
+ const bands = [
125
+ [1, 2, 0.05],
126
+ [2, 5, 0.1],
127
+ [5, 10, 0.2]
128
+ ];
129
+ const out = [];
130
+ for (const [from, to, step] of bands) {
131
+ const steps = Math.round((to - from) / step);
132
+ for (let k = 0; k < steps; k++) {
133
+ const v = Math.round((from + k * step) * 100) / 100;
134
+ out.push({
135
+ v,
136
+ angle: Math.log10(v) * 360 - 90,
137
+ major: Number.isInteger(v)
138
+ });
139
+ }
140
+ }
141
+ return out;
142
+ }
143
+ var innerPitchFor = (spec, count) => spec.innerArc / Math.max(1, count - 1);
144
+ var innerStartAngle = (spec) => -spec.innerArc / 2;
145
+ var innerAngle = (spec, count, pos) => innerStartAngle(spec) + pos * innerPitchFor(spec, count);
146
+ var toothLead = (spec, detent) => Math.min(spec.tooth.lead, detent * 0.45);
147
+ function wedgePath(spec) {
148
+ const { cx, cy } = spec;
149
+ const { halfAngle, ri, ro } = spec.wedge;
150
+ const [ax, ay] = polar(cx, cy, ri, -halfAngle);
151
+ const [bx, by] = polar(cx, cy, ro, -halfAngle);
152
+ const [dx, dy] = polar(cx, cy, ro, halfAngle);
153
+ const [ex, ey] = polar(cx, cy, ri, halfAngle);
154
+ return `M${ax},${ay} L${bx},${by} A${ro},${ro} 0 0 1 ${dx},${dy} L${ex},${ey} A${ri},${ri} 0 0 0 ${ax},${ay} Z`;
155
+ }
156
+
157
+ // src/components/DialNav/variants/Volvelle.jsx
158
+ import { forwardRef, useMemo, useRef } from "react";
159
+ import { jsx, jsxs } from "react/jsx-runtime";
160
+ var Volvelle = forwardRef(function Volvelle2({ spec, count, travel, theta, pitch, onDragStateChange }, ref) {
161
+ const [vbW, vbH] = spec.viewBox;
162
+ const { cx, cy, arm } = spec;
163
+ const a0 = startAngle(spec);
164
+ const discRef = useRef(null);
165
+ const grab = useRef({ angle: 0, pos: 0 });
166
+ const angleAt = (event) => {
167
+ const box = ref.current?.getBoundingClientRect();
168
+ if (!box?.width) return null;
169
+ const scale = vbW / box.width;
170
+ const x = (event.clientX - box.left) * scale - cx;
171
+ const y = (event.clientY - box.top) * scale - cy;
172
+ return Math.atan2(y, x) * 180 / Math.PI;
173
+ };
174
+ usePointerDrag(discRef, {
175
+ onBegin: (event) => {
176
+ const a = angleAt(event);
177
+ if (a == null) return;
178
+ grab.current = { angle: a, pos: travel.pos };
179
+ travel.begin();
180
+ onDragStateChange?.(true);
181
+ },
182
+ onMove: (event) => {
183
+ const a = angleAt(event);
184
+ if (a == null) return;
185
+ travel.track(grab.current.pos - angleDelta(a, grab.current.angle) / pitch);
186
+ },
187
+ onEnd: (_event, wasTap) => {
188
+ if (!wasTap) travel.release();
189
+ onDragStateChange?.(false);
190
+ }
191
+ });
192
+ const rim = useMemo(() => {
193
+ const out = [];
194
+ for (let a = 0; a < 360; a += 2) {
195
+ const len = a % 10 === 0 ? 14 : a % 5 === 0 ? 9 : 5;
196
+ out.push({
197
+ a,
198
+ len,
199
+ faint: a % 10 !== 0
200
+ });
201
+ }
202
+ return out;
203
+ }, []);
204
+ const band = useMemo(() => {
205
+ const out = [];
206
+ for (let q = 0; q <= (count - 1) * 4; q++) {
207
+ const major = q % 4 === 0;
208
+ const half = q % 2 === 0;
209
+ out.push({
210
+ a: a0 + q / 4 * pitch,
211
+ len: major ? 22 : half ? 12 : 7,
212
+ faint: !major,
213
+ station: major
214
+ });
215
+ }
216
+ return out;
217
+ }, [a0, count, pitch]);
218
+ const logTicks = useMemo(logRingTicks, []);
219
+ return /* @__PURE__ */ jsxs(
220
+ "svg",
221
+ {
222
+ ref,
223
+ className: "ds-dial__chrome",
224
+ viewBox: `0 0 ${vbW} ${vbH}`,
225
+ "aria-hidden": "true",
226
+ focusable: "false",
227
+ children: [
228
+ /* @__PURE__ */ jsx("circle", { className: "ds-dial__face", cx, cy, r: spec.r }),
229
+ /* @__PURE__ */ jsx(
230
+ "circle",
231
+ {
232
+ className: "ds-dial__hairline-ring",
233
+ cx,
234
+ cy,
235
+ r: spec.rInner
236
+ }
237
+ ),
238
+ /* @__PURE__ */ jsxs("g", { ref: discRef, className: "ds-dial__disc", children: [
239
+ /* @__PURE__ */ jsx(
240
+ "circle",
241
+ {
242
+ className: "ds-dial__grab",
243
+ cx,
244
+ cy,
245
+ r: spec.rInner
246
+ }
247
+ ),
248
+ /* @__PURE__ */ jsxs("g", { transform: `rotate(${theta} ${cx} ${cy})`, children: [
249
+ rim.map((tick, i) => {
250
+ const [x1, y1] = polar(cx, cy, spec.rInner, tick.a);
251
+ const [x2, y2] = polar(cx, cy, spec.rInner - tick.len, tick.a);
252
+ return /* @__PURE__ */ jsx(
253
+ "line",
254
+ {
255
+ className: "ds-dial__tick" + (tick.faint ? " ds-dial__tick--faint" : ""),
256
+ x1,
257
+ y1,
258
+ x2,
259
+ y2
260
+ },
261
+ i
262
+ );
263
+ }),
264
+ /* @__PURE__ */ jsx(
265
+ "circle",
266
+ {
267
+ className: "ds-dial__hairline-ring",
268
+ cx,
269
+ cy,
270
+ r: spec.rRing
271
+ }
272
+ ),
273
+ band.map((tick, i) => {
274
+ const [x1, y1] = polar(cx, cy, spec.rBand, tick.a);
275
+ const [x2, y2] = polar(cx, cy, spec.rBand - tick.len, tick.a);
276
+ return /* @__PURE__ */ jsx(
277
+ "line",
278
+ {
279
+ className: "ds-dial__tick" + (tick.faint ? " ds-dial__tick--faint" : "") + (tick.station ? " ds-dial__tick--station" : ""),
280
+ x1,
281
+ y1,
282
+ x2,
283
+ y2
284
+ },
285
+ `b${i}`
286
+ );
287
+ }),
288
+ /* @__PURE__ */ jsx(
289
+ "circle",
290
+ {
291
+ className: "ds-dial__log-plate",
292
+ cx,
293
+ cy,
294
+ r: spec.rLogPlate
295
+ }
296
+ ),
297
+ logTicks.map((tick, i) => {
298
+ const outer = spec.rLogInner + (tick.major ? 14 : 6);
299
+ const [x1, y1] = polar(cx, cy, spec.rLogInner, tick.angle);
300
+ const [x2, y2] = polar(cx, cy, outer, tick.angle);
301
+ const [tx, ty] = polar(cx, cy, spec.rLogNumeral, tick.angle);
302
+ return /* @__PURE__ */ jsxs("g", { children: [
303
+ /* @__PURE__ */ jsx(
304
+ "line",
305
+ {
306
+ className: "ds-dial__tick" + (tick.major ? "" : " ds-dial__tick--faint"),
307
+ x1,
308
+ y1,
309
+ x2,
310
+ y2
311
+ }
312
+ ),
313
+ tick.major && /* @__PURE__ */ jsx(
314
+ "text",
315
+ {
316
+ className: "ds-dial__numeral",
317
+ x: tx,
318
+ y: ty + 3,
319
+ textAnchor: "middle",
320
+ transform: `rotate(${tick.angle + 90 + (uprightFlip(tick.angle + 90) ? 180 : 0)} ${tx} ${ty})`,
321
+ children: Math.round(tick.v)
322
+ }
323
+ )
324
+ ] }, `l${i}`);
325
+ })
326
+ ] })
327
+ ] }),
328
+ /* @__PURE__ */ jsx("circle", { className: "ds-dial__hub", cx, cy, r: spec.rHub }),
329
+ /* @__PURE__ */ jsx("circle", { className: "ds-dial__rivet", cx, cy, r: 9 }),
330
+ /* @__PURE__ */ jsx("circle", { className: "ds-dial__rivet-hi", cx, cy, r: 4 }),
331
+ /* @__PURE__ */ jsxs("g", { className: "ds-dial__arm", children: [
332
+ /* @__PURE__ */ jsx(
333
+ "rect",
334
+ {
335
+ className: "ds-dial__glass",
336
+ x: cx + arm.x,
337
+ y: cy - arm.h / 2,
338
+ width: arm.w,
339
+ height: arm.h,
340
+ rx: 2
341
+ }
342
+ ),
343
+ /* @__PURE__ */ jsx(
344
+ "rect",
345
+ {
346
+ className: "ds-dial__frame-line",
347
+ x: cx + arm.x,
348
+ y: cy - arm.h / 2,
349
+ width: arm.w,
350
+ height: arm.h,
351
+ rx: 3
352
+ }
353
+ ),
354
+ /* @__PURE__ */ jsx(
355
+ "line",
356
+ {
357
+ className: "ds-dial__hairline",
358
+ x1: cx + arm.x,
359
+ y1: cy,
360
+ x2: cx + arm.hairline,
361
+ y2: cy
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsx(
365
+ "path",
366
+ {
367
+ className: "ds-dial__index",
368
+ d: `M${cx + arm.index},${cy - 11} L${cx + arm.index},${cy + 11} L${cx + arm.index - 18},${cy} Z`
369
+ }
370
+ )
371
+ ] })
372
+ ]
373
+ }
374
+ );
375
+ });
376
+
377
+ // src/components/DialNav/variants/Concentric.jsx
378
+ import { forwardRef as forwardRef2, useMemo as useMemo2, useRef as useRef2 } from "react";
379
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
380
+ var Concentric = forwardRef2(function Concentric2({
381
+ spec,
382
+ count,
383
+ travel,
384
+ theta,
385
+ pitch,
386
+ onDragStateChange,
387
+ pages,
388
+ innerTravel,
389
+ renderStation,
390
+ renderPage
391
+ }, ref) {
392
+ const [vbW, vbH] = spec.viewBox;
393
+ const { cx, cy, arm, index } = spec;
394
+ const ringRef = useRef2(null);
395
+ const armRef = useRef2(null);
396
+ const grabRing = useRef2({ angle: 0, pos: 0 });
397
+ const grabArm = useRef2({ angle: 0, pos: 0 });
398
+ const angleAt = (event) => {
399
+ const box = ref.current?.getBoundingClientRect();
400
+ if (!box?.width) return null;
401
+ const scale = vbW / box.width;
402
+ const x = (event.clientX - box.left) * scale - cx;
403
+ const y = (event.clientY - box.top) * scale - cy;
404
+ return Math.atan2(y, x) * 180 / Math.PI;
405
+ };
406
+ usePointerDrag(ringRef, {
407
+ onBegin: (event) => {
408
+ const a = angleAt(event);
409
+ if (a == null) return;
410
+ grabRing.current = { angle: a, pos: travel.pos };
411
+ travel.begin();
412
+ onDragStateChange?.(true);
413
+ },
414
+ onMove: (event) => {
415
+ const a = angleAt(event);
416
+ if (a == null) return;
417
+ travel.track(
418
+ grabRing.current.pos - angleDelta(a, grabRing.current.angle) / pitch
419
+ );
420
+ },
421
+ onEnd: (_e, wasTap) => {
422
+ if (!wasTap) travel.release();
423
+ onDragStateChange?.(false);
424
+ }
425
+ });
426
+ const innerPitch = innerPitchFor(spec, pages.length);
427
+ usePointerDrag(armRef, {
428
+ onBegin: (event) => {
429
+ const a = angleAt(event);
430
+ if (a == null) return;
431
+ grabArm.current = { angle: a, pos: innerTravel.pos };
432
+ innerTravel.begin();
433
+ onDragStateChange?.(true);
434
+ },
435
+ onMove: (event) => {
436
+ const a = angleAt(event);
437
+ if (a == null) return;
438
+ innerTravel.track(
439
+ grabArm.current.pos + angleDelta(a, grabArm.current.angle) / innerPitch
440
+ );
441
+ },
442
+ onEnd: (_e, wasTap) => {
443
+ if (!wasTap) innerTravel.release();
444
+ onDragStateChange?.(false);
445
+ }
446
+ });
447
+ const ringTicks = useMemo2(() => {
448
+ const out = [];
449
+ for (let q = 0; q < 96; q++) {
450
+ const a = q * 3.75;
451
+ out.push({
452
+ a,
453
+ len: q % 16 === 0 ? 18 : q % 4 === 0 ? 10 : 5,
454
+ faint: q % 16 !== 0
455
+ });
456
+ }
457
+ return out;
458
+ }, []);
459
+ const innerTicks = useMemo2(() => {
460
+ const out = [];
461
+ for (let q = 0; q <= (pages.length - 1) * 4; q++) {
462
+ const major = q % 4 === 0;
463
+ out.push({
464
+ a: innerAngle(spec, pages.length, q / 4),
465
+ len: major ? 16 : q % 2 === 0 ? 9 : 5,
466
+ major
467
+ });
468
+ }
469
+ return out;
470
+ }, [pages.length, spec]);
471
+ const armAngle = innerAngle(spec, pages.length, innerTravel.pos);
472
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
473
+ /* @__PURE__ */ jsxs2(
474
+ "svg",
475
+ {
476
+ ref,
477
+ className: "ds-dial__chrome",
478
+ viewBox: `0 0 ${vbW} ${vbH}`,
479
+ "aria-hidden": "true",
480
+ focusable: "false",
481
+ children: [
482
+ /* @__PURE__ */ jsx2("circle", { className: "ds-dial__face", cx, cy, r: spec.r }),
483
+ /* @__PURE__ */ jsxs2("g", { ref: ringRef, className: "ds-dial__disc", children: [
484
+ /* @__PURE__ */ jsx2(
485
+ "circle",
486
+ {
487
+ className: "ds-dial__grab",
488
+ cx,
489
+ cy,
490
+ r: spec.rOuter
491
+ }
492
+ ),
493
+ /* @__PURE__ */ jsx2(
494
+ "circle",
495
+ {
496
+ className: "ds-dial__grab-hole",
497
+ cx,
498
+ cy,
499
+ r: spec.rOuterInner
500
+ }
501
+ ),
502
+ /* @__PURE__ */ jsxs2("g", { transform: `rotate(${theta} ${cx} ${cy})`, children: [
503
+ /* @__PURE__ */ jsx2(
504
+ "circle",
505
+ {
506
+ className: "ds-dial__hairline-ring",
507
+ cx,
508
+ cy,
509
+ r: spec.rOuter
510
+ }
511
+ ),
512
+ /* @__PURE__ */ jsx2(
513
+ "circle",
514
+ {
515
+ className: "ds-dial__hairline-ring",
516
+ cx,
517
+ cy,
518
+ r: spec.rOuterInner
519
+ }
520
+ ),
521
+ ringTicks.map((tick, i) => {
522
+ const [x1, y1] = polar(cx, cy, spec.rOuter, tick.a);
523
+ const [x2, y2] = polar(cx, cy, spec.rOuter - tick.len, tick.a);
524
+ return /* @__PURE__ */ jsx2(
525
+ "line",
526
+ {
527
+ className: "ds-dial__tick" + (tick.faint ? " ds-dial__tick--faint" : ""),
528
+ x1,
529
+ y1,
530
+ x2,
531
+ y2
532
+ },
533
+ i
534
+ );
535
+ })
536
+ ] })
537
+ ] }),
538
+ /* @__PURE__ */ jsx2(
539
+ "circle",
540
+ {
541
+ className: "ds-dial__log-plate",
542
+ cx,
543
+ cy,
544
+ r: spec.rInnerPlate
545
+ }
546
+ ),
547
+ innerTicks.map((tick, i) => {
548
+ const [x1, y1] = polar(cx, cy, spec.rInnerPlate, tick.a);
549
+ const [x2, y2] = polar(cx, cy, spec.rInnerPlate - tick.len, tick.a);
550
+ return /* @__PURE__ */ jsx2(
551
+ "line",
552
+ {
553
+ className: "ds-dial__tick" + (tick.major ? " ds-dial__tick--station" : " ds-dial__tick--faint"),
554
+ x1,
555
+ y1,
556
+ x2,
557
+ y2
558
+ },
559
+ `i${i}`
560
+ );
561
+ }),
562
+ /* @__PURE__ */ jsx2(
563
+ "circle",
564
+ {
565
+ className: "ds-dial__hub",
566
+ cx,
567
+ cy,
568
+ r: spec.rHubPlate
569
+ }
570
+ ),
571
+ /* @__PURE__ */ jsxs2(
572
+ "g",
573
+ {
574
+ ref: armRef,
575
+ className: "ds-dial__arm-sweep",
576
+ transform: `rotate(${armAngle} ${cx} ${cy})`,
577
+ children: [
578
+ /* @__PURE__ */ jsx2(
579
+ "rect",
580
+ {
581
+ className: "ds-dial__glass",
582
+ x: cx + arm.x,
583
+ y: cy - arm.h / 2,
584
+ width: arm.w,
585
+ height: arm.h,
586
+ rx: 2
587
+ }
588
+ ),
589
+ /* @__PURE__ */ jsx2(
590
+ "line",
591
+ {
592
+ className: "ds-dial__brass-rail",
593
+ x1: cx + arm.x,
594
+ y1: cy - arm.h / 2,
595
+ x2: cx + arm.tip,
596
+ y2: cy - arm.h / 2
597
+ }
598
+ ),
599
+ /* @__PURE__ */ jsx2(
600
+ "line",
601
+ {
602
+ className: "ds-dial__brass-rail",
603
+ x1: cx + arm.x,
604
+ y1: cy + arm.h / 2,
605
+ x2: cx + arm.tip,
606
+ y2: cy + arm.h / 2
607
+ }
608
+ ),
609
+ /* @__PURE__ */ jsx2(
610
+ "line",
611
+ {
612
+ className: "ds-dial__hairline",
613
+ x1: cx + arm.x,
614
+ y1: cy,
615
+ x2: cx + arm.tip,
616
+ y2: cy
617
+ }
618
+ ),
619
+ /* @__PURE__ */ jsx2(
620
+ "path",
621
+ {
622
+ className: "ds-dial__brass-point",
623
+ d: `M${cx + arm.tip},${cy - 9} L${cx + arm.point},${cy} L${cx + arm.tip},${cy + 9} Z`
624
+ }
625
+ )
626
+ ]
627
+ }
628
+ ),
629
+ /* @__PURE__ */ jsx2("circle", { className: "ds-dial__rivet", cx, cy, r: 11 }),
630
+ /* @__PURE__ */ jsx2("circle", { className: "ds-dial__rivet-hi", cx, cy, r: 5 }),
631
+ /* @__PURE__ */ jsxs2("g", { className: "ds-dial__arm", children: [
632
+ /* @__PURE__ */ jsx2(
633
+ "line",
634
+ {
635
+ className: "ds-dial__hairline",
636
+ x1: cx + index.from,
637
+ y1: cy,
638
+ x2: cx + index.to,
639
+ y2: cy
640
+ }
641
+ ),
642
+ /* @__PURE__ */ jsx2(
643
+ "path",
644
+ {
645
+ className: "ds-dial__index",
646
+ d: `M${cx + index.chevron},${cy - 12} L${cx + index.chevron},${cy + 12} L${cx + index.back},${cy} Z`
647
+ }
648
+ )
649
+ ] })
650
+ ]
651
+ }
652
+ ),
653
+ /* @__PURE__ */ jsx2("ul", { className: "ds-dial__stations", style: { transform: `rotate(${theta}deg)` }, children: Array.from({ length: count }, (_, i) => {
654
+ const a = i * pitch;
655
+ return /* @__PURE__ */ jsx2(
656
+ "li",
657
+ {
658
+ className: "ds-dial__station ds-dial__station--outer",
659
+ style: {
660
+ "--a": `${a}deg`,
661
+ "--r": `${spec.labelR}px`,
662
+ "--flip": uprightFlip(a + theta) ? "180deg" : "0deg"
663
+ },
664
+ children: renderStation(i)
665
+ },
666
+ i
667
+ );
668
+ }) }),
669
+ /* @__PURE__ */ jsx2("ul", { className: "ds-dial__stations ds-dial__stations--inner", children: pages.map((page, k) => {
670
+ const a = innerAngle(spec, pages.length, k);
671
+ return /* @__PURE__ */ jsx2(
672
+ "li",
673
+ {
674
+ className: "ds-dial__station",
675
+ style: {
676
+ "--a": `${a}deg`,
677
+ "--r": `${spec.innerLabelR}px`,
678
+ "--flip": "0deg"
679
+ },
680
+ children: renderPage(page, k)
681
+ },
682
+ page.id
683
+ );
684
+ }) })
685
+ ] });
686
+ });
687
+
688
+ // src/components/DialNav/variants/RatchetFan.jsx
689
+ import { forwardRef as forwardRef3, useMemo as useMemo3, useRef as useRef3 } from "react";
690
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
691
+ var RatchetFan = forwardRef3(function RatchetFan2({ spec, count, travel, pitch, onDragStateChange }, ref) {
692
+ const [vbW, vbH] = spec.viewBox;
693
+ const { cx, cy, r, hub } = spec;
694
+ const a0 = startAngle(spec);
695
+ const detent = pitch / 4;
696
+ const lead = toothLead(spec, detent);
697
+ const pawlRef = useRef3(null);
698
+ const grab = useRef3({ angle: 0, pos: 0 });
699
+ const angleAt = (event) => {
700
+ const box = ref.current?.getBoundingClientRect();
701
+ if (!box?.width) return null;
702
+ const scale = vbW / box.width;
703
+ const x = (event.clientX - box.left) * scale - cx;
704
+ const y = (event.clientY - box.top) * scale - cy;
705
+ return Math.atan2(y, x) * 180 / Math.PI;
706
+ };
707
+ usePointerDrag(pawlRef, {
708
+ onBegin: (event) => {
709
+ const a = angleAt(event);
710
+ if (a == null) return;
711
+ grab.current = { angle: a, pos: travel.pos };
712
+ travel.begin();
713
+ onDragStateChange?.(true);
714
+ },
715
+ onMove: (event) => {
716
+ const a = angleAt(event);
717
+ if (a == null) return;
718
+ travel.track(grab.current.pos + angleDelta(a, grab.current.angle) / pitch);
719
+ },
720
+ onEnd: (_event, wasTap) => {
721
+ if (!wasTap) travel.release();
722
+ onDragStateChange?.(false);
723
+ }
724
+ });
725
+ const ratchet = useMemo3(() => {
726
+ let d = "";
727
+ for (let q = 0; q <= (count - 1) * 4; q++) {
728
+ const a = a0 + q / 4 * pitch;
729
+ const [x1, y1] = polar(cx, cy, r + spec.tooth.from, a);
730
+ const [x2, y2] = polar(cx, cy, r + spec.tooth.to, a + lead);
731
+ d += `${q ? "L" : "M"}${x1},${y1} L${x2},${y2} `;
732
+ }
733
+ return d;
734
+ }, [a0, count, cx, cy, lead, pitch, r, spec.tooth.from, spec.tooth.to]);
735
+ const detents = useMemo3(() => {
736
+ const out = [];
737
+ for (let q = 0; q <= (count - 1) * 4; q++) {
738
+ const major = q % 4 === 0;
739
+ const half = q % 2 === 0;
740
+ out.push({
741
+ a: a0 + q / 4 * pitch,
742
+ len: major ? 22 : half ? 11 : 6,
743
+ major
744
+ });
745
+ }
746
+ return out;
747
+ }, [a0, count, pitch]);
748
+ const [arcX0, arcY0] = polar(cx, cy, r, a0);
749
+ const [arcX1, arcY1] = polar(cx, cy, r, stationAngle(spec, count, count - 1));
750
+ const pawlAngle = stationAngle(spec, count, travel.pos);
751
+ const frac = Math.abs(travel.pos * 4 - Math.round(travel.pos * 4));
752
+ return /* @__PURE__ */ jsxs3(
753
+ "svg",
754
+ {
755
+ ref,
756
+ className: "ds-dial__chrome",
757
+ viewBox: `0 0 ${vbW} ${vbH}`,
758
+ "aria-hidden": "true",
759
+ focusable: "false",
760
+ children: [
761
+ /* @__PURE__ */ jsx3("circle", { className: "ds-dial__log-plate", cx, cy, r: hub.outer }),
762
+ /* @__PURE__ */ jsx3("circle", { className: "ds-dial__hub", cx, cy, r: hub.inner }),
763
+ /* @__PURE__ */ jsxs3("g", { className: "ds-dial__fanchrome", children: [
764
+ /* @__PURE__ */ jsx3("path", { className: "ds-dial__wedge", d: wedgePath(spec) }),
765
+ /* @__PURE__ */ jsx3("path", { className: "ds-dial__ratchet", d: ratchet }),
766
+ /* @__PURE__ */ jsx3(
767
+ "path",
768
+ {
769
+ className: "ds-dial__hairline-ring",
770
+ d: `M${arcX0},${arcY0} A${r},${r} 0 0 1 ${arcX1},${arcY1}`
771
+ }
772
+ ),
773
+ detents.map((tick, i) => {
774
+ const [x1, y1] = polar(cx, cy, r - tick.len, tick.a);
775
+ const [x2, y2] = polar(cx, cy, r, tick.a);
776
+ return /* @__PURE__ */ jsx3(
777
+ "line",
778
+ {
779
+ className: "ds-dial__tick" + (tick.major ? " ds-dial__tick--station" : " ds-dial__tick--faint"),
780
+ x1,
781
+ y1,
782
+ x2,
783
+ y2
784
+ },
785
+ i
786
+ );
787
+ })
788
+ ] }),
789
+ Array.from({ length: count }, (_, i) => {
790
+ const a = stationAngle(spec, count, i);
791
+ return /* @__PURE__ */ jsx3("g", { transform: `rotate(${a} ${cx} ${cy})`, children: /* @__PURE__ */ jsx3(
792
+ "g",
793
+ {
794
+ className: "ds-dial__spoke",
795
+ style: {
796
+ transformOrigin: `${cx}px ${cy}px`,
797
+ transitionDelay: `${i * spec.bloomStaggerMs}ms`
798
+ },
799
+ children: /* @__PURE__ */ jsx3(
800
+ "line",
801
+ {
802
+ className: "ds-dial__tick ds-dial__tick--faint",
803
+ x1: cx + hub.outer - 6,
804
+ y1: cy,
805
+ x2: cx + r - 8,
806
+ y2: cy
807
+ }
808
+ )
809
+ }
810
+ ) }, i);
811
+ }),
812
+ /* @__PURE__ */ jsxs3(
813
+ "g",
814
+ {
815
+ ref: pawlRef,
816
+ className: "ds-dial__pawl",
817
+ transform: `rotate(${pawlAngle} ${cx} ${cy})`,
818
+ children: [
819
+ /* @__PURE__ */ jsx3(
820
+ "rect",
821
+ {
822
+ className: "ds-dial__glass",
823
+ x: cx + 50,
824
+ y: cy - 16,
825
+ width: r - 38,
826
+ height: 32,
827
+ rx: 2
828
+ }
829
+ ),
830
+ /* @__PURE__ */ jsx3(
831
+ "rect",
832
+ {
833
+ className: "ds-dial__frame-line",
834
+ x: cx + 50,
835
+ y: cy - 16,
836
+ width: r - 38,
837
+ height: 32,
838
+ rx: 3
839
+ }
840
+ ),
841
+ /* @__PURE__ */ jsx3(
842
+ "line",
843
+ {
844
+ className: "ds-dial__hairline",
845
+ x1: cx + 50,
846
+ y1: cy,
847
+ x2: cx + r + 2,
848
+ y2: cy
849
+ }
850
+ ),
851
+ /* @__PURE__ */ jsx3(
852
+ "path",
853
+ {
854
+ className: "ds-dial__index",
855
+ d: `M${cx + r + 2},${cy - 8} L${cx + r + 16},${cy} L${cx + r + 2},${cy + 8} Z`,
856
+ transform: `translate(${-frac * 5} 0)`
857
+ }
858
+ )
859
+ ]
860
+ }
861
+ ),
862
+ /* @__PURE__ */ jsx3("circle", { className: "ds-dial__rivet", cx, cy, r: 9 }),
863
+ /* @__PURE__ */ jsx3(
864
+ "text",
865
+ {
866
+ className: "ds-dial__caption",
867
+ x: cx,
868
+ y: cy + 30,
869
+ textAnchor: "middle",
870
+ children: "PIVOT"
871
+ }
872
+ )
873
+ ]
874
+ }
875
+ );
876
+ });
877
+
878
+ // src/components/DialNav/DialNav.jsx
879
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
880
+ var VARIANTS = {
881
+ volvelle: {
882
+ spec: DIAL.volvelle,
883
+ render: Volvelle,
884
+ layout: (spec, count, pos) => {
885
+ const theta = rotationFor(spec, count, pos);
886
+ return {
887
+ railRotation: theta,
888
+ angleOf: (i) => stationAngle(spec, count, i),
889
+ flipOf: (i) => uprightFlip(stationAngle(spec, count, i) + theta) ? 180 : 0
890
+ };
891
+ }
892
+ },
893
+ concentric: {
894
+ spec: DIAL.concentric,
895
+ render: Concentric,
896
+ ownsStations: true,
897
+ twoLevel: true,
898
+ layout: (spec, count, pos) => ({
899
+ railRotation: -(pos * (360 / Math.max(1, count))),
900
+ angleOf: () => 0,
901
+ flipOf: () => 0
902
+ })
903
+ },
904
+ fan: {
905
+ spec: DIAL.fan,
906
+ render: RatchetFan,
907
+ layout: (spec, count, pos, station) => ({
908
+ railRotation: 0,
909
+ angleOf: (i) => stationAngle(spec, count, i),
910
+ // The indexed spoke's label rights itself; the rest stay radial.
911
+ flipOf: (i) => i === station ? -stationAngle(spec, count, i) : 0,
912
+ bloom: true
913
+ })
914
+ }
915
+ };
916
+ var isDev = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
917
+ function ActiveProbe({ isActive, index, onRouteActive, children }) {
918
+ useEffect(() => {
919
+ if (isActive) onRouteActive(index);
920
+ }, [isActive, index, onRouteActive]);
921
+ return children;
922
+ }
923
+ function DialNav({
924
+ variant = "volvelle",
925
+ items = [],
926
+ value,
927
+ defaultValue,
928
+ onChange,
929
+ expanded,
930
+ defaultExpanded = false,
931
+ onExpandedChange,
932
+ label = "Sections",
933
+ className = ""
934
+ }) {
935
+ const resolved = VARIANTS[variant] ? variant : (isDev && console.warn(
936
+ `[DialNav] variant "${variant}" is not implemented yet; falling back to "volvelle".`
937
+ ), "volvelle");
938
+ const { spec, render: Chrome, layout, ownsStations, twoLevel } = VARIANTS[resolved];
939
+ const count = validateStationCount(items.length, "DialNav");
940
+ const stations = useMemo4(() => items.slice(0, count), [items, count]);
941
+ const [vbW, vbH] = spec.viewBox;
942
+ const indexOfId = useCallback(
943
+ (id) => {
944
+ const i = stations.findIndex((item) => item.id === id);
945
+ return i < 0 ? null : i;
946
+ },
947
+ [stations]
948
+ );
949
+ const routerMode = stations.some((item) => item.to);
950
+ const [routeIndex, setRouteIndex] = useState(null);
951
+ const syncedOnce = useRef4(false);
952
+ const controlledIndex = value != null ? indexOfId(value) : routerMode ? routeIndex : void 0;
953
+ const stageRef = useRef4(null);
954
+ const chromeRef = useRef4(null);
955
+ const linkRefs = useRef4([]);
956
+ const pageRefs = useRef4([]);
957
+ const draggingRef = useRef4(false);
958
+ const leftDuringDragRef = useRef4(false);
959
+ const handleCommit = useCallback(
960
+ (index) => {
961
+ const item = stations[index];
962
+ if (!item) return;
963
+ if (item.to && linkRefs.current[index]) linkRefs.current[index].click();
964
+ item.onSelect?.();
965
+ onChange?.(item.id);
966
+ },
967
+ [onChange, stations]
968
+ );
969
+ const travel = useDetentTravel({
970
+ count,
971
+ value: controlledIndex ?? void 0,
972
+ defaultValue: indexOfId(defaultValue) ?? 0,
973
+ onChange: handleCommit,
974
+ settleMs: spec.settleMs
975
+ });
976
+ const pages = twoLevel ? stations[travel.station]?.pages ?? [] : [];
977
+ const pageCount = Math.max(1, pages.length);
978
+ const [pageIndex, setPageIndex] = useState(0);
979
+ const handlePageCommit = useCallback(
980
+ (k) => {
981
+ const page = pages[k];
982
+ if (!page) return;
983
+ setPageIndex(k);
984
+ if (page.to && pageRefs.current[k]) pageRefs.current[k].click();
985
+ page.onSelect?.();
986
+ onChange?.(page.id);
987
+ },
988
+ [onChange, pages]
989
+ );
990
+ const innerTravel = useDetentTravel({
991
+ count: pageCount,
992
+ defaultValue: 0,
993
+ onChange: handlePageCommit,
994
+ settleMs: spec.innerSettleMs ?? spec.settleMs
995
+ });
996
+ const { goTo: innerGoTo } = innerTravel;
997
+ const sectionKey = twoLevel ? stations[travel.station]?.id : null;
998
+ useEffect(() => {
999
+ if (twoLevel) innerGoTo(0, 0);
1000
+ }, [sectionKey, twoLevel, innerGoTo]);
1001
+ const { goTo } = travel;
1002
+ const onRouteActive = useCallback(
1003
+ (index) => {
1004
+ setRouteIndex(index);
1005
+ if (!syncedOnce.current) {
1006
+ syncedOnce.current = true;
1007
+ goTo(index, 0);
1008
+ }
1009
+ },
1010
+ [goTo]
1011
+ );
1012
+ const isControlledOpen = expanded != null;
1013
+ const [internalOpen, setInternalOpen] = useState(defaultExpanded);
1014
+ const open = isControlledOpen ? expanded : internalOpen;
1015
+ const setOpen = useCallback(
1016
+ (next) => {
1017
+ if (!isControlledOpen) setInternalOpen(next);
1018
+ onExpandedChange?.(next);
1019
+ },
1020
+ [isControlledOpen, onExpandedChange]
1021
+ );
1022
+ const closeIfIdle = useCallback(() => {
1023
+ const stage = stageRef.current;
1024
+ if (stage && stage.contains(document.activeElement)) return;
1025
+ if (draggingRef.current) {
1026
+ leftDuringDragRef.current = true;
1027
+ return;
1028
+ }
1029
+ setOpen(false);
1030
+ }, [setOpen]);
1031
+ const onDragStateChange = useCallback(
1032
+ (isDragging) => {
1033
+ draggingRef.current = isDragging;
1034
+ if (isDragging) {
1035
+ leftDuringDragRef.current = false;
1036
+ return;
1037
+ }
1038
+ if (leftDuringDragRef.current) {
1039
+ leftDuringDragRef.current = false;
1040
+ closeIfIdle();
1041
+ }
1042
+ },
1043
+ [closeIfIdle]
1044
+ );
1045
+ const reducedMotion = useReducedMotion();
1046
+ const pitch = twoLevel ? 360 / Math.max(1, count) : pitchFor(spec, count);
1047
+ const active = twoLevel ? (travel.station % count + count) % count : travel.station;
1048
+ const place = layout(spec, count, travel.pos, active);
1049
+ const theta = place.railRotation;
1050
+ function onKeyDown(event) {
1051
+ const { key } = event;
1052
+ if (key === "ArrowDown" || key === "ArrowRight") travel.step(1);
1053
+ else if (key === "ArrowUp" || key === "ArrowLeft") travel.step(-1);
1054
+ else if (key === "Home") travel.goTo(0);
1055
+ else if (key === "End") travel.goTo(count - 1);
1056
+ else if (key === "Escape") {
1057
+ setOpen(false);
1058
+ return;
1059
+ } else return;
1060
+ event.preventDefault();
1061
+ }
1062
+ const linkClass = (isActive) => "ds-dial__link" + (isActive ? " ds-dial__link--active" : "");
1063
+ const station = (item, i, { refs, isActive, onPick, extraClass = "" }) => item.to ? /* @__PURE__ */ jsx4(
1064
+ NavLink,
1065
+ {
1066
+ to: item.to,
1067
+ end: item.end,
1068
+ ref: (el) => refs.current[i] = el,
1069
+ className: ({ isActive: routerActive }) => linkClass(routerActive) + extraClass,
1070
+ children: ({ isActive: routerActive }) => /* @__PURE__ */ jsx4(
1071
+ ActiveProbe,
1072
+ {
1073
+ isActive: routerActive,
1074
+ index: i,
1075
+ onRouteActive,
1076
+ children: item.label
1077
+ }
1078
+ )
1079
+ }
1080
+ ) : /* @__PURE__ */ jsx4(
1081
+ "button",
1082
+ {
1083
+ type: "button",
1084
+ className: linkClass(isActive) + extraClass,
1085
+ "aria-current": isActive ? "true" : void 0,
1086
+ onClick: onPick,
1087
+ children: item.label
1088
+ }
1089
+ );
1090
+ const renderStation = (i) => station(stations[i], i, {
1091
+ refs: linkRefs,
1092
+ isActive: i === active,
1093
+ onPick: () => travel.goTo(i)
1094
+ });
1095
+ const renderPage = (page, k) => station(page, k, {
1096
+ refs: pageRefs,
1097
+ isActive: k === pageIndex,
1098
+ onPick: () => innerTravel.goTo(k),
1099
+ extraClass: " ds-dial__link--page"
1100
+ });
1101
+ return /* @__PURE__ */ jsx4(
1102
+ "nav",
1103
+ {
1104
+ className: [
1105
+ "ds-dial",
1106
+ `ds-dial--${resolved}`,
1107
+ open ? "is-out" : "",
1108
+ className
1109
+ ].filter(Boolean).join(" "),
1110
+ "aria-label": label,
1111
+ onKeyDown,
1112
+ children: /* @__PURE__ */ jsxs4(
1113
+ "div",
1114
+ {
1115
+ className: "ds-dial__stage",
1116
+ ref: stageRef,
1117
+ onPointerLeave: closeIfIdle,
1118
+ onFocus: () => setOpen(true),
1119
+ style: {
1120
+ "--dial-w": `${vbW}px`,
1121
+ "--dial-h": `${vbH}px`,
1122
+ "--dial-cx": `${spec.cx}px`,
1123
+ "--dial-cy": `${spec.cy}px`
1124
+ },
1125
+ children: [
1126
+ /* @__PURE__ */ jsx4("span", { className: "ds-dial__peek", "aria-hidden": "true", children: stations[active]?.label }),
1127
+ /* @__PURE__ */ jsx4("div", { className: "ds-dial__frame", children: /* @__PURE__ */ jsxs4(
1128
+ "div",
1129
+ {
1130
+ className: "ds-dial__dock",
1131
+ style: spec.restClip != null ? {
1132
+ // C-III pivots at the edge, so it is unveiled by opening a
1133
+ // clip rather than by sliding sideways.
1134
+ clipPath: open ? "inset(0 0 0 0)" : `inset(0 ${spec.restClip}px 0 0)`
1135
+ } : {
1136
+ transform: `translateX(${open ? spec.out : spec.rest}px)`
1137
+ },
1138
+ children: [
1139
+ /* @__PURE__ */ jsx4(
1140
+ Chrome,
1141
+ {
1142
+ ref: chromeRef,
1143
+ spec,
1144
+ count,
1145
+ travel,
1146
+ theta,
1147
+ pitch,
1148
+ onDragStateChange,
1149
+ reducedMotion,
1150
+ pages,
1151
+ innerTravel,
1152
+ renderStation,
1153
+ renderPage
1154
+ }
1155
+ ),
1156
+ !ownsStations && /* @__PURE__ */ jsx4(
1157
+ "ul",
1158
+ {
1159
+ className: "ds-dial__stations",
1160
+ style: { transform: `rotate(${theta}deg)` },
1161
+ children: stations.map((item, index) => /* @__PURE__ */ jsx4(
1162
+ "li",
1163
+ {
1164
+ className: "ds-dial__station",
1165
+ style: {
1166
+ "--a": `${place.angleOf(index)}deg`,
1167
+ "--r": `${spec.labelR}px`,
1168
+ "--flip": `${place.flipOf(index)}deg`,
1169
+ "--bloom-delay": place.bloom ? `${index * spec.bloomStaggerMs}ms` : "0ms"
1170
+ },
1171
+ children: renderStation(index)
1172
+ },
1173
+ item.id
1174
+ ))
1175
+ }
1176
+ )
1177
+ ]
1178
+ }
1179
+ ) }),
1180
+ /* @__PURE__ */ jsx4(
1181
+ "button",
1182
+ {
1183
+ type: "button",
1184
+ className: "ds-dial__hotzone",
1185
+ "aria-expanded": open,
1186
+ "aria-label": open ? `Close ${label}` : `Open ${label}`,
1187
+ onPointerEnter: (e) => {
1188
+ if (e.pointerType === "mouse") setOpen(true);
1189
+ },
1190
+ onPointerDown: (e) => {
1191
+ if (e.pointerType !== "mouse") setOpen(!open);
1192
+ },
1193
+ onClick: (e) => {
1194
+ if (!e.detail) setOpen(!open);
1195
+ }
1196
+ }
1197
+ )
1198
+ ]
1199
+ }
1200
+ )
1201
+ }
1202
+ );
1203
+ }
1204
+
1205
+ export {
1206
+ DialNav
1207
+ };
1208
+ //# sourceMappingURL=chunk-NJ2SBQYA.js.map