pixifact 0.3.0 → 0.5.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 (73) hide show
  1. package/README.md +14 -0
  2. package/dist/compiler/builtinScenes.d.ts +0 -5
  3. package/dist/compiler/builtinScenes.js +0 -26
  4. package/dist/compiler/commands/index.js +8 -5
  5. package/dist/compiler/compileScenes.d.ts +4 -1
  6. package/dist/compiler/compileScenes.js +40 -11
  7. package/dist/compiler/index.d.ts +0 -5
  8. package/dist/compiler/index.js +0 -5
  9. package/dist/compiler/pixiNodeSchema.d.ts +4 -3
  10. package/dist/compiler/pixiNodeSchema.js +60 -6
  11. package/dist/compiler/sceneValidation.d.ts +3 -1
  12. package/dist/compiler/sceneValidation.js +105 -6
  13. package/dist/compiler/spec.d.ts +19 -8
  14. package/dist/compiler/spec.js +3 -1
  15. package/dist/compiler/templateParser.js +19 -0
  16. package/dist/compiler/templateSerializer.js +7 -1
  17. package/dist/compiler/typescriptCompiler.js +118 -51
  18. package/dist/compiler-node/builtinSceneInterfaces.d.ts +6 -0
  19. package/dist/compiler-node/builtinSceneInterfaces.js +27 -0
  20. package/dist/compiler-node/index.d.ts +2 -0
  21. package/dist/compiler-node/index.js +2 -0
  22. package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.d.ts +1 -1
  23. package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.js +157 -25
  24. package/dist/compiler-node/vite.d.ts +2 -1
  25. package/dist/compiler-node/vite.js +29 -2
  26. package/dist/platform/wechat/adapter.d.ts +316 -0
  27. package/dist/platform/wechat/adapter.js +26 -0
  28. package/dist/platform/wechat/application.d.ts +32 -0
  29. package/dist/platform/wechat/application.js +79 -0
  30. package/dist/platform/wechat/fetch.d.ts +1 -0
  31. package/dist/platform/wechat/fetch.js +145 -0
  32. package/dist/platform/wechat/index.d.ts +4 -0
  33. package/dist/platform/wechat/index.js +3 -0
  34. package/dist/platform/wechat/input.d.ts +12 -0
  35. package/dist/platform/wechat/input.js +93 -0
  36. package/dist/platform/wechat/lifecycle.d.ts +14 -0
  37. package/dist/platform/wechat/lifecycle.js +42 -0
  38. package/dist/platform/wechat/runtime.d.ts +49 -0
  39. package/dist/platform/wechat/runtime.js +189 -0
  40. package/dist/platform/wechat/subpackage.d.ts +1 -0
  41. package/dist/platform/wechat/subpackage.js +27 -0
  42. package/dist/platform/wechat/types.d.ts +105 -0
  43. package/dist/platform/wechat/types.js +3 -0
  44. package/dist/project/index.d.ts +23 -0
  45. package/dist/project/index.js +69 -0
  46. package/dist/runtime/BitmapLabel.d.ts +9 -0
  47. package/dist/runtime/BitmapLabel.js +7 -0
  48. package/dist/runtime/Label.d.ts +9 -0
  49. package/dist/runtime/Label.js +7 -0
  50. package/dist/runtime/ScrollContainer.d.ts +2 -0
  51. package/dist/runtime/ScrollContainer.js +12 -2
  52. package/dist/runtime/TextBoxControl.d.ts +48 -0
  53. package/dist/runtime/TextBoxControl.js +162 -0
  54. package/dist/runtime/index.d.ts +2 -0
  55. package/dist/runtime/index.js +2 -0
  56. package/dist/runtime/runtimeBehavior.d.ts +3 -0
  57. package/dist/runtime/runtimeBehavior.js +11 -0
  58. package/dist/{compiler → scene}/decorators.d.ts +2 -1
  59. package/dist/{compiler → scene}/decorators.js +12 -7
  60. package/dist/scene/index.d.ts +6 -0
  61. package/dist/scene/index.js +6 -0
  62. package/dist/scene/sceneAssetRuntime.d.ts +11 -0
  63. package/dist/scene/sceneAssetRuntime.js +43 -0
  64. package/dist/scene/sceneBindingRuntime.d.ts +10 -0
  65. package/dist/scene/sceneBindingRuntime.js +124 -0
  66. package/dist/{compiler → scene}/sceneRuntime.d.ts +7 -0
  67. package/dist/scene/sceneRuntime.js +85 -0
  68. package/package.json +11 -1
  69. package/dist/compiler/sceneRuntime.js +0 -22
  70. /package/dist/{compiler → scene}/eventRuntime.d.ts +0 -0
  71. /package/dist/{compiler → scene}/eventRuntime.js +0 -0
  72. /package/dist/{compiler → scene}/slotRuntime.d.ts +0 -0
  73. /package/dist/{compiler → scene}/slotRuntime.js +0 -0
