@fcannizzaro/streamdeck-react 0.1.11 → 0.1.12

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 (43) hide show
  1. package/dist/action.js +0 -1
  2. package/dist/adapter/index.d.ts +2 -0
  3. package/dist/adapter/physical-device.d.ts +2 -0
  4. package/dist/adapter/physical-device.js +153 -0
  5. package/dist/adapter/types.d.ts +127 -0
  6. package/dist/devtools/bridge.d.ts +2 -2
  7. package/dist/devtools/bridge.js +7 -8
  8. package/dist/devtools/highlight.d.ts +1 -2
  9. package/dist/devtools/highlight.js +4 -3
  10. package/dist/devtools/types.d.ts +5 -5
  11. package/dist/hooks/animation.d.ts +1 -1
  12. package/dist/hooks/animation.js +2 -2
  13. package/dist/hooks/events.js +1 -1
  14. package/dist/hooks/sdk.js +11 -11
  15. package/dist/hooks/utility.js +3 -2
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.js +2 -1
  18. package/dist/plugin.js +69 -100
  19. package/dist/reconciler/vnode.d.ts +0 -2
  20. package/dist/reconciler/vnode.js +0 -1
  21. package/dist/render/cache.d.ts +5 -17
  22. package/dist/render/cache.js +7 -29
  23. package/dist/render/image-cache.d.ts +8 -7
  24. package/dist/render/image-cache.js +33 -17
  25. package/dist/render/metrics.d.ts +9 -10
  26. package/dist/render/metrics.js +36 -39
  27. package/dist/render/pipeline.d.ts +4 -14
  28. package/dist/render/pipeline.js +47 -111
  29. package/dist/render/png.d.ts +0 -9
  30. package/dist/render/png.js +5 -8
  31. package/dist/render/render-pool.d.ts +0 -2
  32. package/dist/render/render-pool.js +1 -12
  33. package/dist/roots/registry.d.ts +5 -9
  34. package/dist/roots/registry.js +10 -27
  35. package/dist/roots/root.d.ts +7 -34
  36. package/dist/roots/root.js +23 -90
  37. package/dist/roots/touchstrip-root.d.ts +6 -32
  38. package/dist/roots/touchstrip-root.js +61 -181
  39. package/dist/types.d.ts +23 -19
  40. package/package.json +6 -4
  41. package/dist/node_modules/.bun/xxhash-wasm@1.1.0/node_modules/xxhash-wasm/esm/xxhash-wasm.js +0 -3157
  42. package/dist/roots/flush-coordinator.d.ts +0 -18
  43. package/dist/roots/flush-coordinator.js +0 -38
@@ -1,9 +1,8 @@
1
1
  import { clearDirtyFlags, createVContainer, isContainerDirty } from "../reconciler/vnode.js";
2
2
  import { reconciler } from "../reconciler/renderer.js";
3
- import { computeNativeTouchstripCacheKey, computeTreeHash, fnv1a } from "../render/cache.js";
4
- import { getTouchstripNativeCache } from "../render/image-cache.js";
5
- import { metrics } from "../render/metrics.js";
6
- import { buildTakumiChildren, measureTree, renderSegmentToDataUri, renderToRaw, sliceToDataUriAsync } from "../render/pipeline.js";
3
+ import { computeTouchStripSegmentCacheKey, computeTreeHash, fnv1a } from "../render/cache.js";
4
+ import { getTouchStripSegmentCache } from "../render/image-cache.js";
5
+ import { measureTree, renderToRaw, sliceToDataUri } from "../render/pipeline.js";
7
6
  import { EventBus } from "../context/event-bus.js";
8
7
  import { DeviceContext, EventBusContext, GlobalSettingsContext } from "../context/providers.js";
9
8
  import { partialHasChanges, shallowEqualSettings } from "./settings-equality.js";
@@ -12,7 +11,7 @@ import { createElement } from "react";
12
11
  //#region src/roots/touchstrip-root.ts
13
12
  var SEGMENT_WIDTH = 200;
14
13
  var SEGMENT_HEIGHT = 100;
