@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.
- package/dist/_virtual/_rolldown/runtime.js +8 -0
- package/dist/catalog.json +12 -0
- package/dist/components/avatar/avatar.d.ts +6 -0
- package/dist/components/avatar/avatar.js +2 -2
- package/dist/components/badge/badge.css +1 -1
- package/dist/components/button/button.css +1 -1
- package/dist/components/dialog/dialog-sheet.d.ts +1 -1
- package/dist/components/gauge/arcs.d.ts +134 -0
- package/dist/components/gauge/arcs.js +145 -0
- package/dist/components/gauge/context.d.ts +134 -0
- package/dist/components/gauge/context.js +83 -0
- package/dist/components/gauge/control.d.ts +107 -0
- package/dist/components/gauge/control.js +223 -0
- package/dist/components/gauge/demos/_shared.d.ts +28 -0
- package/dist/components/gauge/demos/activity-ring.d.ts +9 -0
- package/dist/components/gauge/demos/air-quality.d.ts +9 -0
- package/dist/components/gauge/demos/analog-dial.d.ts +9 -0
- package/dist/components/gauge/demos/battery.d.ts +9 -0
- package/dist/components/gauge/demos/chronograph.d.ts +9 -0
- package/dist/components/gauge/demos/clock.d.ts +9 -0
- package/dist/components/gauge/demos/compass.d.ts +9 -0
- package/dist/components/gauge/demos/data-usage.d.ts +9 -0
- package/dist/components/gauge/demos/dimmer.d.ts +9 -0
- package/dist/components/gauge/demos/fuel.d.ts +9 -0
- package/dist/components/gauge/demos/heart-rate.d.ts +9 -0
- package/dist/components/gauge/demos/internet-speed.d.ts +9 -0
- package/dist/components/gauge/demos/nested-rings.d.ts +11 -0
- package/dist/components/gauge/demos/orrery.d.ts +12 -0
- package/dist/components/gauge/demos/power-meter.d.ts +9 -0
- package/dist/components/gauge/demos/progress-ring.d.ts +9 -0
- package/dist/components/gauge/demos/range.d.ts +10 -0
- package/dist/components/gauge/demos/saas-metric.d.ts +9 -0
- package/dist/components/gauge/demos/segments.d.ts +9 -0
- package/dist/components/gauge/demos/simple.d.ts +9 -0
- package/dist/components/gauge/demos/speed-fuel.d.ts +10 -0
- package/dist/components/gauge/demos/speedometer.d.ts +9 -0
- package/dist/components/gauge/demos/sprint.d.ts +9 -0
- package/dist/components/gauge/demos/sun-path.d.ts +9 -0
- package/dist/components/gauge/demos/system.d.ts +11 -0
- package/dist/components/gauge/demos/tachometer.d.ts +9 -0
- package/dist/components/gauge/demos/temperature.d.ts +9 -0
- package/dist/components/gauge/demos/thermostat.d.ts +9 -0
- package/dist/components/gauge/demos/timer.d.ts +9 -0
- package/dist/components/gauge/demos/volume.d.ts +10 -0
- package/dist/components/gauge/demos/wind.d.ts +9 -0
- package/dist/components/gauge/dot.d.ts +55 -0
- package/dist/components/gauge/dot.js +34 -0
- package/dist/components/gauge/gauge.css +1 -0
- package/dist/components/gauge/gauge.d.ts +84 -0
- package/dist/components/gauge/gauge.js +62 -0
- package/dist/components/gauge/gauge.module.js +24 -0
- package/dist/components/gauge/index.d.ts +15 -0
- package/dist/components/gauge/inset.d.ts +30 -0
- package/dist/components/gauge/inset.js +39 -0
- package/dist/components/gauge/math.d.ts +69 -0
- package/dist/components/gauge/math.js +135 -0
- package/dist/components/gauge/needle.d.ts +81 -0
- package/dist/components/gauge/needle.js +162 -0
- package/dist/components/gauge/scale.d.ts +44 -0
- package/dist/components/gauge/scale.js +21 -0
- package/dist/components/gauge/text.d.ts +79 -0
- package/dist/components/gauge/text.js +42 -0
- package/dist/components/gauge/ticks.d.ts +169 -0
- package/dist/components/gauge/ticks.js +124 -0
- package/dist/components/gauge/tooltip.d.ts +117 -0
- package/dist/components/gauge/tooltip.js +337 -0
- package/dist/components/gauge/transition.d.ts +60 -0
- package/dist/components/gauge/transition.js +129 -0
- package/dist/components/gauge/use-animated-value.d.ts +14 -0
- package/dist/components/gauge/use-animated-value.js +111 -0
- package/dist/components/gauge/utils.d.ts +51 -0
- package/dist/components/gauge/utils.js +80 -0
- package/dist/components/icon-meter/icon-meter.css +1 -0
- package/dist/components/icon-meter/icon-meter.d.ts +62 -0
- package/dist/components/icon-meter/icon-meter.js +105 -0
- package/dist/components/icon-meter/icon-meter.module.js +11 -0
- package/dist/components/icon-meter/index.d.ts +2 -0
- package/dist/components/icon-meter/utils.d.ts +22 -0
- package/dist/components/icon-meter/utils.js +40 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/popover/popover-sheet.d.ts +1 -2
- package/dist/components/stack/stack.d.ts +9 -2
- package/dist/components/tabs/tabs.d.ts +1 -2
- package/dist/index.js +17 -1
- package/dist/internal/chrome-icons/chrome-icons.d.ts +4 -0
- package/dist/internal/chrome-icons/chrome-icons.js +12 -4
- 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
- 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
- package/dist/node_modules/.pnpm/react-dom@19.3.0_react@19.3.0/node_modules/react-dom/index.js +22 -0
- package/dist/preflight.css +1 -1
- package/dist/specs/components/avatar/props.xml +3 -0
- package/dist/specs/components/avatar/usage.xml +1 -0
- package/dist/specs/components/badge/a11y.xml +1 -1
- package/dist/specs/components/button/props.xml +2 -2
- package/dist/specs/components/button/usage.xml +1 -1
- package/dist/specs/components/gauge/a11y.xml +8 -0
- package/dist/specs/components/gauge/meta.xml +38 -0
- package/dist/specs/components/gauge/patterns.xml +1158 -0
- package/dist/specs/components/gauge/props.xml +452 -0
- package/dist/specs/components/gauge/usage.xml +21 -0
- package/dist/specs/components/icon-button/props.xml +2 -2
- package/dist/specs/components/icon-button/usage.xml +1 -1
- package/dist/specs/components/icon-meter/a11y.xml +9 -0
- package/dist/specs/components/icon-meter/meta.xml +21 -0
- package/dist/specs/components/icon-meter/patterns.xml +23 -0
- package/dist/specs/components/icon-meter/props.xml +44 -0
- package/dist/specs/components/icon-meter/usage.xml +13 -0
- package/dist/specs/components/linear-progress/usage.xml +1 -0
- package/dist/specs/components/meter/meta.xml +1 -0
- package/dist/specs/components/meter/props.xml +1 -0
- package/dist/specs/components/meter/usage.xml +1 -0
- package/dist/specs/components/stack/patterns.xml +21 -21
- package/dist/specs/components/stack/props.xml +6 -6
- package/dist/specs/components/stack/usage.xml +4 -2
- package/dist/specs/components/text/patterns.xml +1 -1
- package/dist/specs/components/text/usage.xml +1 -1
- package/dist/specs/components/toggle-button/props.xml +1 -1
- package/dist/specs/components/toggle-button/usage.xml +1 -1
- package/dist/specs/components/toggle-text-button/props.xml +1 -1
- package/dist/specs/foundation/attributes.xml +3 -3
- package/dist/specs/foundation/modules.xml +1 -1
- package/dist/specs/foundation/theme-topics.xml +7 -2
- package/dist/specs/index.xml +21 -1
- package/dist/specs/patterns/compose-layout-first.xml +1 -1
- package/dist/specs/patterns/settings-panel.xml +1 -1
- package/dist/specs/patterns/settings-row.xml +1 -1
- 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;
|