@solidtv/renderer 1.0.3 → 1.0.5

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/src/core/Stage.ts CHANGED
@@ -979,6 +979,9 @@ export class Stage {
979
979
  data,
980
980
  imageType: props.imageType,
981
981
  interactive: props.interactive ?? false,
982
+ preventDestroy: props.preventDestroy,
983
+ componentName: props.componentName,
984
+ componentLocation: props.componentLocation,
982
985
  };
983
986
  }
984
987
 
@@ -9,7 +9,7 @@ import type {
9
9
  IAnimationController,
10
10
  AnimationControllerState,
11
11
  } from '../common/IAnimationController.js';
12
- import { isProductionEnvironment } from '../utils.js';
12
+ import { ENABLE_INSPECTOR } from '../utils.js';
13
13
  import { CoreTextNode, type CoreTextNodeProps } from '../core/CoreTextNode.js';
14
14
  import type { Texture } from '../core/textures/Texture.js';
15
15
  import { TextureType } from '../core/textures/Texture.js';
@@ -230,8 +230,59 @@ const knownProperties = new Set<string>([
230
230
  'parent',
231
231
  'data',
232
232
  'text',
233
+ 'componentName',
234
+ 'componentLocation',
233
235
  ]);
234
236
 
237
+ /**
238
+ * Convert a PascalCase or camelCase component name to a valid custom-element
239
+ * tag name by inserting hyphens before uppercase letters and lower-casing.
240
+ * A mandatory "x-" prefix is added so the name always contains a hyphen,
241
+ * which is required by the Custom Elements spec.
242
+ *
243
+ * Examples:
244
+ * "MyButton" → "x-my-button"
245
+ * "FocusRing" → "x-focus-ring"
246
+ * "view" → "x-view"
247
+ */
248
+ function toCustomElementTag(componentName: string): string {
249
+ const kebab = componentName
250
+ .replace(/([A-Z])/g, '-$1')
251
+ .toLowerCase()
252
+ .replace(/^-/, ''); // strip leading hyphen if name starts with uppercase
253
+ return `${kebab}-x`;
254
+ }
255
+
256
+ /**
257
+ * Registry of custom element tag names that have already been defined so we
258
+ * only call customElements.define() once per unique component name.
259
+ */
260
+ const registeredCustomElements = new Set<string>();
261
+
262
+ /**
263
+ * Ensure a minimal custom element class is registered for the given tag name.
264
+ * The element extends HTMLElement with no additional behaviour; its only
265
+ * purpose is to make Chrome DevTools show the component name in the Elements
266
+ * panel.
267
+ */
268
+ function ensureCustomElement(tagName: string): void {
269
+ if (registeredCustomElements.has(tagName)) {
270
+ return;
271
+ }
272
+ registeredCustomElements.add(tagName);
273
+
274
+ // Guard: skip if already defined (e.g. by another module) or if the
275
+ // Custom Elements API is unavailable (SSR / test environments).
276
+ if (
277
+ typeof customElements === 'undefined' ||
278
+ customElements.get(tagName) !== undefined
279
+ ) {
280
+ return;
281
+ }
282
+
283
+ customElements.define(tagName, class extends HTMLElement {});
284
+ }
285
+
235
286
  export class Inspector {
236
287
  private root: HTMLElement | null = null;
237
288
  private canvas: HTMLCanvasElement | null = null;
@@ -289,7 +340,7 @@ export class Inspector {
289
340
  private animationStatsTimer: number | null = null;
290
341
 
291
342
  constructor(canvas: HTMLCanvasElement, settings: RendererMainSettings) {
292
- if (isProductionEnvironment === true) return;
343
+ if (!ENABLE_INSPECTOR) return;
293
344
 
294
345
  if (!settings) {
295
346
  throw new Error('settings is required');
@@ -732,14 +783,29 @@ export class Inspector {
732
783
  id: number,
733
784
  properties: CoreNodeProps | CoreTextNodeProps,
734
785
  ): HTMLElement {
735
- const div = document.createElement('div');
736
- div.style.position = 'absolute';
737
- div.id = id.toString();
786
+ // Use a custom element tag when the node carries a componentName so that
787
+ // Chrome DevTools' Elements panel shows the framework component name
788
+ // instead of a generic <div>.
789
+ const componentName = (properties as CoreNodeProps).componentName;
790
+ let el: HTMLElement;
791
+
792
+ if (componentName) {
793
+ const tagName = toCustomElementTag(componentName);
794
+ ensureCustomElement(tagName);
795
+ el = document.createElement(tagName);
796
+ // Store the original PascalCase name as a readable attribute.
797
+ // el.setAttribute('data-component', componentName);
798
+ } else {
799
+ el = document.createElement('div');
800
+ }
801
+
802
+ el.style.position = 'absolute';
803
+ el.id = id.toString();
738
804
 
739
805
  // set initial properties
740
806
  for (const key in properties) {
741
807
  this.updateNodeProperty(
742
- div,
808
+ el,
743
809
  // really typescript? really?
744
810
  key as keyof CoreNodeProps,
745
811
  properties[key as keyof CoreNodeProps],
@@ -747,7 +813,7 @@ export class Inspector {
747
813
  );
748
814
  }
749
815
 
750
- return div;
816
+ return el;
751
817
  }
752
818
 
753
819
  createNodes(node: CoreNode): boolean {
@@ -1205,6 +1271,22 @@ export class Inspector {
1205
1271
  }
1206
1272
  return;
1207
1273
  }
1274
+
1275
+ // Component metadata – handled when the element is created in createDiv;
1276
+ // re-applied here to support dynamic updates (rare but possible).
1277
+ if (property === 'componentName' && value) {
1278
+ const tagName = toCustomElementTag(String(value));
1279
+ ensureCustomElement(tagName);
1280
+ // div.setAttribute('data-component', String(value));
1281
+ return;
1282
+ }
1283
+
1284
+ if (property === 'componentLocation' && value) {
1285
+ // Expose the source path so Chrome DevTools extensions and VS Code's
1286
+ // browser debugger can offer "open in editor" functionality.
1287
+ // div.setAttribute('data-location', String(value));
1288
+ return;
1289
+ }
1208
1290
  }
1209
1291
 
1210
1292
  updateViewport(
@@ -1,6 +1,6 @@
1
1
  import type { ExtractProps, TextureMap } from '../core/CoreTextureManager.js';
2
2
  import { EventEmitter } from '../common/EventEmitter.js';
3
- import { isProductionEnvironment } from '../utils.js';
3
+ import { ENABLE_INSPECTOR } from '../utils.js';
4
4
  import { Stage, type StageOptions } from '../core/Stage.js';
5
5
  import { CoreNode, type CoreNodeProps } from '../core/CoreNode.js';
6
6
  import { type CoreTextNodeProps } from '../core/CoreTextNode.js';
@@ -620,7 +620,7 @@ export class RendererMain extends EventEmitter {
620
620
  }
621
621
 
622
622
  // Initialize inspector (if enabled)
623
- if (inspector && isProductionEnvironment === false) {
623
+ if (inspector && ENABLE_INSPECTOR) {
624
624
  this.inspector = new inspector(canvas, settings as RendererMainSettings);
625
625
  }
626
626
  }
@@ -683,7 +683,7 @@ export class RendererMain extends EventEmitter {
683
683
  ): INode<ShNode> {
684
684
  const node = this.stage.createNode(props as Partial<CoreNodeProps>);
685
685
 
686
- if (!isProductionEnvironment && this.inspector) {
686
+ if (ENABLE_INSPECTOR && this.inspector) {
687
687
  return this.inspector.createNode(node) as unknown as INode<ShNode>;
688
688
  }
689
689
 
@@ -707,7 +707,7 @@ export class RendererMain extends EventEmitter {
707
707
  createTextNode(props: Partial<ITextNodeProps>): ITextNode {
708
708
  const textNode = this.stage.createTextNode(props as CoreTextNodeProps);
709
709
 
710
- if (!isProductionEnvironment && this.inspector) {
710
+ if (ENABLE_INSPECTOR && this.inspector) {
711
711
  return this.inspector.createTextNode(textNode) as unknown as ITextNode;
712
712
  }
713
713
 
@@ -724,7 +724,7 @@ export class RendererMain extends EventEmitter {
724
724
  * @returns
725
725
  */
726
726
  destroyNode(node: INode) {
727
- if (!isProductionEnvironment && this.inspector) {
727
+ if (ENABLE_INSPECTOR && this.inspector) {
728
728
  this.inspector.destroyNode(node.id);
729
729
  }
730
730
 
@@ -888,7 +888,7 @@ export class RendererMain extends EventEmitter {
888
888
  stageOptions[key] = options[key]!;
889
889
  }
890
890
 
891
- if (options.inspector !== undefined && !isProductionEnvironment) {
891
+ if (options.inspector !== undefined && ENABLE_INSPECTOR) {
892
892
  if (options.inspector === false) {
893
893
  this.inspector?.destroy();
894
894
  this.inspector = null;
package/src/utils.ts CHANGED
@@ -77,6 +77,12 @@ export const CALCULATE_FPS =
77
77
  ? __calculateFps__
78
78
  : !isProductionEnvironment;
79
79
 
80
+ declare const __enableInspector__: boolean;
81
+ export const ENABLE_INSPECTOR =
82
+ typeof __enableInspector__ !== 'undefined'
83
+ ? __enableInspector__
84
+ : !isProductionEnvironment;
85
+
80
86
  // Works on WebOS 4!!
81
87
  declare const __dirtyQuadBuffer__: boolean;
82
88
  export const DIRTY_QUAD_BUFFER =