@roku-sdk/solid 2.18.0 → 2.19.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/EXPORTS.md CHANGED
@@ -192,6 +192,7 @@ These are SDK-owned symbols — not re-exports from `solid-js`. They power the R
192
192
  |---|---|---|
193
193
  | `registerTagAlias` | Function | `registerTagAlias(tag, resolve)` — create the SceneGraph node type `resolve()` returns whenever `tag` is created. Used by `@roku-sdk/runtime` and `@roku-sdk/compat` to choose between a firmware node and its shim; apps do not normally call it. |
194
194
  | `hasTagAlias` | Function | `hasTagAlias(tag)` — whether `tag` already has an alias, so a default can be registered without replacing one installed earlier. |
195
+ | `registerSetPropertyObserver` | Function | `registerSetPropertyObserver(observer)` — invoke `observer(node, name, value)` after every `tmTransaction.setProperty`. Used by `__DEV__` diagnostics in `@roku-sdk/components` (Effect gradient validation); apps do not normally call it. Empty-list fast path when nothing is registered. |
195
196
 
196
197
  ### Renderer Runtime Hooks
197
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roku-sdk/solid",
3
- "version": "2.18.0",
3
+ "version": "2.19.0",
4
4
  "description": "Roku SceneGraph modules",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -28,13 +28,13 @@
28
28
  "node": ">=24.0.0"
29
29
  },
30
30
  "dependencies": {
31
- "solid-js": "^1.9.10",
32
- "@roku-sdk/common": "2.18.0",
33
- "@roku-sdk/interfaces": "2.18.0"
31
+ "solid-js": "^1.9.17",
32
+ "@roku-sdk/common": "2.19.0",
33
+ "@roku-sdk/interfaces": "2.19.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "typescript": "^5.9.3",
37
- "@roku-sdk/dev-tools": "2.18.0"
37
+ "@roku-sdk/dev-tools": "2.19.0"
38
38
  },
39
39
  "author": "Roku Inc",
40
40
  "license": "MIT",
package/src/solid.ts CHANGED
@@ -173,6 +173,43 @@ type TmTransaction = {
173
173
  preCommit(): void;
174
174
  };
175
175
 
176
+ /** Called after `tmTransaction.setProperty` applies a property (including event handlers). */
177
+ export type SetPropertyObserver = (node: DomNode, name: string, value: TmValue) => void;
178
+
179
+ const setPropertyObservers: SetPropertyObserver[] = [];
180
+
181
+ /**
182
+ * Register a callback invoked after every successful `tmTransaction.setProperty`.
183
+ * Observers are skipped entirely when none are registered (empty-list fast path).
184
+ *
185
+ * Intended for `__DEV__` diagnostics that need to see intrinsic JSX props (e.g. Effect
186
+ * gradient validation). Production builds that never register pay only an array-length check.
187
+ *
188
+ * Returns an unsubscribe function.
189
+ */
190
+ export const registerSetPropertyObserver = (observer: SetPropertyObserver): (() => void) => {
191
+ setPropertyObservers.push(observer);
192
+ return () => {
193
+ const index = setPropertyObservers.indexOf(observer);
194
+ if (index !== -1) {
195
+ setPropertyObservers.splice(index, 1);
196
+ }
197
+ };
198
+ };
199
+
200
+ const notifySetPropertyObservers = (node: DomNode, name: string, value: TmValue): void => {
201
+ if (setPropertyObservers.length === 0) return;
202
+ for (const observer of setPropertyObservers) {
203
+ // Observers are dev-only diagnostics; one that throws must not abort the
204
+ // transaction's setProperty call for every other observer and consumer.
205
+ try {
206
+ observer(node, name, value);
207
+ } catch (error) {
208
+ console.error("[solid] setProperty observer threw:", error);
209
+ }
210
+ }
211
+ };
212
+
176
213
  export interface Renderer<NodeType> {
177
214
  render(code: () => NodeType, node: NodeType): () => void;
178
215
  effect<T>(fn: (prev?: T) => T, init?: T): void;
@@ -456,6 +493,7 @@ export const tmTransaction: TmTransaction = {
456
493
  observerCallbacks.set(nodeRef, callbacks);
457
494
  }
458
495
  callbacks.set(propRef, value as () => void);
496
+ notifySetPropertyObservers(node, name, value);
459
497
  return;
460
498
  } else {
461
499
  switch (typeof value) {
@@ -499,6 +537,7 @@ export const tmTransaction: TmTransaction = {
499
537
  if (typeof prev === "object" && isDomNode(prev as DomNode)) {
500
538
  nodesToRemove.add(prev as DomNode);
501
539
  }
540
+ notifySetPropertyObservers(node, name, value);
502
541
  },
503
542
  insertNode: (parent: DomNode, node: DomNode, anchor?: DomNode): void => {
504
543
  // Do nothing if the node is anchor itself