@viraui/react 2026.10.0 → 2026.11.0-next.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 (127) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +8 -0
  2. package/dist/catalog.json +12 -0
  3. package/dist/components/avatar/avatar.d.ts +6 -0
  4. package/dist/components/avatar/avatar.js +2 -2
  5. package/dist/components/badge/badge.css +1 -1
  6. package/dist/components/button/button.css +1 -1
  7. package/dist/components/dialog/dialog-sheet.d.ts +1 -1
  8. package/dist/components/gauge/arcs.d.ts +134 -0
  9. package/dist/components/gauge/arcs.js +145 -0
  10. package/dist/components/gauge/context.d.ts +134 -0
  11. package/dist/components/gauge/context.js +83 -0
  12. package/dist/components/gauge/control.d.ts +107 -0
  13. package/dist/components/gauge/control.js +223 -0
  14. package/dist/components/gauge/demos/_shared.d.ts +28 -0
  15. package/dist/components/gauge/demos/activity-ring.d.ts +9 -0
  16. package/dist/components/gauge/demos/air-quality.d.ts +9 -0
  17. package/dist/components/gauge/demos/analog-dial.d.ts +9 -0
  18. package/dist/components/gauge/demos/battery.d.ts +9 -0
  19. package/dist/components/gauge/demos/chronograph.d.ts +9 -0
  20. package/dist/components/gauge/demos/clock.d.ts +9 -0
  21. package/dist/components/gauge/demos/compass.d.ts +9 -0
  22. package/dist/components/gauge/demos/data-usage.d.ts +9 -0
  23. package/dist/components/gauge/demos/dimmer.d.ts +9 -0
  24. package/dist/components/gauge/demos/fuel.d.ts +9 -0
  25. package/dist/components/gauge/demos/heart-rate.d.ts +9 -0
  26. package/dist/components/gauge/demos/internet-speed.d.ts +9 -0
  27. package/dist/components/gauge/demos/nested-rings.d.ts +11 -0
  28. package/dist/components/gauge/demos/orrery.d.ts +12 -0
  29. package/dist/components/gauge/demos/power-meter.d.ts +9 -0
  30. package/dist/components/gauge/demos/progress-ring.d.ts +9 -0
  31. package/dist/components/gauge/demos/range.d.ts +10 -0
  32. package/dist/components/gauge/demos/saas-metric.d.ts +9 -0
  33. package/dist/components/gauge/demos/segments.d.ts +9 -0
  34. package/dist/components/gauge/demos/simple.d.ts +9 -0
  35. package/dist/components/gauge/demos/speed-fuel.d.ts +10 -0
  36. package/dist/components/gauge/demos/speedometer.d.ts +9 -0
  37. package/dist/components/gauge/demos/sprint.d.ts +9 -0
  38. package/dist/components/gauge/demos/sun-path.d.ts +9 -0
  39. package/dist/components/gauge/demos/system.d.ts +11 -0
  40. package/dist/components/gauge/demos/tachometer.d.ts +9 -0
  41. package/dist/components/gauge/demos/temperature.d.ts +9 -0
  42. package/dist/components/gauge/demos/thermostat.d.ts +9 -0
  43. package/dist/components/gauge/demos/timer.d.ts +9 -0
  44. package/dist/components/gauge/demos/volume.d.ts +10 -0
  45. package/dist/components/gauge/demos/wind.d.ts +9 -0
  46. package/dist/components/gauge/dot.d.ts +55 -0
  47. package/dist/components/gauge/dot.js +34 -0
  48. package/dist/components/gauge/gauge.css +1 -0
  49. package/dist/components/gauge/gauge.d.ts +84 -0
  50. package/dist/components/gauge/gauge.js +62 -0
  51. package/dist/components/gauge/gauge.module.js +24 -0
  52. package/dist/components/gauge/index.d.ts +15 -0
  53. package/dist/components/gauge/inset.d.ts +30 -0
  54. package/dist/components/gauge/inset.js +39 -0
  55. package/dist/components/gauge/math.d.ts +69 -0
  56. package/dist/components/gauge/math.js +135 -0
  57. package/dist/components/gauge/needle.d.ts +81 -0
  58. package/dist/components/gauge/needle.js +162 -0
  59. package/dist/components/gauge/scale.d.ts +44 -0
  60. package/dist/components/gauge/scale.js +21 -0
  61. package/dist/components/gauge/text.d.ts +79 -0
  62. package/dist/components/gauge/text.js +42 -0
  63. package/dist/components/gauge/ticks.d.ts +169 -0
  64. package/dist/components/gauge/ticks.js +124 -0
  65. package/dist/components/gauge/tooltip.d.ts +117 -0
  66. package/dist/components/gauge/tooltip.js +337 -0
  67. package/dist/components/gauge/transition.d.ts +60 -0
  68. package/dist/components/gauge/transition.js +129 -0
  69. package/dist/components/gauge/use-animated-value.d.ts +14 -0
  70. package/dist/components/gauge/use-animated-value.js +111 -0
  71. package/dist/components/gauge/utils.d.ts +51 -0
  72. package/dist/components/gauge/utils.js +80 -0
  73. package/dist/components/icon-meter/icon-meter.css +1 -0
  74. package/dist/components/icon-meter/icon-meter.d.ts +62 -0
  75. package/dist/components/icon-meter/icon-meter.js +105 -0
  76. package/dist/components/icon-meter/icon-meter.module.js +11 -0
  77. package/dist/components/icon-meter/index.d.ts +2 -0
  78. package/dist/components/icon-meter/utils.d.ts +22 -0
  79. package/dist/components/icon-meter/utils.js +40 -0
  80. package/dist/components/index.d.ts +2 -0
  81. package/dist/components/popover/popover-sheet.d.ts +1 -2
  82. package/dist/components/stack/stack.d.ts +9 -2
  83. package/dist/components/tabs/tabs.d.ts +1 -2
  84. package/dist/index.js +17 -1
  85. package/dist/internal/chrome-icons/chrome-icons.d.ts +4 -0
  86. package/dist/internal/chrome-icons/chrome-icons.js +12 -4
  87. package/dist/node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom.development.js +192 -0
  88. package/dist/node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/cjs/react-dom.production.js +156 -0
  89. package/dist/node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/index.js +22 -0
  90. package/dist/preflight.css +1 -1
  91. package/dist/specs/components/avatar/props.xml +3 -0
  92. package/dist/specs/components/avatar/usage.xml +1 -0
  93. package/dist/specs/components/badge/a11y.xml +1 -1
  94. package/dist/specs/components/button/props.xml +2 -2
  95. package/dist/specs/components/button/usage.xml +1 -1
  96. package/dist/specs/components/gauge/a11y.xml +8 -0
  97. package/dist/specs/components/gauge/meta.xml +38 -0
  98. package/dist/specs/components/gauge/patterns.xml +1158 -0
  99. package/dist/specs/components/gauge/props.xml +452 -0
  100. package/dist/specs/components/gauge/usage.xml +21 -0
  101. package/dist/specs/components/icon-button/props.xml +2 -2
  102. package/dist/specs/components/icon-button/usage.xml +1 -1
  103. package/dist/specs/components/icon-meter/a11y.xml +9 -0
  104. package/dist/specs/components/icon-meter/meta.xml +21 -0
  105. package/dist/specs/components/icon-meter/patterns.xml +23 -0
  106. package/dist/specs/components/icon-meter/props.xml +44 -0
  107. package/dist/specs/components/icon-meter/usage.xml +13 -0
  108. package/dist/specs/components/linear-progress/usage.xml +1 -0
  109. package/dist/specs/components/meter/meta.xml +1 -0
  110. package/dist/specs/components/meter/props.xml +1 -0
  111. package/dist/specs/components/meter/usage.xml +1 -0
  112. package/dist/specs/components/stack/patterns.xml +21 -21
  113. package/dist/specs/components/stack/props.xml +6 -6
  114. package/dist/specs/components/stack/usage.xml +4 -2
  115. package/dist/specs/components/text/patterns.xml +1 -1
  116. package/dist/specs/components/text/usage.xml +1 -1
  117. package/dist/specs/components/toggle-button/props.xml +1 -1
  118. package/dist/specs/components/toggle-button/usage.xml +1 -1
  119. package/dist/specs/components/toggle-text-button/props.xml +1 -1
  120. package/dist/specs/foundation/attributes.xml +3 -3
  121. package/dist/specs/foundation/modules.xml +1 -1
  122. package/dist/specs/foundation/theme-topics.xml +7 -2
  123. package/dist/specs/index.xml +21 -1
  124. package/dist/specs/patterns/compose-layout-first.xml +1 -1
  125. package/dist/specs/patterns/settings-panel.xml +1 -1
  126. package/dist/specs/patterns/settings-row.xml +1 -1
  127. package/package.json +2 -2
