@5voltfx/design-system 0.2.0 → 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.
@@ -1,172 +1,14 @@
1
+ import {
2
+ useDetentTravel,
3
+ usePointerDrag,
4
+ useReducedMotion,
5
+ validateStationCount
6
+ } from "./chunk-UZW4E2UH.js";
7
+
1
8
  // src/components/SlideRuleNav/SlideRuleNav.jsx
2
- import { useCallback as useCallback2, useEffect as useEffect3, useMemo as useMemo4, useRef as useRef6, useState as useState2 } from "react";
9
+ import { useCallback, useEffect, useMemo as useMemo4, useRef as useRef4, useState } from "react";
3
10
  import { NavLink } from "react-router-dom";
4
11
 
5
- // src/components/_shared/useDetentTravel.js
6
- import { useCallback, useEffect, useRef, useState } from "react";
7
-
8
- // src/components/_shared/useReducedMotion.js
9
- import { useSyncExternalStore } from "react";
10
- var QUERY = "(prefers-reduced-motion: reduce)";
11
- function subscribe(onChange) {
12
- const mq = window.matchMedia(QUERY);
13
- mq.addEventListener("change", onChange);
14
- return () => mq.removeEventListener("change", onChange);
15
- }
16
- var getSnapshot = () => window.matchMedia(QUERY).matches;
17
- var getServerSnapshot = () => false;
18
- function useReducedMotion() {
19
- return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
20
- }
21
-
22
- // src/components/_shared/useDetentTravel.js
23
- function easeBack(x) {
24
- const c1 = 1.02;
25
- const c3 = c1 + 1;
26
- return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2);
27
- }
28
- var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
29
- function useDetentTravel({
30
- count,
31
- value,
32
- defaultValue = 0,
33
- onChange,
34
- subdivisions = 4,
35
- settleMs = 420,
36
- onDetentCross
37
- } = {}) {
38
- const last = Math.max(0, count - 1);
39
- const isControlled = value != null;
40
- const [internalStation, setInternalStation] = useState(
41
- () => clamp(Math.round(defaultValue), 0, last)
42
- );
43
- const station = clamp(
44
- Math.round(isControlled ? value : internalStation),
45
- 0,
46
- last
47
- );
48
- const [pos, setPos] = useState(station);
49
- const [dragging, setDragging] = useState(false);
50
- const [settling, setSettling] = useState(false);
51
- const reduced = useReducedMotion();
52
- const posRef = useRef(pos);
53
- const rafRef = useRef(0);
54
- const targetRef = useRef(station);
55
- const stationRef = useRef(station);
56
- const detentRef = useRef(Math.round(station * subdivisions));
57
- const onChangeRef = useRef(onChange);
58
- const onDetentCrossRef = useRef(onDetentCross);
59
- posRef.current = pos;
60
- stationRef.current = station;
61
- onChangeRef.current = onChange;
62
- onDetentCrossRef.current = onDetentCross;
63
- const cancel = useCallback(() => {
64
- if (rafRef.current) {
65
- cancelAnimationFrame(rafRef.current);
66
- rafRef.current = 0;
67
- }
68
- }, []);
69
- useEffect(() => cancel, [cancel]);
70
- const writePos = useCallback((v) => {
71
- posRef.current = v;
72
- setPos(v);
73
- }, []);
74
- const animateTo = useCallback(
75
- (target, ms, done) => {
76
- cancel();
77
- const from = posRef.current;
78
- if (reduced || ms <= 0 || from === target) {
79
- writePos(target);
80
- setSettling(false);
81
- done?.();
82
- return;
83
- }
84
- setSettling(true);
85
- const t0 = performance.now();
86
- const frame = (now) => {
87
- const k = Math.min(1, (now - t0) / ms);
88
- if (k < 1) {
89
- writePos(from + (target - from) * easeBack(k));
90
- rafRef.current = requestAnimationFrame(frame);
91
- return;
92
- }
93
- rafRef.current = 0;
94
- writePos(target);
95
- setSettling(false);
96
- done?.();
97
- };
98
- rafRef.current = requestAnimationFrame(frame);
99
- },
100
- [cancel, reduced, writePos]
101
- );
102
- const goTo = useCallback(
103
- (i, ms = settleMs) => {
104
- const next = clamp(Math.round(i), 0, last);
105
- targetRef.current = next;
106
- detentRef.current = Math.round(next * subdivisions);
107
- animateTo(next, ms, () => {
108
- if (next === stationRef.current) return;
109
- if (!isControlled) setInternalStation(next);
110
- onChangeRef.current?.(next);
111
- });
112
- },
113
- [animateTo, isControlled, last, settleMs, subdivisions]
114
- );
115
- useEffect(() => {
116
- if (targetRef.current === station) return;
117
- targetRef.current = station;
118
- detentRef.current = Math.round(station * subdivisions);
119
- animateTo(station, settleMs);
120
- }, [station, animateTo, settleMs, subdivisions]);
121
- const begin = useCallback(() => {
122
- cancel();
123
- setSettling(false);
124
- setDragging(true);
125
- }, [cancel]);
126
- const track = useCallback(
127
- (raw) => {
128
- cancel();
129
- const detent = Math.round(clamp(raw, 0, last) * subdivisions);
130
- if (detent !== detentRef.current) {
131
- detentRef.current = detent;
132
- onDetentCrossRef.current?.();
133
- }
134
- writePos(detent / subdivisions);
135
- },
136
- [cancel, last, subdivisions, writePos]
137
- );
138
- const release = useCallback(() => {
139
- setDragging(false);
140
- goTo(Math.round(posRef.current));
141
- }, [goTo]);
142
- const step = useCallback(
143
- (delta) => goTo(stationRef.current + delta),
144
- [goTo]
145
- );
146
- return { pos, station, dragging, settling, goTo, step, track, begin, release };
147
- }
148
-
149
- // src/components/_shared/validateStationCount.js
150
- var MIN_STATIONS = 2;
151
- var MAX_STATIONS = 8;
152
- var isDev = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
153
- function validateStationCount(n, component) {
154
- if (n > MAX_STATIONS) {
155
- if (isDev) {
156
- console.warn(
157
- `[${component}] ${n} stations exceeds the supported maximum of ${MAX_STATIONS}; extra items are not rendered. Slide-rule navigations stay legible up to ${MAX_STATIONS} stations.`
158
- );
159
- }
160
- return MAX_STATIONS;
161
- }
162
- if (n < MIN_STATIONS && isDev) {
163
- console.warn(
164
- `[${component}] needs at least ${MIN_STATIONS} stations; got ${n}.`
165
- );
166
- }
167
- return n;
168
- }
169
-
170
12
  // src/components/SlideRuleNav/geometry.js
171
13
  var MINOR_TARGET = 38;