15
- var DEFAULT_TOUCHSTRIP_FPS = 60;
14
+ var DEFAULT_TOUCH_STRIP_FPS = 30;
16
15
  var TouchStripRoot = class TouchStripRoot {
17
16
  eventBus = new EventBus();
18
17
  container;
@@ -20,34 +19,15 @@ var TouchStripRoot = class TouchStripRoot {
20
19
  columns = /* @__PURE__ */ new Map();
21
20
  globalSettings;
22
21
  setGlobalSettingsFn;
23
- renderDebounceMs;
24
22
  renderConfig;
25
23
  deviceInfo;
26
24
  disposed = false;
27
- fps;
28
25
  pluginWrapper;
26
+ renderDebounceMs = 17;
29
27
  _renderCount = 0;
30
28
  _lastRenderReport = 0;
31
- static RENDER_WARN_THRESHOLD = 65;
32
- _rendering = false;
33
- _pendingFlush = false;
34
- _recentRenders = [];
35
- _lastInteraction = 0;
36
- static ANIMATION_WINDOW_MS = 100;
37
- static ANIMATION_THRESHOLD = 2;
38
- static INTERACTION_COOLDOWN_MS = 500;
39
- static IDLE_THRESHOLD_MS = 2e3;
40
- _lastFlushTime = 0;
41
- /** Current render priority (lower = higher priority). Used by flush coordinator. */
42
- get priority() {
43
- const now = Date.now();
44
- const cutoff = now - TouchStripRoot.ANIMATION_WINDOW_MS;
45
- while (this._recentRenders.length > 0 && this._recentRenders[0] < cutoff) this._recentRenders.shift();
46
- if (this._recentRenders.length > TouchStripRoot.ANIMATION_THRESHOLD) return 0;
47
- if (now - this._lastInteraction < TouchStripRoot.INTERACTION_COOLDOWN_MS) return 1;
48
- if (this._lastFlushTime > 0 && now - this._lastFlushTime > TouchStripRoot.IDLE_THRESHOLD_MS) return 3;
49
- return 2;
50
- }
29
+ static RENDER_WARN_THRESHOLD = 35;
30
+ _lastSegmentUriHash = 0;
51
31
  /** Last rendered per-column data URIs. Used by devtools snapshots. */
52
32
  lastSegmentUris = /* @__PURE__ */ new Map();
53
33
  /**
@@ -93,14 +73,11 @@ var TouchStripRoot = class TouchStripRoot {
93
73
  }
94
74
  globalSettingsValue;
95
75
  touchStripValue;
96
- constructor(component, deviceInfo, initialGlobalSettings, renderConfig, renderDebounceMs, onGlobalSettingsChange, pluginWrapper, touchStripFPS, flushCoordinator) {
76
+ constructor(component, deviceInfo, initialGlobalSettings, renderConfig, onGlobalSettingsChange, pluginWrapper) {
97
77
  this.component = component;
98
- this.flushCoordinator = flushCoordinator;
99
78
  this.deviceInfo = deviceInfo;
100
79
  this.globalSettings = { ...initialGlobalSettings };
101
80
  this.renderConfig = renderConfig;
102
- this.fps = touchStripFPS ?? DEFAULT_TOUCHSTRIP_FPS;
103
- this.renderDebounceMs = touchStripFPS != null ? Math.max(1, Math.round(1e3 / touchStripFPS)) : renderDebounceMs;
104
81
  this.pluginWrapper = pluginWrapper;
105
82
  this.setGlobalSettingsFn = (partial) => {
106
83
  const hasChanges = partialHasChanges(this.globalSettings, partial);
@@ -125,8 +102,7 @@ var TouchStripRoot = class TouchStripRoot {
125
102
  width: 0,
126
103
  height: SEGMENT_HEIGHT,
127
104
  columns: [],
128
- segmentWidth: SEGMENT_WIDTH,
129
- fps: this.fps
105
+ segmentWidth: SEGMENT_WIDTH
130
106
  };
131
107
  this.container = createVContainer(() => {
132
108
  this.flush();
@@ -138,7 +114,7 @@ var TouchStripRoot = class TouchStripRoot {
138
114
  }, (err) => {
139
115
  console.error("[@fcannizzaro/streamdeck-react] TouchStrip recoverable error:", err);
140
116
  }, () => {});
141
- this.eventBus.ownerId = `touchstrip:${deviceInfo.id}`;
117
+ this.eventBus.ownerId = `touchStrip:${deviceInfo.id}`;
142
118
  }
143
119
  addColumn(column, actionId, sdkAction) {
144
120
  this.columns.set(column, {
@@ -166,8 +142,7 @@ var TouchStripRoot = class TouchStripRoot {
166
142
  width: (sortedColumns.length > 0 ? sortedColumns[sortedColumns.length - 1] + 1 : 0) * SEGMENT_WIDTH,
167
143
  height: SEGMENT_HEIGHT,
168
144
  columns: sortedColumns,
169
- segmentWidth: SEGMENT_WIDTH,
170
- fps: this.fps
145
+ segmentWidth: SEGMENT_WIDTH
171
146
  };
172
147
  }
173
148
  render() {
@@ -184,59 +159,21 @@ var TouchStripRoot = class TouchStripRoot {
184
159
  if (this.disposed) return;
185
160
  this.render();
186
161
  }
187
- /** Record a user interaction for adaptive debounce. */
188
- markInteraction() {
189
- this._lastInteraction = Date.now();
190
- }
191
- get effectiveDebounceMs() {
192
- const now = Date.now();
193
- const cutoff = now - TouchStripRoot.ANIMATION_WINDOW_MS;
194
- while (this._recentRenders.length > 0 && this._recentRenders[0] < cutoff) this._recentRenders.shift();
195
- if (this._recentRenders.length > TouchStripRoot.ANIMATION_THRESHOLD) return 0;
196
- if (now - this._lastInteraction < TouchStripRoot.INTERACTION_COOLDOWN_MS) return Math.min(this.renderDebounceMs, 16);
197
- return this.renderDebounceMs;
198
- }
199
162
  async flush() {
200
163
  if (this.disposed) return;
201
- if (this._rendering) {
202
- this._pendingFlush = true;
203
- return;
204
- }
205
- this._recentRenders.push(Date.now());
206
- const debounce = this.effectiveDebounceMs;
207
- if (debounce > 0 && this.container.renderTimer !== null) clearTimeout(this.container.renderTimer);
208
- if (debounce > 0) this.container.renderTimer = setTimeout(() => {
164
+ if (this.renderDebounceMs > 0) this.container.renderTimer = setTimeout(async () => {
209
165
  this.container.renderTimer = null;
210
- this.submitFlush();
211
- }, debounce);
212
- else this.submitFlush();
213
- }
214
- /**
215
- * Submit this root for flushing. Routes through the coordinator
216
- * (priority-ordered) when available, or flushes directly.
217
- */
218
- submitFlush() {
219
- if (this.disposed) return;
220
- if (this.flushCoordinator) this.flushCoordinator.requestFlush(this);
221
- else this.doFlush();
222
- }
223
- /**
224
- * Execute the flush. Called by FlushCoordinator in priority order,
225
- * or directly when no coordinator is present.
226
- */
227
- async executeFlush() {
228
- await this.doFlush();
166
+ await this.doFlush();
167
+ }, this.renderDebounceMs);
168
+ else await this.doFlush();
229
169
  }
230
170
  async doFlush() {
231
171
  if (this.disposed || this.columns.size === 0) return;
232
- this._rendering = true;
233
- this._pendingFlush = false;
234
- this._lastFlushTime = Date.now();
235
172
  if (this.renderConfig.debug) {
236
173
  this._renderCount++;
237
174
  const now = Date.now();
238
175
  if (now - this._lastRenderReport > 1e3) {
239
- if (this._renderCount > TouchStripRoot.RENDER_WARN_THRESHOLD) console.warn(`[@fcannizzaro/streamdeck-react] TouchStrip rendered ${this._renderCount}x in 1s (FPS target: ${this.fps})`);
176
+ if (this._renderCount > TouchStripRoot.RENDER_WARN_THRESHOLD) console.warn(`[@fcannizzaro/streamdeck-react] TouchStrip rendered ${this._renderCount}x in 1s (max ${DEFAULT_TOUCH_STRIP_FPS}fps)`);
240
177
  this._renderCount = 0;
241
178
  this._lastRenderReport = now;
242
179
  }
@@ -244,121 +181,64 @@ var TouchStripRoot = class TouchStripRoot {
244
181
  try {
245
182
  const width = this.touchStripValue.width;
246
183
  if (width === 0) return;
247
- if (this.renderConfig.touchstripImageFormat !== "png") {
248
- metrics.recordFlush();
249
- if (this.renderConfig.caching && !isContainerDirty(this.container)) {
250
- metrics.recordDirtySkip();
251
- return;
252
- }
253
- const profiling = this.renderConfig.onProfile != null;
254
- const t0 = profiling ? performance.now() : 0;
255
- const sortedColumns = [...this.columns.keys()].sort((a, b) => a - b);
256
- let treeHash;
257
- let cacheKey;
258
- let cacheHit = false;
259
- if (this.renderConfig.caching && this.renderConfig.touchstripCacheMaxBytes > 0) {
260
- treeHash = computeTreeHash(this.container);
261
- cacheKey = computeNativeTouchstripCacheKey(treeHash, width, SEGMENT_HEIGHT, this.renderConfig.devicePixelRatio, this.renderConfig.touchstripImageFormat, sortedColumns);
262
- const cache = getTouchstripNativeCache(this.renderConfig.touchstripCacheMaxBytes);
263
- const cached = cache.get(cacheKey);
264
- if (cached !== void 0) {
265
- metrics.recordCacheHit();
266
- cacheHit = true;
267
- for (const [column, uri] of cached) {
268
- this.lastSegmentUris.set(column, uri);
269
- if (!this.suppressHardwarePush) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
270
- }
271
- if (profiling) {
272
- const tNow = performance.now();
273
- const stats = measureTree(this.container.children);
274
- this.renderConfig.onProfile({
275
- vnodeToElementMs: 0,
276
- fromJsxMs: 0,
277
- takumiRenderMs: tNow - t0,
278
- hashMs: 0,
279
- base64Ms: 0,
280
- totalMs: tNow - t0,
281
- skipped: false,
282
- cacheHit: true,
283
- treeDepth: stats.depth,
284
- nodeCount: stats.count,
285
- cacheStats: cache.stats
286
- });
287
- }
288
- clearDirtyFlags(this.container);
289
- }
290
- }
291
- if (!cacheHit) {
292
- const takumiChildren = buildTakumiChildren(this.container);
293
- const segmentResults = [];
294
- const renderPromises = [...this.columns.entries()].map(async ([column]) => {
295
- const sliceUri = await renderSegmentToDataUri(this.container, width, SEGMENT_HEIGHT, column, SEGMENT_WIDTH, this.renderConfig.touchstripImageFormat, this.renderConfig, takumiChildren);
296
- if (sliceUri != null) {
297
- segmentResults.push([column, sliceUri]);
298
- this.lastSegmentUris.set(column, sliceUri);
299
- }
300
- });
301
- await Promise.all(renderPromises);
302
- segmentResults.sort((a, b) => a[0] - b[0]);
303
- let skipped = false;
304
- if (this.renderConfig.caching) {
305
- const uriHash = fnv1a(segmentResults.map(([col, uri]) => `${col}:${uri}`).join("\0"));
306
- if (uriHash === this.container.lastSvgHash) {
307
- metrics.recordHashDedup();
308
- skipped = true;
309
- } else this.container.lastSvgHash = uriHash;
310
- }
311
- if (!skipped && !this.suppressHardwarePush) for (const [column, uri] of segmentResults) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
312
- const elapsedMs = (profiling ? performance.now() : 0) - t0;
313
- metrics.recordRender(elapsedMs);
314
- if (this.renderConfig.caching && this.renderConfig.touchstripCacheMaxBytes > 0) {
315
- if (treeHash === void 0 || cacheKey === void 0) {
316
- treeHash = computeTreeHash(this.container);
317
- cacheKey = computeNativeTouchstripCacheKey(treeHash, width, SEGMENT_HEIGHT, this.renderConfig.devicePixelRatio, this.renderConfig.touchstripImageFormat, sortedColumns);
318
- }
319
- const cache = getTouchstripNativeCache(this.renderConfig.touchstripCacheMaxBytes);
320
- let byteSize = 64;
321
- for (const [, uri] of segmentResults) byteSize += uri.length * 2 + 16;
322
- cache.set(cacheKey, segmentResults, byteSize);
323
- }
184
+ if (this.renderConfig.caching && !isContainerDirty(this.container)) return;
185
+ const sortedColumns = [...this.columns.keys()].sort((a, b) => a - b);
186
+ let cacheKey;
187
+ const profiling = this.renderConfig.onProfile != null;
188
+ const tPhase2 = profiling ? performance.now() : 0;
189
+ if (this.renderConfig.caching && this.renderConfig.touchStripCacheMaxBytes > 0) {
190
+ cacheKey = computeTouchStripSegmentCacheKey(computeTreeHash(this.container), width, SEGMENT_HEIGHT, this.renderConfig.devicePixelRatio, sortedColumns);
191
+ const cached = getTouchStripSegmentCache(this.renderConfig.touchStripCacheMaxBytes).get(cacheKey);
192
+ if (cached !== void 0) {
193
+ for (const [column, uri] of cached) this.lastSegmentUris.set(column, uri);
194
+ if (!this.suppressHardwarePush) for (const [column, uri] of cached) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
195
+ clearDirtyFlags(this.container);
324
196
  if (profiling) {
197
+ const tEnd = performance.now();
325
198
  const stats = measureTree(this.container.children);
326
- const nativeCache = this.renderConfig.touchstripCacheMaxBytes > 0 ? getTouchstripNativeCache(this.renderConfig.touchstripCacheMaxBytes) : null;
327
199
  this.renderConfig.onProfile({
328
- vnodeToElementMs: 0,
329
- fromJsxMs: 0,
330
- takumiRenderMs: elapsedMs,
331
- hashMs: 0,
200
+ vnodeConversionMs: 0,
201
+ takumiRenderMs: 0,
202
+ hashMs: tEnd - tPhase2,
332
203
  base64Ms: 0,
333
- totalMs: elapsedMs,
334
- skipped,
335
- cacheHit: false,
204
+ totalMs: tEnd - tPhase2,
205
+ skipped: false,
206
+ cacheHit: true,
336
207
  treeDepth: stats.depth,
337
208
  nodeCount: stats.count,
338
- cacheStats: nativeCache?.stats ?? null
209
+ cacheStats: null
339
210
  });
340
211
  }
341
- clearDirtyFlags(this.container);
212
+ this.renderConfig.onRender?.(this.container, "");
213
+ return;
342
214
  }
343
- } else {
344
- const result = await renderToRaw(this.container, width, SEGMENT_HEIGHT, this.renderConfig);
345
- if (result === null || this.disposed) return;
346
- const feedbackPromises = [...this.columns.entries()].map(async ([column, entry]) => {
347
- const sliceUri = await sliceToDataUriAsync(result.buffer, result.width, result.height, column, SEGMENT_WIDTH, SEGMENT_HEIGHT);
348
- this.lastSegmentUris.set(column, sliceUri);
349
- if (!this.suppressHardwarePush) entry.sdkAction.setFeedback({ canvas: sliceUri }).catch(() => {});
350
- });
351
- await Promise.all(feedbackPromises);
352
215
  }
216
+ const result = await renderToRaw(this.container, width, SEGMENT_HEIGHT, this.renderConfig);
217
+ if (result === null || this.disposed) return;
218
+ const segmentResults = [];
219
+ for (const [column] of this.columns) {
220
+ const sliceUri = sliceToDataUri(result.buffer, result.width, result.height, column, SEGMENT_WIDTH, SEGMENT_HEIGHT);
221
+ segmentResults.push([column, sliceUri]);
222
+ this.lastSegmentUris.set(column, sliceUri);
223
+ }
224
+ segmentResults.sort((a, b) => a[0] - b[0]);
225
+ let skipped = false;
226
+ if (this.renderConfig.caching) {
227
+ const uriHash = fnv1a(segmentResults.map(([col, uri]) => `${col}:${uri}`).join("\0"));
228
+ if (uriHash === this._lastSegmentUriHash) skipped = true;
229
+ else this._lastSegmentUriHash = uriHash;
230
+ }
231
+ if (!skipped && !this.suppressHardwarePush) for (const [column, uri] of segmentResults) this.columns.get(column)?.sdkAction.setFeedback({ canvas: uri }).catch(() => {});
232
+ if (cacheKey !== void 0 && this.renderConfig.caching && this.renderConfig.touchStripCacheMaxBytes > 0) {
233
+ const cache = getTouchStripSegmentCache(this.renderConfig.touchStripCacheMaxBytes);
234
+ let byteSize = 64;
235
+ for (const [, uri] of segmentResults) byteSize += uri.length * 2 + 16;
236
+ cache.set(cacheKey, segmentResults, byteSize);
237
+ }
238
+ clearDirtyFlags(this.container);
353
239
  this.renderConfig.onRender?.(this.container, "");
354
240
  } catch (err) {
355
241
  console.error("[@fcannizzaro/streamdeck-react] TouchStrip render error:", err);
356
- } finally {
357
- this._rendering = false;
358
- if (this._pendingFlush && !this.disposed) {
359
- this._pendingFlush = false;
360
- await this.doFlush();
361
- }
362
242
  }
363
243
  }
364
244
  updateGlobalSettings(settings) {
package/dist/types.d.ts CHANGED
@@ -1,6 +1,18 @@
1
1
  import { ComponentType, ReactNode } from 'react';
2
- import { Action, Controller, Coordinates, DeviceType, DialAction, KeyAction, Size } from '@elgato/streamdeck';
3
2
  import { JsonObject, JsonValue } from '@elgato/utils';
3
+ import { AdapterActionHandle, StreamDeckAdapter } from './adapter/types';
4
+ /** Controller surface type. */
5
+ export type Controller = "Keypad" | "Encoder";
6
+ /** Grid coordinates for a key or encoder on a device. */
7
+ export interface Coordinates {
8
+ column: number;
9
+ row: number;
10
+ }
11
+ /** Device grid size (number of key columns and rows). */
12
+ export interface Size {
13
+ columns: number;
14
+ rows: number;
15
+ }
4
16
  export interface FontConfig {
5
17
  name: string;
6
18
  data: ArrayBuffer | Buffer;
@@ -62,10 +74,11 @@ export interface TouchStripLayout {
62
74
  }
63
75
  export type EncoderLayout = string | TouchStripLayout;
64
76
  export interface PluginConfig {
77
+ /** Stream Deck adapter. Defaults to physicalDevice() (Elgato SDK). */
78
+ adapter?: StreamDeckAdapter;
65
79
  fonts: FontConfig[];
66
80
  actions: ActionDefinition[];
67
81
  wrapper?: WrapperComponent;
68
- renderDebounceMs?: number;
69
82
  imageFormat?: "png" | "webp";
70
83
  caching?: boolean;
71
84
  devicePixelRatio?: number;
@@ -76,12 +89,10 @@ export interface PluginConfig {
76
89
  debug?: boolean;
77
90
  /** Maximum image cache size in bytes for key/dial renders. Set to 0 to disable. @default 16777216 (16 MB) */
78
91
  imageCacheMaxBytes?: number;
79
- /** Maximum touchstrip raw buffer cache size in bytes. Set to 0 to disable. @default 8388608 (8 MB) */
80
- touchstripCacheMaxBytes?: number;
92
+ /** Maximum TouchStrip raw buffer cache size in bytes. Set to 0 to disable. @default 8388608 (8 MB) */
93
+ touchStripCacheMaxBytes?: number;
81
94
  /** Offload Takumi rendering to a worker thread. Set to false to disable. @default true */
82
95
  useWorker?: boolean;
83
- /** Image format for touchstrip segment encoding. `"webp"` renders each segment directly via Takumi (faster, no custom PNG encoder). `"png"` uses the raw+crop+deflate path. @default "webp" */
84
- touchstripImageFormat?: "webp" | "png";
85
96
  }
86
97
  export interface Plugin {
87
98
  connect(): Promise<void>;
@@ -112,10 +123,8 @@ export interface ActionConfig<S extends JsonObject = JsonObject> {
112
123
  uuid: string;
113
124
  key?: ComponentType;
114
125
  dial?: ComponentType;
115
- /** Full-strip touchstrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
126
+ /** Full-strip TouchStrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
116
127
  touchStrip?: ComponentType;
117
- /** Target frame rate for the touchstrip animation loop and render pipeline. Controls both `useTick` cadence (via `useTouchStrip().fps`) and the render debounce. @default 60 */
118
- touchStripFPS?: number;
119
128
  /** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
120
129
  dialLayout?: EncoderLayout;
121
130
  wrapper?: WrapperComponent;
@@ -127,8 +136,6 @@ export type ActionConfigInput<S extends JsonObject = JsonObject> = [keyof Manife
127
136
  ] ? ActionConfig<S> : {
128
137
  [UUID in ActionUUID]: {
129
138
  uuid: UUID;
130
- /** Target frame rate for the touchstrip animation loop and render pipeline. Controls both `useTick` cadence (via `useTouchStrip().fps`) and the render debounce. @default 60 */
131
- touchStripFPS?: number;
132
139
  /** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
133
140
  dialLayout?: EncoderLayout;
134
141
  wrapper?: WrapperComponent;
@@ -139,10 +146,8 @@ export interface ActionDefinition<S extends JsonObject = JsonObject> {
139
146
  uuid: string;
140
147
  key?: ComponentType;
141
148
  dial?: ComponentType;
142
- /** Full-strip touchstrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
149
+ /** Full-strip TouchStrip component. When set, replaces per-encoder `dial` display with a single shared React tree that spans the entire touch strip. */
143
150
  touchStrip?: ComponentType;
144
- /** Target frame rate for the touchstrip animation loop and render pipeline. @default 60 */
145
- touchStripFPS?: number;
146
151
  /** Encoder feedback layout. Defaults to a full-width `pixmap` canvas layout. Custom layouts should include a `pixmap` item keyed as `canvas`. */
147
152
  dialLayout?: EncoderLayout;
148
153
  wrapper?: WrapperComponent;
@@ -150,7 +155,8 @@ export interface ActionDefinition<S extends JsonObject = JsonObject> {
150
155
  }
151
156
  export interface DeviceInfo {
152
157
  id: string;
153
- type: DeviceType;
158
+ /** Numeric device type matching Elgato DeviceType enum values (e.g. 7 = StreamDeckPlus). */
159
+ type: number;
154
160
  size: Size;
155
161
  name: string;
156
162
  }
@@ -167,8 +173,8 @@ export interface CanvasInfo {
167
173
  type: "key" | "dial" | "touch";
168
174
  }
169
175
  export interface StreamDeckAccess {
170
- action: Action | DialAction | KeyAction;
171
- sdk: typeof import('@elgato/streamdeck').default;
176
+ action: AdapterActionHandle;
177
+ adapter: StreamDeckAdapter;
172
178
  }
173
179
  export interface KeyDownPayload {
174
180
  settings: JsonObject;
@@ -216,8 +222,6 @@ export interface TouchStripInfo {
216
222
  columns: number[];
217
223
  /** Width of each encoder segment in pixels (always 200). */
218
224
  segmentWidth: number;
219
- /** Target frame rate for the animation loop. Pass to `useTick` for matched cadence. */
220
- fps: number;
221
225
  }
222
226
  export interface TouchStripTapPayload {
223
227
  /** Absolute tap position across the full strip width. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fcannizzaro/streamdeck-react",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Build Stream Deck plugins with React — render components directly to keys, dials, and touch screens",
5
5
  "keywords": [
6
6
  "elgato",
@@ -60,11 +60,9 @@
60
60
  "test": "bun test ./src"
61
61
  },
62
62
  "dependencies": {
63
- "@elgato/streamdeck": "^2.0.2",
64
63
  "@takumi-rs/core": "^0.71.7",
65
64
  "@takumi-rs/helpers": "^0.71.7",
66
- "react-reconciler": "^0.33.0",
67
- "xxhash-wasm": "^1.1.0"
65
+ "react-reconciler": "^0.33.0"
68
66
  },
69
67
  "devDependencies": {
70
68
  "@happy-dom/global-registrator": "^20.8.3",
@@ -77,12 +75,16 @@
77
75
  "vite-plugin-dts": "^4.5.4"
78
76
  },
79
77
  "peerDependencies": {
78
+ "@elgato/streamdeck": "^2.0.2",
80
79
  "react": "^18.0.0 || ^19.0.0",
81
80
  "rollup": "^4.0.0",
82
81
  "typescript": "^5",
83
82
  "vite": "^7.0.0 || ^8.0.0"
84
83
  },
85
84
  "peerDependenciesMeta": {
85
+ "@elgato/streamdeck": {
86
+ "optional": true
87
+ },
86
88
  "rollup": {
87
89
  "optional": true
88
90
  },