@fcannizzaro/streamdeck-react 0.1.11 → 0.1.13

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 (53) hide show
  1. package/README.md +11 -8
  2. package/dist/action.js +0 -1
  3. package/dist/adapter/index.d.ts +2 -0
  4. package/dist/adapter/physical-device.d.ts +2 -0
  5. package/dist/adapter/physical-device.js +153 -0
  6. package/dist/adapter/types.d.ts +127 -0
  7. package/dist/bundler-shared.d.ts +10 -0
  8. package/dist/bundler-shared.js +28 -1
  9. package/dist/devtools/bridge.d.ts +2 -2
  10. package/dist/devtools/bridge.js +7 -8
  11. package/dist/devtools/highlight.d.ts +1 -2
  12. package/dist/devtools/highlight.js +4 -3
  13. package/dist/devtools/types.d.ts +5 -5
  14. package/dist/font-inline.js +1 -1
  15. package/dist/google-font.d.ts +61 -0
  16. package/dist/google-font.js +124 -0
  17. package/dist/hooks/animation.d.ts +1 -1
  18. package/dist/hooks/animation.js +2 -2
  19. package/dist/hooks/events.js +1 -1
  20. package/dist/hooks/sdk.js +11 -11
  21. package/dist/hooks/utility.js +3 -2
  22. package/dist/index.d.ts +5 -1
  23. package/dist/index.js +3 -1
  24. package/dist/plugin.js +102 -124
  25. package/dist/reconciler/vnode.d.ts +0 -2
  26. package/dist/reconciler/vnode.js +0 -1
  27. package/dist/render/cache.d.ts +5 -17
  28. package/dist/render/cache.js +7 -29
  29. package/dist/render/image-cache.d.ts +8 -7
  30. package/dist/render/image-cache.js +33 -17
  31. package/dist/render/metrics.d.ts +9 -10
  32. package/dist/render/metrics.js +36 -39
  33. package/dist/render/pipeline.d.ts +4 -14
  34. package/dist/render/pipeline.js +47 -111
  35. package/dist/render/png.d.ts +0 -9
  36. package/dist/render/png.js +5 -8
  37. package/dist/render/render-pool.d.ts +0 -2
  38. package/dist/render/render-pool.js +1 -12
  39. package/dist/rollup.d.ts +1 -1
  40. package/dist/rollup.js +3 -1
  41. package/dist/roots/registry.d.ts +5 -9
  42. package/dist/roots/registry.js +30 -47
  43. package/dist/roots/root.d.ts +7 -34
  44. package/dist/roots/root.js +23 -90
  45. package/dist/roots/touchstrip-root.d.ts +6 -32
  46. package/dist/roots/touchstrip-root.js +61 -181
  47. package/dist/types.d.ts +38 -20
  48. package/dist/vite.d.ts +1 -1
  49. package/dist/vite.js +3 -1
  50. package/package.json +14 -8
  51. package/dist/node_modules/.bun/xxhash-wasm@1.1.0/node_modules/xxhash-wasm/esm/xxhash-wasm.js +0 -3157
  52. package/dist/roots/flush-coordinator.d.ts +0 -18
  53. package/dist/roots/flush-coordinator.js +0 -38