172
14
  var LINEAR = Object.freeze({
@@ -244,7 +86,7 @@ var SLIDE_VALUES = Object.freeze({
244
86
  8: Object.freeze([1, 1.5, 2, 2.5, 3, 4, 6, 8])
245
87
  });
246
88
  var pct = (v, extent) => `${v / extent * 100}%`;
247
- var clamp2 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
89
+ var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
248
90
  function pitchFor(spec, count) {
249
91
  return (spec.x1 - spec.x0) / Math.max(1, count - 1);
250
92
  }
@@ -281,8 +123,8 @@ var logStops = (spec, count) => slideValues(count).map((v) => logX(spec, v));
281
123
  var statorX = (spec, v) => spec.statorL0 + Math.log10(v) * spec.statorLw;
282
124
  function localAt(stops, pos) {
283
125
  const last = stops.length - 1;
284
- const i = clamp2(Math.floor(pos), 0, last - 1);
285
- const f = clamp2(pos - i, 0, 1);
126
+ const i = clamp(Math.floor(pos), 0, last - 1);
127
+ const f = clamp(pos - i, 0, 1);
286
128
  return stops[i] + (stops[i + 1] - stops[i]) * f;
287
129
  }
288
130
  function posFromLocal(stops, want) {
@@ -298,52 +140,14 @@ function posFromLocal(stops, want) {
298
140
  }
299
141
 
300
142
  // src/components/SlideRuleNav/variants/CursorRule.jsx
301
- import { forwardRef, useMemo, useRef as useRef3 } from "react";
302
-
303
- // src/components/_shared/usePointerDrag.js
304
- import { useEffect as useEffect2, useRef as useRef2 } from "react";
305
- function usePointerDrag(ref, { onBegin, onMove, onEnd, threshold = 3 } = {}) {
306
- const latest = useRef2({ onBegin, onMove, onEnd, threshold });
307
- latest.current = { onBegin, onMove, onEnd, threshold };
308
- useEffect2(() => {
309
- const node = ref.current;
310
- if (!node) return void 0;
311
- function onPointerDown(event) {
312
- if (event.button) return;
313
- const startX = event.clientX;
314
- const startY = event.clientY;
315
- let moved = false;
316
- latest.current.onBegin?.(event);
317
- function onPointerMove(moveEvent) {
318
- if (!moved && Math.hypot(moveEvent.clientX - startX, moveEvent.clientY - startY) < latest.current.threshold) {
319
- return;
320
- }
321
- moved = true;
322
- latest.current.onMove?.(moveEvent);
323
- }
324
- function onPointerUp(upEvent) {
325
- window.removeEventListener("pointermove", onPointerMove);
326
- window.removeEventListener("pointerup", onPointerUp);
327
- window.removeEventListener("pointercancel", onPointerUp);
328
- latest.current.onEnd?.(upEvent, !moved);
329
- }
330
- window.addEventListener("pointermove", onPointerMove);
331
- window.addEventListener("pointerup", onPointerUp);
332
- window.addEventListener("pointercancel", onPointerUp);
333
- }
334
- node.addEventListener("pointerdown", onPointerDown);
335
- return () => node.removeEventListener("pointerdown", onPointerDown);
336
- }, [ref]);
337
- }
338
-
339
- // src/components/SlideRuleNav/variants/CursorRule.jsx
143
+ import { forwardRef, useMemo, useRef } from "react";
340
144
  import { jsx, jsxs } from "react/jsx-runtime";
341
145
  var CursorRule = forwardRef(function CursorRule2({ spec, count, stations, travel, toViewBoxX }, ref) {
342
146
  const [vbW, vbH] = spec.viewBox;
343
147
  const { x0, railX0, railX1, top, bot, cursorBox } = spec;
344
148
  const pitch = pitchFor(spec, count);
345
149
  const { minorsPerPitch, minorsPerDetent } = minorDivisions(pitch);
346
- const cursorRef = useRef3(null);
150
+ const cursorRef = useRef(null);
347
151
  usePointerDrag(cursorRef, {
348
152
  onBegin: () => travel.begin(),
349
153
  onMove: (event) => {
@@ -529,7 +333,7 @@ var CursorRule = forwardRef(function CursorRule2({ spec, count, stations, travel
529
333
  });
530
334
 
531
335
  // src/components/SlideRuleNav/variants/SlideStator.jsx
532
- import { forwardRef as forwardRef2, useId, useMemo as useMemo2, useRef as useRef4 } from "react";
336
+ import { forwardRef as forwardRef2, useId, useMemo as useMemo2, useRef as useRef2 } from "react";
533
337
  import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
534
338
  function decadeTicks(mapX) {
535
339
  const bands = [
@@ -560,8 +364,8 @@ var SlideStator = forwardRef2(function SlideStator2({ spec, count, stations, tra
560
364
  const stops = useMemo2(() => logStops(spec, count), [spec, count]);
561
365
  const values = slideValues(count);
562
366
  const offset = index - localAt(stops, travel.pos);
563
- const grabbed = useRef4(0);
564
- const slideRef = useRef4(null);
367
+ const grabbed = useRef2(0);
368
+ const slideRef = useRef2(null);
565
369
  usePointerDrag(slideRef, {
566
370
  onBegin: (event) => {
567
371
  travel.begin();
@@ -803,7 +607,7 @@ var SlideStator = forwardRef2(function SlideStator2({ spec, count, stations, tra
803
607
  });
804
608
 
805
609
  // src/components/SlideRuleNav/variants/VernierCarriage.jsx
806
- import { forwardRef as forwardRef3, useMemo as useMemo3, useRef as useRef5 } from "react";
610
+ import { forwardRef as forwardRef3, useMemo as useMemo3, useRef as useRef3 } from "react";
807
611
  import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
808
612
  var VernierCarriage = forwardRef3(function VernierCarriage2({ spec, count, stations, travel, toViewBoxX }, ref) {
809
613
  const [vbW, vbH] = spec.viewBox;
@@ -811,7 +615,7 @@ var VernierCarriage = forwardRef3(function VernierCarriage2({ spec, count, stati
811
615
  const bot = railY + railH;
812
616
  const pitch = pitchFor(spec, count);
813
617
  const { minor, vdiv, minorsPerPitch, minorsPerDetent } = minorDivisions(pitch);
814
- const carriageRef = useRef5(null);
618
+ const carriageRef = useRef3(null);
815
619
  usePointerDrag(carriageRef, {
816
620
  onBegin: () => travel.begin(),
817
621
  onMove: (event) => {
@@ -1025,9 +829,9 @@ var VARIANTS = {
1025
829
  layout: fixedLayout
1026
830
  }
1027
831
  };
1028
- var isDev2 = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
832
+ var isDev = typeof process !== "undefined" && process.env?.NODE_ENV !== "production";
1029
833
  function ActiveProbe({ isActive, index, onRouteActive, children }) {
1030
- useEffect3(() => {
834
+ useEffect(() => {
1031
835
  if (isActive) onRouteActive(index);
1032
836
  }, [isActive, index, onRouteActive]);
1033
837
  return children;
@@ -1042,14 +846,14 @@ function SlideRuleNav({
1042
846
  label = "Main",
1043
847
  className = ""
1044
848
  }) {
1045
- const resolved = VARIANTS[variant] ? variant : (isDev2 && console.warn(
849
+ const resolved = VARIANTS[variant] ? variant : (isDev && console.warn(
1046
850
  `[SlideRuleNav] variant "${variant}" is not implemented yet; falling back to "vernier".`
1047
851
  ), "vernier");
1048
852
  const { spec, render: Chrome, layout } = VARIANTS[resolved];
1049
853
  const count = validateStationCount(items.length, "SlideRuleNav");
1050
854
  const stations = useMemo4(() => items.slice(0, count), [items, count]);
1051
855
  const [vbW, vbH] = spec.viewBox;
1052
- const indexOfId = useCallback2(
856
+ const indexOfId = useCallback(
1053
857
  (id) => {
1054
858
  const i = stations.findIndex((item) => item.id === id);
1055
859
  return i < 0 ? null : i;
@@ -1057,15 +861,15 @@ function SlideRuleNav({
1057
861
  [stations]
1058
862
  );
1059
863
  const routerMode = stations.some((item) => item.to);
1060
- const [routeIndex, setRouteIndex] = useState2(null);
1061
- const syncedOnce = useRef6(false);
864
+ const [routeIndex, setRouteIndex] = useState(null);
865
+ const syncedOnce = useRef4(false);
1062
866
  const controlledIndex = value != null ? indexOfId(value) : routerMode ? routeIndex : void 0;
1063
- const chromeRef = useRef6(null);
1064
- const navRef = useRef6(null);
1065
- const linkRefs = useRef6([]);
1066
- const stationRefs = useRef6([]);
867
+ const chromeRef = useRef4(null);
868
+ const navRef = useRef4(null);
869
+ const linkRefs = useRef4([]);
870
+ const stationRefs = useRef4([]);
1067
871
  const reducedMotion = useReducedMotion();
1068
- const handleCommit = useCallback2(
872
+ const handleCommit = useCallback(
1069
873
  (index) => {
1070
874
  const item = stations[index];
1071
875
  if (!item) return;
@@ -1083,7 +887,7 @@ function SlideRuleNav({
1083
887
  settleMs: spec.settleMs
1084
888
  });
1085
889
  const { goTo } = travel;
1086
- const onRouteActive = useCallback2(
890
+ const onRouteActive = useCallback(
1087
891
  (index) => {
1088
892
  setRouteIndex(index);
1089
893
  if (!syncedOnce.current) {
@@ -1093,7 +897,7 @@ function SlideRuleNav({
1093
897
  },
1094
898
  [goTo]
1095
899
  );
1096
- const toViewBoxX = useCallback2(
900
+ const toViewBoxX = useCallback(
1097
901
  (clientX) => {
1098
902
  const box = chromeRef.current?.getBoundingClientRect();
1099
903
  if (!box?.width) return null;
@@ -1102,7 +906,7 @@ function SlideRuleNav({
1102
906
  [vbW]
1103
907
  );
1104
908
  const active = travel.station;
1105
- useEffect3(() => {
909
+ useEffect(() => {
1106
910
  const nav = navRef.current;
1107
911
  if (!nav) return void 0;
1108
912
  const centre = (behavior) => {
@@ -1216,4 +1020,4 @@ function SlideRuleNav({
1216
1020
  export {
1217
1021
  SlideRuleNav
1218
1022
  };
1219
- //# sourceMappingURL=chunk-SUYKOWXJ.js.map
1023
+ //# sourceMappingURL=chunk-YLUQANAI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/SlideRuleNav/SlideRuleNav.jsx","../src/components/SlideRuleNav/geometry.js","../src/components/SlideRuleNav/variants/CursorRule.jsx","../src/components/SlideRuleNav/variants/SlideStator.jsx","../src/components/SlideRuleNav/variants/VernierCarriage.jsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { NavLink } from \"react-router-dom\";\nimport { useDetentTravel } from \"../_shared/useDetentTravel.js\";\nimport { validateStationCount } from \"../_shared/validateStationCount.js\";\nimport { useReducedMotion } from \"../_shared/useReducedMotion.js\";\nimport { LINEAR, pitchFor, pct, logStops, localAt } from \"./geometry.js\";\nimport { CursorRule } from \"./variants/CursorRule.jsx\";\nimport { SlideStator } from \"./variants/SlideStator.jsx\";\nimport { VernierCarriage } from \"./variants/VernierCarriage.jsx\";\nimport \"./SlideRuleNav.css\";\n\n/**\n * @typedef {object} SlideRuleItem\n * @property {string} id\n * @property {import(\"react\").ReactNode} label\n * @property {string} [to] - Route path. Renders a NavLink; requires a Router.\n * @property {boolean} [end] - Passed to NavLink for exact matching.\n * @property {() => void} [onSelect] - Used when the item has no `to`.\n * @property {string} [value] - Scarlet scale numeral. Defaults to `i.toFixed(2)`.\n */\n\n/**\n * Where each station label sits, and how far the whole label rail is shifted.\n *\n * L-I and L-III hold a fixed scale and move a cursor over it, so the labels\n * never move. L-II inverts that: the index is fixed and the *slide* travels,\n * carrying its stations with it — so the labels ride the same offset the\n * engraved slide does.\n */\nconst fixedLayout = (spec, count) => ({\n stationX: (i) => spec.x0 + i * pitchFor(spec, count),\n offset: 0,\n});\n\nconst slideLayout = (spec, count, pos) => {\n const stops = logStops(spec, count);\n return {\n stationX: (i) => stops[i],\n offset: spec.index - localAt(stops, pos),\n };\n};\n\nconst VARIANTS = {\n cursor: { spec: LINEAR.cursor, render: CursorRule, layout: fixedLayout },\n slide: { spec: LINEAR.slide, render: SlideStator, layout: slideLayout },\n vernier: {\n spec: LINEAR.vernier,\n render: VernierCarriage,\n layout: fixedLayout,\n },\n};\n\nconst isDev =\n typeof process !== \"undefined\" && process.env?.NODE_ENV !== \"production\";\n\n/**\n * Reports its own router-active state upward. NavLink already knows which link\n * matches the location, so the rule reads activeness from it rather than\n * calling useLocation — which would make a Router mandatory even for items\n * that only use `onSelect`.\n */\nfunction ActiveProbe({ isActive, index, onRouteActive, children }) {\n useEffect(() => {\n if (isActive) onRouteActive(index);\n }, [isActive, index, onRouteActive]);\n return children;\n}\n\n/**\n * Navigation built on a linear slide rule. Travel is quantized to quarter-tick\n * detents: the mechanism ratchets between stations while you drag it and\n * settles onto one when you let go.\n *\n * Items carrying `to` render as NavLinks and the router drives the cursor;\n * items without one render as buttons and the component tracks its own\n * selection. Both forms may be mixed.\n *\n * The engraved chrome is decorative and hidden from assistive technology — the\n * navigation itself is an ordinary list of links, fully operable by keyboard.\n *\n * @param {object} props\n * @param {\"cursor\"|\"slide\"|\"vernier\"} [props.variant]\n * @param {SlideRuleItem[]} props.items\n * @param {string} [props.value] - Selected item id (controlled).\n * @param {string} [props.defaultValue] - Initial item id (uncontrolled).\n * @param {(id: string) => void} [props.onChange]\n * @param {boolean} [props.readout] - Show the instrument readout strip.\n * @param {string} [props.label] - Accessible name for the nav landmark.\n * @param {string} [props.className]\n * @returns {import(\"react\").ReactElement}\n */\nexport function SlideRuleNav({\n variant = \"vernier\",\n items = [],\n value,\n defaultValue,\n onChange,\n readout = false,\n label = \"Main\",\n className = \"\",\n}) {\n const resolved = VARIANTS[variant]\n ? variant\n : (isDev &&\n console.warn(\n `[SlideRuleNav] variant \"${variant}\" is not implemented yet; ` +\n `falling back to \"vernier\".`\n ),\n \"vernier\");\n const { spec, render: Chrome, layout } = VARIANTS[resolved];\n\n const count = validateStationCount(items.length, \"SlideRuleNav\");\n const stations = useMemo(() => items.slice(0, count), [items, count]);\n const [vbW, vbH] = spec.viewBox;\n\n const indexOfId = useCallback(\n (id) => {\n const i = stations.findIndex((item) => item.id === id);\n return i < 0 ? null : i;\n },\n [stations]\n );\n\n // In router mode the router is the controller: releasing the carriage clicks\n // the target link, the location changes, and the travel follows.\n const routerMode = stations.some((item) => item.to);\n const [routeIndex, setRouteIndex] = useState(null);\n const syncedOnce = useRef(false);\n\n const controlledIndex =\n value != null ? indexOfId(value) : routerMode ? routeIndex : undefined;\n\n const chromeRef = useRef(null);\n const navRef = useRef(null);\n const linkRefs = useRef([]);\n const stationRefs = useRef([]);\n const reducedMotion = useReducedMotion();\n\n const handleCommit = useCallback(\n (index) => {\n const item = stations[index];\n if (!item) return;\n if (item.to && linkRefs.current[index]) linkRefs.current[index].click();\n item.onSelect?.();\n onChange?.(item.id);\n },\n [onChange, stations]\n );\n\n const travel = useDetentTravel({\n count,\n value: controlledIndex ?? undefined,\n defaultValue: indexOfId(defaultValue) ?? 0,\n onChange: handleCommit,\n settleMs: spec.settleMs,\n });\n\n const { goTo } = travel;\n const onRouteActive = useCallback(\n (index) => {\n setRouteIndex(index);\n // Land on the current route without a settle animation on first paint.\n if (!syncedOnce.current) {\n syncedOnce.current = true;\n goTo(index, 0);\n }\n },\n [goTo]\n );\n\n /**\n * Pointer x (client space) to a viewBox x. Each variant turns that into\n * travel itself, because a fixed scale and a moving slide read it\n * differently.\n */\n const toViewBoxX = useCallback(\n (clientX) => {\n const box = chromeRef.current?.getBoundingClientRect();\n if (!box?.width) return null;\n return ((clientX - box.left) / box.width) * vbW;\n },\n [vbW]\n );\n\n const active = travel.station;\n\n // A rule too long for the viewport scrolls; keep the selected station in\n // view so the carriage never settles off-screen. Scrolls the nav's own\n // container, never the page.\n useEffect(() => {\n const nav = navRef.current;\n if (!nav) return undefined;\n\n const centre = (behavior) => {\n const station = stationRefs.current[active];\n if (!station || nav.scrollWidth <= nav.clientWidth) return;\n nav.scrollTo({\n left: Math.max(0, station.offsetLeft - nav.clientWidth / 2),\n behavior,\n });\n };\n\n centre(reducedMotion ? \"auto\" : \"smooth\");\n\n // The rule may start out fitting and only overflow once the viewport\n // narrows — on rotation, say — so re-centre when the container resizes.\n if (typeof ResizeObserver === \"undefined\") return undefined;\n const observer = new ResizeObserver(() => centre(\"auto\"));\n observer.observe(nav);\n return () => observer.disconnect();\n }, [active, reducedMotion]);\n\n function onKeyDown(event) {\n if (event.key === \"ArrowRight\") travel.step(1);\n else if (event.key === \"ArrowLeft\") travel.step(-1);\n else if (event.key === \"Home\") travel.goTo(0);\n else if (event.key === \"End\") travel.goTo(count - 1);\n else return;\n event.preventDefault();\n }\n\n const place = layout(spec, count, travel.pos);\n const linkClass = (isActive) =>\n \"ds-rule__link\" + (isActive ? \" ds-rule__link--active\" : \"\");\n\n return (\n <nav\n ref={navRef}\n className={[\"ds-rule\", `ds-rule--${resolved}`, className]\n .filter(Boolean)\n .join(\" \")}\n aria-label={label}\n onKeyDown={onKeyDown}\n >\n <div className=\"ds-rule__stage\">\n <Chrome\n ref={chromeRef}\n spec={spec}\n count={count}\n stations={stations}\n travel={travel}\n toViewBoxX={toViewBoxX}\n />\n\n <ul\n className=\"ds-rule__stations\"\n style={{ transform: `translateX(${pct(place.offset, vbW)})` }}\n >\n {stations.map((item, index) => (\n <li\n key={item.id}\n ref={(el) => (stationRefs.current[index] = el)}\n className=\"ds-rule__station\"\n style={{\n \"--station-x\": pct(place.stationX(index), vbW),\n \"--station-y\": pct(spec.labelY, vbH),\n }}\n >\n {item.to ? (\n <NavLink\n to={item.to}\n end={item.end}\n ref={(el) => (linkRefs.current[index] = el)}\n className={({ isActive }) => linkClass(isActive)}\n >\n {({ isActive }) => (\n <ActiveProbe\n isActive={isActive}\n index={index}\n onRouteActive={onRouteActive}\n >\n {item.label}\n </ActiveProbe>\n )}\n </NavLink>\n ) : (\n <button\n type=\"button\"\n className={linkClass(index === active)}\n aria-current={index === active ? \"true\" : undefined}\n onClick={() => travel.goTo(index)}\n >\n {item.label}\n </button>\n )}\n </li>\n ))}\n </ul>\n </div>\n\n {readout && (\n <div className=\"ds-rule__readout\">\n <span aria-hidden=\"true\">\n <span className=\"ds-rule__readout-key\">Carriage</span>{\" \"}\n <span className=\"ds-rule__readout-val\">\n {travel.dragging ? \"——\" : stations[active]?.label}\n </span>{\" \"}\n <span className=\"ds-rule__readout-key\">Vernier</span>{\" \"}\n <span className=\"ds-rule__readout-num\">\n {travel.pos.toFixed(2)}\n </span>\n </span>\n {/* Announce only committed changes, never every drag frame. */}\n <span className=\"ds-rule__sr-only\" aria-live=\"polite\">\n {travel.dragging || travel.settling\n ? \"\"\n : `${stations[active]?.label ?? \"\"} selected`}\n </span>\n </div>\n )}\n </nav>\n );\n}\n","/*\n Frozen geometry for the linear plates (L-I, L-II, L-III).\n\n These numbers come from the design artboards and were tuned against real\n clipping bounds in a browser. Treat the *span* as constant and derive pitch\n from the station count — the artboards were drawn with 6 stations, but the\n first integration (5voltfx.com, L-III) has 3.\n*/\n\n/** Preferred main-scale minor division, in viewBox units. */\nexport const MINOR_TARGET = 38;\n\nexport const LINEAR = Object.freeze({\n /**\n * L-I — fixed scale, cursor travels.\n *\n * The stock runs from `x0 - cursorBox.w / 2` to `x1 + cursorBox.w / 2` for\n * the same reason as L-III: the cursor is centred on the station it reads,\n * so the stock has to extend past the end graduations for it to sit on the\n * instrument at station 0 and at the last station.\n */\n cursor: Object.freeze({\n viewBox: Object.freeze([900, 136]),\n x0: 62,\n x1: 838,\n railX0: 30,\n railX1: 870,\n top: 40,\n bot: 112,\n labelY: 24,\n cursorBox: Object.freeze({ w: 60, h: 100, y: 28 }),\n settleMs: 420,\n }),\n /** L-II — fixed index, slide travels, true log10 station spacing. */\n slide: Object.freeze({\n viewBox: Object.freeze([900, 150]),\n l0: 60,\n lw: 1400,\n index: 450,\n clip: Object.freeze([30, 870]),\n statorL0: 40,\n statorLw: 800,\n slideY: 46,\n slideH: 58,\n labelY: 88,\n settleMs: 460,\n }),\n /**\n * L-III — notched rail, vernier carriage. Reference plate.\n *\n * The station span is 772 units, exactly as drawn in the artboards, but it\n * is inset 124 units from the left rather than 64. The carriage is centred\n * on the station it reads, and its widest form (5 stations) is 224 units\n * across — so at station 0 a 64-unit inset put half the vernier off the end\n * of the rule. The artboards never showed it because they opened on station\n * 2. Keeping the span identical preserves every derived division.\n *\n * The rail then runs from `x0 - maxCarriageHalf` to `x1 + maxCarriageHalf`,\n * because a real stock extends past its scale far enough for the cursor to\n * reach the end graduations without hanging off the instrument.\n */\n vernier: Object.freeze({\n viewBox: Object.freeze([1020, 160]),\n x0: 124,\n x1: 896,\n railX0: 12,\n railX1: 1008,\n railY: 46,\n railH: 42,\n labelY: 26,\n vernierDivisions: 5,\n mainDivisionsSpanned: 4,\n /** Widest half-carriage across all supported counts; the inset floor. */\n maxCarriageHalf: 112,\n settleMs: 380,\n }),\n});\n\n/**\n * Station scale values for L-II, per station count.\n *\n * The point of L-II is that the gaps are *uneven* — stations sit at true\n * log10 positions, so travel compresses toward the right of the rule. Spacing\n * values evenly in log space would make the gaps equal and collapse L-II into\n * L-I, so each count gets a hand-picked set of slide-rule gradations. The\n * six-station row is the artboards', unchanged.\n */\nexport const SLIDE_VALUES = Object.freeze({\n 2: Object.freeze([1, 8]),\n 3: Object.freeze([1, 2, 8]),\n 4: Object.freeze([1, 2, 3, 8]),\n 5: Object.freeze([1, 1.5, 2, 3, 8]),\n 6: Object.freeze([1, 1.5, 2, 3, 5, 8]),\n 7: Object.freeze([1, 1.5, 2, 3, 4, 6, 8]),\n 8: Object.freeze([1, 1.5, 2, 2.5, 3, 4, 6, 8]),\n});\n\n/** CSS percentage for a viewBox coordinate, for HTML overlaid on the SVG. */\nexport const pct = (v, extent) => `${(v / extent) * 100}%`;\n\nconst clamp = (v, lo, hi) => (v < lo ? lo : v > hi ? hi : v);\n\n/**\n * Distance between adjacent stations, in viewBox units.\n *\n * @param {{x0: number, x1: number}} spec\n * @param {number} count\n * @returns {number}\n */\nexport function pitchFor(spec, count) {\n return (spec.x1 - spec.x0) / Math.max(1, count - 1);\n}\n\n/** Travel position (0…count-1) to a viewBox x. */\nexport const travelX = (spec, count, pos) =>\n spec.x0 + pos * pitchFor(spec, count);\n\n/**\n * How many main-scale minor divisions fill one station gap.\n *\n * Always a multiple of 4, so a quarter-pitch detent is always a whole number\n * of minor divisions and the vernier's zero line coincides exactly when the\n * pawl seats. Without this the carriage scales with pitch: at 3 stations a\n * naive `pitch / 4` gives a vernier half the width of the rule.\n *\n * @param {number} pitch\n * @returns {{minorsPerPitch: number, minor: number, vdiv: number,\n * minorsPerDetent: number}}\n */\nexport function minorDivisions(pitch) {\n const minorsPerPitch =\n 4 * Math.max(1, Math.round(pitch / (4 * MINOR_TARGET)));\n const minor = pitch / minorsPerPitch;\n return {\n minorsPerPitch,\n minor,\n vdiv: (minor * 4) / 5,\n minorsPerDetent: minorsPerPitch / 4,\n };\n}\n\n/**\n * Which vernier line currently coincides with a main-scale line — the whole\n * trick of L-III. Five carriage divisions span four rail divisions, so at any\n * offset exactly one line pair lines up, and which one gives the fraction.\n *\n * @param {object} options\n * @param {number} options.pos - Travel, 0…count-1.\n * @param {number} options.count\n * @param {{x0: number, x1: number}} [options.spec]\n * @returns {{k: number, d: number, lit: boolean}} Coinciding line index, its\n * distance from the main line, and whether it is close enough to light.\n */\nexport function coincidence({ pos, count, spec = LINEAR.vernier }) {\n const pitch = pitchFor(spec, count);\n const { minor, vdiv } = minorDivisions(pitch);\n const cx = spec.x0 + pos * pitch;\n\n // Six lines are drawn but only five are readable: the closing line sits\n // exactly four minor divisions from the zero line, so both always coincide\n // together. Searching 0…4 keeps the reading unambiguous, and the epsilon\n // stops floating-point noise from picking a later line on an exact tie.\n let k = 0;\n let d = Infinity;\n for (let i = 0; i < LINEAR.vernier.vernierDivisions; i++) {\n const vx = cx + (i - 2.5) * vdiv;\n const off = vx - spec.x0;\n const dist = Math.abs(off - Math.round(off / minor) * minor);\n if (dist < d - 1e-9) {\n d = dist;\n k = i;\n }\n }\n return { k, d, lit: d < vdiv * 0.34 };\n}\n\n/* ── L-II: true log10 station spacing ─────────────────────────────────── */\n\n/** Slide-local x for a scale value. */\nexport const logX = (spec, v) => spec.l0 + Math.log10(v) * spec.lw;\n\n/** The scale values a slide of `count` stations is engraved with. */\nexport const slideValues = (count) =>\n SLIDE_VALUES[count] ?? SLIDE_VALUES[6];\n\n/** Slide-local x of every station. */\nexport const logStops = (spec, count) =>\n slideValues(count).map((v) => logX(spec, v));\n\n/** Static stator scale x — the whole decade, sized to the window. */\nexport const statorX = (spec, v) =>\n spec.statorL0 + Math.log10(v) * spec.statorLw;\n\n/**\n * Slide-local x for continuous travel. Linear *between* stops, so each gap's\n * quarter-detents inherit that gap's own width — wide at the left of the rule,\n * tight at the right. The unevenness is the point.\n *\n * @param {number[]} stops\n * @param {number} pos\n * @returns {number}\n */\nexport function localAt(stops, pos) {\n const last = stops.length - 1;\n const i = clamp(Math.floor(pos), 0, last - 1);\n const f = clamp(pos - i, 0, 1);\n return stops[i] + (stops[i + 1] - stops[i]) * f;\n}\n\n/**\n * Inverse of {@link localAt} — needed to turn a pointer position back into\n * travel while dragging the slide.\n *\n * @param {number[]} stops\n * @param {number} want - Slide-local x.\n * @returns {number}\n */\nexport function posFromLocal(stops, want) {\n const last = stops.length - 1;\n if (want <= stops[0]) return 0;\n if (want >= stops[last]) return last;\n for (let i = 0; i < last; i++) {\n if (want <= stops[i + 1]) {\n return i + (want - stops[i]) / (stops[i + 1] - stops[i]);\n }\n }\n return last;\n}\n","import { forwardRef, useMemo, useRef } from \"react\";\nimport { usePointerDrag } from \"../../_shared/usePointerDrag.js\";\nimport { minorDivisions, pitchFor } from \"../geometry.js\";\n\n/**\n * L-I — Cursor Rule.\n *\n * The scale is fixed and the cursor travels: a glass runner with a scarlet\n * hairline, brass shoes at each end. Mechanically the simplest of the three\n * linear plates, and a strict subset of L-III.\n *\n * Decorative only — the navigation is the HTML list the parent renders above.\n */\nexport const CursorRule = forwardRef(function CursorRule(\n { spec, count, stations, travel, toViewBoxX },\n ref\n) {\n const [vbW, vbH] = spec.viewBox;\n const { x0, railX0, railX1, top, bot, cursorBox } = spec;\n const pitch = pitchFor(spec, count);\n const { minorsPerPitch, minorsPerDetent } = minorDivisions(pitch);\n\n const cursorRef = useRef(null);\n usePointerDrag(cursorRef, {\n onBegin: () => travel.begin(),\n onMove: (event) => {\n const x = toViewBoxX(event.clientX);\n if (x != null) travel.track((x - x0) / pitch);\n },\n onEnd: (_event, wasTap) => {\n if (!wasTap) travel.release();\n },\n });\n\n // Tick density follows L-III's minor divisions, so a three-station rule is\n // engraved as finely as a six-station one instead of showing three lonely\n // quarter marks per gap.\n const ticks = useMemo(() => {\n const out = [];\n for (let q = 0; q <= (count - 1) * minorsPerPitch; q++) {\n const isStation = q % minorsPerPitch === 0;\n const isDetent = q % minorsPerDetent === 0;\n out.push({\n x: x0 + (q / minorsPerPitch) * pitch,\n kind: isStation ? \"major\" : isDetent ? \"half\" : \"quarter\",\n down: isStation ? 22 : isDetent ? 13 : 8,\n up: isStation ? 18 : isDetent ? 11 : 7,\n station: isStation ? q / minorsPerPitch : null,\n });\n }\n return out;\n }, [count, minorsPerDetent, minorsPerPitch, pitch, x0]);\n\n const cursorX = x0 + travel.pos * pitch;\n const half = cursorBox.w / 2;\n const cursorTop = cursorBox.y;\n const cursorBottom = cursorBox.y + cursorBox.h;\n\n return (\n <svg\n ref={ref}\n className=\"ds-rule__chrome\"\n viewBox={`0 0 ${vbW} ${vbH}`}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <rect\n className=\"ds-rule__rail\"\n x={railX0}\n y={top}\n width={railX1 - railX0}\n height={bot - top}\n rx={2}\n />\n <line\n className=\"ds-rule__tick ds-rule__tick--quarter\"\n x1={railX0}\n y1={(top + bot) / 2}\n x2={railX1}\n y2={(top + bot) / 2}\n />\n {[\n [railX0 + 10, top + 10],\n [railX1 - 10, top + 10],\n [railX0 + 10, bot - 10],\n [railX1 - 10, bot - 10],\n ].map(([cx, cy], i) => (\n <circle key={i} className=\"ds-rule__rivet\" cx={cx} cy={cy} r={3} />\n ))}\n\n {ticks.map((tick, i) => (\n <g key={i}>\n <line\n className={`ds-rule__tick ds-rule__tick--${tick.kind}`}\n x1={tick.x}\n y1={top}\n x2={tick.x}\n y2={top + tick.down}\n />\n <line\n className={`ds-rule__tick ds-rule__tick--${tick.kind}`}\n x1={tick.x}\n y1={bot}\n x2={tick.x}\n y2={bot - tick.up}\n />\n </g>\n ))}\n\n {/* Two number rows, one meaning each: the station's own scale value\n above, and the travel position below. */}\n {ticks\n .filter((tick) => tick.station != null)\n .map((tick) => (\n <g key={`n${tick.station}`}>\n <text\n className=\"ds-rule__numeral ds-rule__numeral--accent\"\n x={tick.x}\n y={top + 34}\n textAnchor=\"middle\"\n >\n {stations[tick.station]?.value ?? tick.station.toFixed(2)}\n </text>\n <text\n className=\"ds-rule__numeral\"\n x={tick.x}\n y={bot - 22}\n textAnchor=\"middle\"\n >\n {tick.station.toFixed(2)}\n </text>\n </g>\n ))}\n\n <g\n ref={cursorRef}\n className=\"ds-rule__carriage\"\n transform={`translate(${cursorX},0)`}\n >\n <rect\n className=\"ds-rule__glass\"\n x={-half}\n y={cursorTop}\n width={cursorBox.w}\n height={cursorBox.h}\n rx={2}\n />\n <rect\n className=\"ds-rule__frame\"\n x={-half}\n y={cursorTop}\n width={cursorBox.w}\n height={cursorBox.h}\n rx={3}\n />\n <rect\n className=\"ds-rule__shoe\"\n x={-half - 2}\n y={cursorTop}\n width={cursorBox.w + 4}\n height={7}\n rx={2}\n />\n <rect\n className=\"ds-rule__shoe\"\n x={-half - 2}\n y={cursorBottom - 7}\n width={cursorBox.w + 4}\n height={7}\n rx={2}\n />\n {[\n [-24, cursorTop + 3.5],\n [24, cursorTop + 3.5],\n [-24, cursorBottom - 3.5],\n [24, cursorBottom - 3.5],\n ].map(([cx, cy], i) => (\n <circle key={i} className=\"ds-rule__rivet\" cx={cx} cy={cy} r={2.2} />\n ))}\n <line\n className=\"ds-rule__hairline\"\n x1={0}\n y1={cursorTop}\n x2={0}\n y2={cursorBottom}\n />\n </g>\n </svg>\n );\n});\n","import { forwardRef, useId, useMemo, useRef } from \"react\";\nimport { usePointerDrag } from \"../../_shared/usePointerDrag.js\";\nimport {\n logStops,\n localAt,\n posFromLocal,\n slideValues,\n statorX,\n} from \"../geometry.js\";\n\n/** Ticks for a full log10 decade, at slide-rule gradations. */\nfunction decadeTicks(mapX) {\n const bands = [\n [1, 2, 0.02, 0.1],\n [2, 5, 0.05, 0.5],\n [5, 10, 0.1, 1],\n ];\n const out = [];\n for (const [a, b, step, major] of bands) {\n for (let v = a; v < b + 1e-9; v += step) {\n const isMajor = Math.abs(v / major - Math.round(v / major)) < 1e-6;\n const half = Math.abs(v / (major / 2) - Math.round(v / (major / 2))) < 1e-6;\n out.push({\n x: mapX(v),\n v,\n len: isMajor ? 12 : half ? 8 : 5,\n kind: isMajor ? \"major\" : half ? \"half\" : \"quarter\",\n label: isMajor ? String(Math.round(v * 10) / 10) : null,\n });\n }\n }\n return out;\n}\n\n/**\n * L-II — Slide & Stator.\n *\n * Inverted from L-I: the index stays put and the slide travels beneath it.\n * Stations sit at true log10 positions, so the gaps compress toward the right\n * of the rule and each quarter-detent inherits that compression — the\n * mechanism genuinely tightens at the far end. The unevenness is the point.\n *\n * Decorative only — the navigation is the HTML list the parent renders above,\n * which rides the same offset this slide does.\n */\nexport const SlideStator = forwardRef(function SlideStator(\n { spec, count, stations, travel, toViewBoxX },\n ref\n) {\n const [vbW, vbH] = spec.viewBox;\n const { index, slideY, slideH, clip } = spec;\n const clipId = useId();\n\n const stops = useMemo(() => logStops(spec, count), [spec, count]);\n const values = slideValues(count);\n const offset = index - localAt(stops, travel.pos);\n\n const grabbed = useRef(0);\n const slideRef = useRef(null);\n usePointerDrag(slideRef, {\n onBegin: (event) => {\n travel.begin();\n const x = toViewBoxX(event.clientX);\n grabbed.current = localAt(stops, travel.pos) + (x == null ? 0 : x - index);\n },\n onMove: (event) => {\n const x = toViewBoxX(event.clientX);\n if (x == null) return;\n travel.track(posFromLocal(stops, grabbed.current - (x - index)));\n },\n onEnd: (_event, wasTap) => {\n if (!wasTap) travel.release();\n },\n });\n\n const statorTicks = useMemo(\n () => decadeTicks((v) => statorX(spec, v)),\n [spec]\n );\n\n // Quarter-detent marks on the slide, unevenly spaced by construction.\n const detents = useMemo(() => {\n const out = [];\n for (let q = 0; q <= (count - 1) * 4; q++) {\n if (q % 4 === 0) continue;\n out.push({ x: localAt(stops, q / 4), half: q % 2 === 0 });\n }\n return out;\n }, [count, stops]);\n\n const slideBottom = slideY + slideH;\n\n return (\n <svg\n ref={ref}\n className=\"ds-rule__chrome\"\n viewBox={`0 0 ${vbW} ${vbH}`}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <defs>\n <clipPath id={clipId}>\n <rect x={clip[0]} y={0} width={clip[1] - clip[0]} height={vbH} />\n </clipPath>\n </defs>\n\n {/* Fixed stators, above and below the slide. */}\n <rect\n className=\"ds-rule__rail\"\n x={clip[0]}\n y={6}\n width={clip[1] - clip[0]}\n height={40}\n />\n <rect\n className=\"ds-rule__rail\"\n x={clip[0]}\n y={slideBottom}\n width={clip[1] - clip[0]}\n height={40}\n />\n {statorTicks.map((tick, i) => (\n <g key={i}>\n <line\n className={`ds-rule__tick ds-rule__tick--${tick.kind}`}\n x1={tick.x}\n y1={46}\n x2={tick.x}\n y2={46 - tick.len}\n />\n <line\n className={`ds-rule__tick ds-rule__tick--${tick.kind}`}\n x1={tick.x}\n y1={slideBottom}\n x2={tick.x}\n y2={slideBottom + tick.len}\n />\n {tick.label && (\n <>\n <text\n className=\"ds-rule__numeral\"\n x={tick.x}\n y={46 - tick.len - 3}\n textAnchor=\"middle\"\n >\n {tick.label}\n </text>\n <text\n className=\"ds-rule__numeral\"\n x={tick.x}\n y={slideBottom + tick.len + 9}\n textAnchor=\"middle\"\n >\n {tick.label}\n </text>\n </>\n )}\n </g>\n ))}\n <text className=\"ds-rule__caption\" x={clip[0] + 6} y={44}>\n C\n </text>\n <text className=\"ds-rule__caption\" x={clip[0] + 6} y={slideBottom + 12}>\n D\n </text>\n\n {/* The slide itself, clipped to the window. */}\n <g clipPath={`url(#${clipId})`}>\n <g\n ref={slideRef}\n className=\"ds-rule__carriage\"\n transform={`translate(${offset},0)`}\n >\n <rect\n className=\"ds-rule__slide\"\n x={stops[0] - 400}\n y={slideY}\n width={stops[stops.length - 1] - stops[0] + 800}\n height={slideH}\n />\n {detents.map((d, i) => (\n <g key={i}>\n <line\n className=\"ds-rule__tick ds-rule__tick--quarter\"\n x1={d.x}\n y1={slideY}\n x2={d.x}\n y2={slideY + (d.half ? 10 : 6)}\n />\n <line\n className=\"ds-rule__tick ds-rule__tick--quarter\"\n x1={d.x}\n y1={slideBottom}\n x2={d.x}\n y2={slideBottom - (d.half ? 10 : 6)}\n />\n </g>\n ))}\n {stops.map((x, i) => (\n <g key={i}>\n <line\n className=\"ds-rule__tick ds-rule__tick--major\"\n x1={x}\n y1={slideY}\n x2={x}\n y2={slideY + 14}\n />\n <line\n className=\"ds-rule__tick ds-rule__tick--major\"\n x1={x}\n y1={slideBottom}\n x2={x}\n y2={slideBottom - 14}\n />\n <text\n className=\"ds-rule__numeral ds-rule__numeral--accent\"\n x={x}\n y={slideBottom - 4}\n textAnchor=\"middle\"\n >\n {stations[i]?.value ?? values[i].toFixed(2)}\n </text>\n </g>\n ))}\n </g>\n </g>\n\n {/* Fixed index and glass window. */}\n <g className=\"ds-rule__window\">\n <rect\n className=\"ds-rule__glass\"\n x={index - 58}\n y={2}\n width={116}\n height={vbH - 4}\n rx={2}\n />\n <rect\n className=\"ds-rule__frame\"\n x={index - 58}\n y={2}\n width={116}\n height={vbH - 4}\n rx={3}\n />\n <line\n className=\"ds-rule__hairline\"\n x1={index}\n y1={2}\n x2={index}\n y2={vbH - 2}\n />\n <path\n className=\"ds-rule__index\"\n d={`M${index - 7},2 L${index + 7},2 L${index},13 Z`}\n />\n <path\n className=\"ds-rule__index\"\n d={`M${index - 7},${vbH - 2} L${index + 7},${vbH - 2} L${index},${\n vbH - 13\n } Z`}\n />\n <text\n className=\"ds-rule__caption ds-rule__caption--accent\"\n x={index + 64}\n y={12}\n >\n INDEX\n </text>\n </g>\n </svg>\n );\n});\n","import { forwardRef, useMemo, useRef } from \"react\";\nimport { usePointerDrag } from \"../../_shared/usePointerDrag.js\";\nimport { minorDivisions, pitchFor, coincidence } from \"../geometry.js\";\n\n/**\n * L-III — Vernier Carriage.\n *\n * A notched rail with a sprung pawl and a working vernier: five carriage\n * divisions span four rail divisions, so at any offset exactly one line pair\n * coincides and its index gives the fraction. Release and the pawl seats, the\n * zero lines coincide, and the reading goes whole.\n *\n * Everything here is decorative and hidden from assistive technology; the\n * navigation is the HTML list the parent renders over it.\n */\nexport const VernierCarriage = forwardRef(function VernierCarriage(\n { spec, count, stations, travel, toViewBoxX },\n ref\n) {\n const [vbW, vbH] = spec.viewBox;\n const { x0, railX0, railX1, railY, railH } = spec;\n const bot = railY + railH;\n const pitch = pitchFor(spec, count);\n const { minor, vdiv, minorsPerPitch, minorsPerDetent } =\n minorDivisions(pitch);\n\n const carriageRef = useRef(null);\n usePointerDrag(carriageRef, {\n onBegin: () => travel.begin(),\n onMove: (event) => {\n const x = toViewBoxX(event.clientX);\n if (x != null) travel.track((x - x0) / pitch);\n },\n onEnd: (_event, wasTap) => {\n if (!wasTap) travel.release();\n },\n });\n\n const totalMinors = (count - 1) * minorsPerPitch;\n\n /* Rail outline: the notches are cut geometry, so they survive the fill. */\n const rail = useMemo(() => {\n let d = `M${railX0},${bot} L${railX0},${railY} `;\n for (let q = 0; q <= totalMinors; q++) {\n const tx = x0 + (q / minorsPerPitch) * pitch;\n const deep = q % minorsPerDetent === 0;\n const w = deep ? 7 : 4;\n const dep = deep ? 9 : 5;\n d += `L${tx - w},${railY} L${tx},${railY + dep} L${tx + w},${railY} `;\n }\n return `${d}L${railX1},${railY} L${railX1},${bot} Z`;\n }, [bot, minorsPerDetent, minorsPerPitch, pitch, railX0, railX1, railY, totalMinors, x0]);\n\n const ticks = useMemo(() => {\n const out = [];\n for (let q = 0; q <= totalMinors; q++) {\n const tx = x0 + (q / minorsPerPitch) * pitch;\n const isStation = q % minorsPerPitch === 0;\n const isDetent = q % minorsPerDetent === 0;\n out.push({\n x: tx,\n len: isStation ? 20 : isDetent ? 13 : 8,\n kind: isStation ? \"major\" : isDetent ? \"half\" : \"quarter\",\n station: isStation ? q / minorsPerPitch : null,\n });\n }\n return out;\n }, [minorsPerDetent, minorsPerPitch, pitch, totalMinors, x0]);\n\n const { pos } = travel;\n const carriageX = x0 + pos * pitch;\n const lit = coincidence({ pos, count, spec });\n\n // The pawl rides up out of its notch between detents and drops back in.\n const frac = Math.abs(pos * minorsPerPitch - Math.round(pos * minorsPerPitch));\n const pawlY = railY + 2 - frac * 13;\n\n const glassW = vdiv * 5.8;\n const captionLeft = carriageX + glassW / 2 + 60 > railX1;\n\n return (\n <svg\n ref={ref}\n className=\"ds-rule__chrome\"\n viewBox={`0 0 ${vbW} ${vbH}`}\n aria-hidden=\"true\"\n focusable=\"false\"\n >\n <path className=\"ds-rule__rail\" d={rail} />\n <circle className=\"ds-rule__rivet\" cx={railX0 + 10} cy={bot - 8} r={3} />\n <circle className=\"ds-rule__rivet\" cx={railX1 - 10} cy={bot - 8} r={3} />\n\n {ticks.map((tick, i) => (\n <line\n key={i}\n className={`ds-rule__tick ds-rule__tick--${tick.kind}`}\n x1={tick.x}\n y1={bot}\n x2={tick.x}\n y2={bot - tick.len}\n />\n ))}\n\n {ticks\n .filter((tick) => tick.station != null)\n .map((tick) => (\n <text\n key={`n${tick.station}`}\n className=\"ds-rule__numeral ds-rule__numeral--accent\"\n x={tick.x}\n y={bot - 24}\n textAnchor=\"middle\"\n >\n {stations[tick.station]?.value ?? tick.station.toFixed(2)}\n </text>\n ))}\n\n <text className=\"ds-rule__caption\" x={railX0 + 6} y={railY - 8}>\n MAIN\n </text>\n\n {/* Leader ticks up to the HTML station labels. */}\n {ticks\n .filter((tick) => tick.station != null)\n .map((tick) => (\n <line\n key={`l${tick.station}`}\n className=\"ds-rule__tick ds-rule__tick--quarter\"\n x1={tick.x}\n y1={railY - 16}\n x2={tick.x}\n y2={railY - 9}\n />\n ))}\n\n <g\n ref={carriageRef}\n className=\"ds-rule__carriage\"\n transform={`translate(${carriageX},0)`}\n >\n <rect\n className=\"ds-rule__pawl-spring\"\n x={-2}\n y={pawlY - 24}\n width={4}\n height={11}\n />\n <path\n className=\"ds-rule__pawl\"\n d=\"M-6,-11 L6,-11 L0,2 Z\"\n transform={`translate(0,${pawlY})`}\n />\n <rect\n className=\"ds-rule__glass\"\n x={-glassW / 2}\n y={railY - 2}\n width={glassW}\n height={railH + 50}\n rx={2}\n />\n <rect\n className=\"ds-rule__frame\"\n x={-glassW / 2}\n y={railY - 2}\n width={glassW}\n height={railH + 50}\n rx={3}\n />\n <line\n className=\"ds-rule__hairline\"\n x1={0}\n y1={railY - 2}\n x2={0}\n y2={bot + 48}\n />\n {Array.from({ length: 6 }, (_, k) => {\n const vx = (k - 2.5) * vdiv;\n const on = k === lit.k && lit.lit;\n return (\n <g key={k}>\n <line\n className={\n \"ds-rule__vernier-line\" +\n (on ? \" ds-rule__vernier-line--lit\" : \"\")\n }\n x1={vx}\n y1={bot + 2}\n x2={vx}\n y2={bot + 18}\n />\n {k < 5 && (\n <text\n className=\"ds-rule__numeral\"\n x={vx}\n y={bot + 28}\n textAnchor=\"middle\"\n >\n {k}\n </text>\n )}\n </g>\n );\n })}\n {/* Flip the caption inboard once the carriage nears the right end. */}\n <text\n className=\"ds-rule__caption\"\n x={captionLeft ? -glassW / 2 - 6 : glassW / 2 + 6}\n y={bot + 18}\n textAnchor={captionLeft ? \"end\" : \"start\"}\n >\n VERNIER\n </text>\n </g>\n </svg>\n );\n});\n"],"mappings":"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","names":["useMemo","useRef","CursorRule","forwardRef","useMemo","useRef","jsx","jsxs","forwardRef","SlideStator","useMemo","useRef","forwardRef","useMemo","useRef","jsx","jsxs","forwardRef","VernierCarriage","useRef","useMemo","jsx","jsxs","useMemo","useRef"]}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Sidebar navigation built on a circular slide rule.
3
+ *
4
+ * At rest only a crescent of the disc breaks the edge — enough to read as an
5
+ * instrument, not enough to cost layout. Approaching the rail swings the whole
6
+ * rule out; spinning it brings a station to the fixed index, ratcheting
7
+ * through quarter-pitch detents on the way.
8
+ *
9
+ * As with SlideRuleNav, the engraved chrome is an aria-hidden SVG and the
10
+ * navigation is an ordinary list of links positioned over it, so the dial is
11
+ * fully operable by keyboard and the spin is a progressive enhancement.
12
+ *
13
+ * @param {object} props
14
+ * @param {"volvelle"|"concentric"|"fan"} [props.variant]
15
+ * @param {DialItem[]} props.items
16
+ * @param {string} [props.value] - Selected item id (controlled).
17
+ * @param {string} [props.defaultValue]
18
+ * @param {(id: string) => void} [props.onChange]
19
+ * @param {boolean} [props.expanded] - Controlled extract state.
20
+ * @param {boolean} [props.defaultExpanded]
21
+ * @param {(expanded: boolean) => void} [props.onExpandedChange]
22
+ * @param {string} [props.label] - Accessible name for the nav landmark.
23
+ * @param {string} [props.className]
24
+ * @returns {import("react").ReactElement}
25
+ */
26
+ export function DialNav({ variant, items, value, defaultValue, onChange, expanded, defaultExpanded, onExpandedChange, label, className, }: {
27
+ variant?: "volvelle" | "concentric" | "fan" | undefined;
28
+ items: DialItem[];
29
+ value?: string | undefined;
30
+ defaultValue?: string | undefined;
31
+ onChange?: ((id: string) => void) | undefined;
32
+ expanded?: boolean | undefined;
33
+ defaultExpanded?: boolean | undefined;
34
+ onExpandedChange?: ((expanded: boolean) => void) | undefined;
35
+ label?: string | undefined;
36
+ className?: string | undefined;
37
+ }): import("react").ReactElement;
38
+ export type DialItem = {
39
+ id: string;
40
+ label: import("react").ReactNode;
41
+ /**
42
+ * - Route path. Renders a NavLink; requires a Router.
43
+ */
44
+ to?: string | undefined;
45
+ end?: boolean | undefined;
46
+ onSelect?: (() => void) | undefined;
47
+ };
48
+ //# sourceMappingURL=DialNav.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DialNav.d.ts","sourceRoot":"","sources":["../../../src/components/DialNav/DialNav.jsx"],"names":[],"mappings":"AAmFA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,2IAZG;IAA8C,OAAO;IAC3B,KAAK,EAAvB,QAAQ,EAAE;IACK,KAAK;IACL,YAAY;IACE,QAAQ,SAAhC,MAAM,KAAK,IAAI;IACJ,QAAQ;IACR,eAAe;IACK,gBAAgB,eAAzC,OAAO,KAAK,IAAI;IACZ,KAAK;IACL,SAAS;CAChC,GAAU,OAAO,OAAO,EAAE,YAAY,CAmVxC;;QA1aa,MAAM;WACN,OAAO,OAAO,EAAE,SAAS;;;;;;sBAGnB,IAAI"}