@zkmake/three-meter 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,6 +8,10 @@ geometries, textures and programs, with a small dockable HUD. Zero dependencies.
8
8
  bun add -d @zkmake/three-meter # or npm i -D / pnpm add -D
9
9
  ```
10
10
 
11
+ Live demo: [three-meter.pages.dev](https://three-meter.pages.dev/), with a vanilla three and a
12
+ React Three Fiber take on the same scene. Add `?webgpu` for the WebGPU renderer and `?count=5000` to
13
+ load the scene up.
14
+
11
15
  The sampler and the card are separate components, so toggling the HUD never remounts your canvas.
12
16
  GPU time is measured on WebGPU as well as WebGL. The counters are draw calls, render passes and
13
17
  resource counts, which is what you watch when a scene is instanced. The card docks to a screen edge,
@@ -68,18 +72,46 @@ Canvas is its own React root. To run two canvases on one page, pass the same `st
68
72
 
69
73
  `mountPerfHud` and `PerfHud` take:
70
74
 
71
- | Option | Default | Meaning |
72
- | ------------------ | ------------------ | -------------------------------------------------------------------------------------------- |
73
- | `mode` | `"compact"` | `compact` is the card. `full` is the checkbox list that configures it. |
74
- | `defaultPlacement` | `{ edge: "left" }` | First-visit dock, as `edge` plus `align` of `start`, `center` or `end`. A drag overrides it. |
75
- | `storageKey` | `three-meter` | localStorage key for the selection and the dock. `null` disables persistence. |
76
- | `parent` | `document.body` | Where the host element is appended. |
77
- | `injectStyles` | `true` | Append the stylesheet once per document. |
78
- | `refreshHz` | `10` | Repaint rate. |
75
+ | Option | Default | Meaning |
76
+ | ------------------ | ------------------ | --------------------------------------------------------------------------------------------- |
77
+ | `mode` | `"compact"` | `compact` is the card. `full` is the checkbox list that configures it. |
78
+ | `theme` | `"system"` | `dark`, `light`, or `system` to follow `prefers-color-scheme` live. A pick in the panel wins. |
79
+ | `defaultPlacement` | `{ edge: "left" }` | First-visit dock, as `edge` plus `align` of `start`, `center` or `end`. A drag overrides it. |
80
+ | `storageKey` | `three-meter` | localStorage key for the selection and the dock. `null` disables persistence. |
81
+ | `parent` | `document.body` | Where the host element is appended. |
82
+ | `injectStyles` | `true` | Append the stylesheet once per document. |
83
+ | `refreshHz` | `10` | Repaint rate. |
79
84
 
80
85
  `PerformanceMonitor` and `PerfSampler` take `trackGPU` (default true), `gpuQueryPoolSize` (default 5)
81
86
  and `historySize` (default 120).
82
87
 
88
+ ## Theme
89
+
90
+ The HUD ships a dark and a light palette. Two layers decide which shows:
91
+
92
+ 1. **The panel's own pick.** The full view has a light / system / dark row. A pick there is kept
93
+ with the other settings under `storageKey` and wins while set.
94
+ 2. **Your `theme` option.** `dark`, `light`, or `system` (the default), which follows the OS and
95
+ switches when it does. Applies whenever the person using the HUD hasn't picked anything.
96
+
97
+ Change your layer later from the handle, or from the `PerfHud` prop in React, which applies without
98
+ remounting:
99
+
100
+ ```ts
101
+ const hud = mountPerfHud(monitor, { theme: "system" });
102
+ hud.setTheme("light"); // your layer: "dark" | "light" | "system"
103
+ hud.getTheme(); // your layer, as asked for
104
+ hud.settings.theme; // the panel's pick, or null
105
+ hud.settings.setTheme(null); // clear the pick so your layer applies again
106
+ hud.theme.effective; // whichever layer is in force
107
+ hud.theme.resolved; // "dark" | "light", what is on screen right now
108
+ hud.theme.subscribe(() => syncMyPageWith(hud.theme.resolved));
109
+ ```
110
+
111
+ The resolved theme lands on `data-theme` of `.perf-hud` and `.perf-monitor`. To restyle either
112
+ palette, override the `--perf-*` custom properties (`bg`, `fg`, `fg-dim`, `muted`, `row`,
113
+ `border`, `accent`, `shadow`) on those selectors.
114
+
83
115
  ## GPU timing
84
116
 
85
117
  On WebGL2 the monitor uses `EXT_disjoint_timer_query_webgl2`, which Chrome and Edge expose. Safari
@@ -89,9 +121,13 @@ On WebGPU, construct the renderer with `trackTimestamp: true`. Timings resolve a
89
121
 
90
122
  ## Examples
91
123
 
