@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,162 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { polar, valueToTurnedAngle } from "./math.js";
|
|
3
|
+
import { useRoundGauge } from "./context.js";
|
|
4
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
//#region src/components/gauge/needle.tsx
|
|
7
|
+
var PRIMARY = "var(--global-primary)";
|
|
8
|
+
var FOREGROUND = "var(--global-foreground)";
|
|
9
|
+
var pts = (points) => points.map((p) => `${p.x},${p.y}`).join(" ");
|
|
10
|
+
var NeedleShape = ({ kind = "line", length = .85, width = 6, color = PRIMARY, tail = 30, tailColor = color, tailDot = 0, turns = 1, end = "to" }) => {
|
|
11
|
+
const gauge = useRoundGauge("GaugeNeedle");
|
|
12
|
+
const { radius, min, max, startAngle, endAngle } = gauge;
|
|
13
|
+
const value = end === "from" ? gauge.from : gauge.value;
|
|
14
|
+
const angle = valueToTurnedAngle(value, min, max, startAngle, endAngle, turns);
|
|
15
|
+
const tip = polar(radius * length, angle);
|
|
16
|
+
const back = polar(-tail, angle);
|
|
17
|
+
const endDot = tail > 0 && tailDot > 0 ? /* @__PURE__ */ jsx("circle", {
|
|
18
|
+
cx: back.x,
|
|
19
|
+
cy: back.y,
|
|
20
|
+
r: tailDot,
|
|
21
|
+
fill: tailColor
|
|
22
|
+
}) : null;
|
|
23
|
+
if (kind === "arrow") {
|
|
24
|
+
const reach = radius * length;
|
|
25
|
+
const headLength = Math.min(width * 4, reach);
|
|
26
|
+
const base = polar(reach - headLength, angle);
|
|
27
|
+
const shaftLeft = polar(width / 2, angle - 90);
|
|
28
|
+
const shaftRight = polar(width / 2, angle + 90);
|
|
29
|
+
const headLeft = polar(width * 2, angle - 90);
|
|
30
|
+
const headRight = polar(width * 2, angle + 90);
|
|
31
|
+
const offset = (p, d) => ({
|
|
32
|
+
x: p.x + d.x,
|
|
33
|
+
y: p.y + d.y
|
|
34
|
+
});
|
|
35
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
36
|
+
tail > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("polygon", {
|
|
37
|
+
points: pts([
|
|
38
|
+
shaftLeft,
|
|
39
|
+
shaftRight,
|
|
40
|
+
offset(back, shaftRight),
|
|
41
|
+
offset(back, shaftLeft)
|
|
42
|
+
]),
|
|
43
|
+
fill: tailColor
|
|
44
|
+
}), /* @__PURE__ */ jsx("circle", {
|
|
45
|
+
cx: back.x,
|
|
46
|
+
cy: back.y,
|
|
47
|
+
r: width / 2,
|
|
48
|
+
fill: tailColor
|
|
49
|
+
})] }),
|
|
50
|
+
/* @__PURE__ */ jsx("polygon", {
|
|
51
|
+
points: pts([
|
|
52
|
+
shaftLeft,
|
|
53
|
+
offset(base, shaftLeft),
|
|
54
|
+
offset(base, headLeft),
|
|
55
|
+
tip,
|
|
56
|
+
offset(base, headRight),
|
|
57
|
+
offset(base, shaftRight),
|
|
58
|
+
shaftRight
|
|
59
|
+
]),
|
|
60
|
+
fill: color
|
|
61
|
+
}),
|
|
62
|
+
endDot
|
|
63
|
+
] });
|
|
64
|
+
}
|
|
65
|
+
if (kind === "line") return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("line", {
|
|
66
|
+
x1: back.x,
|
|
67
|
+
y1: back.y,
|
|
68
|
+
x2: tip.x,
|
|
69
|
+
y2: tip.y,
|
|
70
|
+
stroke: color,
|
|
71
|
+
strokeWidth: width,
|
|
72
|
+
strokeLinecap: "round"
|
|
73
|
+
}), endDot] });
|
|
74
|
+
const left = polar(width / 2, angle - 90);
|
|
75
|
+
const right = polar(width / 2, angle + 90);
|
|
76
|
+
const endRadius = width * .2;
|
|
77
|
+
const endLeft = polar(endRadius, angle - 90);
|
|
78
|
+
const endRight = polar(endRadius, angle + 90);
|
|
79
|
+
const blade = (endPoint) => pts([
|
|
80
|
+
{
|
|
81
|
+
x: endPoint.x + endLeft.x,
|
|
82
|
+
y: endPoint.y + endLeft.y
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
x: endPoint.x + endRight.x,
|
|
86
|
+
y: endPoint.y + endRight.y
|
|
87
|
+
},
|
|
88
|
+
right,
|
|
89
|
+
left
|
|
90
|
+
]);
|
|
91
|
+
const rear = kind === "compass" ? blade(back) : pts([
|
|
92
|
+
{
|
|
93
|
+
x: back.x + left.x * .6,
|
|
94
|
+
y: back.y + left.y * .6
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
x: back.x + right.x * .6,
|
|
98
|
+
y: back.y + right.y * .6
|
|
99
|
+
},
|
|
100
|
+
right,
|
|
101
|
+
left
|
|
102
|
+
]);
|
|
103
|
+
return /* @__PURE__ */ jsxs("g", { children: [
|
|
104
|
+
tail > 0 && /* @__PURE__ */ jsx("polygon", {
|
|
105
|
+
points: rear,
|
|
106
|
+
fill: tailColor
|
|
107
|
+
}),
|
|
108
|
+
tail > 0 && kind === "compass" && /* @__PURE__ */ jsx("circle", {
|
|
109
|
+
cx: back.x,
|
|
110
|
+
cy: back.y,
|
|
111
|
+
r: endRadius,
|
|
112
|
+
fill: tailColor
|
|
113
|
+
}),
|
|
114
|
+
/* @__PURE__ */ jsx("polygon", {
|
|
115
|
+
points: blade(tip),
|
|
116
|
+
fill: color
|
|
117
|
+
}),
|
|
118
|
+
/* @__PURE__ */ jsx("circle", {
|
|
119
|
+
cx: tip.x,
|
|
120
|
+
cy: tip.y,
|
|
121
|
+
r: endRadius,
|
|
122
|
+
fill: color
|
|
123
|
+
}),
|
|
124
|
+
endDot
|
|
125
|
+
] });
|
|
126
|
+
};
|
|
127
|
+
/**
|
|
128
|
+
* A needle pointing at the current value. Add a `GaugeHub` for the pivot, or
|
|
129
|
+
* a `gap` to clear the centre for a readout instead.
|
|
130
|
+
*/
|
|
131
|
+
var GaugeNeedle = ({ gap = 0, ...props }) => {
|
|
132
|
+
const maskId = `gauge-needle-gap${React.useId().replace(/\W/g, "")}`;
|
|
133
|
+
if (gap <= 0) return /* @__PURE__ */ jsx(NeedleShape, { ...props });
|
|
134
|
+
return /* @__PURE__ */ jsxs("g", { children: [/* @__PURE__ */ jsxs("mask", {
|
|
135
|
+
id: maskId,
|
|
136
|
+
maskUnits: "userSpaceOnUse",
|
|
137
|
+
x: -1e4,
|
|
138
|
+
y: -1e4,
|
|
139
|
+
width: 2e4,
|
|
140
|
+
height: 2e4,
|
|
141
|
+
children: [/* @__PURE__ */ jsx("rect", {
|
|
142
|
+
x: -1e4,
|
|
143
|
+
y: -1e4,
|
|
144
|
+
width: 2e4,
|
|
145
|
+
height: 2e4,
|
|
146
|
+
fill: "white"
|
|
147
|
+
}), /* @__PURE__ */ jsx("circle", {
|
|
148
|
+
r: gap,
|
|
149
|
+
fill: "black"
|
|
150
|
+
})]
|
|
151
|
+
}), /* @__PURE__ */ jsx("g", {
|
|
152
|
+
mask: `url(#${maskId})`,
|
|
153
|
+
children: /* @__PURE__ */ jsx(NeedleShape, { ...props })
|
|
154
|
+
})] });
|
|
155
|
+
};
|
|
156
|
+
/** The pivot disc at the centre, drawn once however many needles there are. */
|
|
157
|
+
var GaugeHub = ({ radius = 12, color = FOREGROUND }) => radius > 0 ? /* @__PURE__ */ jsx("circle", {
|
|
158
|
+
r: radius,
|
|
159
|
+
fill: color
|
|
160
|
+
}) : null;
|
|
161
|
+
//#endregion
|
|
162
|
+
export { GaugeHub, GaugeNeedle };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Arc scale for a round dial. Parts ask the scale where a domain position
|
|
3
|
+
* lands rather than doing polar math themselves.
|
|
4
|
+
*
|
|
5
|
+
* Positions are gauge degrees. An offset moves a point outward from the
|
|
6
|
+
* reference circle (away from centre).
|
|
7
|
+
*/
|
|
8
|
+
export type GaugePoint = {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
};
|
|
12
|
+
export type GaugeBox = {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
};
|
|
18
|
+
export type GaugeScale = {
|
|
19
|
+
/** Position of the start of the domain. */
|
|
20
|
+
start: number;
|
|
21
|
+
/** Position of the end of the domain. */
|
|
22
|
+
end: number;
|
|
23
|
+
/**
|
|
24
|
+
* The scale runs right round and meets itself, as a full ring does, so
|
|
25
|
+
* its two ends land on the same spot.
|
|
26
|
+
*/
|
|
27
|
+
closed: boolean;
|
|
28
|
+
/** The point at a position, `offset` outward from the reference circle. */
|
|
29
|
+
point: (position: number, offset?: number) => GaugePoint;
|
|
30
|
+
/**
|
|
31
|
+
* SVG path along the scale between two positions, `offset` outward. Empty
|
|
32
|
+
* when `to` is not past `from`.
|
|
33
|
+
*/
|
|
34
|
+
path: (from: number, to: number, offset?: number) => string;
|
|
35
|
+
/** The outward direction at a position, as a unit vector. */
|
|
36
|
+
normal: (position: number) => GaugePoint;
|
|
37
|
+
/**
|
|
38
|
+
* Box round the whole scale with its reference circle moved `offset`
|
|
39
|
+
* outward. Includes the centre, where a needle pivots.
|
|
40
|
+
*/
|
|
41
|
+
box: (offset?: number) => GaugeBox;
|
|
42
|
+
};
|
|
43
|
+
/** A dial's scale: a circle of `radius` swept from one gauge angle to another. */
|
|
44
|
+
export declare const arcScale: (radius: number, startAngle: number, endAngle: number) => GaugeScale;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { arcBox, arcPath, polar } from "./math.js";
|
|
2
|
+
//#region src/components/gauge/scale.ts
|
|
3
|
+
/**
|
|
4
|
+
* Arc scale for a round dial. Parts ask the scale where a domain position
|
|
5
|
+
* lands rather than doing polar math themselves.
|
|
6
|
+
*
|
|
7
|
+
* Positions are gauge degrees. An offset moves a point outward from the
|
|
8
|
+
* reference circle (away from centre).
|
|
9
|
+
*/
|
|
10
|
+
/** A dial's scale: a circle of `radius` swept from one gauge angle to another. */
|
|
11
|
+
var arcScale = (radius, startAngle, endAngle) => ({
|
|
12
|
+
start: startAngle,
|
|
13
|
+
end: endAngle,
|
|
14
|
+
closed: endAngle - startAngle >= 360,
|
|
15
|
+
point: (angle, offset = 0) => polar(radius + offset, angle),
|
|
16
|
+
path: (from, to, offset = 0) => arcPath(radius + offset, from, to),
|
|
17
|
+
normal: (angle) => polar(1, angle),
|
|
18
|
+
box: (offset = 0) => arcBox(radius + offset, startAngle, endAngle)
|
|
19
|
+
});
|
|
20
|
+
//#endregion
|
|
21
|
+
export { arcScale };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { FontFamily, FontWeight, GaugeEnd, TextAnchor } from './utils';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
export type GaugeTextProps = {
|
|
4
|
+
/**
|
|
5
|
+
* Position relative to the gauge centre in SVG units. Positive y is down.
|
|
6
|
+
* @defaultValue 0
|
|
7
|
+
*/
|
|
8
|
+
x?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Position relative to the gauge centre in SVG units. Positive y is down.
|
|
11
|
+
* @defaultValue 0
|
|
12
|
+
*/
|
|
13
|
+
y?: number;
|
|
14
|
+
/**
|
|
15
|
+
* Font size in SVG units.
|
|
16
|
+
* @defaultValue 24
|
|
17
|
+
*/
|
|
18
|
+
fontSize?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Fill colour. Unset uses theme foreground.
|
|
21
|
+
* @defaultValue `var(--global-foreground)`
|
|
22
|
+
*/
|
|
23
|
+
color?: string;
|
|
24
|
+
/**
|
|
25
|
+
* Typeface map: sans/rounded → body; mono → mono token.
|
|
26
|
+
* @defaultValue `'sans'`
|
|
27
|
+
*/
|
|
28
|
+
font?: FontFamily;
|
|
29
|
+
/**
|
|
30
|
+
* Font weight token.
|
|
31
|
+
* @defaultValue `'medium'`
|
|
32
|
+
*/
|
|
33
|
+
weight?: FontWeight;
|
|
34
|
+
/**
|
|
35
|
+
* Which edge of the text sits at `(x, y)`.
|
|
36
|
+
* @defaultValue `'middle'`
|
|
37
|
+
*/
|
|
38
|
+
anchor?: TextAnchor;
|
|
39
|
+
/**
|
|
40
|
+
* Extra class names on the SVG `text` node.
|
|
41
|
+
* @defaultValue undefined
|
|
42
|
+
*/
|
|
43
|
+
className?: string;
|
|
44
|
+
/**
|
|
45
|
+
* Inline styles merged onto the SVG `text` node (e.g. tabular nums).
|
|
46
|
+
* @defaultValue undefined
|
|
47
|
+
*/
|
|
48
|
+
style?: React.CSSProperties;
|
|
49
|
+
/**
|
|
50
|
+
* Label content.
|
|
51
|
+
*/
|
|
52
|
+
children: React.ReactNode;
|
|
53
|
+
};
|
|
54
|
+
/** A free-standing label anywhere on the gauge. */
|
|
55
|
+
export declare const GaugeText: React.FC<GaugeTextProps>;
|
|
56
|
+
export type GaugeValueProps = Omit<GaugeTextProps, 'children'> & {
|
|
57
|
+
/**
|
|
58
|
+
* Fixed decimal places when `format` is omitted.
|
|
59
|
+
* @defaultValue 0
|
|
60
|
+
*/
|
|
61
|
+
decimals?: number;
|
|
62
|
+
/**
|
|
63
|
+
* Formats each number shown; a range formats its two ends apart.
|
|
64
|
+
* @defaultValue `value.toFixed(decimals)`
|
|
65
|
+
*/
|
|
66
|
+
format?: (value: number) => string;
|
|
67
|
+
/**
|
|
68
|
+
* Placed between the two ends of a range.
|
|
69
|
+
* @defaultValue `'–'`
|
|
70
|
+
*/
|
|
71
|
+
separator?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Show only one end of a range rather than both.
|
|
74
|
+
* @defaultValue both ends when `range`, else `value`
|
|
75
|
+
*/
|
|
76
|
+
end?: GaugeEnd;
|
|
77
|
+
};
|
|
78
|
+
/** The current value as text, or a range as its two ends. */
|
|
79
|
+
export declare const GaugeValue: React.FC<GaugeValueProps>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useGauge } from "./context.js";
|
|
3
|
+
import { fontFamily, fontWeight } from "./utils.js";
|
|
4
|
+
import { jsx } from "react/jsx-runtime";
|
|
5
|
+
import "react";
|
|
6
|
+
//#region src/components/gauge/text.tsx
|
|
7
|
+
var FOREGROUND = "var(--global-foreground)";
|
|
8
|
+
/** A free-standing label anywhere on the gauge. */
|
|
9
|
+
var GaugeText = ({ x = 0, y = 0, fontSize = 24, color = FOREGROUND, font = "sans", weight = "medium", anchor = "middle", className, style, children }) => /* @__PURE__ */ jsx("text", {
|
|
10
|
+
x,
|
|
11
|
+
y,
|
|
12
|
+
fill: color,
|
|
13
|
+
fontSize,
|
|
14
|
+
fontFamily: fontFamily(font),
|
|
15
|
+
fontWeight: fontWeight(weight),
|
|
16
|
+
textAnchor: anchor,
|
|
17
|
+
dominantBaseline: "central",
|
|
18
|
+
className,
|
|
19
|
+
style,
|
|
20
|
+
children
|
|
21
|
+
});
|
|
22
|
+
/** The current value as text, or a range as its two ends. */
|
|
23
|
+
var GaugeValue = ({ decimals = 0, format, separator = "–", end, className, style, fontSize = 96, weight = "semibold", ...props }) => {
|
|
24
|
+
const { value, from, range } = useGauge();
|
|
25
|
+
const text = (v) => format ? format(v) : v.toFixed(decimals);
|
|
26
|
+
let valueText = text(value);
|
|
27
|
+
if (end === "from") valueText = text(from);
|
|
28
|
+
else if (range && !end) valueText = `${text(from)}${separator}${text(value)}`;
|
|
29
|
+
return /* @__PURE__ */ jsx(GaugeText, {
|
|
30
|
+
fontSize,
|
|
31
|
+
weight,
|
|
32
|
+
className,
|
|
33
|
+
style: {
|
|
34
|
+
fontVariantNumeric: "tabular-nums",
|
|
35
|
+
...style
|
|
36
|
+
},
|
|
37
|
+
...props,
|
|
38
|
+
children: valueText
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { GaugeText, GaugeValue };
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { FontFamily, FontWeight, StrokeCap, TextAnchor, ValueColor } from './utils';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
type CrossLineProps = {
|
|
4
|
+
/**
|
|
5
|
+
* Length of each line across the scale: along the radius of a dial.
|
|
6
|
+
* @defaultValue 12
|
|
7
|
+
*/
|
|
8
|
+
length?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Stroke width of each tick line.
|
|
11
|
+
* @defaultValue 2
|
|
12
|
+
*/
|
|
13
|
+
width?: number;
|
|
14
|
+
/**
|
|
15
|
+
* One colour, or a function of each line's value to colour some apart:
|
|
16
|
+
* the ticks inside a range, or each in the colour of its zone.
|
|
17
|
+
* @defaultValue `var(--global-foreground)`
|
|
18
|
+
*/
|
|
19
|
+
color?: ValueColor;
|
|
20
|
+
/**
|
|
21
|
+
* Stroke opacity for the tick group.
|
|
22
|
+
* @defaultValue 1
|
|
23
|
+
*/
|
|
24
|
+
opacity?: number;
|
|
25
|
+
/**
|
|
26
|
+
* Stroke line cap.
|
|
27
|
+
* @defaultValue `'round'`
|
|
28
|
+
*/
|
|
29
|
+
cap?: StrokeCap;
|
|
30
|
+
/**
|
|
31
|
+
* Offset of the line's midpoint from the scale's reference line.
|
|
32
|
+
* @defaultValue 0
|
|
33
|
+
*/
|
|
34
|
+
offset?: number;
|
|
35
|
+
};
|
|
36
|
+
export type GaugeTicksProps = CrossLineProps & {
|
|
37
|
+
/**
|
|
38
|
+
* Number of intervals across the domain; renders `count + 1` ticks.
|
|
39
|
+
* @defaultValue 10
|
|
40
|
+
*/
|
|
41
|
+
count?: number;
|
|
42
|
+
};
|
|
43
|
+
/** Evenly spaced tick marks along the whole scale. */
|
|
44
|
+
export declare const GaugeTicks: React.FC<GaugeTicksProps>;
|
|
45
|
+
export type GaugeTickFillProps = Omit<CrossLineProps, 'color'> & {
|
|
46
|
+
/**
|
|
47
|
+
* Number of intervals across the domain; renders `count + 1` ticks.
|
|
48
|
+
* @defaultValue 40
|
|
49
|
+
*/
|
|
50
|
+
count?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Where the lit stretch starts. Defaults to the start of the gauge's
|
|
53
|
+
* range, or `min` on a single value.
|
|
54
|
+
* @defaultValue gauge `from`
|
|
55
|
+
*/
|
|
56
|
+
from?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Where it ends. Defaults to the current value, or a range's far end.
|
|
59
|
+
* @defaultValue current gauge `value`
|
|
60
|
+
*/
|
|
61
|
+
to?: number;
|
|
62
|
+
/**
|
|
63
|
+
* `fill` lights every tick from `from` to `to`, as an arc would fill.
|
|
64
|
+
* `nearest` lights only the tick nearest each end: on a single value a
|
|
65
|
+
* pointer made of ticks, on a range one at either end.
|
|
66
|
+
* @defaultValue `'fill'`
|
|
67
|
+
*/
|
|
68
|
+
mode?: 'fill' | 'nearest';
|
|
69
|
+
/**
|
|
70
|
+
* Colour of the lit ticks: one, or a function of each tick's value, so a
|
|
71
|
+
* fill can run through zones or a gradient.
|
|
72
|
+
* @defaultValue `var(--global-foreground)`
|
|
73
|
+
*/
|
|
74
|
+
color?: ValueColor;
|
|
75
|
+
/**
|
|
76
|
+
* Colour of the ticks left unlit. Defaults to a faint theme foreground.
|
|
77
|
+
* @defaultValue `fadeColor('var(--global-foreground)', 15)`
|
|
78
|
+
*/
|
|
79
|
+
dimColor?: ValueColor;
|
|
80
|
+
/**
|
|
81
|
+
* Length of a lit tick, for meters whose live segments stand out.
|
|
82
|
+
* Defaults to `length`.
|
|
83
|
+
* @defaultValue same as `length`
|
|
84
|
+
*/
|
|
85
|
+
litLength?: number;
|
|
86
|
+
};
|
|
87
|
+
/**
|
|
88
|
+
* Ticks that are the fill: the whole sweep is ticked, and those between the
|
|
89
|
+
* ends of the value are lit while the rest stay dim. It follows the value
|
|
90
|
+
* frame by frame as `GaugeArc` does, fills a range between its ends, and on
|
|
91
|
+
* a wrapped gauge runs forward from the start across the seam. With
|
|
92
|
+
* `mode="nearest"` it lights a single tick at each end instead, for a
|
|
93
|
+
* pointer drawn in ticks. Lit and dim colours can each be a function of the
|
|
94
|
+
* tick's value, so one primitive covers segmented meters, LED rings, zoned
|
|
95
|
+
* dials and dot-matrix pointers.
|
|
96
|
+
*/
|
|
97
|
+
export declare const GaugeTickFill: React.FC<GaugeTickFillProps>;
|
|
98
|
+
export type GaugeMarksProps = CrossLineProps & {
|
|
99
|
+
/**
|
|
100
|
+
* Domain values to mark, for example the cutoffs between zones.
|
|
101
|
+
* @defaultValue derived from `count`, or empty
|
|
102
|
+
*/
|
|
103
|
+
values?: number[];
|
|
104
|
+
/**
|
|
105
|
+
* Cut the domain into this many equal segments instead: the marks fall
|
|
106
|
+
* between the segments, none at the two ends. On a closed ring the seam is
|
|
107
|
+
* cut too, since the last segment meets the first there. Drawn in the
|
|
108
|
+
* surface's colour over a band, they slice it the way a water ring is cut
|
|
109
|
+
* into glasses.
|
|
110
|
+
* @defaultValue omitted (use `values`)
|
|
111
|
+
*/
|
|
112
|
+
count?: number;
|
|
113
|
+
};
|
|
114
|
+
/**
|
|
115
|
+
* Marks across the scale at arbitrary domain values, or at equal cuts of
|
|
116
|
+
* the domain.
|
|
117
|
+
*/
|
|
118
|
+
export declare const GaugeMarks: React.FC<GaugeMarksProps>;
|
|
119
|
+
export type GaugeTickLabelsProps = {
|
|
120
|
+
/**
|
|
121
|
+
* Number of intervals across the domain; renders `count + 1` labels.
|
|
122
|
+
* @defaultValue 10
|
|
123
|
+
*/
|
|
124
|
+
count?: number;
|
|
125
|
+
/**
|
|
126
|
+
* Offset of the label from the scale's reference line.
|
|
127
|
+
* @defaultValue -48
|
|
128
|
+
*/
|
|
129
|
+
offset?: number;
|
|
130
|
+
/**
|
|
131
|
+
* Which edge of each label sits at its point: `end` lines labels up
|
|
132
|
+
* against the left of a vertical bar.
|
|
133
|
+
* @defaultValue `'middle'`
|
|
134
|
+
*/
|
|
135
|
+
anchor?: TextAnchor;
|
|
136
|
+
/**
|
|
137
|
+
* Font size in SVG units.
|
|
138
|
+
* @defaultValue 16
|
|
139
|
+
*/
|
|
140
|
+
fontSize?: number;
|
|
141
|
+
/**
|
|
142
|
+
* One colour, or a function of each label's value.
|
|
143
|
+
* @defaultValue `var(--global-foreground)`
|
|
144
|
+
*/
|
|
145
|
+
color?: ValueColor;
|
|
146
|
+
/**
|
|
147
|
+
* Typeface map: sans/rounded → body; mono → mono token.
|
|
148
|
+
* @defaultValue `'sans'`
|
|
149
|
+
*/
|
|
150
|
+
font?: FontFamily;
|
|
151
|
+
/**
|
|
152
|
+
* Font weight token.
|
|
153
|
+
* @defaultValue `'medium'`
|
|
154
|
+
*/
|
|
155
|
+
weight?: FontWeight;
|
|
156
|
+
/**
|
|
157
|
+
* Fixed decimal places when `format` is omitted.
|
|
158
|
+
* @defaultValue 0
|
|
159
|
+
*/
|
|
160
|
+
decimals?: number;
|
|
161
|
+
/**
|
|
162
|
+
* Custom label formatter. Overrides `decimals` when set.
|
|
163
|
+
* @defaultValue `value.toFixed(decimals)`
|
|
164
|
+
*/
|
|
165
|
+
format?: (value: number) => string;
|
|
166
|
+
};
|
|
167
|
+
/** Numeric labels at evenly spaced values along the scale. */
|
|
168
|
+
export declare const GaugeTickLabels: React.FC<GaugeTickLabelsProps>;
|
|
169
|
+
export {};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { cutValues, foldValue, stepValues } from "./math.js";
|
|
3
|
+
import { useGauge } from "./context.js";
|
|
4
|
+
import { fadeColor, fontFamily, fontWeight } from "./utils.js";
|
|
5
|
+
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import "react";
|
|
7
|
+
//#region src/components/gauge/ticks.tsx
|
|
8
|
+
var FOREGROUND = "var(--global-foreground)";
|
|
9
|
+
/**
|
|
10
|
+
* Evenly spaced domain values for ticks. On a closed ring the last step lands
|
|
11
|
+
* on the first, so it is dropped rather than drawn twice.
|
|
12
|
+
*/
|
|
13
|
+
var tickValues = ({ min, max, scale }, count) => {
|
|
14
|
+
const values = stepValues(min, max, count);
|
|
15
|
+
if (scale.closed) values.pop();
|
|
16
|
+
return values;
|
|
17
|
+
};
|
|
18
|
+
/** Lines standing across the scale at domain values, as ticks do. */
|
|
19
|
+
var CrossLines = ({ values, length = 12, lengthOf, width = 2, color = FOREGROUND, opacity = 1, cap = "round", offset = 0 }) => {
|
|
20
|
+
const { scale, positionOf } = useGauge();
|
|
21
|
+
const each = typeof color === "function" ? color : void 0;
|
|
22
|
+
return /* @__PURE__ */ jsx("g", {
|
|
23
|
+
stroke: typeof color === "string" ? color : void 0,
|
|
24
|
+
strokeOpacity: opacity,
|
|
25
|
+
strokeWidth: width,
|
|
26
|
+
strokeLinecap: cap,
|
|
27
|
+
children: values.map((value) => {
|
|
28
|
+
const half = (lengthOf?.(value) ?? length) / 2;
|
|
29
|
+
const a = scale.point(positionOf(value), offset - half);
|
|
30
|
+
const b = scale.point(positionOf(value), offset + half);
|
|
31
|
+
return /* @__PURE__ */ jsx("line", {
|
|
32
|
+
x1: a.x,
|
|
33
|
+
y1: a.y,
|
|
34
|
+
x2: b.x,
|
|
35
|
+
y2: b.y,
|
|
36
|
+
stroke: each?.(value)
|
|
37
|
+
}, value);
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
/** Evenly spaced tick marks along the whole scale. */
|
|
42
|
+
var GaugeTicks = ({ count = 10, ...props }) => {
|
|
43
|
+
const gauge = useGauge();
|
|
44
|
+
return /* @__PURE__ */ jsx(CrossLines, {
|
|
45
|
+
values: tickValues(gauge, count),
|
|
46
|
+
...props
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Ticks that are the fill: the whole sweep is ticked, and those between the
|
|
51
|
+
* ends of the value are lit while the rest stay dim. It follows the value
|
|
52
|
+
* frame by frame as `GaugeArc` does, fills a range between its ends, and on
|
|
53
|
+
* a wrapped gauge runs forward from the start across the seam. With
|
|
54
|
+
* `mode="nearest"` it lights a single tick at each end instead, for a
|
|
55
|
+
* pointer drawn in ticks. Lit and dim colours can each be a function of the
|
|
56
|
+
* tick's value, so one primitive covers segmented meters, LED rings, zoned
|
|
57
|
+
* dials and dot-matrix pointers.
|
|
58
|
+
*/
|
|
59
|
+
var GaugeTickFill = ({ count = 40, from, to, mode = "fill", color = FOREGROUND, dimColor = fadeColor(FOREGROUND, 15), length = 12, litLength = length, ...props }) => {
|
|
60
|
+
const gauge = useGauge();
|
|
61
|
+
const values = tickValues(gauge, count);
|
|
62
|
+
const tail = from ?? gauge.from;
|
|
63
|
+
const head = to ?? gauge.value;
|
|
64
|
+
const span = Math.abs(gauge.max - gauge.min);
|
|
65
|
+
const eps = span * 1e-6;
|
|
66
|
+
const ahead = (v) => foldValue(v - tail + eps, 0, span);
|
|
67
|
+
const between = (v) => gauge.wrap ? ahead(v) <= ahead(head) + eps : v >= Math.min(tail, head) - eps && v <= Math.max(tail, head) + eps;
|
|
68
|
+
const nearestTo = (target) => {
|
|
69
|
+
const gap = (v) => {
|
|
70
|
+
const d = Math.abs(v - target);
|
|
71
|
+
return gauge.wrap ? Math.min(d, span - d) : d;
|
|
72
|
+
};
|
|
73
|
+
return values.reduce((best, v) => gap(v) < gap(best) ? v : best);
|
|
74
|
+
};
|
|
75
|
+
const ends = mode === "nearest" ? new Set(gauge.range || from !== void 0 ? [nearestTo(tail), nearestTo(head)] : [nearestTo(head)]) : null;
|
|
76
|
+
const lit = new Map(values.map((v) => [v, ends ? ends.has(v) : between(v)]));
|
|
77
|
+
const paint = (c, v) => typeof c === "function" ? c(v) : c;
|
|
78
|
+
return /* @__PURE__ */ jsx(CrossLines, {
|
|
79
|
+
values,
|
|
80
|
+
length,
|
|
81
|
+
lengthOf: (v) => lit.get(v) ? litLength : length,
|
|
82
|
+
color: (v) => paint(lit.get(v) ? color : dimColor, v),
|
|
83
|
+
...props
|
|
84
|
+
});
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Marks across the scale at arbitrary domain values, or at equal cuts of
|
|
88
|
+
* the domain.
|
|
89
|
+
*/
|
|
90
|
+
var GaugeMarks = ({ values, count, ...props }) => {
|
|
91
|
+
const { min, max, scale } = useGauge();
|
|
92
|
+
const marks = values ?? (count && count > 1 ? [...scale.closed ? [min] : [], ...cutValues(min, max, count)] : []);
|
|
93
|
+
return /* @__PURE__ */ jsx(CrossLines, {
|
|
94
|
+
values: marks,
|
|
95
|
+
...props
|
|
96
|
+
});
|
|
97
|
+
};
|
|
98
|
+
/** Numeric labels at evenly spaced values along the scale. */
|
|
99
|
+
var GaugeTickLabels = ({ count = 10, offset = -48, anchor = "middle", fontSize = 16, color = FOREGROUND, font = "sans", weight = "medium", decimals = 0, format }) => {
|
|
100
|
+
const gauge = useGauge();
|
|
101
|
+
const { scale, positionOf } = gauge;
|
|
102
|
+
const values = tickValues(gauge, count);
|
|
103
|
+
const each = typeof color === "function" ? color : void 0;
|
|
104
|
+
return /* @__PURE__ */ jsx("g", {
|
|
105
|
+
fill: typeof color === "string" ? color : void 0,
|
|
106
|
+
fontSize,
|
|
107
|
+
fontFamily: fontFamily(font),
|
|
108
|
+
fontWeight: fontWeight(weight),
|
|
109
|
+
textAnchor: anchor,
|
|
110
|
+
dominantBaseline: "central",
|
|
111
|
+
style: { fontVariantNumeric: "tabular-nums" },
|
|
112
|
+
children: values.map((value) => {
|
|
113
|
+
const p = scale.point(positionOf(value), offset);
|
|
114
|
+
return /* @__PURE__ */ jsx("text", {
|
|
115
|
+
x: p.x,
|
|
116
|
+
y: p.y,
|
|
117
|
+
fill: each?.(value),
|
|
118
|
+
children: format ? format(value) : value.toFixed(decimals)
|
|
119
|
+
}, value);
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
};
|
|
123
|
+
//#endregion
|
|
124
|
+
export { GaugeMarks, GaugeTickFill, GaugeTickLabels, GaugeTicks };
|