@@ -0,0 +1,117 @@
1
+ import * as React from 'react';
2
+ /**
3
+ * What the tooltip is pinned to. `pointer` follows the cursor round the
4
+ * gauge, `value` sits on the scale at the value, and the rest are fixed
5
+ * points of the gauge: its centre, or the middle of an edge or a corner of
6
+ * the box its scale fills.
7
+ */
8
+ export type GaugeTooltipPosition = 'pointer' | 'value' | 'center' | 'top' | 'right' | 'bottom' | 'left' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
9
+ /**
10
+ * Which way the bubble stands off its point. `auto` picks for the position:
11
+ * outward from the scale at the value, so the bubble swings round a dial with it,
12
+ * above the pointer, centred on the centre, and away from the dial at an
13
+ * edge or corner.
14
+ */
15
+ export type GaugeTooltipSide = 'auto' | 'center' | 'top' | 'right' | 'bottom' | 'left';
16
+ export type GaugeTooltipProps = {
17
+ /**
18
+ * Anchor on the gauge the bubble pins to.
19
+ * @defaultValue 'value'
20
+ */
21
+ position?: GaugeTooltipPosition;
22
+ /**
23
+ * Side the bubble stands off its point.
24
+ * @defaultValue 'auto'
25
+ */
26
+ side?: GaugeTooltipSide;
27
+ /**
28
+ * Domain value a `value` tooltip points at and shows. Defaults to the
29
+ * current value, or for a range the middle of it, showing both ends; a
30
+ * fixed one marks a target or a cutoff.
31
+ * @defaultValue Current gauge value (or range middle)
32
+ */
33
+ at?: number;
34
+ /**
35
+ * How far out from the scale's reference line the point sits, in SVG
36
+ * units: the spot on the scale for `value`, and the box round the scale
37
+ * for the edges and corners.
38
+ * @defaultValue 24
39
+ */
40
+ offset?: number;
41
+ /**
42
+ * Room between the point and the bubble, in CSS pixels.
43
+ * @defaultValue 8
44
+ */
45
+ gap?: number;
46
+ /**
47
+ * Keep the bubble on screen: flip it to the far side of its point when
48
+ * that side has more room, then slide it sideways in from the left or
49
+ * right edge while it still overlaps its point. On by default.
50
+ * @defaultValue true
51
+ */
52
+ avoidCollisions?: boolean;
53
+ /**
54
+ * Room kept clear between the bubble and the viewport edge, in CSS pixels.
55
+ * @defaultValue 8
56
+ */
57
+ collisionPadding?: number;
58
+ /**
59
+ * Hold the tooltip open, or shut, instead of showing it on hover, while
60
+ * a press that started on the gauge is held, and on keyboard focus.
61
+ * @defaultValue undefined
62
+ */
63
+ open?: boolean;
64
+ /**
65
+ * Small line above the value. Leave out for the value on its own.
66
+ * @defaultValue undefined
67
+ */
68
+ label?: React.ReactNode;
69
+ /**
70
+ * Printed after the value, a size down.
71
+ * @defaultValue undefined
72
+ */
73
+ unit?: React.ReactNode;
74
+ /**
75
+ * Fraction digits when formatting with `toFixed`.
76
+ * @defaultValue 0
77
+ */
78
+ decimals?: number;
79
+ /**
80
+ * Formats each number shown; a range formats its two ends apart.
81
+ * @defaultValue undefined
82
+ */
83
+ format?: (value: number) => string;
84
+ /**
85
+ * Placed between the two ends of a range.
86
+ * @defaultValue '–'
87
+ */
88
+ separator?: string;
89
+ /**
90
+ * Classes for the bubble, merged over its defaults.
91
+ * @defaultValue undefined
92
+ */
93
+ className?: string;
94
+ /**
95
+ * Inline style for the bubble. Prefer `--*` custom properties.
96
+ * @defaultValue undefined
97
+ */
98
+ style?: React.CSSProperties;
99
+ /**
100
+ * Replaces the default label, value and unit with any HTML, or a function
101
+ * of the value being shown for content that has to follow it. On a range
102
+ * the function gets its start as well.
103
+ * @defaultValue Default label / value / unit
104
+ */
105
+ children?: React.ReactNode | ((value: number, from?: number) => React.ReactNode);
106
+ };
107
+ /**
108
+ * An HTML tooltip for the gauge, shown while the pointer is over it. It reads
109
+ * the value with an optional label and unit by default, or renders whatever
110
+ * its children are, and can be pinned to the value on the arc, follow the
111
+ * pointer, or hold a fixed spot on the dial.
112
+ *
113
+ * The bubble is portalled to the body and placed through the SVG's screen
114
+ * transform, so it is never clipped by the gauge and keeps its own CSS size
115
+ * however the gauge, or an inset it sits in, is scaled.
116
+ */
117
+ export declare const GaugeTooltip: React.FC<GaugeTooltipProps>;
@@ -0,0 +1,337 @@
1
+ "use client";
2
+ import { clsx } from "../../core/clsx.js";
3
+ import { foldValue } from "./math.js";
4
+ import { useGauge } from "./context.js";
5
+ import gauge_module_default from "./gauge.module.js";
6
+ import { require_react_dom } from "../../node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/index.js";
7
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
8
+ import * as React from "react";
9
+ //#region src/components/gauge/tooltip.tsx
10
+ var import_react_dom = require_react_dom();
11
+ var extent = {
12
+ x: "width",
13
+ y: "height"
14
+ };
15
+ /**
16
+ * The bubble's top left corner for a direction off the point: centred on the
17
+ * point, moved `gap` along the direction, then pushed half its own size the
18
+ * same way, so a side tooltip touches the point with that edge.
19
+ */
20
+ var corner = (p, d, size, gap) => {
21
+ const len = Math.hypot(d.x, d.y) || 1;
22
+ return {
23
+ x: p.x + d.x / len * gap + (d.x - 1) / 2 * size.width,
24
+ y: p.y + d.y / len * gap + (d.y - 1) / 2 * size.height
25
+ };
26
+ };
27
+ /** Directions in screen space, y down, scaled so the longer axis is 1. */
28
+ var sides = {
29
+ center: {
30
+ x: 0,
31
+ y: 0
32
+ },
33
+ top: {
34
+ x: 0,
35
+ y: -1
36
+ },
37
+ right: {
38
+ x: 1,
39
+ y: 0
40
+ },
41
+ bottom: {
42
+ x: 0,
43
+ y: 1
44
+ },
45
+ left: {
46
+ x: -1,
47
+ y: 0
48
+ }
49
+ };
50
+ /** Where each fixed position sits in the sweep's box, 0 to 1 across. */
51
+ var boxPoints = {
52
+ top: {
53
+ x: .5,
54
+ y: 0
55
+ },
56
+ right: {
57
+ x: 1,
58
+ y: .5
59
+ },
60
+ bottom: {
61
+ x: .5,
62
+ y: 1
63
+ },
64
+ left: {
65
+ x: 0,
66
+ y: .5
67
+ },
68
+ "top-left": {
69
+ x: 0,
70
+ y: 0
71
+ },
72
+ "top-right": {
73
+ x: 1,
74
+ y: 0
75
+ },
76
+ "bottom-left": {
77
+ x: 0,
78
+ y: 1
79
+ },
80
+ "bottom-right": {
81
+ x: 1,
82
+ y: 1
83
+ }
84
+ };
85
+ /** What can take keyboard focus, to find the element that owns the gauge's.
86
+ A range control is not focusable itself but holds the thumbs that are. */
87
+ var FOCUSABLE = "[tabindex]:not([tabindex=\"-1\"]), a[href], button, input, select, textarea, [data-slot=\"gauge-control\"]";
88
+ var subscribe = () => () => void 0;
89
+ var useIsClient = () => React.useSyncExternalStore(subscribe, () => true, () => false);
90
+ /** The label over the value and its unit. */
91
+ var DefaultContent = ({ text, label, unit }) => /* @__PURE__ */ jsxs("div", {
92
+ className: gauge_module_default.TooltipBody,
93
+ children: [label && /* @__PURE__ */ jsx("span", {
94
+ className: gauge_module_default.TooltipLabel,
95
+ children: label
96
+ }), /* @__PURE__ */ jsxs("span", {
97
+ className: gauge_module_default.TooltipValue,
98
+ children: [text, unit && /* @__PURE__ */ jsx("span", {
99
+ className: gauge_module_default.TooltipUnit,
100
+ children: unit
101
+ })]
102
+ })]
103
+ });
104
+ /**
105
+ * An HTML tooltip for the gauge, shown while the pointer is over it. It reads
106
+ * the value with an optional label and unit by default, or renders whatever
107
+ * its children are, and can be pinned to the value on the arc, follow the
108
+ * pointer, or hold a fixed spot on the dial.
109
+ *
110
+ * The bubble is portalled to the body and placed through the SVG's screen
111
+ * transform, so it is never clipped by the gauge and keeps its own CSS size
112
+ * however the gauge, or an inset it sits in, is scaled.
113
+ */
114
+ var GaugeTooltip = ({ position = "value", side = "auto", at, offset = 24, gap = 8, avoidCollisions = true, collisionPadding = 8, open, label, unit, decimals = 0, format, separator = "–", className, style, children }) => {
115
+ const gauge = useGauge();
116
+ const isClient = useIsClient();
117
+ const anchor = React.useRef(null);
118
+ const box = React.useRef(null);
119
+ const pointer = React.useRef(null);
120
+ const [hovered, setHovered] = React.useState(false);
121
+ const [pressed, setPressed] = React.useState(false);
122
+ const [focused, setFocused] = React.useState(false);
123
+ const shown = open ?? (hovered || pressed || focused);
124
+ const value = at ?? gauge.value;
125
+ const spread = at === void 0 && gauge.range;
126
+ let middle = value;
127
+ if (spread) {
128
+ if (gauge.wrap) middle = foldValue(gauge.from + foldValue(gauge.value - gauge.from, 0, gauge.max - gauge.min) / 2, gauge.min, gauge.max);
129
+ else middle = (gauge.from + gauge.value) / 2;
130
+ }
131
+ const along = gauge.positionOf(middle);
132
+ /** The point the bubble is pinned to, in client pixels. */
133
+ const pinned = () => {
134
+ if (position === "pointer") return pointer.current;
135
+ const ctm = anchor.current?.getScreenCTM();
136
+ if (!ctm) return null;
137
+ let p = {
138
+ x: 0,
139
+ y: 0
140
+ };
141
+ if (position === "value") p = gauge.scale.point(along, offset);
142
+ else if (position !== "center") {
143
+ const b = gauge.scale.box(offset);
144
+ const spot = boxPoints[position] ?? {
145
+ x: .5,
146
+ y: .5
147
+ };
148
+ p = {
149
+ x: b.x + b.width * spot.x,
150
+ y: b.y + b.height * spot.y
151
+ };
152
+ }
153
+ return new DOMPoint(p.x, p.y).matrixTransform(ctm);
154
+ };
155
+ /** The way the bubble stands off its point. */
156
+ const direction = () => {
157
+ if (side !== "auto") return sides[side];
158
+ if (position === "pointer") return sides.top;
159
+ if (position === "center") return sides.center;
160
+ if (position !== "value") {
161
+ const spot = boxPoints[position] ?? {
162
+ x: .5,
163
+ y: .5
164
+ };
165
+ return {
166
+ x: spot.x * 2 - 1,
167
+ y: spot.y * 2 - 1
168
+ };
169
+ }
170
+ const r = gauge.scale.normal(along);
171
+ const m = Math.max(Math.abs(r.x), Math.abs(r.y)) || 1;
172
+ return {
173
+ x: r.x / m,
174
+ y: r.y / m
175
+ };
176
+ };
177
+ const place = React.useEffectEvent(() => {
178
+ const el = box.current;
179
+ if (!el) return;
180
+ const p = pinned();
181
+ if (!p) {
182
+ el.style.visibility = "hidden";
183
+ return;
184
+ }
185
+ const size = {
186
+ width: el.offsetWidth,
187
+ height: el.offsetHeight
188
+ };
189
+ let d = direction();
190
+ let atPoint = corner(p, d, size, gap);
191
+ if (avoidCollisions) {
192
+ const view = {
193
+ width: document.documentElement.clientWidth,
194
+ height: document.documentElement.clientHeight
195
+ };
196
+ const pad = collisionPadding;
197
+ const spill = (spot, axis) => {
198
+ const span = size[extent[axis]];
199
+ const room = view[extent[axis]] - pad;
200
+ const out = Math.max(0, pad - spot[axis]) + Math.max(0, spot[axis] + span - room);
201
+ return Math.min(span, out);
202
+ };
203
+ for (const axis of ["x", "y"]) {
204
+ const before = spill(atPoint, axis);
205
+ if (before !== 0 && d[axis] !== 0) {
206
+ const flipped = {
207
+ ...d,
208
+ [axis]: -d[axis]
209
+ };
210
+ const there = corner(p, flipped, size, gap);
211
+ if (spill(there, axis) < before) {
212
+ d = flipped;
213
+ atPoint = there;
214
+ }
215
+ }
216
+ }
217
+ const { width } = size;
218
+ if (p.x >= atPoint.x && p.x <= atPoint.x + width) {
219
+ const inside = Math.max(pad, Math.min(atPoint.x, view.width - pad - width));
220
+ atPoint = {
221
+ ...atPoint,
222
+ x: Math.max(p.x - width, Math.min(inside, p.x))
223
+ };
224
+ }
225
+ }
226
+ el.style.visibility = "";
227
+ el.style.left = `${atPoint.x + window.scrollX}px`;
228
+ el.style.top = `${atPoint.y + window.scrollY}px`;
229
+ });
230
+ React.useEffect(() => {
231
+ const svg = anchor.current?.ownerSVGElement;
232
+ if (!svg) return;
233
+ const track = (e) => {
234
+ pointer.current = {
235
+ x: e.clientX,
236
+ y: e.clientY
237
+ };
238
+ };
239
+ const enter = (e) => {
240
+ track(e);
241
+ setHovered(true);
242
+ };
243
+ const move = (e) => {
244
+ track(e);
245
+ place();
246
+ };
247
+ const leave = () => setHovered(false);
248
+ const down = (e) => {
249
+ track(e);
250
+ setPressed(true);
251
+ };
252
+ svg.addEventListener("pointerenter", enter);
253
+ svg.addEventListener("pointermove", move);
254
+ svg.addEventListener("pointerleave", leave);
255
+ svg.addEventListener("pointerdown", down);
256
+ return () => {
257
+ svg.removeEventListener("pointerenter", enter);
258
+ svg.removeEventListener("pointermove", move);
259
+ svg.removeEventListener("pointerleave", leave);
260
+ svg.removeEventListener("pointerdown", down);
261
+ };
262
+ }, []);
263
+ React.useEffect(() => {
264
+ const owner = anchor.current?.ownerSVGElement?.closest(FOCUSABLE);
265
+ if (!owner) return;
266
+ const within = () => owner.contains(document.activeElement);
267
+ const check = () => setFocused(within() && Boolean(document.activeElement?.matches(":focus-visible:not([data-pointer])")));
268
+ const key = () => setFocused(within());
269
+ const blur = (e) => {
270
+ const next = e.relatedTarget;
271
+ if (!owner.contains(next)) setFocused(false);
272
+ };
273
+ check();
274
+ owner.addEventListener("focusin", check);
275
+ owner.addEventListener("keydown", key);
276
+ owner.addEventListener("focusout", blur);
277
+ return () => {
278
+ owner.removeEventListener("focusin", check);
279
+ owner.removeEventListener("keydown", key);
280
+ owner.removeEventListener("focusout", blur);
281
+ };
282
+ }, []);
283
+ React.useEffect(() => {
284
+ if (!pressed) return;
285
+ const move = (e) => {
286
+ pointer.current = {
287
+ x: e.clientX,
288
+ y: e.clientY
289
+ };
290
+ place();
291
+ };
292
+ const release = () => setPressed(false);
293
+ window.addEventListener("pointermove", move);
294
+ window.addEventListener("pointerup", release);
295
+ window.addEventListener("pointercancel", release);
296
+ return () => {
297
+ window.removeEventListener("pointermove", move);
298
+ window.removeEventListener("pointerup", release);
299
+ window.removeEventListener("pointercancel", release);
300
+ };
301
+ }, [pressed]);
302
+ React.useEffect(() => {
303
+ if (!shown) return;
304
+ const update = () => place();
305
+ window.addEventListener("scroll", update, true);
306
+ window.addEventListener("resize", update);
307
+ return () => {
308
+ window.removeEventListener("scroll", update, true);
309
+ window.removeEventListener("resize", update);
310
+ };
311
+ }, [shown]);
312
+ React.useLayoutEffect(() => {
313
+ if (shown) place();
314
+ });
315
+ const text = (v) => format ? format(v) : v.toFixed(decimals);
316
+ let content;
317
+ if (typeof children === "function") content = spread ? children(value, gauge.from) : children(value);
318
+ else content = children ?? /* @__PURE__ */ jsx(DefaultContent, {
319
+ text: spread ? `${text(gauge.from)}${separator}${text(value)}` : text(value),
320
+ label,
321
+ unit
322
+ });
323
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("g", { ref: anchor }), shown && isClient && (0, import_react_dom.createPortal)(/* @__PURE__ */ jsx("div", {
324
+ ref: box,
325
+ className: gauge_module_default.TooltipPositioner,
326
+ children: /* @__PURE__ */ jsx("div", {
327
+ role: "tooltip",
328
+ "data-slot": "gauge-tooltip",
329
+ "data-elevation": "1",
330
+ style,
331
+ className: clsx(gauge_module_default.Tooltip, className),
332
+ children: content
333
+ })
334
+ }), document.body)] });
335
+ };
336
+ //#endregion
337
+ export { GaugeTooltip };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Dependency-free value animation for the gauge: a damped spring and a
3
+ * cubic-bezier tween, both stepped per frame. Pure functions only; the hook
4
+ * that drives them lives in `use-animated-value.ts`.
5
+ */
6
+ /** A named curve, CSS-style cubic-bezier control points, or your own function. */
7
+ export type GaugeEasing = 'linear' | 'easeIn' | 'easeOut' | 'easeInOut' | [number, number, number, number] | ((t: number) => number);
8
+ export type GaugeSpringTransition = {
9
+ type: 'spring';
10
+ /**
11
+ * Physics parameters. When any is given they take precedence over the
12
+ * perceptual `visualDuration` and `bounce` pair.
13
+ */
14
+ stiffness?: number;
15
+ damping?: number;
16
+ mass?: number;
17
+ /** Seconds the spring visibly takes to reach the target. Default 0.5. */
18
+ visualDuration?: number;
19
+ /** 0 settles without overshoot, 1 rings freely. Default 0.1. */
20
+ bounce?: number;
21
+ };
22
+ export type GaugeTweenTransition = {
23
+ type: 'tween';
24
+ /** Seconds. Default 0.5. */
25
+ duration?: number;
26
+ /** Default "easeInOut". */
27
+ ease?: GaugeEasing;
28
+ };
29
+ export type GaugeTransition = GaugeSpringTransition | GaugeTweenTransition;
30
+ export declare const DEFAULT_TRANSITION: GaugeSpringTransition;
31
+ /** Turns the `transition` prop into a concrete config, or null when off. */
32
+ export declare const resolveTransition: (transition: GaugeTransition | boolean | undefined) => GaugeTransition | null;
33
+ export type SpringPhysics = {
34
+ stiffness: number;
35
+ damping: number;
36
+ mass: number;
37
+ };
38
+ /**
39
+ * Physics for a spring config. The perceptual pair maps onto stiffness and
40
+ * damping the same way the motion library does, so a spring tuned against
41
+ * that library feels the same here.
42
+ */
43
+ export declare const springPhysics: (t: GaugeSpringTransition) => SpringPhysics;
44
+ export type SpringState = {
45
+ value: number;
46
+ velocity: number;
47
+ };
48
+ /**
49
+ * Advances a spring by `dt` seconds towards `target` with semi-implicit
50
+ * Euler in small substeps, which stays stable for stiff springs. Returns the
51
+ * new state and whether it has come to rest within `precision`.
52
+ */
53
+ export declare const stepSpring: (state: SpringState, target: number, physics: SpringPhysics, dt: number, precision: number) => {
54
+ state: SpringState;
55
+ done: boolean;
56
+ };
57
+ /** A CSS-compatible cubic-bezier easing function. */
58
+ export declare const cubicBezier: (x1: number, y1: number, x2: number, y2: number) => (x: number) => number;
59
+ /** Resolves any `GaugeEasing` into a function of progress. */
60
+ export declare const easingFunction: (ease?: GaugeEasing) => (t: number) => number;
@@ -0,0 +1,129 @@
1
+ //#region src/components/gauge/transition.ts
2
+ var DEFAULT_TRANSITION = {
3
+ type: "spring",
4
+ visualDuration: .5,
5
+ bounce: .1
6
+ };
7
+ /** Turns the `transition` prop into a concrete config, or null when off. */
8
+ var resolveTransition = (transition) => {
9
+ if (!transition) return null;
10
+ return transition === true ? DEFAULT_TRANSITION : transition;
11
+ };
12
+ /**
13
+ * Physics for a spring config. The perceptual pair maps onto stiffness and
14
+ * damping the same way the motion library does, so a spring tuned against
15
+ * that library feels the same here.
16
+ */
17
+ var springPhysics = (t) => {
18
+ if (t.stiffness !== void 0 || t.damping !== void 0 || t.mass !== void 0) return {
19
+ stiffness: t.stiffness ?? 100,
20
+ damping: t.damping ?? 10,
21
+ mass: t.mass ?? 1
22
+ };
23
+ const visualDuration = t.visualDuration ?? .5;
24
+ const bounce = t.bounce ?? .1;
25
+ const root = 2 * Math.PI / (visualDuration * 1.2);
26
+ const stiffness = root * root;
27
+ return {
28
+ stiffness,
29
+ damping: 2 * Math.min(1, Math.max(.05, 1 - bounce)) * Math.sqrt(stiffness),
30
+ mass: 1
31
+ };
32
+ };
33
+ var SUBSTEP = 1 / 240;
34
+ /**
35
+ * Advances a spring by `dt` seconds towards `target` with semi-implicit
36
+ * Euler in small substeps, which stays stable for stiff springs. Returns the
37
+ * new state and whether it has come to rest within `precision`.
38
+ */
39
+ var stepSpring = (state, target, physics, dt, precision) => {
40
+ let { value, velocity } = state;
41
+ let remaining = Math.min(dt, .064);
42
+ while (remaining > 0) {
43
+ const h = Math.min(SUBSTEP, remaining);
44
+ const force = -physics.stiffness * (value - target) - physics.damping * velocity;
45
+ velocity += force / physics.mass * h;
46
+ value += velocity * h;
47
+ remaining -= h;
48
+ }
49
+ const done = Math.abs(value - target) < precision && Math.abs(velocity) < precision * 10;
50
+ return done ? {
51
+ state: {
52
+ value: target,
53
+ velocity: 0
54
+ },
55
+ done
56
+ } : {
57
+ state: {
58
+ value,
59
+ velocity
60
+ },
61
+ done
62
+ };
63
+ };
64
+ var namedCurves = {
65
+ linear: [
66
+ 0,
67
+ 0,
68
+ 1,
69
+ 1
70
+ ],
71
+ easeIn: [
72
+ .42,
73
+ 0,
74
+ 1,
75
+ 1
76
+ ],
77
+ easeOut: [
78
+ 0,
79
+ 0,
80
+ .58,
81
+ 1
82
+ ],
83
+ easeInOut: [
84
+ .42,
85
+ 0,
86
+ .58,
87
+ 1
88
+ ]
89
+ };
90
+ /** A CSS-compatible cubic-bezier easing function. */
91
+ var cubicBezier = (x1, y1, x2, y2) => {
92
+ const bezier = (t, p1, p2) => {
93
+ const u = 1 - t;
94
+ return 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t;
95
+ };
96
+ const slope = (t, p1, p2) => {
97
+ const u = 1 - t;
98
+ return 3 * u * u * p1 + 6 * u * t * (p2 - p1) + 3 * t * t * (1 - p2);
99
+ };
100
+ return (x) => {
101
+ if (x <= 0) return 0;
102
+ if (x >= 1) return 1;
103
+ let t = x;
104
+ for (let i = 0; i < 8; i += 1) {
105
+ const dx = bezier(t, x1, x2) - x;
106
+ const d = slope(t, x1, x2);
107
+ if (Math.abs(dx) < 1e-6) return bezier(t, y1, y2);
108
+ if (Math.abs(d) < 1e-6) break;
109
+ t -= dx / d;
110
+ }
111
+ let lo = 0;
112
+ let hi = 1;
113
+ t = x;
114
+ while (hi - lo > 1e-6) {
115
+ if (bezier(t, x1, x2) < x) lo = t;
116
+ else hi = t;
117
+ t = (lo + hi) / 2;
118
+ }
119
+ return bezier(t, y1, y2);
120
+ };
121
+ };
122
+ /** Resolves any `GaugeEasing` into a function of progress. */
123
+ var easingFunction = (ease = "easeInOut") => {
124
+ if (typeof ease === "function") return ease;
125
+ const [x1, y1, x2, y2] = Array.isArray(ease) ? ease : namedCurves[ease];
126
+ return cubicBezier(x1, y1, x2, y2);
127
+ };
128
+ //#endregion
129
+ export { DEFAULT_TRANSITION, cubicBezier, easingFunction, resolveTransition, springPhysics, stepSpring };
@@ -0,0 +1,14 @@
1
+ import { GaugeTransition } from './transition';
2
+ /**
3
+ * Follows `target` with the given transition and returns the value to draw
4
+ * this frame. With no transition it returns `target` as is and renders
5
+ * nothing extra. Honours `prefers-reduced-motion` by snapping.
6
+ *
7
+ * `span` is the size of the domain and sets the rest threshold. `initial` is
8
+ * where the value starts on mount, for a sweep-in from the minimum. `wrap`
9
+ * treats the domain as circular: every change is followed the shortest way
10
+ * round, so a heading crossing north never swings the long way back. The
11
+ * returned value may then sit outside the domain mid-flight; map it back
12
+ * with a modulo.
13
+ */
14
+ export declare const useAnimatedValue: (target: number, transition: GaugeTransition | boolean | undefined, span: number, initial?: number, wrap?: boolean) => number;