pixifact 0.4.0 → 0.6.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.
Files changed (51) hide show
  1. package/dist/compiler/builtinScenes.d.ts +0 -5
  2. package/dist/compiler/builtinScenes.js +0 -26
  3. package/dist/compiler/commands/index.js +8 -5
  4. package/dist/compiler/compileScenes.d.ts +3 -1
  5. package/dist/compiler/compileScenes.js +32 -2
  6. package/dist/compiler/index.d.ts +0 -1
  7. package/dist/compiler/index.js +0 -1
  8. package/dist/compiler/pixiNodeSchema.d.ts +5 -3
  9. package/dist/compiler/pixiNodeSchema.js +61 -7
  10. package/dist/compiler/sceneValidation.d.ts +3 -1
  11. package/dist/compiler/sceneValidation.js +105 -6
  12. package/dist/compiler/spec.d.ts +18 -7
  13. package/dist/compiler/spec.js +3 -1
  14. package/dist/compiler/templateParser.js +19 -0
  15. package/dist/compiler/templateSerializer.js +7 -1
  16. package/dist/compiler/typescriptCompiler.js +66 -8
  17. package/dist/compiler-node/builtinSceneInterfaces.d.ts +6 -0
  18. package/dist/compiler-node/builtinSceneInterfaces.js +27 -0
  19. package/dist/compiler-node/index.d.ts +3 -0
  20. package/dist/compiler-node/index.js +3 -0
  21. package/dist/compiler-node/runtimeVite.d.ts +89 -0
  22. package/dist/compiler-node/runtimeVite.js +420 -0
  23. package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.d.ts +1 -1
  24. package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.js +157 -25
  25. package/dist/errors.d.ts +1 -1
  26. package/dist/errors.js +4 -1
  27. package/dist/runtime/BitmapLabel.d.ts +9 -0
  28. package/dist/runtime/BitmapLabel.js +7 -0
  29. package/dist/runtime/Label.d.ts +9 -0
  30. package/dist/runtime/Label.js +7 -0
  31. package/dist/runtime/ScrollContainer.d.ts +2 -0
  32. package/dist/runtime/ScrollContainer.js +12 -2
  33. package/dist/runtime/TextBoxControl.d.ts +48 -0
  34. package/dist/runtime/TextBoxControl.js +162 -0
  35. package/dist/runtime/index.d.ts +2 -0
  36. package/dist/runtime/index.js +2 -0
  37. package/dist/runtime/runtimeBehavior.d.ts +3 -0
  38. package/dist/runtime/runtimeBehavior.js +11 -0
  39. package/dist/runtime-dev/index.d.ts +5 -0
  40. package/dist/runtime-dev/index.js +22 -0
  41. package/dist/runtime-dev/protocol.d.ts +58 -0
  42. package/dist/runtime-dev/protocol.js +1 -0
  43. package/dist/runtime-dev/runtimeClient.d.ts +22 -0
  44. package/dist/runtime-dev/runtimeClient.js +382 -0
  45. package/dist/scene/decorators.d.ts +2 -1
  46. package/dist/scene/decorators.js +12 -7
  47. package/dist/scene/index.d.ts +1 -0
  48. package/dist/scene/index.js +1 -0
  49. package/dist/scene/sceneBindingRuntime.d.ts +10 -0
  50. package/dist/scene/sceneBindingRuntime.js +124 -0
  51. package/package.json +6 -1
@@ -1,4 +1,3 @@
1
- import type { SceneTemplateInterface } from './spec.js';
2
1
  export declare const builtinScenePrefix = "pixifact:";
3
2
  export declare const builtinSceneNames: readonly [];
4
3
  export type BuiltinSceneName = typeof builtinSceneNames[number];
@@ -8,7 +7,3 @@ export declare function builtinSceneAssetId(name: string): string;
8
7
  export declare function isBuiltinSceneAssetId(value: string): boolean;
9
8
  export declare function builtinSceneNameFromAssetId(assetId: string): never;
10
9
  export declare function builtinSceneAssetIds(): string[];
11
- export declare function builtinSceneInterface(assetId: string, scriptSources: BuiltinSceneScriptSources): SceneTemplateInterface;
12
- export declare function builtinSceneInterfaces(scriptSources: BuiltinSceneScriptSources): {
13
- [k: string]: SceneTemplateInterface;
14
- };
@@ -1,4 +1,3 @@
1
- import { extractSceneScriptInterfaces } from './scriptInterfaceExtractor.js';
2
1
  export const builtinScenePrefix = 'pixifact:';
3
2
  export const builtinSceneNames = [];
4
3
  const builtinSceneNameSet = new Set(builtinSceneNames);
