pixifact 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compiler/commands/index.d.ts +8 -0
- package/dist/compiler/commands/index.js +24 -4
- package/dist/compiler/compileScenes.js +6 -24
- package/dist/compiler/sceneValidation.js +48 -10
- package/dist/compiler/spec.d.ts +4 -0
- package/dist/compiler/spec.js +1 -0
- package/dist/compiler/templateParser.js +17 -5
- package/dist/compiler/templateSerializer.js +9 -0
- package/dist/compiler/typescriptCompiler.js +42 -32
- package/dist/scene/decorators.js +2 -2
- package/dist/scene/eventRuntime.d.ts +2 -0
- package/dist/scene/eventRuntime.js +13 -0
- package/dist/scene/sceneBindingRuntime.d.ts +2 -2
- package/dist/scene/sceneBindingRuntime.js +9 -5
- package/dist/scene/sceneRuntime.d.ts +2 -0
- package/dist/scene/sceneRuntime.js +4 -0
- package/package.json +1 -1
|
@@ -12,6 +12,14 @@ export type CompilerSceneCommand = {
|
|
|
12
12
|
op: 'setSceneProp';
|
|
13
13
|
prop: string;
|
|
14
14
|
value?: SceneTemplateValue;
|
|
15
|
+
} | {
|
|
16
|
+
op: 'setSceneDefaultProp';
|
|
17
|
+
prop: string;
|
|
18
|
+
value?: SceneTemplateValue;
|
|
19
|
+
} | {
|
|
20
|
+
op: 'setSceneEvent';
|
|
21
|
+
event: string;
|
|
22
|
+
value?: string;
|
|
15
23
|
} | {
|
|
16
24
|
op: 'setNodeId';
|
|
17
25
|
node: string;
|
|
@@ -174,6 +174,21 @@ function setSceneProp(template, command) {
|
|
|
174
174
|
selection: { type: 'scene' },
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
|
+
function setSceneDefaultProp(template, command) {
|
|
178
|
+
const defaults = template.propDefaults ??= {};
|
|
179
|
+
const previous = getPropPathValue(defaults, command.prop);
|
|
180
|
+
setPropPathValue(defaults, command.prop, command.value);
|
|
181
|
+
return { ok: true, command, inverse: { op: 'setSceneDefaultProp', prop: command.prop, value: previous }, selection: { type: 'scene' } };
|
|
182
|
+
}
|
|
183
|
+
function setSceneEvent(template, command) {
|
|
184
|
+
const events = template.events ??= {};
|
|
185
|
+
const previous = events[command.event];
|
|
186
|
+
if (command.value === undefined)
|
|
187
|
+
delete events[command.event];
|
|
188
|
+
else
|
|
189
|
+
events[command.event] = command.value;
|
|
190
|
+
return { ok: true, command, inverse: { op: 'setSceneEvent', event: command.event, value: previous }, selection: { type: 'scene' } };
|
|
191
|
+
}
|
|
177
192
|
function setNodeId(template, command, context) {
|
|
178
193
|
const located = findCompilerSceneNodeLocation(template.children, command.node, context);
|
|
179
194
|
if (!located || located.node.kind === 'slotOutlet') {
|
|
@@ -275,15 +290,16 @@ function setPropPathValue(props, prop, value) {
|
|
|
275
290
|
}
|
|
276
291
|
function setNodeEvent(template, command, context) {
|
|
277
292
|
const located = findCompilerSceneNodeLocation(template.children, command.node, context);
|
|
278
|
-
if (!located || located.node.kind
|
|
293
|
+
if (!located || located.node.kind === 'slotOutlet') {
|
|
279
294
|
return { ok: false, command, error: 'Node event target was not found.' };
|
|
280
295
|
}
|
|
281
|
-
const
|
|
296
|
+
const events = located.node.events ??= {};
|
|
297
|
+
const previous = events[command.event];
|
|
282
298
|
if (command.value === undefined) {
|
|
283
|
-
delete
|
|
299
|
+
delete events[command.event];
|
|
284
300
|
}
|
|
285
301
|
else {
|
|
286
|
-
|
|
302
|
+
events[command.event] = command.value;
|
|
287
303
|
}
|
|
288
304
|
return {
|
|
289
305
|
ok: true,
|
|
@@ -422,6 +438,10 @@ export function applyCompilerSceneCommand(template, command, context = {}) {
|
|
|
422
438
|
return setSceneName(template, command);
|
|
423
439
|
case 'setSceneProp':
|
|
424
440
|
return setSceneProp(template, command);
|
|
441
|
+
case 'setSceneDefaultProp':
|
|
442
|
+
return setSceneDefaultProp(template, command);
|
|
443
|
+
case 'setSceneEvent':
|
|
444
|
+
return setSceneEvent(template, command);
|
|
425
445
|
case 'setNodeId':
|
|
426
446
|
return setNodeId(template, command, context);
|
|
427
447
|
case 'setNodeProp':
|
|
@@ -282,7 +282,7 @@ function normalizeSceneReferences(scenePath, template) {
|
|
|
282
282
|
return next;
|
|
283
283
|
}
|
|
284
284
|
function sceneImportsFor(template, templates, projectRoot, generatedFileDir) {
|
|
285
|
-
return [...collectSceneImportPaths(template
|
|
285
|
+
return [...collectSceneImportPaths(template)]
|
|
286
286
|
.sort()
|
|
287
287
|
.map((scenePath) => {
|
|
288
288
|
const sceneTemplate = templates.get(scenePath);
|
|
@@ -297,31 +297,13 @@ function sceneImportsFor(template, templates, projectRoot, generatedFileDir) {
|
|
|
297
297
|
};
|
|
298
298
|
});
|
|
299
299
|
}
|
|
300
|
-
function collectSceneImportPaths(template
|
|
300
|
+
function collectSceneImportPaths(template) {
|
|
301
301
|
const scenePaths = collectSceneInstancePaths(template.children);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
function collectSceneStructSourcePaths(nodes, templates, scenePaths) {
|
|
306
|
-
for (const node of nodes) {
|
|
307
|
-
if (node.kind === 'slotOutlet') {
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
if (node.kind === 'pixi') {
|
|
311
|
-
collectSceneStructSourcePaths(node.children, templates, scenePaths);
|
|
312
|
-
continue;
|
|
313
|
-
}
|
|
314
|
-
const sceneInterface = templates.get(node.scene)?.interface;
|
|
315
|
-
for (const [key, value] of Object.entries(node.props)) {
|
|
316
|
-
const contract = value && typeof value === 'object' ? sceneInterface?.props[key] : undefined;
|
|
317
|
-
if (contract?.type === 'struct' && contract.sourceScene) {
|
|
318
|
-
scenePaths.add(contract.sourceScene);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
for (const children of Object.values(node.slots)) {
|
|
322
|
-
collectSceneStructSourcePaths(children, templates, scenePaths);
|
|
323
|
-
}
|
|
302
|
+
for (const contract of Object.values(template.interface.props)) {
|
|
303
|
+
if (contract.type === 'struct' && contract.sourceScene)
|
|
304
|
+
scenePaths.add(contract.sourceScene);
|
|
324
305
|
}
|
|
306
|
+
return scenePaths;
|
|
325
307
|
}
|
|
326
308
|
function sceneClassAliasesFor(template) {
|
|
327
309
|
return Object.fromEntries([...collectSceneInstancePaths(template.children)].map((scenePath) => [scenePath, sceneClassAlias(scenePath)]));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isSceneTemplateBindingValue, } from './spec.js';
|
|
1
|
+
import { isSceneTemplateBindingValue, sceneNativeEventNames, } from './spec.js';
|
|
2
2
|
import { isPixiSceneNodeType, pixiSceneFieldSchema, pixiSceneLayoutProps, pixiSceneNodeAcceptsChildren, pixiSceneNodePropKeys, pixiSceneTransformProps, pixiSceneDisplayProps, } from './pixiNodeSchema.js';
|
|
3
3
|
import { parseSceneTemplate } from './templateParser.js';
|
|
4
4
|
import { serializeSceneTemplate } from './templateSerializer.js';
|
|
@@ -119,7 +119,40 @@ function sourcePositionFromMessage(source, message) {
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
function validateSceneTemplate(template, context) {
|
|
122
|
-
return
|
|
122
|
+
return [
|
|
123
|
+
...validateSceneDefaults(template, context),
|
|
124
|
+
...validateNativeEvents(template.events, '__scene__'),
|
|
125
|
+
...template.children.flatMap((child, index) => validateSceneNode(child, nodePathSegment(index, child), { ...context, sceneDefaults: template.propDefaults })),
|
|
126
|
+
];
|
|
127
|
+
}
|
|
128
|
+
function validateSceneDefaults(template, context) {
|
|
129
|
+
if (context.bindingsOnly || !context.sceneInterface)
|
|
130
|
+
return [];
|
|
131
|
+
const diagnostics = [];
|
|
132
|
+
for (const [name, value] of Object.entries(template.propDefaults ?? {})) {
|
|
133
|
+
const contract = context.sceneInterface?.props[name];
|
|
134
|
+
if (!contract) {
|
|
135
|
+
diagnostics.push({ path: '__scene__', prop: `default.${name}`, expected: 'Prop declared by the paired script', actual: 'unknown Prop' });
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (contract.type === 'struct') {
|
|
139
|
+
diagnostics.push(...validateStructFields('__scene__', `default.${name}`, value, contract, template.name));
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const expectedType = contract.type === 'variant' ? 'string' : contract.type;
|
|
143
|
+
if (!sceneValueMatchesContractType(value, expectedType) || (contract.type === 'variant' && !contract.variants[String(value)])) {
|
|
144
|
+
diagnostics.push({ path: '__scene__', prop: `default.${name}`, expected: contract.type === 'variant' ? `one of ${Object.keys(contract.variants).join(', ')}` : expectedType, actual: sceneValueType(value) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return diagnostics;
|
|
148
|
+
}
|
|
149
|
+
function validateNativeEvents(events, path) {
|
|
150
|
+
return Object.entries(events ?? {}).flatMap(([name, action]) => {
|
|
151
|
+
if (!sceneNativeEventNames.includes(name)) {
|
|
152
|
+
return [{ path, prop: `on:${name}`, expected: `one of ${sceneNativeEventNames.join(', ')}`, actual: name }];
|
|
153
|
+
}
|
|
154
|
+
return action.trim() ? [] : [{ path, prop: `on:${name}`, expected: 'nonempty action name', actual: action }];
|
|
155
|
+
});
|
|
123
156
|
}
|
|
124
157
|
function validateSceneNode(node, path, context) {
|
|
125
158
|
if (node.kind === 'slotOutlet') {
|
|
@@ -147,6 +180,7 @@ function validatePixiNode(node, path, context) {
|
|
|
147
180
|
}];
|
|
148
181
|
}
|
|
149
182
|
const diagnostics = [];
|
|
183
|
+
diagnostics.push(...validateNativeEvents(node.events, path));
|
|
150
184
|
if (!pixiSceneNodeAcceptsChildren(node.type) && node.children.length > 0) {
|
|
151
185
|
diagnostics.push({
|
|
152
186
|
path,
|
|
@@ -178,7 +212,7 @@ function validatePixiNode(node, path, context) {
|
|
|
178
212
|
}
|
|
179
213
|
const schema = pixiSceneFieldSchema(prop);
|
|
180
214
|
if (isSceneTemplateBindingValue(value)) {
|
|
181
|
-
diagnostics.push(...validateSceneBinding(path, prop, value, context.sceneInterface, schema?.type, schema?.options));
|
|
215
|
+
diagnostics.push(...validateSceneBinding(path, prop, value, context.sceneInterface, schema?.type, schema?.options, context.sceneDefaults));
|
|
182
216
|
continue;
|
|
183
217
|
}
|
|
184
218
|
if (schema && !sceneValueMatchesFieldType(value, schema.type, schema.options)) {
|
|
@@ -252,7 +286,7 @@ function validateSceneInstanceNode(node, path, context) {
|
|
|
252
286
|
const contract = sceneInterface.props[prop];
|
|
253
287
|
if (isSceneTemplateBindingValue(value)) {
|
|
254
288
|
const expectedType = contract?.type === 'variant' ? 'string' : contract?.type ?? schema?.type;
|
|
255
|
-
diagnostics.push(...validateSceneBinding(path, prop, value, context.sceneInterface, expectedType, schema?.options));
|
|
289
|
+
diagnostics.push(...validateSceneBinding(path, prop, value, context.sceneInterface, expectedType, schema?.options, context.sceneDefaults));
|
|
256
290
|
continue;
|
|
257
291
|
}
|
|
258
292
|
if (contract?.type === 'struct') {
|
|
@@ -304,7 +338,7 @@ function validateSceneInstanceNode(node, path, context) {
|
|
|
304
338
|
}
|
|
305
339
|
return diagnostics;
|
|
306
340
|
}
|
|
307
|
-
function validateSceneBinding(path, targetProp, binding, sceneInterface, expectedType, options) {
|
|
341
|
+
function validateSceneBinding(path, targetProp, binding, sceneInterface, expectedType, options, sceneDefaults) {
|
|
308
342
|
const [prop, field] = binding.path;
|
|
309
343
|
const contract = sceneInterface?.props[prop];
|
|
310
344
|
if (!contract) {
|
|
@@ -349,8 +383,9 @@ function validateSceneBinding(path, targetProp, binding, sceneInterface, expecte
|
|
|
349
383
|
}
|
|
350
384
|
else {
|
|
351
385
|
sourceType = contract.type;
|
|
352
|
-
|
|
353
|
-
|
|
386
|
+
const sourceValue = sceneDefaults?.[prop] ?? contract.default;
|
|
387
|
+
if (sourceValue !== undefined && typeof sourceValue !== 'object') {
|
|
388
|
+
sourceValues = [sourceValue];
|
|
354
389
|
}
|
|
355
390
|
}
|
|
356
391
|
const textAcceptsScalar = targetProp === 'text' && ['string', 'number', 'boolean'].includes(sourceType);
|
|
@@ -375,13 +410,16 @@ function bindingSourceMatchesTarget(sourceType, values, expectedType, options) {
|
|
|
375
410
|
return sourceType === expectedType;
|
|
376
411
|
}
|
|
377
412
|
function validateSceneInstanceStructProp(node, path, prop, value, contract) {
|
|
378
|
-
|
|
413
|
+
return validateStructFields(path, prop, value, contract, node.type);
|
|
414
|
+
}
|
|
415
|
+
function validateStructFields(path, prop, value, contract, ownerType) {
|
|
416
|
+
if (!value || typeof value !== 'object' || isSceneTemplateBindingValue(value)) {
|
|
379
417
|
return [{
|
|
380
418
|
path,
|
|
381
419
|
prop,
|
|
382
420
|
expected: contract.struct,
|
|
383
421
|
actual: sceneValueType(value),
|
|
384
|
-
hint: `Set ${
|
|
422
|
+
hint: `Set ${ownerType}.${prop} fields using dot-path attributes such as ${prop}.x="0".`,
|
|
385
423
|
}];
|
|
386
424
|
}
|
|
387
425
|
const diagnostics = [];
|
|
@@ -403,7 +441,7 @@ function validateSceneInstanceStructProp(node, path, prop, value, contract) {
|
|
|
403
441
|
prop: `${prop}.${field}`,
|
|
404
442
|
expected: fieldContract.type,
|
|
405
443
|
actual: sceneValueType(fieldValue),
|
|
406
|
-
hint: `Set ${
|
|
444
|
+
hint: `Set ${ownerType}.${prop}.${field} to ${fieldTypeDescription(fieldContract.type)}.`,
|
|
407
445
|
});
|
|
408
446
|
}
|
|
409
447
|
}
|
package/dist/compiler/spec.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export interface SceneTemplateBindingValue {
|
|
|
6
6
|
path: [string] | [string, string];
|
|
7
7
|
}
|
|
8
8
|
export type SceneTemplateValue = SceneTemplateScalarValue | SceneTemplateStructValue | SceneTemplateBindingValue;
|
|
9
|
+
export declare const sceneNativeEventNames: readonly ["pointertap", "pointerdown", "pointerup", "pointerupoutside", "pointerover", "pointerout", "click"];
|
|
9
10
|
export declare function isSceneTemplateBindingValue(value: SceneTemplateValue | undefined): value is SceneTemplateBindingValue;
|
|
10
11
|
export type SceneTemplatePrimitivePropType = 'string' | 'number' | 'boolean';
|
|
11
12
|
export interface SceneTemplatePrimitivePropContract {
|
|
@@ -44,6 +45,8 @@ export interface SceneTemplate {
|
|
|
44
45
|
version: 2;
|
|
45
46
|
name: string;
|
|
46
47
|
props: Record<string, SceneTemplateValue>;
|
|
48
|
+
propDefaults?: Record<string, SceneTemplateValue>;
|
|
49
|
+
events?: Record<string, string>;
|
|
47
50
|
interface: SceneTemplateInterface;
|
|
48
51
|
children: SceneTemplateNode[];
|
|
49
52
|
}
|
|
@@ -53,6 +56,7 @@ export interface PixiTemplateNode {
|
|
|
53
56
|
type: SceneTemplatePrimitiveType;
|
|
54
57
|
id?: string;
|
|
55
58
|
props: Record<string, SceneTemplateValue>;
|
|
59
|
+
events?: Record<string, string>;
|
|
56
60
|
children: SceneTemplateNode[];
|
|
57
61
|
}
|
|
58
62
|
export interface SceneInstanceTemplateNode {
|
package/dist/compiler/spec.js
CHANGED
|
@@ -43,7 +43,11 @@ export function parseSceneTemplate(source) {
|
|
|
43
43
|
return {
|
|
44
44
|
version: 2,
|
|
45
45
|
name,
|
|
46
|
-
props: parseProps(root, ['name']),
|
|
46
|
+
props: parseProps(root, ['name'], (attribute) => attribute.startsWith('default.') || attribute.startsWith('on:')),
|
|
47
|
+
propDefaults: parseProps({ ...root, attributes: Object.fromEntries(Object.entries(root.attributes)
|
|
48
|
+
.filter(([attribute]) => attribute.startsWith('default.'))
|
|
49
|
+
.map(([attribute, value]) => [attribute.slice('default.'.length), value])) }, [], undefined, true),
|
|
50
|
+
events: parseNativeEvents(root),
|
|
47
51
|
interface: emptyInterface(),
|
|
48
52
|
children,
|
|
49
53
|
};
|
|
@@ -85,7 +89,8 @@ function parseTemplateNode(element) {
|
|
|
85
89
|
const node = {
|
|
86
90
|
kind: 'pixi',
|
|
87
91
|
type: element.name,
|
|
88
|
-
props: parseProps(element, ['id', 'slot']),
|
|
92
|
+
props: parseProps(element, ['id', 'slot'], (attribute) => attribute.startsWith('on:')),
|
|
93
|
+
events: parseNativeEvents(element),
|
|
89
94
|
children: element.children.map(parseTemplateNode),
|
|
90
95
|
...(element.attributes.id ? { id: element.attributes.id } : {}),
|
|
91
96
|
};
|
|
@@ -93,13 +98,13 @@ function parseTemplateNode(element) {
|
|
|
93
98
|
}
|
|
94
99
|
throw new Error(`Unsupported template tag <${element.name}>. Add scene="..." or use a built-in Scene name.`);
|
|
95
100
|
}
|
|
96
|
-
function parseProps(element, omitted, shouldOmit = () => false) {
|
|
101
|
+
function parseProps(element, omitted, shouldOmit = () => false, allowQuotedStrings = false) {
|
|
97
102
|
const props = {};
|
|
98
103
|
for (const [name, value] of Object.entries(element.attributes)) {
|
|
99
104
|
if (omitted.includes(name) || shouldOmit(name)) {
|
|
100
105
|
continue;
|
|
101
106
|
}
|
|
102
|
-
const parsed = parseAttributeValue(name, value);
|
|
107
|
+
const parsed = parseAttributeValue(name, value, allowQuotedStrings);
|
|
103
108
|
const path = name.split('.');
|
|
104
109
|
if (path.length === 1) {
|
|
105
110
|
if (props[name] && typeof props[name] === 'object') {
|
|
@@ -134,6 +139,11 @@ function parseEvents(element) {
|
|
|
134
139
|
}
|
|
135
140
|
return events;
|
|
136
141
|
}
|
|
142
|
+
function parseNativeEvents(element) {
|
|
143
|
+
return Object.fromEntries(Object.entries(element.attributes)
|
|
144
|
+
.filter(([name]) => name.startsWith('on:'))
|
|
145
|
+
.map(([name, value]) => [name.slice(3), value]));
|
|
146
|
+
}
|
|
137
147
|
function eventName(name) {
|
|
138
148
|
if (name.startsWith('@')) {
|
|
139
149
|
return name.slice(1);
|
|
@@ -149,7 +159,9 @@ function eventName(name) {
|
|
|
149
159
|
function isEventAttribute(name) {
|
|
150
160
|
return eventName(name) !== undefined;
|
|
151
161
|
}
|
|
152
|
-
function parseAttributeValue(name, value) {
|
|
162
|
+
function parseAttributeValue(name, value, allowQuotedStrings = false) {
|
|
163
|
+
if (allowQuotedStrings && value.startsWith('"') && value.endsWith('"'))
|
|
164
|
+
return JSON.parse(value);
|
|
153
165
|
const binding = parseBindingValue(value);
|
|
154
166
|
if (binding) {
|
|
155
167
|
return binding;
|
|
@@ -16,6 +16,8 @@ export function serializeSceneTemplate(template) {
|
|
|
16
16
|
const rootAttributes = [
|
|
17
17
|
['name', template.name],
|
|
18
18
|
...Object.entries(template.props),
|
|
19
|
+
...Object.entries(template.propDefaults ?? {}).map(([name, value]) => [`default.${name}`, value]),
|
|
20
|
+
...Object.entries(template.events ?? {}).map(([name, value]) => [`on:${name}`, value]),
|
|
19
21
|
];
|
|
20
22
|
const lines = [`<Scene${serializeAttributes(rootAttributes)}>`];
|
|
21
23
|
for (const child of template.children) {
|
|
@@ -37,6 +39,7 @@ function serializeTemplateNode(node, depth, extraAttributes = []) {
|
|
|
37
39
|
...(node.id ? [['id', node.id]] : []),
|
|
38
40
|
...extraAttributes,
|
|
39
41
|
...Object.entries(node.props),
|
|
42
|
+
...Object.entries(node.events ?? {}).map(([name, value]) => [`on:${name}`, value]),
|
|
40
43
|
];
|
|
41
44
|
return serializeElement(node.type, attributes, node.children, depth);
|
|
42
45
|
}
|
|
@@ -89,6 +92,12 @@ function formatAttributeValue(name, value) {
|
|
|
89
92
|
if (isBindingValue(value)) {
|
|
90
93
|
return `{${value.path.join('.')}}`;
|
|
91
94
|
}
|
|
95
|
+
if (name.startsWith('default.') && typeof value === 'string'
|
|
96
|
+
&& (/^(?:true|false|-?\d+(?:\.\d+)?|#[0-9a-fA-F]{6})$/.test(value)
|
|
97
|
+
|| value.startsWith('{') || value.endsWith('}')
|
|
98
|
+
|| (value.startsWith('"') && value.endsWith('"')))) {
|
|
99
|
+
return JSON.stringify(value);
|
|
100
|
+
}
|
|
92
101
|
if (typeof value === 'number' && colorPropNames.has(name)) {
|
|
93
102
|
return `#${value.toString(16).padStart(6, '0').slice(-6)}`;
|
|
94
103
|
}
|
|
@@ -70,6 +70,7 @@ class CompileContext {
|
|
|
70
70
|
this.#lines.push(`registerScene(${JSON.stringify(this.options.registrationPath)}, {`);
|
|
71
71
|
this.#lines.push(` assets: ${JSON.stringify([...this.#textures.keys()])},`);
|
|
72
72
|
this.#lines.push(` dependencies: ${JSON.stringify(this.options.sceneDependencies ?? [])},`);
|
|
73
|
+
this.#lines.push(` propDefaults: () => (${this.#sceneDefaultProps()}),`);
|
|
73
74
|
this.#lines.push(` mount: ${functionName},`);
|
|
74
75
|
this.#lines.push(` prepare: ${this.#prepareFunctionName()},`);
|
|
75
76
|
this.#lines.push('});');
|
|
@@ -106,7 +107,10 @@ class CompileContext {
|
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
if (this.options.scriptImport) {
|
|
109
|
-
|
|
110
|
+
const localStructs = Object.values(this.template.interface.props)
|
|
111
|
+
.filter((contract) => contract.type === 'struct' && !contract.sourceScene)
|
|
112
|
+
.map((contract) => contract.struct);
|
|
113
|
+
lines.push(`import { ${[`${this.options.scriptImport.exportName} as ${this.options.scriptImport.localName}`, ...new Set(localStructs)].join(', ')} } from ${JSON.stringify(this.options.scriptImport.source)};`);
|
|
110
114
|
}
|
|
111
115
|
if (this.#runtimeImports.size > 0) {
|
|
112
116
|
lines.push(`import { ${[...this.#runtimeImports].sort().join(', ')} } from 'pixifact/scene';`);
|
|
@@ -226,6 +230,13 @@ class CompileContext {
|
|
|
226
230
|
this.#recordSource(variable, locator);
|
|
227
231
|
this.#applyNodeId(variable, node.id);
|
|
228
232
|
this.#applyPixiProps(variable, node.props, false, undefined, node.type);
|
|
233
|
+
if (Object.keys(node.events ?? {}).length > 0 && node.props.eventMode === undefined) {
|
|
234
|
+
this.#lines.push(` ${variable}.eventMode = 'static';`);
|
|
235
|
+
}
|
|
236
|
+
for (const [eventName, actionName] of Object.entries(node.events ?? {})) {
|
|
237
|
+
this.#runtimeImports.add('connectSceneNodeEvent');
|
|
238
|
+
this.#lines.push(` connectSceneNodeEvent(${variable}, ${JSON.stringify(eventName)}, ${JSON.stringify(actionName)}, root, ${actionsParameter});`);
|
|
239
|
+
}
|
|
229
240
|
this.#applyParentSorting(parent, node.props);
|
|
230
241
|
this.#lines.push(` ${parent}.addChild(${variable});`);
|
|
231
242
|
this.#lines.push(` __pixifactNodes[${JSON.stringify(locator)}] = ${variable};`);
|
|
@@ -237,7 +248,7 @@ class CompileContext {
|
|
|
237
248
|
const variable = node.id || this.#anonymousName(node.type);
|
|
238
249
|
const locator = compilerSceneNodeLocator(node, path);
|
|
239
250
|
const constructorName = this.#sceneConstructorName(node);
|
|
240
|
-
const constructorArgs = this.#sceneInstanceConstructorArgs(
|
|
251
|
+
const constructorArgs = this.#sceneInstanceConstructorArgs(node);
|
|
241
252
|
this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
|
|
242
253
|
this.#recordSource(variable, locator, true);
|
|
243
254
|
this.#applyPixiProps(variable, node.props, true, node);
|
|
@@ -264,7 +275,7 @@ class CompileContext {
|
|
|
264
275
|
const variable = node.id || this.#anonymousName(node.type);
|
|
265
276
|
const locator = compilerSceneNodeLocator(node, path);
|
|
266
277
|
const constructorName = this.#sceneConstructorName(node);
|
|
267
|
-
const constructorArgs = this.#sceneInstanceConstructorArgs(
|
|
278
|
+
const constructorArgs = this.#sceneInstanceConstructorArgs(node);
|
|
268
279
|
this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
|
|
269
280
|
this.#recordSource(variable, locator, true);
|
|
270
281
|
this.#applyPixiProps(variable, node.props, true, node);
|
|
@@ -322,7 +333,7 @@ class CompileContext {
|
|
|
322
333
|
#sceneConstructorName(node) {
|
|
323
334
|
return this.options.sceneClassAliases?.[node.scene] ?? node.type;
|
|
324
335
|
}
|
|
325
|
-
#sceneInstanceConstructorArgs(
|
|
336
|
+
#sceneInstanceConstructorArgs(node) {
|
|
326
337
|
const sceneInterface = this.options.sceneInterfaces?.[node.scene];
|
|
327
338
|
if (!sceneInterface) {
|
|
328
339
|
return '';
|
|
@@ -339,8 +350,7 @@ class CompileContext {
|
|
|
339
350
|
continue;
|
|
340
351
|
}
|
|
341
352
|
if (contract.type === 'struct' && value && typeof value === 'object') {
|
|
342
|
-
|
|
343
|
-
entries.push(`${key}: ${structVariable}`);
|
|
353
|
+
entries.push(`${key}: ${this.#value(value)}`);
|
|
344
354
|
continue;
|
|
345
355
|
}
|
|
346
356
|
entries.push(`${key}: ${this.#value(value)}`);
|
|
@@ -353,29 +363,11 @@ class CompileContext {
|
|
|
353
363
|
return [];
|
|
354
364
|
}
|
|
355
365
|
const structs = new Set();
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
#collectSceneStructImports(nodes, sourceScene, structs) {
|
|
360
|
-
for (const node of nodes) {
|
|
361
|
-
if (node.kind === 'slotOutlet') {
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
if (node.kind === 'pixi') {
|
|
365
|
-
this.#collectSceneStructImports(node.children, sourceScene, structs);
|
|
366
|
-
continue;
|
|
367
|
-
}
|
|
368
|
-
const sceneInterface = this.options.sceneInterfaces?.[node.scene];
|
|
369
|
-
for (const [key, value] of Object.entries(node.props)) {
|
|
370
|
-
const contract = value && typeof value === 'object' ? sceneInterface?.props[key] : undefined;
|
|
371
|
-
if (contract?.type === 'struct' && this.#structContractSourceScene(node.scene, contract) === sourceScene) {
|
|
372
|
-
structs.add(contract.struct);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
for (const children of Object.values(node.slots)) {
|
|
376
|
-
this.#collectSceneStructImports(children, sourceScene, structs);
|
|
377
|
-
}
|
|
366
|
+
for (const contract of Object.values(this.template.interface.props)) {
|
|
367
|
+
if (contract.type === 'struct' && contract.sourceScene === scene)
|
|
368
|
+
structs.add(contract.struct);
|
|
378
369
|
}
|
|
370
|
+
return [...structs].sort();
|
|
379
371
|
}
|
|
380
372
|
#usesSceneClassImport(sceneImport) {
|
|
381
373
|
const scene = this.#sceneImportScene(sceneImport);
|
|
@@ -413,9 +405,6 @@ class CompileContext {
|
|
|
413
405
|
return sceneImport.scene
|
|
414
406
|
?? Object.entries(this.options.sceneClassAliases ?? {}).find(([, alias]) => alias === sceneImport.localName)?.[0];
|
|
415
407
|
}
|
|
416
|
-
#structContractSourceScene(instanceScene, contract) {
|
|
417
|
-
return contract.sourceScene ?? instanceScene;
|
|
418
|
-
}
|
|
419
408
|
#applyPixiProps(variable, props, instance = false, sceneInstance, pixiType) {
|
|
420
409
|
const publicProps = new Set(Object.keys(sceneInstance ? this.options.sceneInterfaces?.[sceneInstance.scene]?.props ?? {} : {}));
|
|
421
410
|
const bindingEntries = Object.entries(props).filter((entry) => (isSceneBindingValue(entry[1]) && !publicProps.has(entry[0])));
|
|
@@ -515,6 +504,25 @@ class CompileContext {
|
|
|
515
504
|
this.#lines.push(` root.setSize(${this.#value(width ?? 0)}, ${this.#value(height ?? 0)});`);
|
|
516
505
|
}
|
|
517
506
|
this.#applyPixiProps('root', props);
|
|
507
|
+
if (Object.keys(this.template.events ?? {}).length > 0 && props.eventMode === undefined) {
|
|
508
|
+
this.#lines.push(" root.eventMode = 'static';");
|
|
509
|
+
}
|
|
510
|
+
for (const [eventName, actionName] of Object.entries(this.template.events ?? {})) {
|
|
511
|
+
this.#runtimeImports.add('connectSceneNodeEvent');
|
|
512
|
+
this.#lines.push(` connectSceneNodeEvent(root, ${JSON.stringify(eventName)}, ${JSON.stringify(actionName)}, root, ${this.options.actionsParameter || 'actions'});`);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
#sceneDefaultProps() {
|
|
516
|
+
const editorDefaults = this.template.propDefaults ?? {};
|
|
517
|
+
const entries = Object.entries(this.template.interface.props).flatMap(([name, contract]) => {
|
|
518
|
+
const explicit = editorDefaults[name];
|
|
519
|
+
if (contract.type === 'struct') {
|
|
520
|
+
const overrides = explicit && typeof explicit === 'object' ? explicit : {};
|
|
521
|
+
return [`${JSON.stringify(name)}: Object.assign(new ${contract.struct}(), ${this.#value(overrides)})`];
|
|
522
|
+
}
|
|
523
|
+
return explicit === undefined ? [] : [`${JSON.stringify(name)}: ${this.#value(explicit)}`];
|
|
524
|
+
});
|
|
525
|
+
return `{ ${entries.join(', ')} }`;
|
|
518
526
|
}
|
|
519
527
|
#applyLayoutProps(variable, props) {
|
|
520
528
|
const entries = pixiSceneLayoutProps.flatMap((key) => {
|
|
@@ -631,6 +639,8 @@ class CompileContext {
|
|
|
631
639
|
: `{ texture: ${this.#textureValue(props.texture)} }`;
|
|
632
640
|
}
|
|
633
641
|
#hasEvents() {
|
|
642
|
+
if (Object.keys(this.template.events ?? {}).length > 0)
|
|
643
|
+
return true;
|
|
634
644
|
for (const child of this.template.children) {
|
|
635
645
|
if (this.#nodeHasEvents(child)) {
|
|
636
646
|
return true;
|
|
@@ -648,7 +658,7 @@ class CompileContext {
|
|
|
648
658
|
}
|
|
649
659
|
return Object.values(node.slots).some((children) => children.some((child) => this.#nodeHasEvents(child)));
|
|
650
660
|
}
|
|
651
|
-
return node.children.some((child) => this.#nodeHasEvents(child));
|
|
661
|
+
return Object.keys(node.events ?? {}).length > 0 || node.children.some((child) => this.#nodeHasEvents(child));
|
|
652
662
|
}
|
|
653
663
|
#isTextStyleProp(key) {
|
|
654
664
|
return textStyleProps.has(key);
|
package/dist/scene/decorators.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { activateRuntimeTree } from '../runtime/runtimeBehavior.js';
|
|
2
|
-
import { mountSceneClass } from './sceneRuntime.js';
|
|
2
|
+
import { mountSceneClass, scenePropDefaultsForClass } from './sceneRuntime.js';
|
|
3
3
|
import { initializeSceneProps } from './sceneBindingRuntime.js';
|
|
4
4
|
const metadataByConstructor = new WeakMap();
|
|
5
5
|
export function scene() {
|
|
@@ -14,7 +14,7 @@ export function scene() {
|
|
|
14
14
|
const initialProps = args[0] && typeof args[0] === 'object'
|
|
15
15
|
? args[0]
|
|
16
16
|
: {};
|
|
17
|
-
initializeSceneProps(this, metadata.props, initialProps);
|
|
17
|
+
initializeSceneProps(this, metadata.props, initialProps, scenePropDefaultsForClass(SceneClass));
|
|
18
18
|
const result = mountSceneClass(this, SceneClass);
|
|
19
19
|
for (const [property, id] of metadata.parts) {
|
|
20
20
|
Object.defineProperty(this, property, {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Container } from 'pixi.js';
|
|
1
2
|
export type SceneEventListener<TPayload = void> = TPayload extends void ? () => void : (payload: TPayload) => void;
|
|
2
3
|
export interface SceneEvent<TPayload = void> {
|
|
3
4
|
connect(listener: SceneEventListener<TPayload>): () => void;
|
|
@@ -6,3 +7,4 @@ export interface SceneEvent<TPayload = void> {
|
|
|
6
7
|
export type SceneActions = Record<string, (() => void) | undefined>;
|
|
7
8
|
export declare function createEvent<TPayload = void>(): SceneEvent<TPayload>;
|
|
8
9
|
export declare function connectSceneEvent(event: SceneEvent, actionName: string, owner: object, actions?: SceneActions): () => void;
|
|
10
|
+
export declare function connectSceneNodeEvent(target: Container, eventName: string, actionName: string, owner: object, actions?: SceneActions): void;
|
|
@@ -27,3 +27,16 @@ export function connectSceneEvent(event, actionName, owner, actions = {}) {
|
|
|
27
27
|
}
|
|
28
28
|
throw new Error(`Scene action "${actionName}" was not found.`);
|
|
29
29
|
}
|
|
30
|
+
export function connectSceneNodeEvent(target, eventName, actionName, owner, actions = {}) {
|
|
31
|
+
const action = actions[actionName];
|
|
32
|
+
if (action) {
|
|
33
|
+
target.on(eventName, action);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const ownerAction = owner[actionName];
|
|
37
|
+
if (typeof ownerAction === 'function') {
|
|
38
|
+
target.on(eventName, () => ownerAction.call(owner));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`Scene action "${actionName}" was not found.`);
|
|
42
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Container } from 'pixi.js';
|
|
2
2
|
import type { ScenePropDecoratorOptions, SceneTemplateBindingValue, SceneTemplateInterface, SceneTemplateScalarValue } from '../compiler/spec.js';
|
|
3
|
-
export declare function initializeSceneProps(target: object, definitions: ReadonlyMap<string, ScenePropDecoratorOptions>, initialProps?: Record<string, unknown>): void;
|
|
4
|
-
export declare function initializeScenePropsFromInterface(target: object, sceneInterface: SceneTemplateInterface, initialProps?: Record<string, unknown>): void;
|
|
3
|
+
export declare function initializeSceneProps(target: object, definitions: ReadonlyMap<string, ScenePropDecoratorOptions>, initialProps?: Record<string, unknown>, sceneDefaults?: Record<string, unknown>): void;
|
|
4
|
+
export declare function initializeScenePropsFromInterface(target: object, sceneInterface: SceneTemplateInterface, initialProps?: Record<string, unknown>, sceneDefaults?: Record<string, unknown>): void;
|
|
5
5
|
export declare function setSceneProp(target: object, name: string, value: unknown): void;
|
|
6
6
|
export declare function getSceneProp<T = unknown>(target: object, name: string): T;
|
|
7
7
|
export declare function readSceneBindingValue<T = unknown>(target: object, binding: SceneTemplateBindingValue): T;
|
|
@@ -3,7 +3,7 @@ const states = new WeakMap();
|
|
|
3
3
|
const layoutProps = new Set(['left', 'right', 'top', 'bottom', 'horizontal', 'vertical']);
|
|
4
4
|
const textStyleProps = new Set(['fontSize', 'fontFamily', 'fontWeight', 'fill']);
|
|
5
5
|
const colorProps = new Set(['fill', 'fillColor', 'strokeColor', 'tint']);
|
|
6
|
-
export function initializeSceneProps(target, definitions, initialProps = {}) {
|
|
6
|
+
export function initializeSceneProps(target, definitions, initialProps = {}, sceneDefaults = {}) {
|
|
7
7
|
const state = {
|
|
8
8
|
definitions: Object.fromEntries(definitions),
|
|
9
9
|
subscribers: new Map(),
|
|
@@ -11,9 +11,13 @@ export function initializeSceneProps(target, definitions, initialProps = {}) {
|
|
|
11
11
|
};
|
|
12
12
|
states.set(target, state);
|
|
13
13
|
for (const [name, definition] of Object.entries(state.definitions)) {
|
|
14
|
+
const fallback = Object.hasOwn(sceneDefaults, name) ? sceneDefaults[name] : definition.default;
|
|
15
|
+
const initial = initialProps[name];
|
|
14
16
|
state.values[name] = Object.hasOwn(initialProps, name)
|
|
15
|
-
?
|
|
16
|
-
|
|
17
|
+
? initial && fallback && typeof initial === 'object' && typeof fallback === 'object'
|
|
18
|
+
? Object.assign(fallback, initial)
|
|
19
|
+
: initial
|
|
20
|
+
: fallback;
|
|
17
21
|
Object.defineProperty(target, name, {
|
|
18
22
|
configurable: true,
|
|
19
23
|
enumerable: true,
|
|
@@ -22,14 +26,14 @@ export function initializeSceneProps(target, definitions, initialProps = {}) {
|
|
|
22
26
|
});
|
|
23
27
|
}
|
|
24
28
|
}
|
|
25
|
-
export function initializeScenePropsFromInterface(target, sceneInterface, initialProps = {}) {
|
|
29
|
+
export function initializeScenePropsFromInterface(target, sceneInterface, initialProps = {}, sceneDefaults = {}) {
|
|
26
30
|
initializeSceneProps(target, new Map(Object.entries(sceneInterface.props).map(([name, contract]) => [
|
|
27
31
|
name,
|
|
28
32
|
{
|
|
29
33
|
...('default' in contract ? { default: contract.default } : {}),
|
|
30
34
|
...(contract.type === 'variant' ? { variants: contract.variants } : {}),
|
|
31
35
|
},
|
|
32
|
-
])), initialProps);
|
|
36
|
+
])), initialProps, sceneDefaults);
|
|
33
37
|
}
|
|
34
38
|
export function setSceneProp(target, name, value) {
|
|
35
39
|
const state = sceneBindingState(target);
|
|
@@ -9,11 +9,13 @@ export interface SceneMountResult {
|
|
|
9
9
|
export interface SceneDefinition {
|
|
10
10
|
assets: string[];
|
|
11
11
|
dependencies: string[];
|
|
12
|
+
propDefaults?: () => Record<string, unknown>;
|
|
12
13
|
mount(root: Group): SceneMountResult;
|
|
13
14
|
prepare(): Promise<void>;
|
|
14
15
|
}
|
|
15
16
|
export declare function registerScene(path: string, definition: SceneDefinition): void;
|
|
16
17
|
export declare function registerSceneClass(constructor: object, path: string): void;
|
|
18
|
+
export declare function scenePropDefaultsForClass(constructor: object): Record<string, unknown>;
|
|
17
19
|
export declare function mountScene(root: Group, path: string): SceneMountResult;
|
|
18
20
|
export declare function mountSceneClass(root: Group, constructor: object): SceneMountResult;
|
|
19
21
|
export declare function prepareScene(path: string): Promise<void>;
|
|
@@ -11,6 +11,10 @@ export function registerScene(path, definition) {
|
|
|
11
11
|
export function registerSceneClass(constructor, path) {
|
|
12
12
|
scenePathsByConstructor.set(constructor, path);
|
|
13
13
|
}
|
|
14
|
+
export function scenePropDefaultsForClass(constructor) {
|
|
15
|
+
const path = scenePathsByConstructor.get(constructor);
|
|
16
|
+
return path ? sceneDefinitions.get(path)?.propDefaults?.() ?? {} : {};
|
|
17
|
+
}
|
|
14
18
|
export function mountScene(root, path) {
|
|
15
19
|
const definition = sceneDefinitions.get(path);
|
|
16
20
|
if (!definition) {
|