package/README.md CHANGED
@@ -63,11 +63,13 @@ export const counterAction = defineAction({
63
63
  Register it in your plugin entrypoint:
64
64
 
65
65
  ```ts
66
- import { createPlugin } from "@fcannizzaro/streamdeck-react";
66
+ import { createPlugin, googleFont } from "@fcannizzaro/streamdeck-react";
67
67
  import { counterAction } from "./actions/counter.tsx";
68
68
 
69
+ const inter = await googleFont("Inter");
70
+
69
71
  const plugin = createPlugin({
70
- fonts: [],
72
+ fonts: [inter],
71
73
  actions: [counterAction],
72
74
  });
73
75
 
@@ -83,12 +85,13 @@ await plugin.connect();
83
85
 
84
86
  ## Samples
85
87
 
86
- - `samples/counter/` - local state, settings hook, dial interaction
87
- - `samples/zustand/` - shared external store across multiple actions
88
- - `samples/jotai/` - shared atom state with a plugin-level wrapper
89
- - `samples/pokemon/` - richer plugin example with custom wrappers
90
- - `samples/snake/` - snake game on the Stream Deck+ touch strip using dial controls and touch tap
91
- - `samples/weather/` - weather forecast dials with animated detail panels and a shared Zustand store
88
+ - `samples/counter/` local state, persisted settings, dial interaction
89
+ - `samples/zustand/` shared state across keys via a module-scope Zustand store
90
+ - `samples/jotai/` shared atom state with a plugin-level Jotai Provider wrapper
91
+ - `samples/pokemon/` data fetching with TanStack Query and remote image rendering
92
+ - `samples/animation/` spring bounce, fade-slide, and spring dial animations
93
+ - `samples/snake/` snake game on the Stream Deck+ TouchStrip using dial controls and touch tap
94
+ - `samples/weather/` — weather forecast dials with animated detail panels and a shared Zustand store
92
95
 
93
96
  ## DevTools
94
97
 
package/dist/action.js CHANGED
@@ -5,7 +5,6 @@ function defineAction(config) {
5
5
  key: config.key,
6
6
  dial: config.dial,
7
7
  touchStrip: config.touchStrip,
8
- touchStripFPS: config.touchStripFPS,
9
8
  dialLayout: config.dialLayout,
10
9
  wrapper: config.wrapper,
11
10
  defaultSettings: config.defaultSettings ?? {}
@@ -0,0 +1,2 @@
1
+ export type { StreamDeckAdapter, AdapterActionHandle, AdapterActionCallbacks, AdapterWillAppearEvent, AdapterActionDevice, AdapterController, AdapterCoordinates, AdapterSize, AdapterTriggerDescription, AdapterKeyDownPayload, AdapterKeyUpPayload, AdapterDialRotatePayload, AdapterDialPressPayload, AdapterTouchTapPayload, } from './types';
2
+ export { physicalDevice } from './physical-device';
@@ -0,0 +1,2 @@
1
+ import { StreamDeckAdapter } from './types';
2
+ export declare function physicalDevice(): StreamDeckAdapter;
@@ -0,0 +1,153 @@
1
+ import streamDeck, { SingletonAction } from "@elgato/streamdeck";
2
+ //#region src/adapter/physical-device.ts
3
+ function wrapSdkAction(action) {
4
+ return {
5
+ id: action.id,
6
+ device: {
7
+ id: action.device.id,
8
+ type: action.device.type,
9
+ size: action.device.size,
10
+ name: action.device.name
11
+ },
12
+ controllerType: action.controllerType,
13
+ coordinates: "coordinates" in action ? action.coordinates : void 0,
14
+ async setImage(dataUri) {
15
+ if ("setImage" in action) await action.setImage(dataUri);
16
+ },
17
+ async setTitle(title) {
18
+ if ("setTitle" in action) await action.setTitle(title);
19
+ },
20
+ async showOk() {
21
+ if ("showOk" in action) await action.showOk();
22
+ },
23
+ async showAlert() {
24
+ await action.showAlert();
25
+ },
26
+ async setSettings(settings) {
27
+ await action.setSettings(settings);
28
+ },
29
+ async setFeedback(payload) {
30
+ if ("setFeedback" in action) await action.setFeedback(payload);
31
+ },
32
+ async setFeedbackLayout(layout) {
33
+ if ("setFeedbackLayout" in action) await action.setFeedbackLayout(layout);
34
+ },
35
+ async setTriggerDescription(hints) {
36
+ if ("setTriggerDescription" in action) await action.setTriggerDescription({
37
+ rotate: hints.rotate,
38
+ push: hints.push,
39
+ touch: hints.touch,
40
+ longTouch: hints.longTouch
41
+ });
42
+ }
43
+ };
44
+ }
45
+ function physicalDevice() {
46
+ return {
47
+ pluginUUID: streamDeck.info.plugin.uuid,
48
+ async connect() {
49
+ await streamDeck.connect();
50
+ },
51
+ async getGlobalSettings() {
52
+ return streamDeck.settings.getGlobalSettings();
53
+ },
54
+ async setGlobalSettings(settings) {
55
+ await streamDeck.settings.setGlobalSettings(settings);
56
+ },
57
+ onGlobalSettingsChanged(callback) {
58
+ streamDeck.settings.onDidReceiveGlobalSettings((ev) => {
59
+ callback(ev.settings);
60
+ });
61
+ },
62
+ registerAction(uuid, callbacks) {
63
+ const singletonAction = new class extends SingletonAction {
64
+ manifestId = uuid;
65
+ onWillAppear(ev) {
66
+ callbacks.onWillAppear({
67
+ action: wrapSdkAction(ev.action),
68
+ payload: {
69
+ settings: ev.payload.settings,
70
+ controller: ev.payload.controller,
71
+ isInMultiAction: ev.payload.isInMultiAction
72
+ }
73
+ });
74
+ }
75
+ onWillDisappear(ev) {
76
+ callbacks.onWillDisappear(ev.action.id);
77
+ }
78
+ onKeyDown(ev) {
79
+ callbacks.onKeyDown(ev.action.id, {
80
+ settings: ev.payload.settings,
81
+ isInMultiAction: ev.payload.isInMultiAction,
82
+ state: ev.payload.state,
83
+ userDesiredState: "userDesiredState" in ev.payload ? ev.payload.userDesiredState : void 0
84
+ });
85
+ }
86
+ onKeyUp(ev) {
87
+ callbacks.onKeyUp(ev.action.id, {
88
+ settings: ev.payload.settings,
89
+ isInMultiAction: ev.payload.isInMultiAction,
90
+ state: ev.payload.state,
91
+ userDesiredState: "userDesiredState" in ev.payload ? ev.payload.userDesiredState : void 0
92
+ });
93
+ }
94
+ onDialRotate(ev) {
95
+ callbacks.onDialRotate(ev.action.id, {
96
+ ticks: ev.payload.ticks,
97
+ pressed: ev.payload.pressed,
98
+ settings: ev.payload.settings
99
+ });
100
+ }
101
+ onDialDown(ev) {
102
+ callbacks.onDialDown(ev.action.id, {
103
+ settings: ev.payload.settings,
104
+ controller: "Encoder"
105
+ });
106
+ }
107
+ onDialUp(ev) {
108
+ callbacks.onDialUp(ev.action.id, {
109
+ settings: ev.payload.settings,
110
+ controller: "Encoder"
111
+ });
112
+ }
113
+ onTouchTap(ev) {
114
+ callbacks.onTouchTap(ev.action.id, {
115
+ tapPos: ev.payload.tapPos,
116
+ hold: ev.payload.hold,
117
+ settings: ev.payload.settings
118
+ });
119
+ }
120
+ onDidReceiveSettings(ev) {
121
+ callbacks.onDidReceiveSettings(ev.action.id, ev.payload.settings);
122
+ }
123
+ onSendToPlugin(ev) {
124
+ callbacks.onSendToPlugin(ev.action.id, ev.payload);
125
+ }
126
+ onPropertyInspectorDidAppear(ev) {
127
+ callbacks.onPropertyInspectorDidAppear(ev.action.id);
128
+ }
129
+ onPropertyInspectorDidDisappear(ev) {
130
+ callbacks.onPropertyInspectorDidDisappear(ev.action.id);
131
+ }
132
+ onTitleParametersDidChange(ev) {
133
+ callbacks.onTitleParametersDidChange(ev.action.id, {
134
+ title: ev.payload.title,
135
+ settings: ev.payload.settings
136
+ });
137
+ }
138
+ }();
139
+ streamDeck.actions.registerAction(singletonAction);
140
+ },
141
+ async openUrl(url) {
142
+ await streamDeck.system.openUrl(url);
143
+ },
144
+ async switchToProfile(deviceId, profile) {
145
+ await streamDeck.profiles.switchToProfile(deviceId, profile);
146
+ },
147
+ async sendToPropertyInspector(payload) {
148
+ await streamDeck.ui.sendToPropertyInspector(payload);
149
+ }
150
+ };
151
+ }
152
+ //#endregion
153
+ export { physicalDevice };
@@ -0,0 +1,127 @@
1
+ import { JsonObject, JsonValue } from '@elgato/utils';
2
+ /** Controller surface type. Matches SDK Controller values exactly. */
3
+ export type AdapterController = "Keypad" | "Encoder";
4
+ /** Grid coordinates for a key or encoder on a device. */
5
+ export interface AdapterCoordinates {
6
+ readonly column: number;
7
+ readonly row: number;
8
+ }
9
+ /** Device grid size (number of key columns and rows). */
10
+ export interface AdapterSize {
11
+ readonly columns: number;
12
+ readonly rows: number;
13
+ }
14
+ /** Hint labels for dial/encoder trigger zones shown on the Stream Deck LCD. */
15
+ export interface AdapterTriggerDescription {
16
+ rotate?: string;
17
+ push?: string;
18
+ touch?: string;
19
+ longTouch?: string;
20
+ }
21
+ export interface AdapterActionHandle {
22
+ readonly id: string;
23
+ readonly device: AdapterActionDevice;
24
+ readonly controllerType: AdapterController;
25
+ readonly coordinates?: AdapterCoordinates;
26
+ /** Push a rendered data URI to the key display. No-op on encoder surfaces. */
27
+ setImage(dataUri: string): Promise<void>;
28
+ /** Set the key title overlay. No-op on encoder surfaces. */
29
+ setTitle(title: string): Promise<void>;
30
+ /** Flash the OK checkmark on the key. No-op on encoder surfaces. */
31
+ showOk(): Promise<void>;
32
+ /** Flash the alert triangle on the action. */
33
+ showAlert(): Promise<void>;
34
+ /** Persist action settings to the Stream Deck. */
35
+ setSettings(settings: JsonObject): Promise<void>;
36
+ /** Push dial/touchstrip feedback payload. No-op on key surfaces. */
37
+ setFeedback(payload: Record<string, unknown>): Promise<void>;
38
+ /** Set the encoder feedback layout (JSON or layout ID). No-op on key surfaces. */
39
+ setFeedbackLayout(layout: string | Record<string, unknown>): Promise<void>;
40
+ /** Set dial hint text (rotate, push, touch labels). No-op on key surfaces. */
41
+ setTriggerDescription(hints: AdapterTriggerDescription): Promise<void>;
42
+ }
43
+ /** Device info as seen from an action event. */
44
+ export interface AdapterActionDevice {
45
+ readonly id: string;
46
+ /**
47
+ * Numeric device type matching Elgato DeviceType enum values.
48
+ * Using `number` instead of the SDK's enum avoids coupling to specific
49
+ * device models while remaining compatible with the KEY_SIZES lookup
50
+ * table in registry.ts.
51
+ */
52
+ readonly type: number;
53
+ readonly size: AdapterSize;
54
+ readonly name: string;
55
+ }
56
+ /** Event payload provided to onWillAppear callbacks. */
57
+ export interface AdapterWillAppearEvent {
58
+ action: AdapterActionHandle;
59
+ payload: {
60
+ settings: JsonObject;
61
+ controller: AdapterController;
62
+ isInMultiAction: boolean;
63
+ };
64
+ }
65
+ export interface AdapterKeyDownPayload {
66
+ settings: JsonObject;
67
+ isInMultiAction: boolean;
68
+ state?: number;
69
+ userDesiredState?: number;
70
+ }
71
+ export interface AdapterKeyUpPayload {
72
+ settings: JsonObject;
73
+ isInMultiAction: boolean;
74
+ state?: number;
75
+ userDesiredState?: number;
76
+ }
77
+ export interface AdapterDialRotatePayload {
78
+ ticks: number;
79
+ pressed: boolean;
80
+ settings: JsonObject;
81
+ }
82
+ export interface AdapterDialPressPayload {
83
+ settings: JsonObject;
84
+ controller: "Encoder";
85
+ }
86
+ export interface AdapterTouchTapPayload {
87
+ tapPos: [x: number, y: number];
88
+ hold: boolean;
89
+ settings: JsonObject;
90
+ }
91
+ export interface AdapterActionCallbacks {
92
+ onWillAppear(ev: AdapterWillAppearEvent): void;
93
+ onWillDisappear(actionId: string): void;
94
+ onKeyDown(actionId: string, payload: AdapterKeyDownPayload): void;
95
+ onKeyUp(actionId: string, payload: AdapterKeyUpPayload): void;
96
+ onDialRotate(actionId: string, payload: AdapterDialRotatePayload): void;
97
+ onDialDown(actionId: string, payload: AdapterDialPressPayload): void;
98
+ onDialUp(actionId: string, payload: AdapterDialPressPayload): void;
99
+ onTouchTap(actionId: string, payload: AdapterTouchTapPayload): void;
100
+ onDidReceiveSettings(actionId: string, settings: JsonObject): void;
101
+ onSendToPlugin(actionId: string, payload: JsonValue): void;
102
+ onPropertyInspectorDidAppear(actionId: string): void;
103
+ onPropertyInspectorDidDisappear(actionId: string): void;
104
+ onTitleParametersDidChange(actionId: string, payload: {
105
+ title: string;
106
+ settings: JsonObject;
107
+ }): void;
108
+ }
109
+ export interface StreamDeckAdapter {
110
+ /** Plugin UUID, used for devtools identification. */
111
+ readonly pluginUUID: string;
112
+ /** Initialize the adapter and connect to the backend. */
113
+ connect(): Promise<void>;
114
+ /** Retrieve plugin-wide global settings. */
115
+ getGlobalSettings<T extends JsonObject = JsonObject>(): Promise<T>;
116
+ /** Persist plugin-wide global settings. */
117
+ setGlobalSettings<T extends JsonObject = JsonObject>(settings: T): Promise<void>;
118
+ /** Subscribe to external global settings changes (e.g. from Property Inspector). */
119
+ onGlobalSettingsChanged(callback: (settings: JsonObject) => void): void;
120
+ registerAction(uuid: string, callbacks: AdapterActionCallbacks): void;
121
+ /** Open a URL in the user's default browser. */
122
+ openUrl(url: string): Promise<void>;
123
+ /** Switch the active Stream Deck profile. */
124
+ switchToProfile(deviceId: string, profile: string): Promise<void>;
125
+ /** Send a payload to the Property Inspector. */
126
+ sendToPropertyInspector(payload: JsonValue): Promise<void>;
127
+ }
@@ -4,8 +4,16 @@ export interface StreamDeckTarget {
4
4
  platform: StreamDeckPlatform;
5
5
  arch: StreamDeckArch;
6
6
  }
7
+ /** Takumi renderer backend selection. Mirrors the runtime `TakumiBackend` type for build-time configuration. */
8
+ export type TakumiBackend = "native-binding" | "wasm";
7
9
  export interface StreamDeckTargetOptions {
8
10
  targets?: StreamDeckTarget[];
11
+ /**
12
+ * Takumi renderer backend. When `"wasm"`, native `.node` binding
13
+ * copying is skipped entirely during the build.
14
+ * @default "native-binding"
15
+ */
16
+ takumi?: TakumiBackend;
9
17
  }
10
18
  export interface ResolvedTarget extends StreamDeckTarget {
11
19
  pkg: string;
@@ -17,6 +25,8 @@ export declare function isArch(value: string): value is StreamDeckArch;
17
25
  export declare function isDevelopmentMode(watchMode: boolean | undefined): boolean;
18
26
  export declare const NOOP_DEVTOOLS_ID = "\0streamdeck-react:noop-devtools";
19
27
  export declare const NOOP_DEVTOOLS_CODE = "export function startDevtoolsServer() {}";
28
+ export declare const TAKUMI_NATIVE_LOADER_ID = "\0streamdeck-react:takumi-native";
29
+ export declare const TAKUMI_NATIVE_LOADER_CODE: string;
20
30
  /**
21
31
  * Returns true when devtools should be stripped from the bundle.
22
32
  * Only strips in explicit production mode (NODE_ENV=production) to avoid
@@ -40,6 +40,32 @@ function isDevelopmentMode(watchMode) {
40
40
  var NOOP_DEVTOOLS_ID = "\0streamdeck-react:noop-devtools";
41
41
  var NOOP_DEVTOOLS_CODE = "export function startDevtoolsServer() {}";
42
42
  var DEVTOOLS_IMPORT_SOURCE = "./devtools/index.js";
43
+ var TAKUMI_NATIVE_LOADER_ID = "\0streamdeck-react:takumi-native";
44
+ var TAKUMI_NATIVE_LOADER_CODE = `
45
+ import { createRequire } from "node:module";
46
+ const require = createRequire(import.meta.url);
47
+ let binding = null;
48
+ if (process.platform === "darwin") {
49
+ if (process.arch === "arm64") {
50
+ try { binding = require("./core.darwin-arm64.node"); } catch {}
51
+ } else if (process.arch === "x64") {
52
+ try { binding = require("./core.darwin-x64.node"); } catch {}
53
+ }
54
+ } else if (process.platform === "win32") {
55
+ if (process.arch === "arm64") {
56
+ try { binding = require("./core.win32-arm64-msvc.node"); } catch {}
57
+ } else if (process.arch === "x64") {
58
+ try { binding = require("./core.win32-x64-msvc.node"); } catch {}
59
+ }
60
+ }
61
+ if (!binding) {
62
+ throw new Error(
63
+ "[@fcannizzaro/streamdeck-react] Failed to load @takumi-rs/core native binding for " +
64
+ process.platform + "-" + process.arch
65
+ );
66
+ }
67
+ export const { Renderer, OutputFormat, DitheringAlgorithm, AnimationOutputFormat, extractResourceUrls } = binding;
68
+ `.trim();
43
69
  /**
44
70
  * Returns true when devtools should be stripped from the bundle.
45
71
  * Only strips in explicit production mode (NODE_ENV=production) to avoid
@@ -92,6 +118,7 @@ function expandTargets(targets) {
92
118
  * In production: missing bindings throw an error (the plugin won't work).
93
119
  */
94
120
  function copyNativeBindings(outDir, isDevelopment, options, warn) {
121
+ if (options.takumi === "wasm") return;
95
122
  try {
96
123
  const requestedTargets = normalizeTargetRequests(options, isDevelopment);
97
124
  if (requestedTargets.length === 0) {
@@ -129,4 +156,4 @@ function copyNativeBindings(outDir, isDevelopment, options, warn) {
129
156
  }
130
157
  }
131
158
  //#endregion
132
- export { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools };
159
+ export { NOOP_DEVTOOLS_CODE, NOOP_DEVTOOLS_ID, TAKUMI_NATIVE_LOADER_CODE, TAKUMI_NATIVE_LOADER_ID, copyNativeBindings, isDevelopmentMode, isLibraryDevtoolsImport, shouldStripDevtools };
@@ -63,7 +63,7 @@ export declare class DevtoolsBridge implements RegistryObserver {
63
63
  private static readonly TB_PREFIX;
64
64
  private handleHighlight;
65
65
  /**
66
- * Restore a highlighted action or touchstrip to its normal state.
66
+ * Restore a highlighted action or TouchStrip to its normal state.
67
67
  * Un-suppresses hardware pushes and restores the original image(s).
68
68
  */
69
69
  private restoreHighlight;
@@ -73,7 +73,7 @@ export declare class DevtoolsBridge implements RegistryObserver {
73
73
  private broadcastHighlightRender;
74
74
  /**
75
75
  * Clear highlight URIs for the given actionId.
76
- * For touchstrip IDs, clears all per-segment keys (touchstrip:*:seg:N).
76
+ * For TouchStrip IDs, clears all per-segment keys (touchStrip:*:seg:N).
77
77
  * For regular actions, clears the single actionId key.
78
78
  */
79
79
  private broadcastHighlightClear;
@@ -141,7 +141,7 @@ var DevtoolsBridge = class DevtoolsBridge {
141
141
  columns: /* @__PURE__ */ new Map()
142
142
  });
143
143
  this.eventBusOwners.set(root.eventBus, {
144
- actionId: `touchstrip:${deviceId}`,
144
+ actionId: `touchStrip:${deviceId}`,
145
145
  uuid: ""
146
146
  });
147
147
  }
@@ -339,8 +339,7 @@ var DevtoolsBridge = class DevtoolsBridge {
339
339
  /** Convert internal RenderProfile to wire-protocol ProfileData. */
340
340
  toProfileData(profile) {
341
341
  return {
342
- vnodeToElementMs: profile.vnodeToElementMs,
343
- fromJsxMs: profile.fromJsxMs,
342
+ vnodeConversionMs: profile.vnodeConversionMs,
344
343
  takumiRenderMs: profile.takumiRenderMs,
345
344
  hashMs: profile.hashMs,
346
345
  base64Ms: profile.base64Ms,
@@ -363,7 +362,7 @@ var DevtoolsBridge = class DevtoolsBridge {
363
362
  });
364
363
  }
365
364
  const msg = {
366
- type: "render:touchstrip",
365
+ type: "render:touchStrip",
367
366
  ts: Date.now(),
368
367
  deviceId,
369
368
  canvas: {
@@ -377,7 +376,7 @@ var DevtoolsBridge = class DevtoolsBridge {
377
376
  };
378
377
  this.server.broadcast(msg);
379
378
  }
380
- static TB_PREFIX = "touchstrip:";
379
+ static TB_PREFIX = "touchStrip:";
381
380
  async handleHighlight(actionId, nodeId) {
382
381
  try {
383
382
  const prevId = this.highlightedActionId;
@@ -419,7 +418,7 @@ var DevtoolsBridge = class DevtoolsBridge {
419
418
  } catch {}
420
419
  }
421
420
  /**
422
- * Restore a highlighted action or touchstrip to its normal state.
421
+ * Restore a highlighted action or TouchStrip to its normal state.
423
422
  * Un-suppresses hardware pushes and restores the original image(s).
424
423
  */
425
424
  async restoreHighlight(id) {
@@ -454,7 +453,7 @@ var DevtoolsBridge = class DevtoolsBridge {
454
453
  const segmentWidth = 200;
455
454
  const segmentHeight = 100;
456
455
  const fullWidth = (Math.max(...columns) + 1) * segmentWidth;
457
- const result = await renderTouchStripWithHighlight(tb.root.vcontainer, fullWidth, segmentHeight, columns, segmentWidth, this.renderConfig.touchstripImageFormat, this.renderConfig, nodeId);
456
+ const result = await renderTouchStripWithHighlight(tb.root.vcontainer, fullWidth, segmentHeight, columns, segmentWidth, this.renderConfig, nodeId);
458
457
  if (result && this.highlightedActionId === actionId && this.highlightedNodeId === nodeId) {
459
458
  await tb.root.pushSegmentImages(result.segmentUris);
460
459
  for (const [col, uri] of result.segmentUris) this.broadcastHighlightRender(`${actionId}:seg:${col}`, uri);
@@ -473,7 +472,7 @@ var DevtoolsBridge = class DevtoolsBridge {
473
472
  }
474
473
  /**
475
474
  * Clear highlight URIs for the given actionId.
476
- * For touchstrip IDs, clears all per-segment keys (touchstrip:*:seg:N).
475
+ * For TouchStrip IDs, clears all per-segment keys (touchStrip:*:seg:N).
477
476
  * For regular actions, clears the single actionId key.
478
477
  */
479
478
  broadcastHighlightClear(id) {
@@ -1,4 +1,3 @@
1
- import { OutputFormat } from '@takumi-rs/core';
2
1
  import { VContainer } from '../reconciler/vnode';
3
2
  import { RenderConfig } from '../render/pipeline';
4
3
  export declare function renderWithHighlight(container: VContainer, width: number, height: number, config: RenderConfig, targetNid: number): Promise<string | null>;
@@ -6,4 +5,4 @@ export interface TouchStripHighlightResult {
6
5
  /** Per-column data URIs for ALL segments. */
7
6
  segmentUris: Map<number, string>;
8
7
  }
9
- export declare function renderTouchStripWithHighlight(container: VContainer, fullWidth: number, segmentHeight: number, columns: number[], segmentWidth: number, format: OutputFormat, config: RenderConfig, targetNid: number): Promise<TouchStripHighlightResult | null>;
8
+ export declare function renderTouchStripWithHighlight(container: VContainer, fullWidth: number, segmentHeight: number, columns: number[], segmentWidth: number, config: RenderConfig, targetNid: number): Promise<TouchStripHighlightResult | null>;
@@ -18,7 +18,8 @@ async function renderWithHighlight(container, width, height, config, targetNid)
18
18
  devicePixelRatio: config.devicePixelRatio
19
19
  }), config.imageFormat);
20
20
  }
21
- async function renderTouchStripWithHighlight(container, fullWidth, segmentHeight, columns, segmentWidth, format, config, targetNid) {
21
+ var TOUCHSTRIP_HIGHLIGHT_FORMAT = "png";
22
+ async function renderTouchStripWithHighlight(container, fullWidth, segmentHeight, columns, segmentWidth, config, targetNid) {
22
23
  if (container.children.length === 0) return null;
23
24
  const effectiveNid = resolveTargetNid(container, targetNid);
24
25
  const rootNode = buildTakumiRoot(container);
@@ -50,10 +51,10 @@ async function renderTouchStripWithHighlight(container, fullWidth, segmentHeight
50
51
  const segBuffer = await config.renderer.render(clipNode, {
51
52
  width: segmentWidth,
52
53
  height: segmentHeight,
53
- format,
54
+ format: TOUCHSTRIP_HIGHLIGHT_FORMAT,
54
55
  devicePixelRatio: config.devicePixelRatio
55
56
  });
56
- segmentUris.set(column, bufferToDataUri(segBuffer, format));
57
+ segmentUris.set(column, bufferToDataUri(segBuffer, TOUCHSTRIP_HIGHLIGHT_FORMAT));
57
58
  });
58
59
  await Promise.all(segmentPromises);
59
60
  return { segmentUris };
@@ -150,7 +150,7 @@ export interface RenderMessage extends BaseMessage {
150
150
  profile?: ProfileData;
151
151
  }
152
152
  export interface TouchStripRenderMessage extends BaseMessage {
153
- type: "render:touchstrip";
153
+ type: "render:touchStrip";
154
154
  deviceId: string;
155
155
  canvas: {
156
156
  width: number;
@@ -179,7 +179,7 @@ export interface LifecycleMessage extends BaseMessage {
179
179
  event: "appear" | "disappear";
180
180
  actionId: string;
181
181
  actionUuid: string;
182
- surface: "key" | "dial" | "touch" | "touchstrip";
182
+ surface: "key" | "dial" | "touch" | "touchStrip";
183
183
  device: {
184
184
  id: string;
185
185
  type: number;
@@ -203,8 +203,8 @@ export interface HighlightRenderMessage extends BaseMessage {
203
203
  }
204
204
  /** Per-render pipeline timing data, embedded in RenderMessage. */
205
205
  export interface ProfileData {
206
- vnodeToElementMs: number;
207
- fromJsxMs: number;
206
+ /** Time to convert VNode tree to Takumi node tree (ms). */
207
+ vnodeConversionMs: number;
208
208
  takumiRenderMs: number;
209
209
  hashMs: number;
210
210
  base64Ms: number;
@@ -234,7 +234,7 @@ export interface MetricsData {
234
234
  /** Image cache memory usage in bytes. */
235
235
  imageCacheBytes: number;
236
236
  /** TouchStrip cache memory usage in bytes. */
237
- touchstripCacheBytes: number;
237
+ touchStripCacheBytes: number;
238
238
  }
239
239
  export interface MetricsMessage extends BaseMessage {
240
240
  type: "metrics";
@@ -1,7 +1,7 @@
1
+ import { readFile } from "node:fs/promises";
1
2
  import { dirname, resolve } from "node:path";
2
3
  import { createRequire } from "node:module";
3
4
  import { realpathSync } from "node:fs";
4
- import { readFile } from "node:fs/promises";
5
5
  //#region src/font-inline.ts
6
6
  var FONT_RE = /\.(ttf|otf|woff2?)$/;
7
7
  /**
@@ -0,0 +1,61 @@
1
+ import { FontConfig } from './types';
2
+ type FontWeight = FontConfig["weight"];
3
+ type FontStyle = FontConfig["style"];
4
+ export interface GoogleFontVariant {
5
+ weight?: FontWeight;
6
+ style?: FontStyle;
7
+ }
8
+ /**
9
+ * Fetch a Google Font as a ready-to-use `FontConfig`.
10
+ *
11
+ * Downloads TTF font data directly from Google Fonts — no npm package
12
+ * needed. The returned config can be passed straight into
13
+ * `createPlugin({ fonts: [...] })`.
14
+ *
15
+ * Font files are cached to `.google-fonts/` in the current working
16
+ * directory. Subsequent calls read from disk without network access.
17
+ *
18
+ * @param family - The Google Font family name (e.g. `"Inter"`, `"Roboto"`).
19
+ * @returns A `FontConfig` with weight 400 and normal style.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const inter = await googleFont("Inter");
24
+ * createPlugin({ fonts: [inter], actions: [...] });
25
+ * ```
26
+ */
27
+ export declare function googleFont(family: string): Promise<FontConfig>;
28
+ /**
29
+ * Fetch a specific weight/style variant of a Google Font.
30
+ *
31
+ * @param family - The Google Font family name.
32
+ * @param variant - Weight and/or style to fetch.
33
+ * @returns A single `FontConfig`.
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * const bold = await googleFont("Inter", { weight: 700 });
38
+ * ```
39
+ */
40
+ export declare function googleFont(family: string, variant: GoogleFontVariant): Promise<FontConfig>;
41
+ /**
42
+ * Fetch multiple weight/style variants of a Google Font in one call.
43
+ *
44
+ * All variants are fetched in parallel for maximum throughput.
45
+ *
46
+ * @param family - The Google Font family name.
47
+ * @param variants - Array of weight/style combinations to fetch.
48
+ * @returns An array of `FontConfig` objects, one per variant.
49
+ *
50
+ * @example
51
+ * ```ts
52
+ * const fonts = await googleFont("Inter", [
53
+ * { weight: 400 },
54
+ * { weight: 700 },
55
+ * { weight: 700, style: "italic" },
56
+ * ]);
57
+ * createPlugin({ fonts, actions: [...] });
58
+ * ```
59
+ */
60
+ export declare function googleFont(family: string, variants: GoogleFontVariant[]): Promise<FontConfig[]>;
61
+ export {};