@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
@@ -0,0 +1,124 @@
1
+ import { access, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ //#region src/google-font.ts
4
+ var TTF_USER_AGENT = "Mozilla/4.0";
5
+ var GOOGLE_FONTS_CSS2_BASE = "https://fonts.googleapis.com/css2";
6
+ var CACHE_DIR = ".google-fonts";
7
+ function sanitizeName(family) {
8
+ return family.toLowerCase().replace(/\s+/g, "-");
9
+ }
10
+ function cacheFilePath(family, weight, style) {
11
+ return join(process.cwd(), CACHE_DIR, `${sanitizeName(family)}-${weight}-${style}.ttf`);
12
+ }
13
+ async function fileExists(path) {
14
+ try {
15
+ await access(path);
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+ async function ensureCacheDir() {
22
+ await mkdir(join(process.cwd(), CACHE_DIR), { recursive: true });
23
+ }
24
+ async function readCached(family, weight, style) {
25
+ const path = cacheFilePath(family, weight, style);
26
+ if (!await fileExists(path)) return null;
27
+ return {
28
+ name: family,
29
+ data: (await readFile(path)).buffer,
30
+ weight,
31
+ style
32
+ };
33
+ }
34
+ async function writeCache(family, weight, style, data) {
35
+ await ensureCacheDir();
36
+ await writeFile(cacheFilePath(family, weight, style), Buffer.from(data));
37
+ }
38
+ function buildCss2Url(family, variants) {
39
+ const hasItalic = variants.some((v) => v.style === "italic");
40
+ const encodedFamily = encodeURIComponent(family);
41
+ if (hasItalic) {
42
+ const specs = variants.map((v) => {
43
+ return `${v.style === "italic" ? 1 : 0},${v.weight ?? 400}`;
44
+ });
45
+ specs.sort();
46
+ return `${GOOGLE_FONTS_CSS2_BASE}?family=${encodedFamily}:ital,wght@${specs.join(";")}`;
47
+ }
48
+ const weights = variants.map((v) => v.weight ?? 400);
49
+ weights.sort((a, b) => a - b);
50
+ return `${GOOGLE_FONTS_CSS2_BASE}?family=${encodedFamily}:wght@${weights.join(";")}`;
51
+ }
52
+ function parseFontFaces(css) {
53
+ const results = [];
54
+ const seen = /* @__PURE__ */ new Set();
55
+ const blockRegex = /@font-face\s*\{([^}]+)\}/g;
56
+ let match;
57
+ while ((match = blockRegex.exec(css)) !== null) {
58
+ const block = match[1];
59
+ const urlMatch = block.match(/url\(([^)]+)\)/);
60
+ const weightMatch = block.match(/font-weight:\s*(\d+)/);
61
+ const styleMatch = block.match(/font-style:\s*(normal|italic)/);
62
+ if (!urlMatch?.[1]) continue;
63
+ const weight = Number(weightMatch?.[1] ?? 400);
64
+ const style = styleMatch?.[1] ?? "normal";
65
+ const key = `${weight}:${style}`;
66
+ if (seen.has(key)) continue;
67
+ seen.add(key);
68
+ results.push({
69
+ url: urlMatch[1],
70
+ weight,
71
+ style
72
+ });
73
+ }
74
+ return results;
75
+ }
76
+ async function fetchGoogleFonts(family, variants) {
77
+ const results = [];
78
+ const uncached = [];
79
+ for (const v of variants) {
80
+ const weight = v.weight ?? 400;
81
+ const style = v.style ?? "normal";
82
+ const cached = await readCached(family, weight, style);
83
+ if (cached) results.push(cached);
84
+ else uncached.push({
85
+ weight,
86
+ style
87
+ });
88
+ }
89
+ if (uncached.length === 0) return results;
90
+ const url = buildCss2Url(family, uncached);
91
+ const cssResponse = await fetch(url, { headers: { "User-Agent": TTF_USER_AGENT } });
92
+ if (!cssResponse.ok) throw new Error(`Google Fonts: failed to fetch CSS for "${family}" (HTTP ${cssResponse.status})`);
93
+ const faces = parseFontFaces(await cssResponse.text());
94
+ if (faces.length === 0) throw new Error(`Google Fonts: no font faces found in CSS response for "${family}". Verify the family name is correct at https://fonts.google.com`);
95
+ const downloaded = await Promise.all(faces.map(async (face) => {
96
+ const fontResponse = await fetch(face.url);
97
+ if (!fontResponse.ok) throw new Error(`Google Fonts: failed to download font file for "${family}" weight ${face.weight} (HTTP ${fontResponse.status})`);
98
+ const data = await fontResponse.arrayBuffer();
99
+ writeCache(family, face.weight, face.style, data).catch(() => {});
100
+ return {
101
+ name: family,
102
+ data,
103
+ weight: face.weight,
104
+ style: face.style
105
+ };
106
+ }));
107
+ return [...results, ...downloaded];
108
+ }
109
+ async function googleFont(family, variantOrVariants) {
110
+ if (variantOrVariants === void 0) return (await fetchGoogleFonts(family, [{
111
+ weight: 400,
112
+ style: "normal"
113
+ }]))[0];
114
+ if (!Array.isArray(variantOrVariants)) return (await fetchGoogleFonts(family, [{
115
+ weight: variantOrVariants.weight ?? 400,
116
+ style: variantOrVariants.style ?? "normal"
117
+ }]))[0];
118
+ return fetchGoogleFonts(family, variantOrVariants.map((v) => ({
119
+ weight: v.weight ?? 400,
120
+ style: v.style ?? "normal"
121
+ })));
122
+ }
123
+ //#endregion
124
+ export { googleFont };
@@ -35,7 +35,7 @@ export interface TweenConfig {
35
35
  duration: number;
36
36
  /** Easing function name or custom (t: number) => number. @default "easeOut" */
37
37
  easing: EasingName | EasingFn;
38
- /** Target FPS for the animation tick loop. @default 60 */
38
+ /** Target FPS for the animation tick loop. @default 30 */
39
39
  fps: number;
40
40
  }
41
41
  export interface TweenResult<T extends AnimationTarget> {
@@ -12,7 +12,7 @@ var SPRING_DEFAULTS = {
12
12
  var TWEEN_DEFAULTS = {
13
13
  duration: 300,
14
14
  easing: "easeOut",
15
- fps: 60
15
+ fps: 30
16
16
  };
17
17
  /** Max dt cap to prevent spring explosion after long pauses.
18
18
  * If the process is suspended (debugger, GC pause), dt could be
@@ -196,7 +196,7 @@ function useSpring(target, config) {
196
196
  config?.displacementThreshold,
197
197
  config?.clamp
198
198
  ]);
199
- const fps = config?.fps ?? 60;
199
+ const fps = config?.fps ?? 30;
200
200
  const isObject = typeof target === "object" && target !== null;
201
201
  const stateRef = useRef(null);
202
202
  if (stateRef.current === null) stateRef.current = initializeSpringState(target);
@@ -38,7 +38,7 @@ function useTouchTap(callback) {
38
38
  function useDialHint(hints) {
39
39
  const { action } = useContext(StreamDeckContext);
40
40
  useEffect(() => {
41
- if ("setTriggerDescription" in action) action.setTriggerDescription({
41
+ action.setTriggerDescription({
42
42
  rotate: hints.rotate,
43
43
  push: hints.press,
44
44
  touch: hints.touch,
package/dist/hooks/sdk.js CHANGED
@@ -3,23 +3,23 @@ import { useCallbackRef } from "./internal/useCallbackRef.js";
3
3
  import { useCallback, useContext, useEffect } from "react";
4
4
  //#region src/hooks/sdk.ts
5
5
  function useOpenUrl() {
6
- const { sdk } = useContext(StreamDeckContext);
6
+ const { adapter } = useContext(StreamDeckContext);
7
7
  return useCallback(async (url) => {
8
- await sdk.system.openUrl(url);
9
- }, [sdk]);
8
+ await adapter.openUrl(url);
9
+ }, [adapter]);
10
10
  }
11
11
  function useSwitchProfile() {
12
- const { sdk, action } = useContext(StreamDeckContext);
12
+ const { adapter, action } = useContext(StreamDeckContext);
13
13
  return useCallback(async (profile, deviceId) => {
14
14
  const devId = deviceId ?? action.device.id;
15
- await sdk.profiles.switchToProfile(devId, profile);
16
- }, [sdk, action]);
15
+ await adapter.switchToProfile(devId, profile);
16
+ }, [adapter, action]);
17
17
  }
18
18
  function useSendToPI() {
19
- const { sdk } = useContext(StreamDeckContext);
19
+ const { adapter } = useContext(StreamDeckContext);
20
20
  return useCallback(async (payload) => {
21
- await sdk.ui.sendToPropertyInspector(payload);
22
- }, [sdk]);
21
+ await adapter.sendToPropertyInspector(payload);
22
+ }, [adapter]);
23
23
  }
24
24
  function usePropertyInspector(callback) {
25
25
  const bus = useContext(EventBusContext);
@@ -41,13 +41,13 @@ function useShowAlert() {
41
41
  function useShowOk() {
42
42
  const { action } = useContext(StreamDeckContext);
43
43
  return useCallback(async () => {
44
- if ("showOk" in action) await action.showOk();
44
+ await action.showOk();
45
45
  }, [action]);
46
46
  }
47
47
  function useTitle() {
48
48
  const { action } = useContext(StreamDeckContext);
49
49
  return useCallback(async (title) => {
50
- if ("setTitle" in action) await action.setTitle(title);
50
+ await action.setTitle(title);
51
51
  }, [action]);
52
52
  }
53
53
  //#endregion
@@ -1,10 +1,11 @@
1
1
  import { useCallbackRef } from "./internal/useCallbackRef.js";
2
2
  import { useCallback, useEffect, useMemo, useRef } from "react";
3
3
  //#region src/hooks/utility.ts
4
- var DEFAULT_TICK_FPS = 60;
4
+ var DEFAULT_TICK_FPS = 30;
5
+ var MAX_TICK_FPS = 30;
5
6
  function toTickIntervalMs(fps) {
6
7
  if (!Number.isFinite(fps) || fps <= 0) return Math.round(1e3 / DEFAULT_TICK_FPS);
7
- return Math.max(1, Math.round(1e3 / fps));
8
+ return Math.max(1, Math.round(1e3 / Math.min(fps, MAX_TICK_FPS)));
8
9
  }
9
10
  function useInterval(callback, delayMs) {
10
11
  const callbackRef = useCallbackRef(callback);
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export { defineAction } from './action';
3
3
  export type { RenderProfile } from './render/pipeline';
4
4
  export type { CacheStats } from './render/image-cache';
5
5
  export type { RenderMetrics } from './render/metrics';
6
+ export { physicalDevice } from './adapter/physical-device';
7
+ export type { StreamDeckAdapter, AdapterActionHandle, AdapterActionCallbacks, AdapterWillAppearEvent, AdapterActionDevice, AdapterController, AdapterCoordinates, AdapterSize, AdapterTriggerDescription, } from './adapter/types';
6
8
  export { useKeyDown, useKeyUp, useDialRotate, useDialDown, useDialUp, useTouchTap, useDialHint, } from './hooks/events';
7
9
  export { useTap, useLongPress, useDoubleTap } from './hooks/gestures';
8
10
  export { useSettings, useGlobalSettings } from './hooks/settings';
@@ -21,7 +23,9 @@ export { ProgressBar } from './components/ProgressBar';
21
23
  export { CircularGauge } from './components/CircularGauge';
22
24
  export { ErrorBoundary } from './components/ErrorBoundary';
23
25
  export { tw } from './tw/index';
24
- export type { PluginConfig, FontConfig, ActionConfig, ActionConfigInput, ActionDefinition, ActionUUID, ManifestActions, EncoderLayout, WrapperComponent, DeviceInfo, ActionInfo, CanvasInfo, TouchStripLayout, TouchStripLayoutItem, KeyDownPayload, KeyUpPayload, DialRotatePayload, DialPressPayload, TouchTapPayload, DialHints, StreamDeckAccess, TouchStripInfo, TouchStripTapPayload, TouchStripDialRotatePayload, TouchStripDialPressPayload, } from './types';
26
+ export { googleFont } from './google-font';
27
+ export type { GoogleFontVariant } from './google-font';
28
+ export type { PluginConfig, Plugin, FontConfig, ActionConfig, ActionConfigInput, ActionDefinition, ActionUUID, ManifestActions, EncoderLayout, WrapperComponent, TakumiBackend, Controller, Coordinates, Size, DeviceInfo, ActionInfo, CanvasInfo, TouchStripLayout, TouchStripLayoutItem, KeyDownPayload, KeyUpPayload, DialRotatePayload, DialPressPayload, TouchTapPayload, DialHints, StreamDeckAccess, TouchStripInfo, TouchStripTapPayload, TouchStripDialRotatePayload, TouchStripDialPressPayload, } from './types';
25
29
  export type { TapOptions, LongPressOptions, DoubleTapOptions } from './hooks/gestures';
26
30
  export type { BoxProps } from './components/Box';
27
31
  export type { TextProps } from './components/Text';
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { physicalDevice } from "./adapter/physical-device.js";
1
2
  import { createPlugin } from "./plugin.js";
2
3
  import { defineAction } from "./action.js";
3
4
  import { useDialDown, useDialHint, useDialRotate, useDialUp, useKeyDown, useKeyUp, useTouchTap } from "./hooks/events.js";
@@ -17,4 +18,5 @@ import { ProgressBar } from "./components/ProgressBar.js";
17
18
  import { CircularGauge } from "./components/CircularGauge.js";
18
19
  import { ErrorBoundary } from "./components/ErrorBoundary.js";
19
20
  import { tw } from "./tw/index.js";
20
- export { Box, CircularGauge, Easings, ErrorBoundary, Icon, Image, ProgressBar, SpringPresets, Text, createPlugin, defineAction, tw, useAction, useCanvas, useDevice, useDialDown, useDialHint, useDialRotate, useDialUp, useDoubleTap, useGlobalSettings, useInterval, useKeyDown, useKeyUp, useLongPress, useOpenUrl, usePrevious, usePropertyInspector, useSendToPI, useSettings, useShowAlert, useShowOk, useSpring, useStreamDeck, useSwitchProfile, useTap, useTick, useTimeout, useTitle, useTouchStrip, useTouchStripDialDown, useTouchStripDialRotate, useTouchStripDialUp, useTouchStripTap, useTouchTap, useTween, useWillAppear, useWillDisappear };
21
+ import { googleFont } from "./google-font.js";
22
+ export { Box, CircularGauge, Easings, ErrorBoundary, Icon, Image, ProgressBar, SpringPresets, Text, createPlugin, defineAction, googleFont, physicalDevice, tw, useAction, useCanvas, useDevice, useDialDown, useDialHint, useDialRotate, useDialUp, useDoubleTap, useGlobalSettings, useInterval, useKeyDown, useKeyUp, useLongPress, useOpenUrl, usePrevious, usePropertyInspector, useSendToPI, useSettings, useShowAlert, useShowOk, useSpring, useStreamDeck, useSwitchProfile, useTap, useTick, useTimeout, useTitle, useTouchStrip, useTouchStripDialDown, useTouchStripDialRotate, useTouchStripDialUp, useTouchStripTap, useTouchTap, useTween, useWillAppear, useWillDisappear };
package/dist/plugin.js CHANGED
@@ -2,57 +2,67 @@ import { metrics } from "./render/metrics.js";
2
2
  import { RootRegistry } from "./roots/registry.js";
3
3
  import { RenderPool } from "./render/render-pool.js";
4
4
  import { startDevtoolsServer } from "./devtools/index.js";
5
- import streamDeck, { SingletonAction } from "@elgato/streamdeck";
5
+ import { physicalDevice } from "./adapter/physical-device.js";
6
6
  import { Renderer } from "@takumi-rs/core";
7
7
  //#region src/plugin.ts
8
8
  function createPlugin(config) {
9
- const renderer = new Renderer({ fonts: config.fonts.map((f) => ({
9
+ const adapter = config.adapter ?? physicalDevice();
10
+ return { async connect() {
11
+ const takumiMode = config.takumi ?? "native-binding";
12
+ const renderPool = (takumiMode === "wasm" ? false : config.useWorker !== false) ? new RenderPool(config.fonts) : null;
13
+ const renderConfig = {
14
+ renderer: await initializeRenderer(takumiMode, config.fonts),
15
+ imageFormat: config.imageFormat ?? "png",
16
+ caching: config.caching ?? true,
17
+ devicePixelRatio: config.devicePixelRatio ?? 1,
18
+ debug: config.debug ?? process.env.NODE_ENV !== "production",
19
+ imageCacheMaxBytes: config.imageCacheMaxBytes ?? 16 * 1024 * 1024,
20
+ touchStripCacheMaxBytes: config.touchStripCacheMaxBytes ?? 8 * 1024 * 1024,
21
+ renderPool
22
+ };
23
+ const registry = new RootRegistry(renderConfig, adapter, async (settings) => {
24
+ await adapter.setGlobalSettings(settings);
25
+ }, config.wrapper);
26
+ adapter.getGlobalSettings().then((gs) => {
27
+ registry.setGlobalSettings(gs);
28
+ }).catch((err) => {
29
+ console.error("[@fcannizzaro/streamdeck-react] Failed to load global settings:", err);
30
+ });
31
+ adapter.onGlobalSettingsChanged((settings) => {
32
+ registry.setGlobalSettings(settings);
33
+ });
34
+ for (const definition of config.actions) registerActionWithAdapter(adapter, definition, registry, config.onActionError);
35
+ if (renderConfig.debug) metrics.enable();
36
+ if (config.devtools) startDevtoolsServer({
37
+ devtoolsName: adapter.pluginUUID,
38
+ registry,
39
+ renderConfig
40
+ });
41
+ if (renderPool != null) renderPool.initialize().catch(() => {});
42
+ await adapter.connect();
43
+ } };
44
+ }
45
+ async function initializeRenderer(mode, fonts) {
46
+ const fontData = fonts.map((f) => ({
10
47
  name: f.name,
11
48
  data: f.data,
12
49
  weight: f.weight,
13
50
  style: f.style
14
- })) });
15
- const renderPool = config.useWorker !== false ? new RenderPool(config.fonts) : null;
16
- const renderConfig = {
17
- renderer,
18
- imageFormat: config.imageFormat ?? "png",
19
- caching: config.caching ?? true,
20
- devicePixelRatio: config.devicePixelRatio ?? 1,
21
- debug: config.debug ?? process.env.NODE_ENV !== "production",
22
- imageCacheMaxBytes: config.imageCacheMaxBytes ?? 16 * 1024 * 1024,
23
- touchstripCacheMaxBytes: config.touchstripCacheMaxBytes ?? 8 * 1024 * 1024,
24
- renderPool,
25
- touchstripImageFormat: config.touchstripImageFormat ?? "webp"
26
- };
27
- const registry = new RootRegistry(renderConfig, config.renderDebounceMs ?? 16, streamDeck, async (settings) => {
28
- await streamDeck.settings.setGlobalSettings(settings);
29
- }, config.wrapper);
30
- streamDeck.settings.getGlobalSettings().then((gs) => {
31
- registry.setGlobalSettings(gs);
32
- }).catch((err) => {
33
- console.error("[@fcannizzaro/streamdeck-react] Failed to load global settings:", err);
34
- });
35
- streamDeck.settings.onDidReceiveGlobalSettings((ev) => {
36
- registry.setGlobalSettings(ev.settings);
37
- });
38
- for (const definition of config.actions) {
39
- const singletonAction = createSingletonAction(definition, registry, config.onActionError);
40
- streamDeck.actions.registerAction(singletonAction);
51
+ }));
52
+ if (mode === "wasm") {
53
+ const wasm = await import("@takumi-rs/wasm");
54
+ await wasm.default();
55
+ return new wasm.Renderer({ fonts: fontData });
41
56
  }
42
- if (renderConfig.debug) metrics.enable();
43
- if (config.devtools) startDevtoolsServer({
44
- devtoolsName: streamDeck.info.plugin.uuid,
45
- registry,
46
- renderConfig
47
- });
48
- return { async connect() {
49
- if (renderPool != null) renderPool.initialize().catch(() => {});
50
- await streamDeck.connect();
51
- } };
57
+ return new Renderer({ fonts: fontData });
52
58
  }
53
- function createSingletonAction(definition, registry, onError) {
54
- return new class extends SingletonAction {
55
- manifestId = definition.uuid;
59
+ function registerActionWithAdapter(adapter, definition, registry, onError) {
60
+ const handleError = (actionId, err) => {
61
+ const error = err instanceof Error ? err : new Error(String(err));
62
+ console.error(`[@fcannizzaro/streamdeck-react] Error in action ${definition.uuid} (${actionId}):`, error);
63
+ onError?.(definition.uuid, actionId, error);
64
+ };
65
+ adapter.registerAction(definition.uuid, {
56
66
  onWillAppear(ev) {
57
67
  try {
58
68
  const isEncoder = ev.payload.controller === "Encoder";
@@ -64,126 +74,94 @@ function createSingletonAction(definition, registry, onError) {
64
74
  if (!component) return;
65
75
  registry.create(ev, component, definition);
66
76
  } catch (err) {
67
- this.handleError(ev.action.id, err);
77
+ handleError(ev.action.id, err);
68
78
  }
69
- }
70
- onWillDisappear(ev) {
79
+ },
80
+ onWillDisappear(actionId) {
71
81
  try {
72
- registry.destroy(ev.action.id);
82
+ registry.destroy(actionId);
73
83
  } catch (err) {
74
- this.handleError(ev.action.id, err);
84
+ handleError(actionId, err);
75
85
  }
76
- }
77
- onKeyDown(ev) {
86
+ },
87
+ onKeyDown(actionId, payload) {
78
88
  try {
79
- registry.dispatch(ev.action.id, "keyDown", {
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
- });
89
+ registry.dispatch(actionId, "keyDown", payload);
85
90
  } catch (err) {
86
- this.handleError(ev.action.id, err);
91
+ handleError(actionId, err);
87
92
  }
88
- }
89
- onKeyUp(ev) {
93
+ },
94
+ onKeyUp(actionId, payload) {
90
95
  try {
91
- registry.dispatch(ev.action.id, "keyUp", {
92
- settings: ev.payload.settings,
93
- isInMultiAction: ev.payload.isInMultiAction,
94
- state: ev.payload.state,
95
- userDesiredState: "userDesiredState" in ev.payload ? ev.payload.userDesiredState : void 0
96
- });
96
+ registry.dispatch(actionId, "keyUp", payload);
97
97
  } catch (err) {
98
- this.handleError(ev.action.id, err);
98
+ handleError(actionId, err);
99
99
  }
100
- }
101
- onDialRotate(ev) {
100
+ },
101
+ onDialRotate(actionId, payload) {
102
102
  try {
103
- registry.dispatch(ev.action.id, "dialRotate", {
104
- ticks: ev.payload.ticks,
105
- pressed: ev.payload.pressed,
106
- settings: ev.payload.settings
107
- });
103
+ registry.dispatch(actionId, "dialRotate", payload);
108
104
  } catch (err) {
109
- this.handleError(ev.action.id, err);
105
+ handleError(actionId, err);
110
106
  }
111
- }
112
- onDialDown(ev) {
107
+ },
108
+ onDialDown(actionId, payload) {
113
109
  try {
114
- registry.dispatch(ev.action.id, "dialDown", {
115
- settings: ev.payload.settings,
116
- controller: "Encoder"
117
- });
110
+ registry.dispatch(actionId, "dialDown", payload);
118
111
  } catch (err) {
119
- this.handleError(ev.action.id, err);
112
+ handleError(actionId, err);
120
113
  }
121
- }
122
- onDialUp(ev) {
114
+ },
115
+ onDialUp(actionId, payload) {
123
116
  try {
124
- registry.dispatch(ev.action.id, "dialUp", {
125
- settings: ev.payload.settings,
126
- controller: "Encoder"
127
- });
117
+ registry.dispatch(actionId, "dialUp", payload);
128
118
  } catch (err) {
129
- this.handleError(ev.action.id, err);
119
+ handleError(actionId, err);
130
120
  }
131
- }
132
- onTouchTap(ev) {
121
+ },
122
+ onTouchTap(actionId, payload) {
133
123
  try {
134
- registry.dispatch(ev.action.id, "touchTap", {
135
- tapPos: ev.payload.tapPos,
136
- hold: ev.payload.hold,
137
- settings: ev.payload.settings
138
- });
124
+ registry.dispatch(actionId, "touchTap", payload);
139
125
  } catch (err) {
140
- this.handleError(ev.action.id, err);
126
+ handleError(actionId, err);
141
127
  }
142
- }
143
- onDidReceiveSettings(ev) {
128
+ },
129
+ onDidReceiveSettings(actionId, settings) {
144
130
  try {
145
- registry.updateSettings(ev.action.id, ev.payload.settings);
131
+ registry.updateSettings(actionId, settings);
146
132
  } catch (err) {
147
- this.handleError(ev.action.id, err);
133
+ handleError(actionId, err);
148
134
  }
149
- }
150
- onSendToPlugin(ev) {
135
+ },
136
+ onSendToPlugin(actionId, payload) {
151
137
  try {
152
- registry.dispatch(ev.action.id, "sendToPlugin", ev.payload);
138
+ registry.dispatch(actionId, "sendToPlugin", payload);
153
139
  } catch (err) {
154
- this.handleError(ev.action.id, err);
140
+ handleError(actionId, err);
155
141
  }
156
- }
157
- onPropertyInspectorDidAppear(ev) {
142
+ },
143
+ onPropertyInspectorDidAppear(actionId) {
158
144
  try {
159
- registry.dispatch(ev.action.id, "propertyInspectorDidAppear", void 0);
145
+ registry.dispatch(actionId, "propertyInspectorDidAppear", void 0);
160
146
  } catch (err) {
161
- this.handleError(ev.action.id, err);
147
+ handleError(actionId, err);
162
148
  }
163
- }
164
- onPropertyInspectorDidDisappear(ev) {
149
+ },
150
+ onPropertyInspectorDidDisappear(actionId) {
165
151
  try {
166
- registry.dispatch(ev.action.id, "propertyInspectorDidDisappear", void 0);
152
+ registry.dispatch(actionId, "propertyInspectorDidDisappear", void 0);
167
153
  } catch (err) {
168
- this.handleError(ev.action.id, err);
154
+ handleError(actionId, err);
169
155
  }
170
- }
171
- onTitleParametersDidChange(ev) {
156
+ },
157
+ onTitleParametersDidChange(actionId, payload) {
172
158
  try {
173
- registry.dispatch(ev.action.id, "titleParametersDidChange", {
174
- title: ev.payload.title,
175
- settings: ev.payload.settings
176
- });
159
+ registry.dispatch(actionId, "titleParametersDidChange", payload);
177
160
  } catch (err) {
178
- this.handleError(ev.action.id, err);
161
+ handleError(actionId, err);
179
162
  }
180
163
  }
181
- handleError(actionId, err) {
182
- const error = err instanceof Error ? err : new Error(String(err));
183
- console.error(`[@fcannizzaro/streamdeck-react] Error in action ${definition.uuid} (${actionId}):`, error);
184
- onError?.(definition.uuid, actionId, error);
185
- }
186
- }();
164
+ });
187
165
  }
188
166
  //#endregion
189
167
  export { createPlugin };
@@ -1,4 +1,3 @@
1
- import { ReactElement } from 'react';
2
1
  export interface VNode {
3
2
  type: string;
4
3
  props: Record<string, unknown>;
@@ -41,4 +40,3 @@ export declare function clearParent(child: VNode): void;
41
40
  export declare function createVNode(type: string, props: Record<string, unknown>): VNode;
42
41
  export declare function createTextVNode(text: string): VNode;
43
42
  export declare function createVContainer(renderCallback: () => void): VContainer;
44
- export declare function vnodeToElement(node: VNode): ReactElement | string;
@@ -1,4 +1,3 @@
1
- import "react";
2
1
  //#region src/reconciler/vnode.ts
3
2
  /** Mark a node (and its ancestors up to the container) as dirty. */
4
3
  function markDirty(node) {
@@ -1,24 +1,12 @@
1
1
  import { VNode, VContainer } from '../reconciler/vnode';
2
- /**
3
- * Initialize the xxHash-wasm module. Call is idempotent — subsequent
4
- * calls return the same promise. Resolves once `fnv1a()` will use the
5
- * WASM fast path for large buffers.
6
- */
7
- export declare function initBufferHasher(): Promise<void>;
8
- /** Reset the xxHash singleton — for testing only. */
9
- export declare function resetBufferHasher(): void;
10
2
  /**
11
3
  * Hash a raw byte buffer (Uint8Array or Buffer) or string.
12
4
  *
13
- * For buffers larger than {@link STRIDE_THRESHOLD} bytes:
14
- * - **Primary path**: xxHash-wasm `h32Raw()`hashes the entire buffer
15
- * in native WASM code. Faster than JS strided sampling even for
16
- * 320 KB touchstrip frames, with superior hash distribution.
17
- * - **Fallback path**: FNV-1a with strided sampling (every 16th byte)
18
- * when WASM hasn't compiled yet (startup) or is unavailable.
5
+ * For buffers larger than {@link STRIDE_THRESHOLD} bytes, uses strided
6
+ * FNV-1a sampling (every 16th byte) — at 30fps this adds <1ms even
7
+ * for 320KB TouchStrip buffers.
19
8
  *
20
- * Strings and small buffers always use JS FNV-1a (fast enough at those
21
- * sizes, and avoids the overhead of calling into WASM for tiny inputs).
9
+ * Strings and small buffers always use full byte-by-byte FNV-1a.
22
10
  */
23
11
  export declare function fnv1a(input: string | Uint8Array | Buffer): number;
24
12
  /** Feed a string into a running FNV-1a hash. */
@@ -39,4 +27,4 @@ export declare function computeHash(node: VNode): number;
39
27
  */
40
28
  export declare function computeTreeHash(container: VContainer): number;
41
29
  export declare function computeCacheKey(treeHash: number, width: number, height: number, dpr: number, format: string): number;
42
- export declare function computeNativeTouchstripCacheKey(treeHash: number, width: number, height: number, dpr: number, format: string, columns: number[]): number;
30
+ export declare function computeTouchStripSegmentCacheKey(treeHash: number, width: number, height: number, dpr: number, columns: number[]): number;