92
- [`examples/vanilla`](examples/vanilla) is plain three. Add `?webgpu` to use `WebGPURenderer` and
93
- `?count=` to scale the scene. [`examples/r3f`](examples/r3f) is the React Three Fiber version. Each
94
- is a Vite app. Run `bun run dev` inside it.
124
+ [`examples/site`](examples/site) is what runs at [three-meter.pages.dev](https://three-meter.pages.dev/):
125
+ one Vite app with both integrations of the same scene, swapped from the header.
126
+ [`src/demos/vanilla.ts`](examples/site/src/demos/vanilla.ts) is plain three with `mountPerfHud`;
127
+ [`src/demos/r3f.tsx`](examples/site/src/demos/r3f.tsx) is React Three Fiber with `PerfSampler` and
128
+ `PerfHud`. `?r3f` opens on Fiber, `?webgpu` uses `WebGPURenderer` in either, `?count=` scales the
129
+ scene. The header's theme toggle sets the page theme and the HUD's `theme` layer together; the row
130
+ inside the panel overrides the HUD alone. Run `bun run dev` inside it.
95
131
 
96
132
  ## Shipping it
97
133
 
@@ -105,7 +141,7 @@ your call. Gate the import in the app with a `?debug=` query, a build flag or a
105
141
 
106
142
  While a monitor is attached, `renderer.info.autoReset` is off and `begin()` resets `info`. One
107
143
  monitor per renderer. A second throws. `dispose()` restores everything. Theme with the `--perf-*`
108
- custom properties on `.perf-monitor`.
144
+ custom properties on `.perf-hud` and `.perf-monitor`, or the `theme` option.
109
145
 
110
146
  ## Contributing
111
147
 
@@ -32,11 +32,58 @@ type DockHandle = {
32
32
  */
33
33
  declare const dockPanel: (element: HTMLElement, options?: DockPanelOptions) => DockHandle;
34
34
  //#endregion
35
+ //#region src/ui/theme.d.ts
36
+ /**
37
+ * Dark, light, or follow the OS. One instance per HUD: the floating host and
38
+ * the card inside it both paint `resolved` onto `data-theme`, and the
39
+ * stylesheet picks the palette from that attribute.
40
+ *
41
+ * Two layers. `mode` is what the consumer asked for (the `theme` option,
42
+ * `setTheme`). `override` is what the person using the HUD picked in the
43
+ * panel, kept in `HudSettings`; it wins while set. `effective` is whichever
44
+ * applies, and `system` there tracks `prefers-color-scheme` for as long as
45
+ * the instance lives.
46
+ */
47
+ type ThemeMode = "dark" | "light" | "system";
48
+ type ResolvedTheme = Exclude<ThemeMode, "system">;
49
+ declare const THEME_MODES: readonly ThemeMode[];
50
+ declare const isThemeMode: (value: unknown) => value is ThemeMode;
51
+ declare class HudTheme {
52
+ private current;
53
+ private userPick;
54
+ private readonly listeners;
55
+ private media;
56
+ private readonly onSchemeChange;
57
+ constructor(mode?: ThemeMode);
58
+ /** What the consumer asked for. */
59
+ get mode(): ThemeMode;
60
+ /** What the person using the HUD picked in the panel, or `null` for no pick. */
61
+ get override(): ThemeMode | null;
62
+ /** The layer that applies: the user's pick if there is one, else the consumer's mode. */
63
+ get effective(): ThemeMode;
64
+ /**
65
+ * `dark` or `light`, with `system` resolved against the OS preference.
66
+ * Where `matchMedia` is missing, `system` falls back to `dark`, the HUD's
67
+ * original palette.
68
+ */
69
+ get resolved(): ResolvedTheme;
70
+ setMode(mode: ThemeMode): void;
71
+ setOverride(mode: ThemeMode | null): void;
72
+ /** Fires on `setMode`, `setOverride` and, in `system`, whenever the OS preference flips. */
73
+ subscribe(listener: () => void): () => void;
74
+ /** Stop tracking the OS preference and drop every subscriber. */
75
+ dispose(): void;
76
+ private watch;
77
+ private unwatch;
78
+ private notify;
79
+ }
80
+ //#endregion
35
81
  //#region src/ui/hud-settings.d.ts
36
82
  type HudSelection = {
37
83
  dim: boolean;
38
84
  graphs: ReadonlySet<TimingMetric>;
39
85
  numbers: ReadonlySet<string>;
86
+ theme: ThemeMode | null;
40
87
  };
41
88
  type HudDefaults = {
42
89
  dim?: boolean;
@@ -59,6 +106,9 @@ declare class HudSettings {
59
106
  get selection(): HudSelection;
60
107
  get dim(): boolean;
61
108
  setDim(enabled: boolean): void;
109
+ /** The user's pick in the panel; `null` means none, so the consumer's option applies. */
110
+ get theme(): ThemeMode | null;
111
+ setTheme(mode: ThemeMode | null): void;
62
112
  isNumberEnabled(key: string): boolean;
63
113
  isGraphEnabled(metric: TimingMetric): boolean;
64
114
  setNumberEnabled(key: string, enabled: boolean): void;
@@ -78,6 +128,11 @@ type PerformanceViewOptions = {
78
128
  refreshHz?: number;
79
129
  /** Share one between views to keep them in sync; a fresh instance otherwise. */
80
130
  settings?: HudSettings;
131
+ /**
132
+ * Share the host's so the card and its chrome switch together. Otherwise a
133
+ * fresh `system` instance is created and disposed with the view.
134
+ */
135
+ theme?: HudTheme;
81
136
  };
82
137
  /**
83
138
  * Host-agnostic widget that paints {@link Sample}s. Mount {@link element}
@@ -88,6 +143,9 @@ type PerformanceViewOptions = {
88
143
  declare class PerformanceView {
89
144
  readonly element: HTMLDivElement;
90
145
  readonly settings: HudSettings;
146
+ readonly theme: HudTheme;
147
+ private readonly ownsTheme;
148
+ private readonly unsubscribeTheme;
91
149
  private readonly monitor;
92
150
  private readonly minIntervalMs;
93
151
  private mode;
@@ -97,6 +155,7 @@ declare class PerformanceView {
97
155
  private readonly statValueEls;
98
156
  private readonly statCheckboxes;
99
157
  private dimCheckbox;
158
+ private readonly themeRadios;
100
159
  private hudGraphsEl;
101
160
  private hudGridEl;
102
161
  private readonly hudGraphs;
@@ -118,6 +177,8 @@ declare class PerformanceView {
118
177
  private build;
119
178
  private rebuildHud;
120
179
  private onSelectionChanged;
180
+ /** Repaint the palette and mark the radio matching what applies (pick, else consumer mode). */
181
+ private onThemeChanged;
121
182
  private buildCheckbox;
122
183
  private applyModeClass;
123
184
  private resizeCanvases;
@@ -127,6 +188,12 @@ declare class PerformanceView {
127
188
  type MountPerfHudOptions = {
128
189
  /** Start compact (the card) or full (the checkbox list). Default `compact`. */
129
190
  mode?: PerformanceViewMode;
191
+ /**
192
+ * `dark`, `light`, or `system` to follow the OS preference. Default
193
+ * `system`. Change it later with the handle's `setTheme`. A pick made in
194
+ * the panel's own theme row (kept in `settings`) sits on top of this.
195
+ */
196
+ theme?: ThemeMode;
130
197
  /** localStorage key for the selection and the dock. `null` disables persistence. */
131
198
  storageKey?: string | null;
132
199
  /** Bring your own; otherwise one is created from `storageKey`. */
@@ -146,11 +213,21 @@ type PerfHudHandle = {
146
213
  element: HTMLDivElement;
147
214
  view: PerformanceView;
148
215
  settings: HudSettings;
216
+ /**
217
+ * `mode` is the consumer layer, `override` the panel's pick, `effective`
218
+ * whichever applies, `resolved` the `dark` / `light` on screen. Subscribe
219
+ * for changes, including the OS preference moving under `system`.
220
+ */
221
+ theme: HudTheme;
149
222
  getMode: () => PerformanceViewMode;
150
223
  setMode: (mode: PerformanceViewMode) => void;
224
+ /** The consumer layer. The panel's pick, if any, is `settings.theme`. */
225
+ getTheme: () => ThemeMode;
226
+ /** Sets the consumer layer; a pick made in the panel still wins until `settings.setTheme(null)`. */
227
+ setTheme: (mode: ThemeMode) => void;
151
228
  dispose: () => void;
152
229
  };
153
230
  declare const mountPerfHud: (monitor: PerformanceMonitor, options?: MountPerfHudOptions) => PerfHudHandle;
154
231
  //#endregion
155
- export { ScreenEdge as _, PerformanceViewMode as a, HudDefaults as c, HudSettingsOptions as d, DefaultPlacement as f, Placement as g, DockPanelOptions as h, PerformanceView as i, HudSelection as l, DockHandle as m, PerfHudHandle as n, PerformanceViewOptions as o, DockAlign as p, mountPerfHud as r, DEFAULT_STORAGE_KEY as s, MountPerfHudOptions as t, HudSettings as u, dockPanel as v };
156
- //# sourceMappingURL=mount-perf-hud-Dw8KNQTt.d.ts.map
232
+ export { dockPanel as C, ScreenEdge as S, DefaultPlacement as _, PerformanceViewMode as a, DockPanelOptions as b, HudDefaults as c, HudSettingsOptions as d, HudTheme as f, isThemeMode as g, ThemeMode as h, PerformanceView as i, HudSelection as l, THEME_MODES as m, PerfHudHandle as n, PerformanceViewOptions as o, ResolvedTheme as p, mountPerfHud as r, DEFAULT_STORAGE_KEY as s, MountPerfHudOptions as t, HudSettings as u, DockAlign as v, Placement as x, DockHandle as y };
233
+ //# sourceMappingURL=mount-perf-hud-B3wBLrcu.d.ts.map