@@ -27,28 +26,3 @@ export function builtinSceneNameFromAssetId(assetId) {
27
26
  export function builtinSceneAssetIds() {
28
27
  return builtinSceneNames.map(builtinSceneAssetId);
29
28
  }
30
- export function builtinSceneInterface(assetId, scriptSources) {
31
- return builtinSceneInterfaceDescriptors(scriptSources)[assetId].interface;
32
- }
33
- export function builtinSceneInterfaces(scriptSources) {
34
- const descriptors = builtinSceneInterfaceDescriptors(scriptSources);
35
- return Object.fromEntries(builtinSceneAssetIds().map((assetId) => [assetId, descriptors[assetId].interface]));
36
- }
37
- function builtinSceneInterfaceDescriptors(scriptSources) {
38
- const descriptors = extractSceneScriptInterfaces(builtinSceneNames.map((name) => ({
39
- scene: builtinSceneAssetId(name),
40
- fileName: `${name}.ts`,
41
- source: scriptSources[name],
42
- })));
43
- for (const assetId of builtinSceneAssetIds()) {
44
- const name = builtinSceneNameFromAssetId(assetId);
45
- const descriptor = descriptors[assetId];
46
- if (!descriptor) {
47
- throw new Error(`Built-in Scene "${assetId}" is missing a @scene script.`);
48
- }
49
- if (descriptor.className !== name) {
50
- throw new Error(`Built-in Scene "${assetId}" name must match @scene class "${descriptor.className}".`);
51
- }
52
- }
53
- return descriptors;
54
- }
@@ -1,4 +1,5 @@
1
1
  import { pixiSceneNodeAcceptsChildren } from '../pixiNodeSchema.js';
2
+ import { isSceneTemplateBindingValue } from '../spec.js';
2
3
  function cloneNode(node) {
3
4
  return structuredClone(node);
4
5
  }
@@ -217,7 +218,7 @@ function getPropPathValue(props, prop) {
217
218
  return props[prop];
218
219
  }
219
220
  const value = props[root];
220
- return value && typeof value === 'object' ? value[field] : undefined;
221
+ return value && typeof value === 'object' && !isSceneTemplateBindingValue(value) ? value[field] : undefined;
221
222
  }
