@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,1023 @@
1
+ import {
2
+ useDetentTravel,
3
+ usePointerDrag,
4
+ useReducedMotion,
5
+ validateStationCount
6
+ } from "./chunk-UZW4E2UH.js";
7
+
8
+ // src/components/SlideRuleNav/SlideRuleNav.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/SlideRuleNav/geometry.js
13
+ var MINOR_TARGET = 38;
14
+ var LINEAR = Object.freeze({
15
+ /**
16
+ * L-I — fixed scale, cursor travels.
17
+ *
18
+ * The stock runs from `x0 - cursorBox.w / 2` to `x1 + cursorBox.w / 2` for
19
+ * the same reason as L-III: the cursor is centred on the station it reads,
20
+ * so the stock has to extend past the end graduations for it to sit on the
21
+ * instrument at station 0 and at the last station.
22
+ */
23
+ cursor: Object.freeze({
24
+ viewBox: Object.freeze([900, 136]),
25
+ x0: 62,
26
+ x1: 838,
27
+ railX0: 30,
28
+ railX1: 870,
29
+ top: 40,
30
+ bot: 112,
31
+ labelY: 24,
32
+ cursorBox: Object.freeze({ w: 60, h: 100, y: 28 }),
33
+ settleMs: 420
34
+ }),
35
+ /** L-II — fixed index, slide travels, true log10 station spacing. */
36
+ slide: Object.freeze({
37
+ viewBox: Object.freeze([900, 150]),
38
+ l0: 60,
39
+ lw: 1400,
40
+ index: 450,
41
+ clip: Object.freeze([30, 870]),
42
+ statorL0: 40,
43
+ statorLw: 800,
44
+ slideY: 46,
45
+ slideH: 58,
46
+ labelY: 88,
47
+ settleMs: 460
48
+ }),
49
+ /**
50
+ * L-III — notched rail, vernier carriage. Reference plate.
51
+ *
52
+ * The station span is 772 units, exactly as drawn in the artboards, but it
53
+ * is inset 124 units from the left rather than 64. The carriage is centred
54
+ * on the station it reads, and its widest form (5 stations) is 224 units
55
+ * across — so at station 0 a 64-unit inset put half the vernier off the end
56
+ * of the rule. The artboards never showed it because they opened on station
57
+ * 2. Keeping the span identical preserves every derived division.
58
+ *
59
+ * The rail then runs from `x0 - maxCarriageHalf` to `x1 + maxCarriageHalf`,
60
+ * because a real stock extends past its scale far enough for the cursor to
61
+ * reach the end graduations without hanging off the instrument.
62
+ */
63
+ vernier: Object.freeze({
64
+ viewBox: Object.freeze([1020, 160]),
65
+ x0: 124,
66
+ x1: 896,
67
+ railX0: 12,
68
+ railX1: 1008,
69
+ railY: 46,
70
+ railH: 42,
71
+ labelY: 26,
72
+ vernierDivisions: 5,
73
+ mainDivisionsSpanned: 4,
74
+ /** Widest half-carriage across all supported counts; the inset floor. */
75
+ maxCarriageHalf: 112,
76
+ settleMs: 380
77
+ })
78
+ });
79
+ var SLIDE_VALUES = Object.freeze({
80
+ 2: Object.freeze([1, 8]),
81
+ 3: Object.freeze([1, 2, 8]),
82
+ 4: Object.freeze([1, 2, 3, 8]),
83
+ 5: Object.freeze([1, 1.5, 2, 3, 8]),
84
+ 6: Object.freeze([1, 1.5, 2, 3, 5, 8]),
85
+ 7: Object.freeze([1, 1.5, 2, 3, 4, 6, 8]),
86
+ 8: Object.freeze([1, 1.5, 2, 2.5, 3, 4, 6, 8])
87
+ });
88
+ var pct = (v, extent) => `${v / extent * 100}%`;
89
+ var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
90
+ function pitchFor(spec, count) {
91
+ return (spec.x1 - spec.x0) / Math.max(1, count - 1);
92
+ }
93
+ function minorDivisions(pitch) {
94
+ const minorsPerPitch = 4 * Math.max(1, Math.round(pitch / (4 * MINOR_TARGET)));
95
+ const minor = pitch / minorsPerPitch;
96
+ return {
97
+ minorsPerPitch,
98
+ minor,
99
+ vdiv: minor * 4 / 5,
100
+ minorsPerDetent: minorsPerPitch / 4
101
+ };
102
+ }
103
+ function coincidence({ pos, count, spec = LINEAR.vernier }) {
104
+ const pitch = pitchFor(spec, count);
105
+ const { minor, vdiv } = minorDivisions(pitch);
106
+ const cx = spec.x0 + pos * pitch;
107
+ let k = 0;
108
+ let d = Infinity;
109
+ for (let i = 0; i < LINEAR.vernier.vernierDivisions; i++) {
110
+ const vx = cx + (i - 2.5) * vdiv;
111
+ const off = vx - spec.x0;
112
+ const dist = Math.abs(off - Math.round(off / minor) * minor);
113
+ if (dist < d - 1e-9) {
114
+ d = dist;
115
+ k = i;
116
+ }
117
+ }
118
+ return { k, d, lit: d < vdiv * 0.34 };
119
+ }
120
+ var logX = (spec, v) => spec.l0 + Math.log10(v) * spec.lw;
121
+ var slideValues = (count) => SLIDE_VALUES[count] ?? SLIDE_VALUES[6];
122
+ var logStops = (spec, count) => slideValues(count).map((v) => logX(spec, v));
123
+ var statorX = (spec, v) => spec.statorL0 + Math.log10(v) * spec.statorLw;
124
+ function localAt(stops, pos) {
125
+ const last = stops.length - 1;
126
+ const i = clamp(Math.floor(pos), 0, last - 1);
127
+ const f = clamp(pos - i, 0, 1);
128
+ return stops[i] + (stops[i + 1] - stops[i]) * f;
129
+ }
130
+ function posFromLocal(stops, want) {
131
+ const last = stops.length - 1;
132
+ if (want <= stops[0]) return 0;
133
+ if (want >= stops[last]) return last;
134
+ for (let i = 0; i < last; i++) {
135
+ if (want <= stops[i + 1]) {
136
+ return i + (want - stops[i]) / (stops[i + 1] - stops[i]);
137
+ }
138
+ }
139
+ return last;
140
+ }
141
+
142
+ // src/components/SlideRuleNav/variants/CursorRule.jsx
143
+ import { forwardRef, useMemo, useRef } from "react";
144
+ import { jsx, jsxs } from "react/jsx-runtime";
145
+ var CursorRule = forwardRef(function CursorRule2({ spec, count, stations, travel, toViewBoxX }, ref) {
146
+ const [vbW, vbH] = spec.viewBox;
147
+ const { x0, railX0, railX1, top, bot, cursorBox } = spec;
148
+ const pitch = pitchFor(spec, count);
149
+ const { minorsPerPitch, minorsPerDetent } = minorDivisions(pitch);
150
+ const cursorRef = useRef(null);
151
+ usePointerDrag(cursorRef, {
152
+ onBegin: () => travel.begin(),
153
+ onMove: (event) => {
154
+ const x = toViewBoxX(event.clientX);
155
+ if (x != null) travel.track((x - x0) / pitch);
156
+ },
157
+ onEnd: (_event, wasTap) => {
158
+ if (!wasTap) travel.release();
159
+ }
160
+ });
161
+ const ticks = useMemo(() => {
162
+ const out = [];
163
+ for (let q = 0; q <= (count - 1) * minorsPerPitch; q++) {
164
+ const isStation = q % minorsPerPitch === 0;
165
+ const isDetent = q % minorsPerDetent === 0;
166
+ out.push({
167
+ x: x0 + q / minorsPerPitch * pitch,
168
+ kind: isStation ? "major" : isDetent ? "half" : "quarter",
169
+ down: isStation ? 22 : isDetent ? 13 : 8,
170
+ up: isStation ? 18 : isDetent ? 11 : 7,
171
+ station: isStation ? q / minorsPerPitch : null
172
+ });
173
+ }
174
+ return out;
175
+ }, [count, minorsPerDetent, minorsPerPitch, pitch, x0]);
176
+ const cursorX = x0 + travel.pos * pitch;
177
+ const half = cursorBox.w / 2;
178
+ const cursorTop = cursorBox.y;
179
+ const cursorBottom = cursorBox.y + cursorBox.h;
180
+ return /* @__PURE__ */ jsxs(
181
+ "svg",
182
+ {
183
+ ref,
184
+ className: "ds-rule__chrome",
185
+ viewBox: `0 0 ${vbW} ${vbH}`,
186
+ "aria-hidden": "true",
187
+ focusable: "false",
188
+ children: [
189
+ /* @__PURE__ */ jsx(
190
+ "rect",
191
+ {
192
+ className: "ds-rule__rail",
193
+ x: railX0,
194
+ y: top,
195
+ width: railX1 - railX0,
196
+ height: bot - top,
197
+ rx: 2
198
+ }
199
+ ),
200
+ /* @__PURE__ */ jsx(
201
+ "line",
202
+ {
203
+ className: "ds-rule__tick ds-rule__tick--quarter",
204
+ x1: railX0,
205
+ y1: (top + bot) / 2,
206
+ x2: railX1,
207
+ y2: (top + bot) / 2
208
+ }
209
+ ),
210
+ [
211
+ [railX0 + 10, top + 10],
212
+ [railX1 - 10, top + 10],
213
+ [railX0 + 10, bot - 10],
214
+ [railX1 - 10, bot - 10]
215
+ ].map(([cx, cy], i) => /* @__PURE__ */ jsx("circle", { className: "ds-rule__rivet", cx, cy, r: 3 }, i)),
216
+ ticks.map((tick, i) => /* @__PURE__ */ jsxs("g", { children: [
217
+ /* @__PURE__ */ jsx(
218
+ "line",
219
+ {
220
+ className: `ds-rule__tick ds-rule__tick--${tick.kind}`,
221
+ x1: tick.x,
222
+ y1: top,
223
+ x2: tick.x,
224
+ y2: top + tick.down
225
+ }
226
+ ),
227
+ /* @__PURE__ */ jsx(
228
+ "line",
229
+ {
230
+ className: `ds-rule__tick ds-rule__tick--${tick.kind}`,
231
+ x1: tick.x,
232
+ y1: bot,
233
+ x2: tick.x,
234
+ y2: bot - tick.up
235
+ }
236
+ )
237
+ ] }, i)),
238
+ ticks.filter((tick) => tick.station != null).map((tick) => /* @__PURE__ */ jsxs("g", { children: [
239
+ /* @__PURE__ */ jsx(
240
+ "text",
241
+ {
242
+ className: "ds-rule__numeral ds-rule__numeral--accent",
243
+ x: tick.x,
244
+ y: top + 34,
245
+ textAnchor: "middle",
246
+ children: stations[tick.station]?.value ?? tick.station.toFixed(2)
247
+ }
248
+ ),
249
+ /* @__PURE__ */ jsx(
250
+ "text",
251
+ {
252
+ className: "ds-rule__numeral",
253
+ x: tick.x,
254
+ y: bot - 22,
255
+ textAnchor: "middle",
256
+ children: tick.station.toFixed(2)
257
+ }
258
+ )
259
+ ] }, `n${tick.station}`)),
260
+ /* @__PURE__ */ jsxs(
261
+ "g",
262
+ {
263
+ ref: cursorRef,
264
+ className: "ds-rule__carriage",
265
+ transform: `translate(${cursorX},0)`,
266
+ children: [
267
+ /* @__PURE__ */ jsx(
268
+ "rect",
269
+ {
270
+ className: "ds-rule__glass",
271
+ x: -half,
272
+ y: cursorTop,
273
+ width: cursorBox.w,
274
+ height: cursorBox.h,
275
+ rx: 2
276
+ }
277
+ ),
278
+ /* @__PURE__ */ jsx(
279
+ "rect",
280
+ {
281
+ className: "ds-rule__frame",
282
+ x: -half,
283
+ y: cursorTop,
284
+ width: cursorBox.w,
285
+ height: cursorBox.h,
286
+ rx: 3
287
+ }
288
+ ),
289
+ /* @__PURE__ */ jsx(
290
+ "rect",
291
+ {
292
+ className: "ds-rule__shoe",
293
+ x: -half - 2,
294
+ y: cursorTop,
295
+ width: cursorBox.w + 4,
296
+ height: 7,
297
+ rx: 2
298
+ }
299
+ ),
300
+ /* @__PURE__ */ jsx(
301
+ "rect",
302
+ {
303
+ className: "ds-rule__shoe",
304
+ x: -half - 2,
305
+ y: cursorBottom - 7,
306
+ width: cursorBox.w + 4,
307
+ height: 7,
308
+ rx: 2
309
+ }
310
+ ),
311
+ [
312
+ [-24, cursorTop + 3.5],
313
+ [24, cursorTop + 3.5],
314
+ [-24, cursorBottom - 3.5],
315
+ [24, cursorBottom - 3.5]
316
+ ].map(([cx, cy], i) => /* @__PURE__ */ jsx("circle", { className: "ds-rule__rivet", cx, cy, r: 2.2 }, i)),
317
+ /* @__PURE__ */ jsx(
318
+ "line",
319
+ {
320
+ className: "ds-rule__hairline",
321
+ x1: 0,
322
+ y1: cursorTop,
323
+ x2: 0,
324
+ y2: cursorBottom
325
+ }
326
+ )
327
+ ]
328
+ }
329
+ )
330
+ ]
331
+ }
332
+ );
333
+ });
334
+
335
+ // src/components/SlideRuleNav/variants/SlideStator.jsx
336
+ import { forwardRef as forwardRef2, useId, useMemo as useMemo2, useRef as useRef2 } from "react";
337
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
338
+ function decadeTicks(mapX) {
339
+ const bands = [
340
+ [1, 2, 0.02, 0.1],
341
+ [2, 5, 0.05, 0.5],
342
+ [5, 10, 0.1, 1]
343
+ ];
344
+ const out = [];
345
+ for (const [a, b, step, major] of bands) {
346
+ for (let v = a; v < b + 1e-9; v += step) {
347
+ const isMajor = Math.abs(v / major - Math.round(v / major)) < 1e-6;
348
+ const half = Math.abs(v / (major / 2) - Math.round(v / (major / 2))) < 1e-6;
349
+ out.push({
350
+ x: mapX(v),
351
+ v,
352
+ len: isMajor ? 12 : half ? 8 : 5,
353
+ kind: isMajor ? "major" : half ? "half" : "quarter",
354
+ label: isMajor ? String(Math.round(v * 10) / 10) : null
355
+ });
356
+ }
357
+ }
358
+ return out;
359
+ }
360
+ var SlideStator = forwardRef2(function SlideStator2({ spec, count, stations, travel, toViewBoxX }, ref) {
361
+ const [vbW, vbH] = spec.viewBox;
362
+ const { index, slideY, slideH, clip } = spec;
363
+ const clipId = useId();
364
+ const stops = useMemo2(() => logStops(spec, count), [spec, count]);
365
+ const values = slideValues(count);
366
+ const offset = index - localAt(stops, travel.pos);
367
+ const grabbed = useRef2(0);
368
+ const slideRef = useRef2(null);
369
+ usePointerDrag(slideRef, {
370
+ onBegin: (event) => {
371
+ travel.begin();
372
+ const x = toViewBoxX(event.clientX);
373
+ grabbed.current = localAt(stops, travel.pos) + (x == null ? 0 : x - index);
374
+ },
375
+ onMove: (event) => {
376
+ const x = toViewBoxX(event.clientX);
377
+ if (x == null) return;
378
+ travel.track(posFromLocal(stops, grabbed.current - (x - index)));
379
+ },
380
+ onEnd: (_event, wasTap) => {
381
+ if (!wasTap) travel.release();
382
+ }
383
+ });
384
+ const statorTicks = useMemo2(
385
+ () => decadeTicks((v) => statorX(spec, v)),
386
+ [spec]
387
+ );
388
+ const detents = useMemo2(() => {
389
+ const out = [];
390
+ for (let q = 0; q <= (count - 1) * 4; q++) {
391
+ if (q % 4 === 0) continue;
392
+ out.push({ x: localAt(stops, q / 4), half: q % 2 === 0 });
393
+ }
394
+ return out;
395
+ }, [count, stops]);
396
+ const slideBottom = slideY + slideH;
397
+ return /* @__PURE__ */ jsxs2(
398
+ "svg",
399
+ {
400
+ ref,
401
+ className: "ds-rule__chrome",
402
+ viewBox: `0 0 ${vbW} ${vbH}`,
403
+ "aria-hidden": "true",
404
+ focusable: "false",
405
+ children: [
406
+ /* @__PURE__ */ jsx2("defs", { children: /* @__PURE__ */ jsx2("clipPath", { id: clipId, children: /* @__PURE__ */ jsx2("rect", { x: clip[0], y: 0, width: clip[1] - clip[0], height: vbH }) }) }),
407
+ /* @__PURE__ */ jsx2(
408
+ "rect",
409
+ {
410
+ className: "ds-rule__rail",
411
+ x: clip[0],
412
+ y: 6,
413
+ width: clip[1] - clip[0],
414
+ height: 40
415
+ }
416
+ ),
417
+ /* @__PURE__ */ jsx2(
418
+ "rect",
419
+ {
420
+ className: "ds-rule__rail",
421
+ x: clip[0],
422
+ y: slideBottom,
423
+ width: clip[1] - clip[0],
424
+ height: 40
425
+ }
426
+ ),
427
+ statorTicks.map((tick, i) => /* @__PURE__ */ jsxs2("g", { children: [
428
+ /* @__PURE__ */ jsx2(
429
+ "line",
430
+ {
431
+ className: `ds-rule__tick ds-rule__tick--${tick.kind}`,
432
+ x1: tick.x,
433
+ y1: 46,
434
+ x2: tick.x,
435
+ y2: 46 - tick.len
436
+ }
437
+ ),
438
+ /* @__PURE__ */ jsx2(
439
+ "line",
440
+ {
441
+ className: `ds-rule__tick ds-rule__tick--${tick.kind}`,
442
+ x1: tick.x,
443
+ y1: slideBottom,
444
+ x2: tick.x,
445
+ y2: slideBottom + tick.len
446
+ }
447
+ ),
448
+ tick.label && /* @__PURE__ */ jsxs2(Fragment, { children: [
449
+ /* @__PURE__ */ jsx2(
450
+ "text",
451
+ {
452
+ className: "ds-rule__numeral",
453
+ x: tick.x,
454
+ y: 46 - tick.len - 3,
455
+ textAnchor: "middle",
456
+ children: tick.label
457
+ }
458
+ ),
459
+ /* @__PURE__ */ jsx2(
460
+ "text",
461
+ {
462
+ className: "ds-rule__numeral",
463
+ x: tick.x,
464
+ y: slideBottom + tick.len + 9,
465
+ textAnchor: "middle",
466
+ children: tick.label
467
+ }
468
+ )
469
+ ] })
470
+ ] }, i)),
471
+ /* @__PURE__ */ jsx2("text", { className: "ds-rule__caption", x: clip[0] + 6, y: 44, children: "C" }),
472
+ /* @__PURE__ */ jsx2("text", { className: "ds-rule__caption", x: clip[0] + 6, y: slideBottom + 12, children: "D" }),
473
+ /* @__PURE__ */ jsx2("g", { clipPath: `url(#${clipId})`, children: /* @__PURE__ */ jsxs2(
474
+ "g",
475
+ {
476
+ ref: slideRef,
477
+ className: "ds-rule__carriage",
478
+ transform: `translate(${offset},0)`,
479
+ children: [
480
+ /* @__PURE__ */ jsx2(
481
+ "rect",
482
+ {
483
+ className: "ds-rule__slide",
484
+ x: stops[0] - 400,
485
+ y: slideY,
486
+ width: stops[stops.length - 1] - stops[0] + 800,
487
+ height: slideH
488
+ }
489
+ ),
490
+ detents.map((d, i) => /* @__PURE__ */ jsxs2("g", { children: [
491
+ /* @__PURE__ */ jsx2(
492
+ "line",
493
+ {
494
+ className: "ds-rule__tick ds-rule__tick--quarter",
495
+ x1: d.x,
496
+ y1: slideY,
497
+ x2: d.x,
498
+ y2: slideY + (d.half ? 10 : 6)
499
+ }
500
+ ),
501
+ /* @__PURE__ */ jsx2(
502
+ "line",
503
+ {
504
+ className: "ds-rule__tick ds-rule__tick--quarter",
505
+ x1: d.x,
506
+ y1: slideBottom,
507
+ x2: d.x,
508
+ y2: slideBottom - (d.half ? 10 : 6)
509
+ }
510
+ )
511
+ ] }, i)),
512
+ stops.map((x, i) => /* @__PURE__ */ jsxs2("g", { children: [
513
+ /* @__PURE__ */ jsx2(
514
+ "line",
515
+ {
516
+ className: "ds-rule__tick ds-rule__tick--major",
517
+ x1: x,
518
+ y1: slideY,
519
+ x2: x,
520
+ y2: slideY + 14
521
+ }
522
+ ),
523
+ /* @__PURE__ */ jsx2(
524
+ "line",
525
+ {
526
+ className: "ds-rule__tick ds-rule__tick--major",
527
+ x1: x,
528
+ y1: slideBottom,
529
+ x2: x,
530
+ y2: slideBottom - 14
531
+ }
532
+ ),
533
+ /* @__PURE__ */ jsx2(
534
+ "text",
535
+ {
536
+ className: "ds-rule__numeral ds-rule__numeral--accent",
537
+ x,
538
+ y: slideBottom - 4,
539
+ textAnchor: "middle",
540
+ children: stations[i]?.value ?? values[i].toFixed(2)
541
+ }
542
+ )
543
+ ] }, i))
544
+ ]
545
+ }
546
+ ) }),
547
+ /* @__PURE__ */ jsxs2("g", { className: "ds-rule__window", children: [
548
+ /* @__PURE__ */ jsx2(
549
+ "rect",
550
+ {
551
+ className: "ds-rule__glass",
552
+ x: index - 58,
553
+ y: 2,
554
+ width: 116,
555
+ height: vbH - 4,
556
+ rx: 2
557
+ }
558
+ ),
559
+ /* @__PURE__ */ jsx2(
560
+ "rect",
561
+ {
562
+ className: "ds-rule__frame",
563
+ x: index - 58,
564
+ y: 2,
565
+ width: 116,
566
+ height: vbH - 4,
567
+ rx: 3
568
+ }
569
+ ),
570
+ /* @__PURE__ */ jsx2(
571
+ "line",
572
+ {
573
+ className: "ds-rule__hairline",
574
+ x1: index,
575
+ y1: 2,
576
+ x2: index,
577
+ y2: vbH - 2
578
+ }
579
+ ),
580
+ /* @__PURE__ */ jsx2(
581
+ "path",
582
+ {
583
+ className: "ds-rule__index",
584
+ d: `M${index - 7},2 L${index + 7},2 L${index},13 Z`
585
+ }
586
+ ),
587
+ /* @__PURE__ */ jsx2(
588
+ "path",
589
+ {
590
+ className: "ds-rule__index",
591
+ d: `M${index - 7},${vbH - 2} L${index + 7},${vbH - 2} L${index},${vbH - 13} Z`
592
+ }
593
+ ),
594
+ /* @__PURE__ */ jsx2(
595
+ "text",
596
+ {
597
+ className: "ds-rule__caption ds-rule__caption--accent",
598
+ x: index + 64,
599
+ y: 12,
600
+ children: "INDEX"
601
+ }
602
+ )
603
+ ] })
604
+ ]
605
+ }
606
+ );
607
+ });
608
+
609
+ // src/components/SlideRuleNav/variants/VernierCarriage.jsx
610
+ import { forwardRef as forwardRef3, useMemo as useMemo3, useRef as useRef3 } from "react";
611
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
612
+ var VernierCarriage = forwardRef3(function VernierCarriage2({ spec, count, stations, travel, toViewBoxX }, ref) {
613
+ const [vbW, vbH] = spec.viewBox;
614
+ const { x0, railX0, railX1, railY, railH } = spec;
615
+ const bot = railY + railH;
616
+ const pitch = pitchFor(spec, count);
617
+ const { minor, vdiv, minorsPerPitch, minorsPerDetent } = minorDivisions(pitch);
618
+ const carriageRef = useRef3(null);
619
+ usePointerDrag(carriageRef, {
620
+ onBegin: () => travel.begin(),
621
+ onMove: (event) => {
622
+ const x = toViewBoxX(event.clientX);
623
+ if (x != null) travel.track((x - x0) / pitch);
624
+ },
625
+ onEnd: (_event, wasTap) => {
626
+ if (!wasTap) travel.release();
627
+ }
628
+ });
629
+ const totalMinors = (count - 1) * minorsPerPitch;
630
+ const rail = useMemo3(() => {
631
+ let d = `M${railX0},${bot} L${railX0},${railY} `;
632
+ for (let q = 0; q <= totalMinors; q++) {
633
+ const tx = x0 + q / minorsPerPitch * pitch;
634
+ const deep = q % minorsPerDetent === 0;
635
+ const w = deep ? 7 : 4;
636
+ const dep = deep ? 9 : 5;
637
+ d += `L${tx - w},${railY} L${tx},${railY + dep} L${tx + w},${railY} `;
638
+ }
639
+ return `${d}L${railX1},${railY} L${railX1},${bot} Z`;
640
+ }, [bot, minorsPerDetent, minorsPerPitch, pitch, railX0, railX1, railY, totalMinors, x0]);
641
+ const ticks = useMemo3(() => {
642
+ const out = [];
643
+ for (let q = 0; q <= totalMinors; q++) {
644
+ const tx = x0 + q / minorsPerPitch * pitch;
645
+ const isStation = q % minorsPerPitch === 0;
646
+ const isDetent = q % minorsPerDetent === 0;
647
+ out.push({
648
+ x: tx,
649
+ len: isStation ? 20 : isDetent ? 13 : 8,
650
+ kind: isStation ? "major" : isDetent ? "half" : "quarter",
651
+ station: isStation ? q / minorsPerPitch : null
652
+ });
653
+ }
654
+ return out;
655
+ }, [minorsPerDetent, minorsPerPitch, pitch, totalMinors, x0]);
656
+ const { pos } = travel;
657
+ const carriageX = x0 + pos * pitch;
658
+ const lit = coincidence({ pos, count, spec });
659
+ const frac = Math.abs(pos * minorsPerPitch - Math.round(pos * minorsPerPitch));
660
+ const pawlY = railY + 2 - frac * 13;
661
+ const glassW = vdiv * 5.8;
662
+ const captionLeft = carriageX + glassW / 2 + 60 > railX1;
663
+ return /* @__PURE__ */ jsxs3(
664
+ "svg",
665
+ {
666
+ ref,
667
+ className: "ds-rule__chrome",
668
+ viewBox: `0 0 ${vbW} ${vbH}`,
669
+ "aria-hidden": "true",
670
+ focusable: "false",
671
+ children: [
672
+ /* @__PURE__ */ jsx3("path", { className: "ds-rule__rail", d: rail }),
673
+ /* @__PURE__ */ jsx3("circle", { className: "ds-rule__rivet", cx: railX0 + 10, cy: bot - 8, r: 3 }),
674
+ /* @__PURE__ */ jsx3("circle", { className: "ds-rule__rivet", cx: railX1 - 10, cy: bot - 8, r: 3 }),
675
+ ticks.map((tick, i) => /* @__PURE__ */ jsx3(
676
+ "line",
677
+ {
678
+ className: `ds-rule__tick ds-rule__tick--${tick.kind}`,
679
+ x1: tick.x,
680
+ y1: bot,
681
+ x2: tick.x,
682
+ y2: bot - tick.len
683
+ },
684
+ i
685
+ )),
686
+ ticks.filter((tick) => tick.station != null).map((tick) => /* @__PURE__ */ jsx3(
687
+ "text",
688
+ {
689
+ className: "ds-rule__numeral ds-rule__numeral--accent",
690
+ x: tick.x,
691
+ y: bot - 24,
692
+ textAnchor: "middle",
693
+ children: stations[tick.station]?.value ?? tick.station.toFixed(2)
694
+ },
695
+ `n${tick.station}`
696
+ )),
697
+ /* @__PURE__ */ jsx3("text", { className: "ds-rule__caption", x: railX0 + 6, y: railY - 8, children: "MAIN" }),
698
+ ticks.filter((tick) => tick.station != null).map((tick) => /* @__PURE__ */ jsx3(
699
+ "line",
700
+ {
701
+ className: "ds-rule__tick ds-rule__tick--quarter",
702
+ x1: tick.x,
703
+ y1: railY - 16,
704
+ x2: tick.x,
705
+ y2: railY - 9
706
+ },
707
+ `l${tick.station}`
708
+ )),
709
+ /* @__PURE__ */ jsxs3(
710
+ "g",
711
+ {
712
+ ref: carriageRef,
713
+ className: "ds-rule__carriage",
714
+ transform: `translate(${carriageX},0)`,
715
+ children: [
716
+ /* @__PURE__ */ jsx3(
717
+ "rect",
718
+ {
719
+ className: "ds-rule__pawl-spring",
720
+ x: -2,
721
+ y: pawlY - 24,
722
+ width: 4,
723
+ height: 11
724
+ }
725
+ ),
726
+ /* @__PURE__ */ jsx3(
727
+ "path",
728
+ {
729
+ className: "ds-rule__pawl",
730
+ d: "M-6,-11 L6,-11 L0,2 Z",
731
+ transform: `translate(0,${pawlY})`
732
+ }
733
+ ),
734
+ /* @__PURE__ */ jsx3(
735
+ "rect",
736
+ {
737
+ className: "ds-rule__glass",
738
+ x: -glassW / 2,
739
+ y: railY - 2,
740
+ width: glassW,
741
+ height: railH + 50,
742
+ rx: 2
743
+ }
744
+ ),
745
+ /* @__PURE__ */ jsx3(
746
+ "rect",
747
+ {
748
+ className: "ds-rule__frame",
749
+ x: -glassW / 2,
750
+ y: railY - 2,
751
+ width: glassW,
752
+ height: railH + 50,
753
+ rx: 3
754
+ }
755
+ ),
756
+ /* @__PURE__ */ jsx3(
757
+ "line",
758
+ {
759
+ className: "ds-rule__hairline",
760
+ x1: 0,
761
+ y1: railY - 2,
762
+ x2: 0,
763
+ y2: bot + 48
764
+ }
765
+ ),
766
+ Array.from({ length: 6 }, (_, k) => {
767
+ const vx = (k - 2.5) * vdiv;
768
+ const on = k === lit.k && lit.lit;
769
+ return /* @__PURE__ */ jsxs3("g", { children: [
770
+ /* @__PURE__ */ jsx3(
771
+ "line",
772
+ {
773
+ className: "ds-rule__vernier-line" + (on ? " ds-rule__vernier-line--lit" : ""),
774
+ x1: vx,
775
+ y1: bot + 2,
776
+ x2: vx,
777
+ y2: bot + 18
778
+ }
779
+ ),
780
+ k < 5 && /* @__PURE__ */ jsx3(
781
+ "text",
782
+ {
783
+ className: "ds-rule__numeral",
784
+ x: vx,
785
+ y: bot + 28,
786
+ textAnchor: "middle",
787
+ children: k
788
+ }
789
+ )
790
+ ] }, k);
791
+ }),
792
+ /* @__PURE__ */ jsx3(
793
+ "text",
794
+ {
795
+ className: "ds-rule__caption",
796
+ x: captionLeft ? -glassW / 2 - 6 : glassW / 2 + 6,
797
+ y: bot + 18,
798
+ textAnchor: captionLeft ? "end" : "start",
799
+ children: "VERNIER"
800
+ }
801
+ )
802
+ ]
803
+ }
804
+ )
805
+ ]
806
+ }
807
+ );
808
+ });
809
+
810
+ // src/components/SlideRuleNav/SlideRuleNav.jsx
811
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
812
+ var fixedLayout = (spec, count) => ({
813
+ stationX: (i) => spec.x0 + i * pitchFor(spec, count),
814
+ offset: 0
815
+ });
816
+ var slideLayout = (spec, count, pos) => {
817
+ const stops = logStops(spec, count);
818
+ return {
819
+ stationX: (i) => stops[i],
820
+ offset: spec.index - localAt(stops, pos)
821
+ };
822
+ };
823
+ var VARIANTS = {
824
+ cursor: { spec: LINEAR.cursor, render: CursorRule, layout: fixedLayout },
825
+ slide: { spec: LINEAR.slide, render: SlideStator, layout: slideLayout },
826
+ vernier: {
827
+ spec: LINEAR.vernier,
828
+ render: VernierCarriage,
829
+ layout: fixedLayout
830
+ }
831
+ };
832
+ var isDev = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
833
+ function ActiveProbe({ isActive, index, onRouteActive, children }) {
834
+ useEffect(() => {
835
+ if (isActive) onRouteActive(index);
836
+ }, [isActive, index, onRouteActive]);
837
+ return children;
838
+ }
839
+ function SlideRuleNav({
840
+ variant = "vernier",
841
+ items = [],
842
+ value,
843
+ defaultValue,
844
+ onChange,
845
+ readout = false,
846
+ label = "Main",
847
+ className = ""
848
+ }) {
849
+ const resolved = VARIANTS[variant] ? variant : (isDev && console.warn(
850
+ `[SlideRuleNav] variant "${variant}" is not implemented yet; falling back to "vernier".`
851
+ ), "vernier");
852
+ const { spec, render: Chrome, layout } = VARIANTS[resolved];
853
+ const count = validateStationCount(items.length, "SlideRuleNav");
854
+ const stations = useMemo4(() => items.slice(0, count), [items, count]);
855
+ const [vbW, vbH] = spec.viewBox;
856
+ const indexOfId = useCallback(
857
+ (id) => {
858
+ const i = stations.findIndex((item) => item.id === id);
859
+ return i < 0 ? null : i;
860
+ },
861
+ [stations]
862
+ );
863
+ const routerMode = stations.some((item) => item.to);
864
+ const [routeIndex, setRouteIndex] = useState(null);
865
+ const syncedOnce = useRef4(false);
866
+ const controlledIndex = value != null ? indexOfId(value) : routerMode ? routeIndex : void 0;
867
+ const chromeRef = useRef4(null);
868
+ const navRef = useRef4(null);
869
+ const linkRefs = useRef4([]);
870
+ const stationRefs = useRef4([]);
871
+ const reducedMotion = useReducedMotion();
872
+ const handleCommit = useCallback(
873
+ (index) => {
874
+ const item = stations[index];
875
+ if (!item) return;
876
+ if (item.to && linkRefs.current[index]) linkRefs.current[index].click();
877
+ item.onSelect?.();
878
+ onChange?.(item.id);
879
+ },
880
+ [onChange, stations]
881
+ );
882
+ const travel = useDetentTravel({
883
+ count,
884
+ value: controlledIndex ?? void 0,
885
+ defaultValue: indexOfId(defaultValue) ?? 0,
886
+ onChange: handleCommit,
887
+ settleMs: spec.settleMs
888
+ });
889
+ const { goTo } = travel;
890
+ const onRouteActive = useCallback(
891
+ (index) => {
892
+ setRouteIndex(index);
893
+ if (!syncedOnce.current) {
894
+ syncedOnce.current = true;
895
+ goTo(index, 0);
896
+ }
897
+ },
898
+ [goTo]
899
+ );
900
+ const toViewBoxX = useCallback(
901
+ (clientX) => {
902
+ const box = chromeRef.current?.getBoundingClientRect();
903
+ if (!box?.width) return null;
904
+ return (clientX - box.left) / box.width * vbW;
905
+ },
906
+ [vbW]
907
+ );
908
+ const active = travel.station;
909
+ useEffect(() => {
910
+ const nav = navRef.current;
911
+ if (!nav) return void 0;
912
+ const centre = (behavior) => {
913
+ const station = stationRefs.current[active];
914
+ if (!station || nav.scrollWidth <= nav.clientWidth) return;
915
+ nav.scrollTo({
916
+ left: Math.max(0, station.offsetLeft - nav.clientWidth / 2),
917
+ behavior
918
+ });
919
+ };
920
+ centre(reducedMotion ? "auto" : "smooth");
921
+ if (typeof ResizeObserver === "undefined") return void 0;
922
+ const observer = new ResizeObserver(() => centre("auto"));
923
+ observer.observe(nav);
924
+ return () => observer.disconnect();
925
+ }, [active, reducedMotion]);
926
+ function onKeyDown(event) {
927
+ if (event.key === "ArrowRight") travel.step(1);
928
+ else if (event.key === "ArrowLeft") travel.step(-1);
929
+ else if (event.key === "Home") travel.goTo(0);
930
+ else if (event.key === "End") travel.goTo(count - 1);
931
+ else return;
932
+ event.preventDefault();
933
+ }
934
+ const place = layout(spec, count, travel.pos);
935
+ const linkClass = (isActive) => "ds-rule__link" + (isActive ? " ds-rule__link--active" : "");
936
+ return /* @__PURE__ */ jsxs4(
937
+ "nav",
938
+ {
939
+ ref: navRef,
940
+ className: ["ds-rule", `ds-rule--${resolved}`, className].filter(Boolean).join(" "),
941
+ "aria-label": label,
942
+ onKeyDown,
943
+ children: [
944
+ /* @__PURE__ */ jsxs4("div", { className: "ds-rule__stage", children: [
945
+ /* @__PURE__ */ jsx4(
946
+ Chrome,
947
+ {
948
+ ref: chromeRef,
949
+ spec,
950
+ count,
951
+ stations,
952
+ travel,
953
+ toViewBoxX
954
+ }
955
+ ),
956
+ /* @__PURE__ */ jsx4(
957
+ "ul",
958
+ {
959
+ className: "ds-rule__stations",
960
+ style: { transform: `translateX(${pct(place.offset, vbW)})` },
961
+ children: stations.map((item, index) => /* @__PURE__ */ jsx4(
962
+ "li",
963
+ {
964
+ ref: (el) => stationRefs.current[index] = el,
965
+ className: "ds-rule__station",
966
+ style: {
967
+ "--station-x": pct(place.stationX(index), vbW),
968
+ "--station-y": pct(spec.labelY, vbH)
969
+ },
970
+ children: item.to ? /* @__PURE__ */ jsx4(
971
+ NavLink,
972
+ {
973
+ to: item.to,
974
+ end: item.end,
975
+ ref: (el) => linkRefs.current[index] = el,
976
+ className: ({ isActive }) => linkClass(isActive),
977
+ children: ({ isActive }) => /* @__PURE__ */ jsx4(
978
+ ActiveProbe,
979
+ {
980
+ isActive,
981
+ index,
982
+ onRouteActive,
983
+ children: item.label
984
+ }
985
+ )
986
+ }
987
+ ) : /* @__PURE__ */ jsx4(
988
+ "button",
989
+ {
990
+ type: "button",
991
+ className: linkClass(index === active),
992
+ "aria-current": index === active ? "true" : void 0,
993
+ onClick: () => travel.goTo(index),
994
+ children: item.label
995
+ }
996
+ )
997
+ },
998
+ item.id
999
+ ))
1000
+ }
1001
+ )
1002
+ ] }),
1003
+ readout && /* @__PURE__ */ jsxs4("div", { className: "ds-rule__readout", children: [
1004
+ /* @__PURE__ */ jsxs4("span", { "aria-hidden": "true", children: [
1005
+ /* @__PURE__ */ jsx4("span", { className: "ds-rule__readout-key", children: "Carriage" }),
1006
+ " ",
1007
+ /* @__PURE__ */ jsx4("span", { className: "ds-rule__readout-val", children: travel.dragging ? "\u2014\u2014" : stations[active]?.label }),
1008
+ " ",
1009
+ /* @__PURE__ */ jsx4("span", { className: "ds-rule__readout-key", children: "Vernier" }),
1010
+ " ",
1011
+ /* @__PURE__ */ jsx4("span", { className: "ds-rule__readout-num", children: travel.pos.toFixed(2) })
1012
+ ] }),
1013
+ /* @__PURE__ */ jsx4("span", { className: "ds-rule__sr-only", "aria-live": "polite", children: travel.dragging || travel.settling ? "" : `${stations[active]?.label ?? ""} selected` })
1014
+ ] })
1015
+ ]
1016
+ }
1017
+ );
1018
+ }
1019
+
1020
+ export {
1021
+ SlideRuleNav
1022
+ };
1023
+ //# sourceMappingURL=chunk-YLUQANAI.js.map