@@ -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
  }
@@ -73,8 +85,8 @@ export interface CompileSceneTemplateOptions {
73
85
  scriptImport?: SceneTemplateScriptImport;
74
86
  sceneImports?: SceneTemplateScriptImport[];
75
87
  sceneClassAliases?: Record<string, string>;
88
+ sceneDependencies?: string[];
76
89
  sceneInterfaces?: Record<string, SceneTemplateInterface>;
77
- textureImports?: Record<string, string>;
78
90
  }
79
91
  export interface SceneScriptInterface {
80
92
  scene: string;
@@ -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
+ }
@@ -5,6 +5,8 @@ const pixiTypes = new Set([
5
5
  'Container',
6
6
  'Sprite',
7
7
  'Text',
8
+ 'Label',
9
+ 'BitmapLabel',
8
10
  'Graphics',
9
11
  'Rect',
10
12
  'Image',
@@ -148,6 +150,10 @@ function isEventAttribute(name) {
148
150
  return eventName(name) !== undefined;
149
151
  }
150
152
  function parseAttributeValue(name, value) {
153
+ const binding = parseBindingValue(value);
154
+ if (binding) {
155
+ return binding;
156
+ }
151
157
  if (pixiSceneFieldSchema(name)?.type === 'string') {
152
158
  return value;
153
159
  }
@@ -161,6 +167,19 @@ function parseAttributeValue(name, value) {
161
167
  return Number.parseInt(value.slice(1), 16);
162
168
  return value;
163
169
  }
170
+ function parseBindingValue(value) {
171
+ const match = /^\{([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*))?\}$/.exec(value);
172
+ if (!match) {
173
+ if (value.startsWith('{') || value.endsWith('}')) {
174
+ throw new Error(`Unsupported Scene binding "${value}". Use {prop} or {variant.field}.`);
175
+ }
176
+ return undefined;
177
+ }
178
+ return {
179
+ kind: 'binding',
180
+ path: match[2] ? [match[1], match[2]] : [match[1]],
181
+ };
182
+ }
164
183
  function removeAttribute(element, name) {
165
184
  const attributes = { ...element.attributes };
166
185
  delete attributes[name];
@@ -79,18 +79,24 @@ function serializeAttributes(attributes) {
79
79
  }
80
80
  function flattenAttributes(attributes) {
81
81
  return attributes.flatMap(([name, value]) => {
82
- if (!value || typeof value !== 'object') {
82
+ if (!value || typeof value !== 'object' || isBindingValue(value)) {
83
83
  return [[name, value]];
84
84
  }
85
85
  return Object.entries(value).map(([field, fieldValue]) => [`${name}.${field}`, fieldValue]);
86
86
  });
87
87
  }
88
88
  function formatAttributeValue(name, value) {
89
+ if (isBindingValue(value)) {
90
+ return `{${value.path.join('.')}}`;
91
+ }
89
92
  if (typeof value === 'number' && colorPropNames.has(name)) {
90
93
  return `#${value.toString(16).padStart(6, '0').slice(-6)}`;
91
94
  }
92
95
  return String(value);
93
96
  }
97
+ function isBindingValue(value) {
98
+ return typeof value === 'object' && value.kind === 'binding';
99
+ }
94
100
  function escapeAttribute(value) {
95
101
  return value
96
102
  .replaceAll('&', '&amp;')
@@ -1,13 +1,18 @@
1
- import { pixiSceneDisplayProps, pixiSceneGraphicsProps, pixiSceneLayoutProps, pixiSceneRectProps, pixiSceneSpriteLikeProps, pixiSceneTextStyleProps, pixiSceneTransformProps, } from './pixiNodeSchema.js';
1
+ import { pixiSceneDisplayProps, pixiSceneGraphicsProps, pixiSceneLabelProps, pixiSceneLayoutProps, pixiSceneRectProps, pixiSceneSpriteLikeProps, pixiSceneTextStyleProps, pixiSceneTransformProps, } from './pixiNodeSchema.js';
2
+ import { compilerSceneNodeLocator } from './commands/index.js';
2
3
  const transformProps = new Set(pixiSceneTransformProps);
3
4
  const layoutProps = new Set(pixiSceneLayoutProps);
4
5
  const pixiProps = new Set(pixiSceneDisplayProps);
5
6
  const spriteProps = new Set(pixiSceneSpriteLikeProps);
6
7
  const graphicsProps = new Set(pixiSceneGraphicsProps);
8
+ const labelProps = new Set(pixiSceneLabelProps);
7
9
  const rectProps = new Set(pixiSceneRectProps);
8
10
  const textStyleProps = new Set(pixiSceneTextStyleProps);
9
- const runtimeNodeTypes = new Set(['Group', 'GridContainer', 'HBoxContainer', 'ScrollContainer', 'VBoxContainer', 'Rect', 'Image', 'NineImage', 'TileImage']);
11
+ const runtimeNodeTypes = new Set(['Group', 'GridContainer', 'HBoxContainer', 'ScrollContainer', 'VBoxContainer', 'Label', 'BitmapLabel', 'Rect', 'Image', 'NineImage', 'TileImage']);
10
12
  const runtimeNodeProps = new Set(['columns', 'gap', 'gapX', 'gapY', 'alignX', 'alignY', 'justify', 'direction', 'scrollX', 'scrollY']);
13
+ function isSceneBindingValue(value) {
14
+ return !!value && typeof value === 'object' && value.kind === 'binding';
15
+ }
11
16
  export function compileSceneTemplateToTs(template, options = {}) {
12
17
  const context = new CompileContext(template, options);
13
18
  return context.compile();
@@ -37,16 +42,18 @@ class CompileContext {
37
42
  const functionName = this.options.functionName || `mount${this.template.name}Scene`;
38
43
  const actionsParameter = this.options.actionsParameter || 'actions';
39
44
  const partsType = this.#formatPartsType();
40
- const textureLoads = this.#formatTextureLoads();
45
+ const texturePreparation = this.#formatTexturePreparation();
41
46
  this.#lines.push(`export function ${functionName}(root: Group${this.#hasEvents() ? `, ${actionsParameter}: Record<string, () => void> = {}` : ''}) {`);
47
+ this.#lines.push(' const __pixifactNodes: Record<string, Container> = {};');
42
48
  this.#lines.push(' const __pixifactSlots: Record<string, Container> = {};');
43
49
  this.#applyRootProps();
44
- for (const child of this.template.children) {
45
- this.#compileNode(child, 'root', actionsParameter);
50
+ for (const [index, child] of this.template.children.entries()) {
51
+ this.#compileNode(child, 'root', actionsParameter, String(index));
46
52
  }
47
53
  this.#lines.push(' return {');
48
54
  this.#lines.push(' root,');
49
55
  this.#lines.push(` parts: { ${this.#parts.map((part) => part.id).join(', ')} },`);
56
+ this.#lines.push(' nodes: __pixifactNodes,');
50
57
  this.#lines.push(' slots: __pixifactSlots,');
51
58
  this.#lines.push(' };');
52
59
  this.#lines.push('}');
@@ -57,7 +64,10 @@ class CompileContext {
57
64
  }
58
65
  this.#lines.push('');
59
66
  this.#lines.push(`registerScene(${JSON.stringify(this.options.registrationPath)}, {`);
67
+ this.#lines.push(` assets: ${JSON.stringify([...this.#textures.keys()])},`);
68
+ this.#lines.push(` dependencies: ${JSON.stringify(this.options.sceneDependencies ?? [])},`);
60
69
  this.#lines.push(` mount: ${functionName},`);
70
+ this.#lines.push(` prepare: ${this.#prepareFunctionName()},`);
61
71
  this.#lines.push('});');
62
72
  if (this.options.scriptImport) {
63
73
  this.#lines.push(`registerSceneClass(${this.options.scriptImport.localName}, ${JSON.stringify(this.options.registrationPath)});`);
@@ -66,7 +76,7 @@ class CompileContext {
66
76
  const imports = this.#formatImports();
67
77
  return [
68
78
  imports,
69
- textureLoads,
79
+ texturePreparation,
70
80
  partsType,
71
81
  this.#lines.join('\n'),
72
82
  ].filter(Boolean).join('\n\n') + '\n';
@@ -79,9 +89,6 @@ class CompileContext {
79
89
  ])];
80
90
  const runtimeImports = [...new Set(['Group', ...[...this.#pixiRuntimeImports].sort()])];
81
91
  const lines = [`import { ${imports.join(', ')} } from 'pixi.js';`, `import { ${runtimeImports.join(', ')} } from 'pixifact/runtime';`];
82
- for (const [texture, variable] of this.#textureImportEntries()) {
83
- lines.push(`import ${variable} from ${JSON.stringify(this.options.textureImports?.[texture])};`);
84
- }
85
92
  for (const sceneImport of this.options.sceneImports ?? []) {
86
93
  const classImport = sceneImport.exportName === sceneImport.localName
87
94
  ? sceneImport.exportName
@@ -98,7 +105,7 @@ class CompileContext {
98
105
  lines.push(`import { ${this.options.scriptImport.exportName} as ${this.options.scriptImport.localName} } from ${JSON.stringify(this.options.scriptImport.source)};`);
99
106
  }
100
107
  if (this.#runtimeImports.size > 0) {
101
- lines.push(`import { ${[...this.#runtimeImports].sort().join(', ')} } from 'pixifact/compiler';`);
108
+ lines.push(`import { ${[...this.#runtimeImports].sort().join(', ')} } from 'pixifact/scene';`);
102
109
  }
103
110
  return lines.join('\n');
104
111
  }
@@ -109,6 +116,7 @@ class CompileContext {
109
116
  lines.push(` ${part.id}: ${part.type};`);
110
117
  }
111
118
  lines.push(' };');
119
+ lines.push(' nodes: Record<string, Container>;');
112
120
  lines.push(' slots: Record<string, Container>;');
113
121
  lines.push('};');
114
122
  return lines.join('\n');
@@ -174,16 +182,27 @@ class CompileContext {
174
182
  }
175
183
  }
176
184
  }
177
- #formatTextureLoads() {
185
+ #formatTexturePreparation() {
178
186
  if (this.#textures.size === 0) {
179
- return '';
187
+ return `export async function ${this.#prepareFunctionName()}() {}`;
180
188
  }
181
- this.#pixiImports.add('Assets');
182
- return [...this.#textures.entries()]
183
- .map(([texture, variable]) => `const ${variable} = await Assets.load(${this.#textureLoadSource(texture)});`)
184
- .join('\n');
185
- }
186
- #compileNode(node, parent, actionsParameter) {
189
+ this.#pixiImports.add('Texture');
190
+ this.#runtimeImports.add('loadSceneTexture');
191
+ const entries = [...this.#textures.entries()];
192
+ return [
193
+ ...entries.map(([, variable]) => `let ${variable}: Texture;`),
194
+ '',
195
+ `export async function ${this.#prepareFunctionName()}() {`,
196
+ ` [${entries.map(([, variable]) => variable).join(', ')}] = await Promise.all([`,
197
+ ...entries.map(([texture]) => ` loadSceneTexture(${JSON.stringify(texture)}),`),
198
+ ' ]);',
199
+ '}',
200
+ ].join('\n');
201
+ }
202
+ #prepareFunctionName() {
203
+ return `prepare${this.template.name}Scene`;
204
+ }
205
+ #compileNode(node, parent, actionsParameter, path) {
187
206
  if (node.kind === 'slotOutlet') {
188
207
  this.#runtimeImports.add('registerSlot');
189
208
  this.#lines.push(` __pixifactSlots[${JSON.stringify(node.name)}] = ${parent};`);
@@ -191,26 +210,30 @@ class CompileContext {
191
210
  return;
192
211
  }
193
212
  if (node.kind === 'sceneInstance') {
194
- this.#compileSceneInstance(node, parent, actionsParameter);
213
+ this.#compileSceneInstance(node, parent, actionsParameter, path);
195
214
  return;
196
215
  }
197
- this.#compilePixiNode(node, parent, actionsParameter);
216
+ this.#compilePixiNode(node, parent, actionsParameter, path);
198
217
  }
199
- #compilePixiNode(node, parent, actionsParameter) {
218
+ #compilePixiNode(node, parent, actionsParameter, path) {
200
219
  const variable = node.id || this.#anonymousName(node.type);
220
+ const locator = compilerSceneNodeLocator(node, path);
201
221
  this.#lines.push(` const ${variable} = ${this.#constructPixiNode(node)};`);
202
222
  this.#applyNodeId(variable, node.id);
203
223
  this.#applyPixiProps(variable, node.props, false, undefined, node.type);
204
224
  this.#applyParentSorting(parent, node.props);
205
225
  this.#lines.push(` ${parent}.addChild(${variable});`);
206
- for (const child of node.children) {
207
- this.#compileNode(child, variable, actionsParameter);
226
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
227
+ for (const [index, child] of node.children.entries()) {
228
+ this.#compileNode(child, variable, actionsParameter, `${locator}/${index}`);
208
229
  }
209
230
  }
210
- #compileSceneInstance(node, parent, actionsParameter) {
231
+ #compileSceneInstance(node, parent, actionsParameter, path) {
211
232
  const variable = node.id || this.#anonymousName(node.type);
233
+ const locator = compilerSceneNodeLocator(node, path);
212
234
  const constructorName = this.#sceneConstructorName(node);
213
- this.#lines.push(` const ${variable} = new ${constructorName}();`);
235
+ const constructorArgs = this.#sceneInstanceConstructorArgs(variable, node);
236
+ this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
214
237
  this.#applyPixiProps(variable, node.props, true, node);
215
238
  for (const [name, action] of Object.entries(node.events)) {
216
239
  this.#runtimeImports.add('connectSceneEvent');
@@ -218,30 +241,34 @@ class CompileContext {
218
241
  }
219
242
  this.#applyParentSorting(parent, node.props);
220
243
  this.#lines.push(` ${parent}.addChild(${variable});`);
244
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
221
245
  for (const [slot, children] of Object.entries(node.slots)) {
222
- for (const child of children) {
223
- const childVariable = this.#compileSlottedNode(child, variable, actionsParameter);
246
+ for (const [index, child] of children.entries()) {
247
+ const childVariable = this.#compileSlottedNode(child, variable, actionsParameter, `${locator}/slot:${slot}/${index}`);
224
248
  this.#runtimeImports.add('mount');
225
249
  this.#lines.push(` mount(${variable}, ${childVariable}, ${JSON.stringify(slot)});`);
226
250
  }
227
251
  }
228
252
  }
229
- #compileSlottedNode(node, slotTarget, actionsParameter) {
253
+ #compileSlottedNode(node, slotTarget, actionsParameter, path) {
230
254
  if (node.kind === 'slotOutlet') {
231
255
  throw new Error(`Cannot place <slot> inside ${slotTarget}.`);
232
256
  }
233
257
  if (node.kind === 'sceneInstance') {
234
258
  const variable = node.id || this.#anonymousName(node.type);
259
+ const locator = compilerSceneNodeLocator(node, path);
235
260
  const constructorName = this.#sceneConstructorName(node);
236
- this.#lines.push(` const ${variable} = new ${constructorName}();`);
261
+ const constructorArgs = this.#sceneInstanceConstructorArgs(variable, node);
262
+ this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
237
263
  this.#applyPixiProps(variable, node.props, true, node);
238
264
  for (const [name, action] of Object.entries(node.events)) {
239
265
  this.#runtimeImports.add('connectSceneEvent');
240
266
  this.#lines.push(` connectSceneEvent(${variable}.${name}, ${JSON.stringify(action)}, root, ${actionsParameter});`);
241
267
  }
268
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
242
269
  for (const [slot, children] of Object.entries(node.slots)) {
243
- for (const child of children) {
244
- const childVariable = this.#compileSlottedNode(child, variable, actionsParameter);
270
+ for (const [index, child] of children.entries()) {
271
+ const childVariable = this.#compileSlottedNode(child, variable, actionsParameter, `${locator}/slot:${slot}/${index}`);
245
272
  this.#runtimeImports.add('mount');
246
273
  this.#lines.push(` mount(${variable}, ${childVariable}, ${JSON.stringify(slot)});`);
247
274
  }
@@ -249,17 +276,19 @@ class CompileContext {
249
276
  return variable;
250
277
  }
251
278
  const variable = node.id || this.#anonymousName(node.type);
279
+ const locator = compilerSceneNodeLocator(node, path);
252
280
  this.#lines.push(` const ${variable} = ${this.#constructPixiNode(node)};`);
253
281
  this.#applyNodeId(variable, node.id);
254
282
  this.#applyPixiProps(variable, node.props, false, undefined, node.type);
255
- for (const child of node.children) {
256
- this.#compileNode(child, variable, actionsParameter);
283
+ this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
284
+ for (const [index, child] of node.children.entries()) {
285
+ this.#compileNode(child, variable, actionsParameter, `${locator}/${index}`);
257
286
  }
258
287
  return variable;
259
288
  }
260
289
  #constructPixiNode(node) {
261
290
  if (node.type === 'Text' || node.type === 'BitmapText' || node.type === 'HTMLText') {
262
- const text = this.#stringValue(node.props.text ?? '');
291
+ const text = this.#stringValue(isSceneBindingValue(node.props.text) ? '' : node.props.text ?? '');
263
292
  const style = this.#styleObject(node.props);
264
293
  return `new ${node.type}({ text: ${text}${style ? `, style: ${style}` : ''} })`;
265
294
  }
@@ -285,6 +314,31 @@ class CompileContext {
285
314
  #sceneConstructorName(node) {
286
315
  return this.options.sceneClassAliases?.[node.scene] ?? node.type;
287
316
  }
317
+ #sceneInstanceConstructorArgs(variable, node) {
318
+ const sceneInterface = this.options.sceneInterfaces?.[node.scene];
319
+ if (!sceneInterface) {
320
+ return '';
321
+ }
322
+ const entries = [];
323
+ for (const [key, value] of Object.entries(node.props)) {
324
+ const contract = sceneInterface.props[key];
325
+ if (!contract) {
326
+ continue;
327
+ }
328
+ if (isSceneBindingValue(value)) {
329
+ this.#runtimeImports.add('readSceneBindingValue');
330
+ entries.push(`${key}: readSceneBindingValue(root, ${JSON.stringify(value)})`);
331
+ continue;
332
+ }
333
+ if (contract.type === 'struct' && value && typeof value === 'object') {
334
+ const structVariable = this.#createStructValue(variable, key, value, contract);
335
+ entries.push(`${key}: ${structVariable}`);
336
+ continue;
337
+ }
338
+ entries.push(`${key}: ${this.#value(value)}`);
339
+ }
340
+ return entries.length > 0 ? `{ ${entries.join(', ')} }` : '';
341
+ }
288
342
  #sceneStructImports(sceneImport) {
289
343
  const scene = this.#sceneImportScene(sceneImport);
290
344
  if (!scene) {
@@ -355,6 +409,18 @@ class CompileContext {
355
409
  return contract.sourceScene ?? instanceScene;
356
410
  }
357
411
  #applyPixiProps(variable, props, instance = false, sceneInstance, pixiType) {
412
+ const publicProps = new Set(Object.keys(sceneInstance ? this.options.sceneInterfaces?.[sceneInstance.scene]?.props ?? {} : {}));
413
+ const bindingEntries = Object.entries(props).filter((entry) => (isSceneBindingValue(entry[1]) && !publicProps.has(entry[0])));
414
+ props = Object.fromEntries(Object.entries(props).filter(([, value]) => !isSceneBindingValue(value)));
415
+ if (sceneInstance) {
416
+ props = Object.fromEntries(Object.entries(props).filter(([key]) => !publicProps.has(key)));
417
+ for (const [key, value] of Object.entries(sceneInstance.props)) {
418
+ if (publicProps.has(key) && isSceneBindingValue(value)) {
419
+ this.#runtimeImports.add('bindSceneNodeProp');
420
+ this.#lines.push(` bindSceneNodeProp(root, ${JSON.stringify(value)}, ${variable}, ${JSON.stringify(key)});`);
421
+ }
422
+ }
423
+ }
358
424
  const x = props.x;
359
425
  const y = props.y;
360
426
  if (x !== undefined || y !== undefined) {
@@ -390,6 +456,10 @@ class CompileContext {
390
456
  this.#drawGraphics(variable, props);
391
457
  }
392
458
  for (const [key, value] of Object.entries(props)) {
459
+ if (!instance && (pixiType === 'Label' || pixiType === 'BitmapLabel') && labelProps.has(key)) {
460
+ this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
461
+ continue;
462
+ }
393
463
  if (!instance && runtimeNodeProps.has(key)) {
394
464
  this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
395
465
  continue;
@@ -413,7 +483,7 @@ class CompileContext {
413
483
  if (instance && layoutProps.has(key)) {
414
484
  continue;
415
485
  }
416
- if (instance && value && typeof value === 'object') {
486
+ if (instance && value && typeof value === 'object' && !isSceneBindingValue(value)) {
417
487
  this.#applyStructProp(variable, key, value, sceneInstance);
418
488
  continue;
419
489
  }
@@ -421,6 +491,10 @@ class CompileContext {
421
491
  this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
422
492
  }
423
493
  }
494
+ for (const [key, binding] of bindingEntries) {
495
+ this.#runtimeImports.add('bindSceneNodeProp');
496
+ this.#lines.push(` bindSceneNodeProp(root, ${JSON.stringify(binding)}, ${variable}, ${JSON.stringify(key)});`);
497
+ }
424
498
  }
425
499
  #applyRootProps() {
426
500
  const props = { ...this.template.props };
@@ -451,12 +525,16 @@ class CompileContext {
451
525
  this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
452
526
  return;
453
527
  }
528
+ const structVariable = this.#createStructValue(variable, key, value, contract);
529
+ this.#lines.push(` ${variable}.${key} = ${structVariable};`);
530
+ }
531
+ #createStructValue(variable, key, value, contract) {
454
532
  const structVariable = `${variable}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
455
533
  this.#lines.push(` const ${structVariable} = new ${contract.struct}();`);
456
534
  for (const [field, fieldValue] of Object.entries(value)) {
457
535
  this.#lines.push(` ${structVariable}.${field} = ${this.#value(fieldValue)};`);
458
536
  }
459
- this.#lines.push(` ${variable}.${key} = ${structVariable};`);
537
+ return structVariable;
460
538
  }
461
539
  #sceneInstanceStructContract(sceneInstance, key) {
462
540
  const contract = sceneInstance ? this.options.sceneInterfaces?.[sceneInstance.scene]?.props[key] : undefined;
@@ -527,7 +605,7 @@ class CompileContext {
527
605
  const entries = [];
528
606
  for (const key of pixiSceneTextStyleProps) {
529
607
  const value = props[key];
530
- if (value !== undefined) {
608
+ if (value !== undefined && !isSceneBindingValue(value)) {
531
609
  entries.push(`${key}: ${key === 'fontWeight' ? JSON.stringify(String(value)) : this.#value(value)}`);
532
610
  }
533
611
  }
@@ -578,20 +656,6 @@ class CompileContext {
578
656
  this.#textures.set(texture, variable);
579
657
  return variable;
580
658
  }
581
- #textureImportEntries() {
582
- return [...this.#textures.keys()]
583
- .filter((texture) => this.options.textureImports?.[texture])
584
- .map((texture, index) => [texture, `__pixifactTextureUrl${index + 1}`]);
585
- }
586
- #textureImportVariable(texture) {
587
- const index = [...this.#textures.keys()]
588
- .filter((key) => this.options.textureImports?.[key])
589
- .indexOf(texture);
590
- return index >= 0 ? `__pixifactTextureUrl${index + 1}` : undefined;
591
- }
592
- #textureLoadSource(texture) {
593
- return this.#textureImportVariable(texture) ?? this.#value(texture);
594
- }
595
659
  #textureValue(value) {
596
660
  return typeof value === 'string' ? this.#textureVariable(value) : this.#value(value);
597
661
  }
@@ -599,6 +663,9 @@ class CompileContext {
599
663
  if (typeof value === 'string') {
600
664
  return JSON.stringify(value);
601
665
  }
666
+ if (isSceneBindingValue(value)) {
667
+ throw new Error(`Scene binding ${value.path.join('.')} must be compiled as a binding target.`);
668
+ }
602
669
  if (value && typeof value === 'object') {
603
670
  return `{ ${Object.entries(value).map(([key, fieldValue]) => `${key}: ${this.#scalarValue(fieldValue)}`).join(', ')} }`;
604
671
  }
@@ -0,0 +1,6 @@
1
+ import type { SceneTemplateInterface } from '../compiler/spec.js';
2
+ import { type BuiltinSceneScriptSources } from '../compiler/builtinScenes.js';
3
+ export declare function builtinSceneInterface(assetId: string, scriptSources: BuiltinSceneScriptSources): SceneTemplateInterface;
4
+ export declare function builtinSceneInterfaces(scriptSources: BuiltinSceneScriptSources): {
5
+ [k: string]: SceneTemplateInterface;
6
+ };
@@ -0,0 +1,27 @@
1
+ import { builtinSceneAssetId, builtinSceneAssetIds, builtinSceneNameFromAssetId, builtinSceneNames, } from '../compiler/builtinScenes.js';
2
+ import { extractSceneScriptInterfaces } from './scriptInterfaceExtractor.js';
3
+ export function builtinSceneInterface(assetId, scriptSources) {
4
+ return builtinSceneInterfaceDescriptors(scriptSources)[assetId].interface;
5
+ }
6
+ export function builtinSceneInterfaces(scriptSources) {
7
+ const descriptors = builtinSceneInterfaceDescriptors(scriptSources);
8
+ return Object.fromEntries(builtinSceneAssetIds().map((assetId) => [assetId, descriptors[assetId].interface]));
9
+ }
10
+ function builtinSceneInterfaceDescriptors(scriptSources) {
11
+ const descriptors = extractSceneScriptInterfaces(builtinSceneNames.map((name) => ({
12
+ scene: builtinSceneAssetId(name),
13
+ fileName: `${name}.ts`,
14
+ source: scriptSources[name],
15
+ })));
16
+ for (const assetId of builtinSceneAssetIds()) {
17
+ const name = builtinSceneNameFromAssetId(assetId);
18
+ const descriptor = descriptors[assetId];
19
+ if (!descriptor) {
20
+ throw new Error(`Built-in Scene "${assetId}" is missing a @scene script.`);
21
+ }
22
+ if (descriptor.className !== name) {
23
+ throw new Error(`Built-in Scene "${assetId}" name must match @scene class "${descriptor.className}".`);
24
+ }
25
+ }
26
+ return descriptors;
27
+ }
@@ -1,3 +1,5 @@
1
1
  export * from '../compiler/compileScenes.js';
2
2
  export * from './builtinSceneAssets.js';
3
+ export * from './builtinSceneInterfaces.js';
4
+ export * from './scriptInterfaceExtractor.js';
3
5
  export * from './vite.js';
@@ -1,3 +1,5 @@
1
1
  export * from '../compiler/compileScenes.js';
2
2
  export * from './builtinSceneAssets.js';
3
+ export * from './builtinSceneInterfaces.js';
4
+ export * from './scriptInterfaceExtractor.js';
3
5
  export * from './vite.js';
@@ -1,4 +1,4 @@
1
- import type { SceneScriptInterface } from './spec.js';
1
+ import type { SceneScriptInterface } from '../compiler/spec.js';
2
2
  export interface ExtractSceneScriptInterfaceOptions {
3
3
  scene: string;
4
4
  }