222
223
  function setPropPathValue(props, prop, value) {
223
224
  const [root, field, ...rest] = prop.split('.');
@@ -231,7 +232,7 @@ function setPropPathValue(props, prop, value) {
231
232
  return;
232
233
  }
233
234
  const current = props[root];
234
- const objectValue = current && typeof current === 'object'
235
+ const objectValue = current && typeof current === 'object' && !isSceneTemplateBindingValue(current)
235
236
  ? { ...current }
236
237
  : {};
237
238
  if (value === undefined) {
@@ -338,11 +339,13 @@ function moveNode(template, command, context) {
338
339
  if (!source || !target || !canInsertNodeIntoParent(command.parent, source.node, target.acceptsSlotOutlet)) {
339
340
  return { ok: false, command, error: 'Target cannot contain this node.' };
340
341
  }
342
+ const sameChildList = source.nodes === target.nodes;
341
343
  const [node] = source.nodes.splice(source.index, 1);
342
- let index = Math.max(0, Math.min(command.index, target.nodes.length));
343
- if (source.nodes === target.nodes && source.index < index) {
344
+ let index = command.index;
345
+ if (sameChildList && source.index < index) {
344
346
  index -= 1;
345
347
  }
348
+ index = Math.max(0, Math.min(index, target.nodes.length));
346
349
  target.nodes.splice(index, 0, node);
347
350
  const locator = findCompilerSceneNodeLocator(template.children, node);
348
351
  if (!locator) {
@@ -355,7 +358,7 @@ function moveNode(template, command, context) {
355
358
  op: 'moveNode',
356
359
  node: locator,
357
360
  parent: source.parentLocator,
358
- index: source.index,
361
+ index: sameChildList && index < source.index ? source.index + 1 : source.index,
359
362
  },
360
363
  selection: { type: 'node', node: locator },
361
364
  };
@@ -1,3 +1,4 @@
1
+ import { type SceneValidationDiagnostic } from './sceneValidation.js';
1
2
  export interface CompileScenesOptions {
2
3
  projectRoot: string | URL;
3
4
  sourceRoots?: string[];
@@ -7,6 +8,7 @@ export declare const generatedSceneAssetsFileName = "scene-assets.generated.json
7
8
  export declare class CompileSceneError extends Error {
8
9
  readonly scene: string;
9
10
  readonly source?: string | undefined;
10
- constructor(message: string, scene: string, source?: string | undefined);
11
+ readonly diagnostics?: SceneValidationDiagnostic[] | undefined;
12
+ constructor(message: string, scene: string, source?: string | undefined, diagnostics?: SceneValidationDiagnostic[] | undefined);
11
13
  }
12
14
  export declare function compileScenes(options: CompileScenesOptions): Promise<void>;
@@ -4,8 +4,9 @@ import { fileURLToPath } from 'node:url';
4
4
  import { builtinSceneAssetIds, builtinSceneNameFromAssetId, isBuiltinSceneAssetId, } from './builtinScenes.js';
5
5
  import { defaultSceneSourceRoots, generatedSceneModuleImport, generatedSceneModulePath, isIgnoredSceneSourceDirectory, normalizeSceneAssetId, pairedSceneScriptPath, resolveSceneReference, sceneClassAlias, sceneLocalName, toPosixPath, } from './sceneAssetPair.js';
6
6
  import { builtinSceneScriptPath, readBuiltinSceneScriptSources, readBuiltinSceneSource, } from '../compiler-node/builtinSceneAssets.js';
7
- import { extractSceneScriptInterfaces } from './scriptInterfaceExtractor.js';
7
+ import { extractSceneScriptInterfaces } from '../compiler-node/scriptInterfaceExtractor.js';
8
8
  import { findMissingScenePartReferences } from './scenePartValidation.js';
9
+ import { validateSceneContent } from './sceneValidation.js';
9
10
  import { parseSceneTemplate } from './templateParser.js';
10
11
  import { compileSceneTemplateToTs } from './typescriptCompiler.js';
11
12
  import { parsePixifactProjectConfig, pixifactProjectConfigFileName, } from '../project/index.js';
@@ -13,10 +14,12 @@ export const generatedSceneAssetsFileName = 'scene-assets.generated.json';
13
14
  export class CompileSceneError extends Error {
14
15
  scene;
15
16
  source;
16
- constructor(message, scene, source) {
17
+ diagnostics;
18
+ constructor(message, scene, source, diagnostics) {
17
19
  super(message);
18
20
  this.scene = scene;
19
21
  this.source = source;
22
+ this.diagnostics = diagnostics;
20
23
  this.name = 'CompileSceneError';
21
24
  }
22
25
  }
@@ -64,6 +67,33 @@ export async function compileScenes(options) {
64
67
  throw new CompileSceneError(error instanceof Error ? error.message : String(error), scenePath, sceneSources.get(scenePath));
65
68
  }
66
69
  }
70
+ for (const record of sceneRecords) {
71
+ const { scenePath } = record;
72
+ const template = templates.get(scenePath);
73
+ const source = sceneSources.get(scenePath);
74
+ if (!template || source === undefined) {
75
+ throw new Error(`Missing bound template for ${scenePath}.`);
76
+ }
77
+ try {
78
+ const result = validateSceneContent({
79
+ scene: scenePath,
80
+ bindingsOnly: true,
81
+ content: source,
82
+ sceneInterface: template.interface,
83
+ sceneInterfaces: sceneInterfacesFor(template, templates),
84
+ normalizeSceneReference: (scene) => resolveSceneReference(scenePath, scene),
85
+ });
86
+ if (!result.ok) {
87
+ throw new CompileSceneError(result.error, scenePath, source, result.diagnostics);
88
+ }
89
+ }
90
+ catch (error) {
91
+ if (error instanceof CompileSceneError) {
92
+ throw error;
93
+ }
94
+ throw new CompileSceneError(error instanceof Error ? error.message : String(error), scenePath, source);
95
+ }
96
+ }
67
97
  const projectRegistryImports = [];
68
98
  const builtinRegistryImports = [];
69
99
  const projectAssets = new Set();
@@ -2,7 +2,6 @@ export * from './commands/index.js';
2
2
  export * from './builtinScenes.js';
3
3
  export * from './pixiNodeSchema.js';
4
4
  export * from './spec.js';
5
- export * from './scriptInterfaceExtractor.js';
6
5
  export * from './sceneAssetPair.js';
7
6
  export * from './scenePartValidation.js';
8
7
  export * from './sceneValidation.js';
@@ -2,7 +2,6 @@ export * from './commands/index.js';
2
2
  export * from './builtinScenes.js';
3
3
  export * from './pixiNodeSchema.js';
4
4
  export * from './spec.js';
5
- export * from './scriptInterfaceExtractor.js';
6
5
  export * from './sceneAssetPair.js';
7
6
  export * from './scenePartValidation.js';
8
7
  export * from './sceneValidation.js';
@@ -1,9 +1,10 @@
1
1
  import type { SceneTemplatePrimitiveType, SceneTemplateValue } from './spec.js';
2
- export type PixiSceneNodeType = Extract<SceneTemplatePrimitiveType, 'Group' | 'Container' | 'Sprite' | 'NineSliceSprite' | 'TilingSprite' | 'Text' | 'BitmapText' | 'HTMLText' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer'>;
2
+ export type PixiSceneNodeType = Extract<SceneTemplatePrimitiveType, 'Group' | 'Container' | 'Sprite' | 'NineSliceSprite' | 'TilingSprite' | 'Text' | 'Label' | 'BitmapLabel' | 'BitmapText' | 'HTMLText' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer'>;
3
3
  export type PixiSceneFieldType = 'string' | 'number' | 'boolean' | 'color' | 'enum';
4
4
  export type PixiScenePropGroup = 'transform' | 'display' | 'sprite' | 'nineSlice' | 'tiling' | 'text' | 'graphics' | 'stack' | 'props';
5
5
  export interface PixiSceneFieldSchema {
6
6
  key: string;
7
+ resource?: 'image';
7
8
  type: PixiSceneFieldType;
8
9
  options?: readonly (string | number)[];
9
10
  }
@@ -23,6 +24,7 @@ export declare const pixiSceneTileImageProps: readonly ["texture", "tilePosition
23
24
  export declare const pixiSceneNineSliceProps: readonly ["leftWidth", "rightWidth", "topHeight", "bottomHeight"];
24
25
  export declare const pixiSceneTilingProps: readonly ["tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation"];
25
26
  export declare const pixiSceneTextProps: readonly ["text", "fontSize", "fontFamily", "fontWeight", "fill"];
27
+ export declare const pixiSceneLabelProps: readonly ["text", "fontSize", "fontFamily", "fontWeight", "fill", "lineHeight", "letterSpacing", "wordWrap", "alignX", "alignY", "overflow"];
26
28
  export declare const pixiSceneGraphicsProps: readonly ["shape", "radius", "fill", "fillAlpha", "strokeColor", "strokeWidth", "strokeAlpha"];
27
29
  export declare const pixiSceneRectProps: readonly ["fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth", "radius"];
28
30
  export declare const pixiSceneStackHBoxProps: readonly ["gap", "alignY", "justify"];
@@ -32,8 +34,8 @@ export declare const pixiSceneStackProps: readonly ["columns", "gap", "gapX", "g
32
34
  export declare const pixiSceneScrollProps: readonly ["direction", "scrollX", "scrollY"];
33
35
  export declare const pixiSceneTextStyleProps: readonly ["fontSize", "fontFamily", "fontWeight", "fill"];
34
36
  export declare const pixiSceneSpriteLikeProps: readonly ["texture", "anchorX", "anchorY", "tint", "texture", "fit", "anchorX", "anchorY", "tint", "texture", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "anchorX", "anchorY", "tint", "texture", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "anchorX", "anchorY", "tint", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation"];
35
- export declare const pixiSceneKnownProps: readonly ["x", "y", "width", "height", "scaleX", "scaleY", "rotation", "pivotX", "pivotY", "skewX", "skewY", "left", "right", "top", "bottom", "horizontal", "vertical", "alpha", "visible", "zIndex", "eventMode", "cursor", "label", "texture", "anchorX", "anchorY", "tint", "texture", "fit", "anchorX", "anchorY", "tint", "texture", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "anchorX", "anchorY", "tint", "texture", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "anchorX", "anchorY", "tint", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "text", "fontSize", "fontFamily", "fontWeight", "fill", "shape", "radius", "fill", "fillAlpha", "strokeColor", "strokeWidth", "strokeAlpha", "fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth", "radius", "columns", "gap", "gapX", "gapY", "alignX", "alignY", "justify", "direction", "scrollX", "scrollY"];
36
- export declare const pixiSceneAddableNodeTypes: readonly ["Group", "GridContainer", "HBoxContainer", "ScrollContainer", "VBoxContainer", "Container", "Sprite", "NineSliceSprite", "TilingSprite", "Text", "BitmapText", "HTMLText", "Graphics", "Rect", "Image", "NineImage", "TileImage"];
37
+ export declare const pixiSceneKnownProps: readonly ["x", "y", "width", "height", "scaleX", "scaleY", "rotation", "pivotX", "pivotY", "skewX", "skewY", "left", "right", "top", "bottom", "horizontal", "vertical", "alpha", "visible", "zIndex", "eventMode", "cursor", "label", "texture", "anchorX", "anchorY", "tint", "texture", "fit", "anchorX", "anchorY", "tint", "texture", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "anchorX", "anchorY", "tint", "texture", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "anchorX", "anchorY", "tint", "leftWidth", "rightWidth", "topHeight", "bottomHeight", "tilePositionX", "tilePositionY", "tileScaleX", "tileScaleY", "tileRotation", "text", "fontSize", "fontFamily", "fontWeight", "fill", "text", "fontSize", "fontFamily", "fontWeight", "fill", "lineHeight", "letterSpacing", "wordWrap", "alignX", "alignY", "overflow", "shape", "radius", "fill", "fillAlpha", "strokeColor", "strokeWidth", "strokeAlpha", "fillColor", "fillAlpha", "strokeColor", "strokeAlpha", "strokeWidth", "radius", "columns", "gap", "gapX", "gapY", "alignX", "alignY", "justify", "direction", "scrollX", "scrollY"];
38
+ export declare const pixiSceneAddableNodeTypes: readonly ["Group", "GridContainer", "HBoxContainer", "ScrollContainer", "VBoxContainer", "Container", "Sprite", "NineSliceSprite", "TilingSprite", "Label", "BitmapLabel", "Text", "BitmapText", "HTMLText", "Graphics", "Rect", "Image", "NineImage", "TileImage"];
37
39
  export declare const pixiSceneNodePropGroupOrder: readonly ["stack", "sprite", "nineSlice", "tiling", "text", "graphics", "props"];
38
40
  export declare function pixiSceneNodeSchema(type: PixiSceneNodeType): PixiSceneNodeSchema;
39
41
  export declare function isPixiSceneNodeType(type: SceneTemplatePrimitiveType): type is PixiSceneNodeType;
@@ -81,6 +81,15 @@ export const pixiSceneTextProps = [
81
81
  'fontWeight',
82
82
  'fill',
83
83
  ];
84
+ export const pixiSceneLabelProps = [
85
+ ...pixiSceneTextProps,
86
+ 'lineHeight',
87
+ 'letterSpacing',
88
+ 'wordWrap',
89
+ 'alignX',
90
+ 'alignY',
91
+ 'overflow',
92
+ ];
84
93
  export const pixiSceneGraphicsProps = [
85
94
  'shape',
86
95
  'radius',
@@ -149,6 +158,7 @@ export const pixiSceneKnownProps = [
149
158
  ...pixiSceneDisplayProps,
150
159
  ...pixiSceneSpriteLikeProps,
151
160
  ...pixiSceneTextProps,
161
+ ...pixiSceneLabelProps,
152
162
  ...pixiSceneGraphicsProps,
153
163
  ...pixiSceneRectProps,
154
164
  ...pixiSceneStackProps,
@@ -164,6 +174,8 @@ export const pixiSceneAddableNodeTypes = [
164
174
  'Sprite',
165
175
  'NineSliceSprite',
166
176
  'TilingSprite',
177
+ 'Label',
178
+ 'BitmapLabel',
167
179
  'Text',
168
180
  'BitmapText',
169
181
  'HTMLText',
@@ -206,7 +218,7 @@ const pixiSceneFieldSchemas = {
206
218
  eventMode: { key: 'eventMode', type: 'enum', options: ['none', 'passive', 'auto', 'static', 'dynamic'] },
207
219
  cursor: { key: 'cursor', type: 'enum', options: ['default', 'pointer', 'text', 'grab', 'grabbing'] },
208
220
  label: { key: 'label', type: 'string' },
209
- texture: { key: 'texture', type: 'string' },
221
+ texture: { key: 'texture', type: 'string', resource: 'image' },
210
222
  fit: { key: 'fit', type: 'enum', options: ['stretch', 'contain', 'cover', 'none'] },
211
223
  anchorX: { key: 'anchorX', type: 'number' },
212
224
  anchorY: { key: 'anchorY', type: 'number' },
@@ -223,8 +235,12 @@ const pixiSceneFieldSchemas = {
223
235
  text: { key: 'text', type: 'string' },
224
236
  fontSize: { key: 'fontSize', type: 'number' },
225
237
  fontFamily: { key: 'fontFamily', type: 'string' },
226
- fontWeight: { key: 'fontWeight', type: 'enum', options: [400, 500, 600, 700, '400', '500', '600', '700', 'bold'] },
238
+ fontWeight: { key: 'fontWeight', type: 'enum', options: [400, 500, 600, 700, 'bold'] },
227
239
  fill: { key: 'fill', type: 'color' },
240
+ lineHeight: { key: 'lineHeight', type: 'number' },
241
+ letterSpacing: { key: 'letterSpacing', type: 'number' },
242
+ wordWrap: { key: 'wordWrap', type: 'boolean' },
243
+ overflow: { key: 'overflow', type: 'enum', options: ['visible', 'clip'] },
228
244
  fillColor: { key: 'fillColor', type: 'color' },
229
245
  shape: { key: 'shape', type: 'enum', options: ['roundRect', 'rect'] },
230
246
  radius: { key: 'radius', type: 'number' },
@@ -306,24 +322,64 @@ const pixiSceneNodeSchemas = {
306
322
  Text: {
307
323
  type: 'Text',
308
324
  acceptsChildren: false,
325
+ defaults: {
326
+ text: 'Text',
327
+ fontSize: 16,
328
+ fill: 0x111827,
329
+ },
330
+ groups: {
331
+ text: pixiSceneTextProps,
332
+ },
333
+ },
334
+ Label: {
335
+ type: 'Label',
336
+ acceptsChildren: false,
309
337
  defaults: {
310
338
  text: 'Text',
311
339
  width: 120,
312
340
  height: 28,
341
+ fontFamily: 'Arial',
313
342
  fontSize: 16,
343
+ fontWeight: 400,
314
344
  fill: 0x111827,
345
+ lineHeight: 0,
346
+ letterSpacing: 0,
347
+ wordWrap: false,
348
+ alignX: 'start',
349
+ alignY: 'start',
350
+ overflow: 'visible',
315
351
  },
316
352
  groups: {
317
- text: pixiSceneTextProps,
353
+ text: pixiSceneLabelProps,
318
354
  },
319
355
  },
320
- BitmapText: {
321
- type: 'BitmapText',
356
+ BitmapLabel: {
357
+ type: 'BitmapLabel',
322
358
  acceptsChildren: false,
323
359
  defaults: {
324
360
  text: 'Text',
325
361
  width: 120,
326
362
  height: 28,
363
+ fontFamily: 'Arial',
364
+ fontSize: 16,
365
+ fontWeight: 400,
366
+ fill: 0x111827,
367
+ lineHeight: 0,
368
+ letterSpacing: 0,
369
+ wordWrap: false,
370
+ alignX: 'start',
371
+ alignY: 'start',
372
+ overflow: 'visible',
373
+ },
374
+ groups: {
375
+ text: pixiSceneLabelProps,
376
+ },
377
+ },
378
+ BitmapText: {
379
+ type: 'BitmapText',
380
+ acceptsChildren: false,
381
+ defaults: {
382
+ text: 'Text',
327
383
  fontSize: 16,
328
384
  fill: 0x111827,
329
385
  },
@@ -336,8 +392,6 @@ const pixiSceneNodeSchemas = {
336
392
  acceptsChildren: false,
337
393
  defaults: {
338
394
  text: 'Text',
339
- width: 120,
340
- height: 28,
341
395
  fontSize: 16,
342
396
  fill: 0x111827,
343
397
  },
@@ -1,4 +1,4 @@
1
- import type { SceneTemplate, SceneTemplateInterface, SceneTemplateNode, SceneTemplateValue } from './spec.js';
1
+ import { type SceneTemplate, type SceneTemplateInterface, type SceneTemplateNode, type SceneTemplateValue } from './spec.js';
2
2
  export interface SceneTemplateNodeSummary {
3
3
  id?: string;
4
4
  kind: SceneTemplateNode['kind'];
@@ -29,6 +29,8 @@ export interface ValidateSceneContentOptions {
29
29
  content: string;
30
30
  existingAssets?: ReadonlySet<string>;
31
31
  sceneInterfaces?: Record<string, SceneTemplateInterface>;
32
+ sceneInterface?: SceneTemplateInterface;
33
+ bindingsOnly?: boolean;
32
34
  normalizeSceneReference?: (scene: string) => string;
33
35
  }
34
36
  export type SceneContentValidationResult = {
@@ -1,3 +1,4 @@
1
+ import { isSceneTemplateBindingValue, } from './spec.js';
1
2
  import { isPixiSceneNodeType, pixiSceneFieldSchema, pixiSceneLayoutProps, pixiSceneNodeAcceptsChildren, pixiSceneNodePropKeys, pixiSceneTransformProps, pixiSceneDisplayProps, } from './pixiNodeSchema.js';
2
3
  import { parseSceneTemplate } from './templateParser.js';
3
4
  import { serializeSceneTemplate } from './templateSerializer.js';
@@ -57,6 +58,9 @@ export function inspectSceneTemplate(template) {
57
58
  export function validateSceneContent(options) {
58
59
  try {
59
60
  const template = parseSceneTemplate(options.content);
61
+ if (options.sceneInterface) {
62
+ template.interface = options.sceneInterface;
63
+ }
60
64
  const diagnostics = validateSceneTemplate(template, options);
61
65
  const revision = createSceneRevision(options.content);
62
66
  if (diagnostics.length > 0) {
@@ -123,7 +127,7 @@ function validateSceneNode(node, path, context) {
123
127
  }
124
128
  if (node.kind === 'pixi') {
125
129
  return [
126
- ...validatePixiNode(node, path, context.existingAssets),
130
+ ...validatePixiNode(node, path, context),
127
131
  ...node.children.flatMap((child, index) => validateSceneNode(child, `${path}/${nodePathSegment(index, child)}`, context)),
128
132
  ];
129
133
  }
@@ -132,14 +136,14 @@ function validateSceneNode(node, path, context) {
132
136
  ...Object.entries(node.slots).flatMap(([slot, children]) => children.flatMap((child, index) => validateSceneNode(child, `${path}/slot:${slot}/${nodePathSegment(index, child)}`, context))),
133
137
  ];
134
138
  }
135
- function validatePixiNode(node, path, existingAssets) {
139
+ function validatePixiNode(node, path, context) {
136
140
  if (!isPixiSceneNodeType(node.type)) {
137
141
  return [{
138
142
  path,
139
143
  prop: 'type',
140
144
  expected: 'supported compiler Pixi node type',
141
145
  actual: node.type,
142
- hint: 'Use GridContainer, HBoxContainer, ScrollContainer, VBoxContainer, Container, Sprite, NineSliceSprite, TilingSprite, Text, BitmapText, HTMLText, Graphics, Rect, Image, NineImage, or TileImage.',
146
+ hint: 'Use GridContainer, HBoxContainer, ScrollContainer, VBoxContainer, Container, Sprite, NineSliceSprite, TilingSprite, Label, BitmapLabel, Text, BitmapText, HTMLText, Graphics, Rect, Image, NineImage, or TileImage.',
143
147
  }];
144
148
  }
145
149
  const diagnostics = [];
@@ -159,6 +163,9 @@ function validatePixiNode(node, path, existingAssets) {
159
163
  ...pixiSceneNodePropKeys(node.type),
160
164
  ]);
161
165
  for (const [prop, value] of Object.entries(node.props)) {
166
+ if (context.bindingsOnly && !isSceneTemplateBindingValue(value)) {
167
+ continue;
168
+ }
162
169
  if (!knownProps.has(prop)) {
163
170
  diagnostics.push({
164
171
  path,
@@ -170,6 +177,10 @@ function validatePixiNode(node, path, existingAssets) {
170
177
  continue;
171
178
  }
172
179
  const schema = pixiSceneFieldSchema(prop);
180
+ if (isSceneTemplateBindingValue(value)) {
181
+ diagnostics.push(...validateSceneBinding(path, prop, value, context.sceneInterface, schema?.type, schema?.options));
182
+ continue;
183
+ }
173
184
  if (schema && !sceneValueMatchesFieldType(value, schema.type, schema.options)) {
174
185
  diagnostics.push({
175
186
  path,
@@ -182,7 +193,7 @@ function validatePixiNode(node, path, existingAssets) {
182
193
  }
183
194
  const texture = node.props.texture;
184
195
  if (typeof texture === 'string') {
185
- const assetDiagnostic = validateTextureReference(path, texture, existingAssets);
196
+ const assetDiagnostic = validateTextureReference(path, texture, context.existingAssets);
186
197
  if (assetDiagnostic) {
187
198
  diagnostics.push(assetDiagnostic);
188
199
  }
@@ -224,6 +235,9 @@ function validateSceneInstanceNode(node, path, context) {
224
235
  ...Object.keys(sceneInterface.props),
225
236
  ]);
226
237
  for (const [prop, value] of Object.entries(node.props)) {
238
+ if (context.bindingsOnly && !isSceneTemplateBindingValue(value)) {
239
+ continue;
240
+ }
227
241
  if (!allowedProps.has(prop)) {
228
242
  diagnostics.push({
229
243
  path,
@@ -236,11 +250,26 @@ function validateSceneInstanceNode(node, path, context) {
236
250
  }
237
251
  const schema = pixiSceneFieldSchema(prop);
238
252
  const contract = sceneInterface.props[prop];
253
+ if (isSceneTemplateBindingValue(value)) {
254
+ const expectedType = contract?.type === 'variant' ? 'string' : contract?.type ?? schema?.type;
255
+ diagnostics.push(...validateSceneBinding(path, prop, value, context.sceneInterface, expectedType, schema?.options));
256
+ continue;
257
+ }
239
258
  if (contract?.type === 'struct') {
240
259
  diagnostics.push(...validateSceneInstanceStructProp(node, path, prop, value, contract));
241
260
  continue;
242
261
  }
243
262
  const expectedType = contract?.type ?? schema?.type;
263
+ if (contract?.type === 'variant' && typeof value === 'string' && !contract.variants[value]) {
264
+ diagnostics.push({
265
+ path,
266
+ prop,
267
+ expected: `one of ${Object.keys(contract.variants).map((name) => JSON.stringify(name)).join(', ')}`,
268
+ actual: JSON.stringify(value),
269
+ hint: `Set ${node.type}.${prop} to a declared Variant case.`,
270
+ });
271
+ continue;
272
+ }
244
273
  if (expectedType && !sceneValueMatchesContractType(value, expectedType, schema?.options)) {
245
274
  diagnostics.push({
246
275
  path,
@@ -251,7 +280,7 @@ function validateSceneInstanceNode(node, path, context) {
251
280
  });
252
281
  }
253
282
  }
254
- for (const eventName of Object.keys(node.events)) {
283
+ for (const eventName of context.bindingsOnly ? [] : Object.keys(node.events)) {
255
284
  if (!sceneInterface.events[eventName]) {
256
285
  diagnostics.push({
257
286
  path,
@@ -262,7 +291,7 @@ function validateSceneInstanceNode(node, path, context) {
262
291
  });
263
292
  }
264
293
  }
265
- for (const slot of Object.keys(node.slots)) {
294
+ for (const slot of context.bindingsOnly ? [] : Object.keys(node.slots)) {
266
295
  if (!sceneInterface.slots[slot]) {
267
296
  diagnostics.push({
268
297
  path: `${path}/slot:${slot}`,
@@ -275,6 +304,76 @@ function validateSceneInstanceNode(node, path, context) {
275
304
  }
276
305
  return diagnostics;
277
306
  }
307
+ function validateSceneBinding(path, targetProp, binding, sceneInterface, expectedType, options) {
308
+ const [prop, field] = binding.path;
309
+ const contract = sceneInterface?.props[prop];
310
+ if (!contract) {
311
+ return [{
312
+ path,
313
+ prop: targetProp,
314
+ expected: `Scene Prop declared by ${sceneInterface ? 'the paired script' : 'the owner Scene'}`,
315
+ actual: 'unknown binding prop',
316
+ hint: `Declare @prop "${prop}" in the paired Scene script.`,
317
+ }];
318
+ }
319
+ let sourceType;
320
+ let sourceValues;
321
+ if (field) {
322
+ if (contract.type !== 'variant') {
323
+ return [{
324
+ path,
325
+ prop: targetProp,
326
+ expected: `Variant Prop for binding field "${field}"`,
327
+ actual: `${contract.type} binding`,
328
+ hint: `Use {${prop}} or declare ${prop} with defineVariants().`,
329
+ }];
330
+ }
331
+ sourceValues = Object.values(contract.variants).map((variant) => variant[field]).filter((value) => value !== undefined);
332
+ if (sourceValues.length !== Object.keys(contract.variants).length) {
333
+ return [{
334
+ path,
335
+ prop: targetProp,
336
+ expected: `Variant field declared by ${prop}`,
337
+ actual: 'unknown binding field',
338
+ hint: `Use one of ${Object.keys(Object.values(contract.variants)[0] ?? {}).map((name) => JSON.stringify(name)).join(', ')}.`,
339
+ }];
340
+ }
341
+ sourceType = typeof sourceValues[0];
342
+ }
343
+ else if (contract.type === 'struct') {
344
+ sourceType = 'object';
345
+ }
346
+ else if (contract.type === 'variant') {
347
+ sourceType = 'string';
348
+ sourceValues = Object.keys(contract.variants);
349
+ }
350
+ else {
351
+ sourceType = contract.type;
352
+ if (contract.default !== undefined) {
353
+ sourceValues = [contract.default];
354
+ }
355
+ }
356
+ const textAcceptsScalar = targetProp === 'text' && ['string', 'number', 'boolean'].includes(sourceType);
357
+ if (expectedType && !textAcceptsScalar && !bindingSourceMatchesTarget(sourceType, sourceValues, expectedType, options)) {
358
+ return [{
359
+ path,
360
+ prop: targetProp,
361
+ expected: expectedType,
362
+ actual: `${sourceType} binding`,
363
+ hint: `Bind ${targetProp} to a Scene Prop with a compatible value type.`,
364
+ }];
365
+ }
366
+ return [];
367
+ }
368
+ function bindingSourceMatchesTarget(sourceType, values, expectedType, options) {
369
+ if (expectedType === 'color') {
370
+ return sourceType === 'number' || (sourceType === 'string' && values?.every((value) => (typeof value === 'string' && /^#[0-9a-fA-F]{6}$/.test(value))) === true);
371
+ }
372
+ if (expectedType === 'enum') {
373
+ return !!values && values.every((value) => options?.includes(value));
374
+ }
375
+ return sourceType === expectedType;
376
+ }
278
377
  function validateSceneInstanceStructProp(node, path, prop, value, contract) {
279
378
  if (!value || typeof value !== 'object') {
280
379
  return [{
@@ -1,7 +1,12 @@
1
- export type SceneTemplatePrimitiveType = 'Group' | 'Container' | 'Sprite' | 'Text' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'BitmapText' | 'HTMLText' | 'Mesh' | 'NineSliceSprite' | 'TilingSprite' | 'DOMContainer' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer';
1
+ export type SceneTemplatePrimitiveType = 'Group' | 'Container' | 'Sprite' | 'Text' | 'Label' | 'BitmapLabel' | 'Graphics' | 'Rect' | 'Image' | 'NineImage' | 'TileImage' | 'BitmapText' | 'HTMLText' | 'Mesh' | 'NineSliceSprite' | 'TilingSprite' | 'DOMContainer' | 'GridContainer' | 'HBoxContainer' | 'ScrollContainer' | 'VBoxContainer';
2
2
  export type SceneTemplateScalarValue = string | number | boolean;
3
3
  export type SceneTemplateStructValue = Record<string, SceneTemplateScalarValue>;
4
- export type SceneTemplateValue = SceneTemplateScalarValue | SceneTemplateStructValue;
4
+ export interface SceneTemplateBindingValue {
5
+ kind: 'binding';
6
+ path: [string] | [string, string];
7
+ }
8
+ export type SceneTemplateValue = SceneTemplateScalarValue | SceneTemplateStructValue | SceneTemplateBindingValue;
9
+ export declare function isSceneTemplateBindingValue(value: SceneTemplateValue | undefined): value is SceneTemplateBindingValue;
5
10
  export type SceneTemplatePrimitivePropType = 'string' | 'number' | 'boolean';
6
11
  export interface SceneTemplatePrimitivePropContract {
7
12
  type: SceneTemplatePrimitivePropType;
@@ -17,7 +22,14 @@ export interface SceneTemplateStructPropContract {
17
22
  sourceScene?: string;
18
23
  fields: Record<string, SceneTemplateStructFieldContract>;
19
24
  }
20
- export type SceneTemplatePropContract = SceneTemplatePrimitivePropContract | SceneTemplateStructPropContract;
25
+ export type SceneTemplateVariantFields = Record<string, SceneTemplateScalarValue>;
26
+ export type SceneTemplateVariants = Record<string, SceneTemplateVariantFields>;
27
+ export interface SceneTemplateVariantPropContract {
28
+ type: 'variant';
29
+ default: string;
30
+ variants: SceneTemplateVariants;
31
+ }
32
+ export type SceneTemplatePropContract = SceneTemplatePrimitivePropContract | SceneTemplateStructPropContract | SceneTemplateVariantPropContract;
21
33
  export interface SceneTemplateEventContract {
22
34
  type: 'action';
23
35
  }
@@ -84,11 +96,10 @@ export interface SceneScriptInterface {
84
96
  }
85
97
  export type SceneClassDecorator = ClassDecorator;
86
98
  export type SceneMemberDecorator = PropertyDecorator & MethodDecorator;
87
- export type SceneStructConstructor = new () => object;
88
- export type ScenePropType = StringConstructor | NumberConstructor | BooleanConstructor | SceneStructConstructor;
99
+ export type SceneVariants = Record<string, Record<string, SceneTemplateScalarValue>>;
89
100
  export interface ScenePropDecoratorOptions {
90
- type: ScenePropType;
91
- default?: SceneTemplateValue;
101
+ default?: SceneTemplateScalarValue;
102
+ variants?: SceneVariants;
92
103
  }
93
104
  export interface SceneEventDecoratorOptions {
94
105
  name?: string;
@@ -1 +1,3 @@
1
- export {};
1
+ export function isSceneTemplateBindingValue(value) {
2
+ return !!value && typeof value === 'object' && value.kind === 'binding';
3
+ }