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
@@ -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('&', '&')
@@ -1,14 +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
2
  import { compilerSceneNodeLocator } from './commands/index.js';
3
3
  const transformProps = new Set(pixiSceneTransformProps);
4
4
  const layoutProps = new Set(pixiSceneLayoutProps);
5
5
  const pixiProps = new Set(pixiSceneDisplayProps);
6
6
  const spriteProps = new Set(pixiSceneSpriteLikeProps);
7
7
  const graphicsProps = new Set(pixiSceneGraphicsProps);
8
+ const labelProps = new Set(pixiSceneLabelProps);
8
9
  const rectProps = new Set(pixiSceneRectProps);
9
10
  const textStyleProps = new Set(pixiSceneTextStyleProps);
10
- 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']);
11
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
+ }
12
16
  export function compileSceneTemplateToTs(template, options = {}) {
13
17
  const context = new CompileContext(template, options);
14
18
  return context.compile();
@@ -228,7 +232,8 @@ class CompileContext {
228
232
  const variable = node.id || this.#anonymousName(node.type);
229
233
  const locator = compilerSceneNodeLocator(node, path);
230
234
  const constructorName = this.#sceneConstructorName(node);
231
- this.#lines.push(` const ${variable} = new ${constructorName}();`);
235
+ const constructorArgs = this.#sceneInstanceConstructorArgs(variable, node);
236
+ this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
232
237
  this.#applyPixiProps(variable, node.props, true, node);
233
238
  for (const [name, action] of Object.entries(node.events)) {
234
239
  this.#runtimeImports.add('connectSceneEvent');
@@ -253,7 +258,8 @@ class CompileContext {
253
258
  const variable = node.id || this.#anonymousName(node.type);
254
259
  const locator = compilerSceneNodeLocator(node, path);
255
260
  const constructorName = this.#sceneConstructorName(node);
256
- this.#lines.push(` const ${variable} = new ${constructorName}();`);
261
+ const constructorArgs = this.#sceneInstanceConstructorArgs(variable, node);
262
+ this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
257
263
  this.#applyPixiProps(variable, node.props, true, node);
258
264
  for (const [name, action] of Object.entries(node.events)) {
259
265
  this.#runtimeImports.add('connectSceneEvent');
@@ -282,7 +288,7 @@ class CompileContext {
282
288
  }
283
289
  #constructPixiNode(node) {
284
290
  if (node.type === 'Text' || node.type === 'BitmapText' || node.type === 'HTMLText') {
285
- const text = this.#stringValue(node.props.text ?? '');
291
+ const text = this.#stringValue(isSceneBindingValue(node.props.text) ? '' : node.props.text ?? '');
286
292
  const style = this.#styleObject(node.props);
287
293
  return `new ${node.type}({ text: ${text}${style ? `, style: ${style}` : ''} })`;
288
294
  }
@@ -308,6 +314,31 @@ class CompileContext {
308
314
  #sceneConstructorName(node) {
309
315
  return this.options.sceneClassAliases?.[node.scene] ?? node.type;
310
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
+ }
311
342
  #sceneStructImports(sceneImport) {
312
343
  const scene = this.#sceneImportScene(sceneImport);
313
344
  if (!scene) {
@@ -378,6 +409,18 @@ class CompileContext {
378
409
  return contract.sourceScene ?? instanceScene;
379
410
  }
380
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
+ }
381
424
  const x = props.x;
382
425
  const y = props.y;
383
426
  if (x !== undefined || y !== undefined) {
@@ -413,6 +456,10 @@ class CompileContext {
413
456
  this.#drawGraphics(variable, props);
414
457
  }
415
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
+ }
416
463
  if (!instance && runtimeNodeProps.has(key)) {
417
464
  this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
418
465
  continue;
@@ -436,7 +483,7 @@ class CompileContext {
436
483
  if (instance && layoutProps.has(key)) {
437
484
  continue;
438
485
  }
439
- if (instance && value && typeof value === 'object') {
486
+ if (instance && value && typeof value === 'object' && !isSceneBindingValue(value)) {
440
487
  this.#applyStructProp(variable, key, value, sceneInstance);
441
488
  continue;
442
489
  }
@@ -444,6 +491,10 @@ class CompileContext {
444
491
  this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
445
492
  }
446
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
+ }
447
498
  }
448
499
  #applyRootProps() {
449
500
  const props = { ...this.template.props };
@@ -474,12 +525,16 @@ class CompileContext {
474
525
  this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
475
526
  return;
476
527
  }
528
+ const structVariable = this.#createStructValue(variable, key, value, contract);
529
+ this.#lines.push(` ${variable}.${key} = ${structVariable};`);
530
+ }
531
+ #createStructValue(variable, key, value, contract) {
477
532
  const structVariable = `${variable}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
478
533
  this.#lines.push(` const ${structVariable} = new ${contract.struct}();`);
479
534
  for (const [field, fieldValue] of Object.entries(value)) {
480
535
  this.#lines.push(` ${structVariable}.${field} = ${this.#value(fieldValue)};`);
481
536
  }
482
- this.#lines.push(` ${variable}.${key} = ${structVariable};`);
537
+ return structVariable;
483
538
  }
484
539
  #sceneInstanceStructContract(sceneInstance, key) {
485
540
  const contract = sceneInstance ? this.options.sceneInterfaces?.[sceneInstance.scene]?.props[key] : undefined;
@@ -550,7 +605,7 @@ class CompileContext {
550
605
  const entries = [];
551
606
  for (const key of pixiSceneTextStyleProps) {
552
607
  const value = props[key];
553
- if (value !== undefined) {
608
+ if (value !== undefined && !isSceneBindingValue(value)) {
554
609
  entries.push(`${key}: ${key === 'fontWeight' ? JSON.stringify(String(value)) : this.#value(value)}`);
555
610
  }
556
611
  }
@@ -608,6 +663,9 @@ class CompileContext {
608
663
  if (typeof value === 'string') {
609
664
  return JSON.stringify(value);
610
665
  }
666
+ if (isSceneBindingValue(value)) {
667
+ throw new Error(`Scene binding ${value.path.join('.')} must be compiled as a binding target.`);
668
+ }
611
669
  if (value && typeof value === 'object') {
612
670
  return `{ ${Object.entries(value).map(([key, fieldValue]) => `${key}: ${this.#scalarValue(fieldValue)}`).join(', ')} }`;
613
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,6 @@
1
1
  export * from '../compiler/compileScenes.js';
2
2
  export * from './builtinSceneAssets.js';
3
+ export * from './builtinSceneInterfaces.js';
4
+ export * from './scriptInterfaceExtractor.js';
5
+ export * from './runtimeVite.js';
3
6
  export * from './vite.js';
@@ -1,3 +1,6 @@
1
1
  export * from '../compiler/compileScenes.js';
2
2
  export * from './builtinSceneAssets.js';
3
+ export * from './builtinSceneInterfaces.js';
4
+ export * from './scriptInterfaceExtractor.js';
5
+ export * from './runtimeVite.js';
3
6
  export * from './vite.js';
@@ -0,0 +1,89 @@
1
+ import type { IncomingMessage, Server, ServerResponse } from 'node:http';
2
+ export declare const runtimeSessionProtocolVersion = 1;
3
+ export interface RuntimeSessionDescriptor {
4
+ protocolVersion: typeof runtimeSessionProtocolVersion;
5
+ projectRoot: string;
6
+ pid: number;
7
+ origin: string;
8
+ token: string;
9
+ }
10
+ export interface RuntimeHostBrowserSocket {
11
+ send(event: string, data: unknown): void;
12
+ }
13
+ interface RuntimeHostSessionOptions {
14
+ projectRoot: string;
15
+ requestTimeoutMs?: number;
16
+ token: string;
17
+ now?: () => Date;
18
+ }
19
+ interface RuntimeSessionLookupOptions {
20
+ projectRoot: string;
21
+ sessionsRoot?: string;
22
+ fetch?: typeof fetch;
23
+ }
24
+ interface ClaimRuntimeSessionOptions extends RuntimeSessionLookupOptions {
25
+ descriptor: RuntimeSessionDescriptor;
26
+ }
27
+ export interface PixifactRuntimePluginOptions {
28
+ projectRoot?: string | URL;
29
+ }
30
+ interface RuntimeViteClient extends RuntimeHostBrowserSocket {
31
+ socket: {
32
+ once(event: 'close', listener: () => void): void;
33
+ };
34
+ }
35
+ interface RuntimeViteServer {
36
+ config: {
37
+ server: {
38
+ https?: unknown;
39
+ };
40
+ logger: {
41
+ error(message: string): void;
42
+ };
43
+ };
44
+ httpServer: Server | null;
45
+ middlewares: {
46
+ use(handler: (request: IncomingMessage, response: ServerResponse, next: (error?: unknown) => void) => void): void;
47
+ };
48
+ ws: {
49
+ on(event: string, listener: (data: unknown, client: RuntimeViteClient) => void): void;
50
+ };
51
+ }
52
+ export declare function createRuntimeHostSession(options: RuntimeHostSessionOptions): {
53
+ open: (socket: RuntimeHostBrowserSocket) => void;
54
+ close: (socket: RuntimeHostBrowserSocket) => void;
55
+ message: (socket: RuntimeHostBrowserSocket, event: string, data: unknown) => void;
56
+ fetch: (request: Request) => Promise<Response | undefined>;
57
+ };
58
+ export declare function defaultRuntimeSessionsRoot(): string;
59
+ export declare function runtimeSessionDescriptorPath(projectRoot: string, sessionsRoot?: string): string;
60
+ export declare function readRuntimeSessionDescriptor(projectRoot: string, sessionsRoot?: string): RuntimeSessionDescriptor | undefined;
61
+ export declare function writeRuntimeSessionDescriptor(descriptor: RuntimeSessionDescriptor, sessionsRoot?: string): void;
62
+ export declare function removeRuntimeSessionDescriptor(descriptor: RuntimeSessionDescriptor, sessionsRoot?: string): void;
63
+ export declare function findActiveRuntimeSession(options: RuntimeSessionLookupOptions): Promise<RuntimeSessionDescriptor | undefined>;
64
+ export declare function claimRuntimeSession(options: ClaimRuntimeSessionOptions): Promise<{
65
+ descriptor: RuntimeSessionDescriptor;
66
+ owned: false;
67
+ } | {
68
+ descriptor: RuntimeSessionDescriptor;
69
+ owned: true;
70
+ }>;
71
+ export declare function pixifactRuntimePlugin(options?: PixifactRuntimePluginOptions): {
72
+ name: string;
73
+ apply: string;
74
+ configResolved(config: {
75
+ root: string;
76
+ }): void;
77
+ resolveId(id: string): "\0virtual:pixifact-runtime-client" | undefined;
78
+ load(id: string): string | undefined;
79
+ transformIndexHtml(): {
80
+ tag: string;
81
+ attrs: {
82
+ type: string;
83
+ src: string;
84
+ };
85
+ injectTo: "head-prepend";
86
+ }[];
87
+ configureServer(server: RuntimeViteServer): void;
88
+ };
89
+ export {};