@flighthq/scene-document 0.4.1-next.560.2e86cd6 → 0.5.1-edge.1047.858b9b6

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 (47) hide show
  1. package/README.md +2 -2
  2. package/dist/contract.d.ts +2 -0
  3. package/dist/contract.d.ts.map +1 -1
  4. package/dist/contract.js +2 -0
  5. package/dist/contract.js.map +1 -1
  6. package/dist/flightDocumentResourceDependencies.d.ts +3 -0
  7. package/dist/flightDocumentResourceDependencies.d.ts.map +1 -0
  8. package/dist/flightDocumentResourceDependencies.js +12 -0
  9. package/dist/flightDocumentResourceDependencies.js.map +1 -0
  10. package/dist/flightDocumentText.d.ts +3 -1
  11. package/dist/flightDocumentText.d.ts.map +1 -1
  12. package/dist/flightDocumentText.js +391 -24
  13. package/dist/flightDocumentText.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/sceneDocumentInteractiveStateBindings.d.ts +11 -0
  19. package/dist/sceneDocumentInteractiveStateBindings.d.ts.map +1 -0
  20. package/dist/sceneDocumentInteractiveStateBindings.js +108 -0
  21. package/dist/sceneDocumentInteractiveStateBindings.js.map +1 -0
  22. package/dist/sceneDocumentLayoutBindings.d.ts +5 -0
  23. package/dist/sceneDocumentLayoutBindings.d.ts.map +1 -0
  24. package/dist/sceneDocumentLayoutBindings.js +207 -0
  25. package/dist/sceneDocumentLayoutBindings.js.map +1 -0
  26. package/dist/sceneDocumentRefusal.d.ts +5 -1
  27. package/dist/sceneDocumentRefusal.d.ts.map +1 -1
  28. package/dist/sceneDocumentRefusal.js +156 -12
  29. package/dist/sceneDocumentRefusal.js.map +1 -1
  30. package/dist/sceneDocumentScene2DMaterialization.d.ts +4 -2
  31. package/dist/sceneDocumentScene2DMaterialization.d.ts.map +1 -1
  32. package/dist/sceneDocumentScene2DMaterialization.js +86 -16
  33. package/dist/sceneDocumentScene2DMaterialization.js.map +1 -1
  34. package/dist/sceneDocumentScene3DMaterialization.d.ts +4 -2
  35. package/dist/sceneDocumentScene3DMaterialization.d.ts.map +1 -1
  36. package/dist/sceneDocumentScene3DMaterialization.js +89 -19
  37. package/dist/sceneDocumentScene3DMaterialization.js.map +1 -1
  38. package/package.json +10 -10
  39. package/src/flightDocumentResourceDependencies.test.ts +83 -0
  40. package/src/flightDocumentText.test.ts +284 -2
  41. package/src/sceneDocumentInteractiveStateBindings.test.ts +129 -0
  42. package/src/sceneDocumentLayoutBindings.test.ts +141 -0
  43. package/src/sceneDocumentMaterializationSelection.test.ts +4 -0
  44. package/src/sceneDocumentMultiSceneMaterialization.test.ts +6 -0
  45. package/src/sceneDocumentRefusal.test.ts +90 -0
  46. package/src/sceneDocumentScene2DMaterialization.test.ts +448 -58
  47. package/src/sceneDocumentScene3DMaterialization.test.ts +220 -43
package/README.md CHANGED
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/scene-document`. The
13
13
  This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
14
14
 
15
15
  - [Flight project](https://github.com/flighthq/flight)
16
- - [Source for @flighthq/scene-document@0.4.1-next.560.2e86cd6](https://github.com/flighthq/flight/tree/2e86cd620a53121aa6c9230215f44c6c9a3bf816/packages/scene-document)
17
- - [License](https://github.com/flighthq/flight/blob/2e86cd620a53121aa6c9230215f44c6c9a3bf816/LICENSE.md)
16
+ - [Source for @flighthq/scene-document@0.5.1-edge.1047.858b9b6](https://github.com/flighthq/flight/tree/858b9b601de814f6b6940cbb1ffff0916b903268/packages/scene-document)
17
+ - [License](https://github.com/flighthq/flight/blob/858b9b601de814f6b6940cbb1ffff0916b903268/LICENSE.md)
@@ -1,4 +1,6 @@
1
1
  export { explainFlightDocumentText, formatFlightDocumentText, parseFlightDocumentText } from './flightDocumentText';
2
+ export { getFlightDocumentResourceDependencies } from './flightDocumentResourceDependencies';
2
3
  export { createFlightDocumentFromScene2D, createFlightDocumentScene2DMaterialization, createFlightDocumentScene2DMaterializationFromText, explainFlightDocumentRefusal, explainFlightDocumentRefusalFromText, } from './sceneDocumentScene2DMaterialization';
3
4
  export { createFlightDocumentFromScene3D, createFlightDocumentScene3DMaterialization, createFlightDocumentScene3DMaterializationFromText, explainFlightDocumentScene3DRefusal, explainFlightDocumentScene3DRefusalFromText, } from './sceneDocumentScene3DMaterialization';
5
+ export { initializeDocumentRefusal } from './sceneDocumentRefusal';
4
6
  //# sourceMappingURL=contract.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpH,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,4BAA4B,EAC5B,oCAAoC,GACrC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,mCAAmC,EACnC,2CAA2C,GAC5C,MAAM,uCAAuC,CAAC"}
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpH,OAAO,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,4BAA4B,EAC5B,oCAAoC,GACrC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,mCAAmC,EACnC,2CAA2C,GAC5C,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/contract.js CHANGED
@@ -1,4 +1,6 @@
1
1
  export { explainFlightDocumentText, formatFlightDocumentText, parseFlightDocumentText } from './flightDocumentText';
2
+ export { getFlightDocumentResourceDependencies } from './flightDocumentResourceDependencies';
2
3
  export { createFlightDocumentFromScene2D, createFlightDocumentScene2DMaterialization, createFlightDocumentScene2DMaterializationFromText, explainFlightDocumentRefusal, explainFlightDocumentRefusalFromText, } from './sceneDocumentScene2DMaterialization';
3
4
  export { createFlightDocumentFromScene3D, createFlightDocumentScene3DMaterialization, createFlightDocumentScene3DMaterializationFromText, explainFlightDocumentScene3DRefusal, explainFlightDocumentScene3DRefusalFromText, } from './sceneDocumentScene3DMaterialization';
5
+ export { initializeDocumentRefusal } from './sceneDocumentRefusal';
4
6
  //# sourceMappingURL=contract.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpH,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,4BAA4B,EAC5B,oCAAoC,GACrC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,mCAAmC,EACnC,2CAA2C,GAC5C,MAAM,uCAAuC,CAAC"}
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACpH,OAAO,EAAE,qCAAqC,EAAE,MAAM,sCAAsC,CAAC;AAC7F,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,4BAA4B,EAC5B,oCAAoC,GACrC,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EACL,+BAA+B,EAC/B,0CAA0C,EAC1C,kDAAkD,EAClD,mCAAmC,EACnC,2CAA2C,GAC5C,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { FlightDocument, FlightDocumentResourceDescriptor, FlightDocumentSchemaRegistry } from '@flighthq/types/contract';
2
+ export declare function getFlightDocumentResourceDependencies(document: Readonly<FlightDocument>, schemas: Readonly<FlightDocumentSchemaRegistry>): readonly Readonly<FlightDocumentResourceDescriptor>[] | null;
3
+ //# sourceMappingURL=flightDocumentResourceDependencies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flightDocumentResourceDependencies.d.ts","sourceRoot":"","sources":["../src/flightDocumentResourceDependencies.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,gCAAgC,EAChC,4BAA4B,EAC7B,MAAM,0BAA0B,CAAC;AAKlC,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,EAClC,OAAO,EAAE,QAAQ,CAAC,4BAA4B,CAAC,GAC9C,SAAS,QAAQ,CAAC,gCAAgC,CAAC,EAAE,GAAG,IAAI,CAK9D"}
@@ -0,0 +1,12 @@
1
+ import { getRegistryTableEntry } from '@flighthq/registry/contract';
2
+ // The resource table is the document's direct dependency list. This query verifies that every declared
3
+ // kind has an authored schema, but deliberately does not interpret fields as graph edges or resolve any
4
+ // live resource: traversal and loading policy belong to their callers.
5
+ export function getFlightDocumentResourceDependencies(document, schemas) {
6
+ for (const descriptor of document.resources) {
7
+ if (getRegistryTableEntry(schemas.resourceSchemas, descriptor.kind) === null)
8
+ return null;
9
+ }
10
+ return document.resources;
11
+ }
12
+ //# sourceMappingURL=flightDocumentResourceDependencies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flightDocumentResourceDependencies.js","sourceRoot":"","sources":["../src/flightDocumentResourceDependencies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,6BAA6B,CAAC;AAOpE,uGAAuG;AACvG,wGAAwG;AACxG,uEAAuE;AACvE,MAAM,UAAU,qCAAqC,CACnD,QAAkC,EAClC,OAA+C;IAE/C,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC,SAAS,EAAE,CAAC;QAC5C,IAAI,qBAAqB,CAAC,OAAO,CAAC,eAAe,EAAE,UAAU,CAAC,IAAI,CAAC,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;IAC5F,CAAC;IACD,OAAO,QAAQ,CAAC,SAAS,CAAC;AAC5B,CAAC"}
@@ -1,5 +1,7 @@
1
- import type { FlightDocument, FlightDocumentRefusalExplanation, FlightDocumentText } from '@flighthq/types/contract';
1
+ import type { EntityConstruction, FlightDocument, FlightDocumentRefusalExplanation, FlightDocumentText } from '@flighthq/types/contract';
2
+ import { FlightDocumentRefusalReason } from '@flighthq/types/contract';
2
3
  export declare function explainFlightDocumentText(text: FlightDocumentText): FlightDocumentRefusalExplanation | null;
3
4
  export declare function formatFlightDocumentText(document: Readonly<FlightDocument>): FlightDocumentText;
5
+ export declare function initializeFlightDocumentRefusalExplanation(out: EntityConstruction<FlightDocumentRefusalExplanation>, actual: number | null, column: number | null, kind: string | null, limit: number | null, line: number | null, mode: string | null, offset: number | null, path: string, reason: FlightDocumentRefusalReason, resourceKey: string | null, tokenKey: string | null, version: number | null): void;
4
6
  export declare function parseFlightDocumentText(text: FlightDocumentText): FlightDocument | null;
5
7
  //# sourceMappingURL=flightDocumentText.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"flightDocumentText.d.ts","sourceRoot":"","sources":["../src/flightDocumentText.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EAGd,gCAAgC,EAKhC,kBAAkB,EAOnB,MAAM,0BAA0B,CAAC;AAMlC,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,kBAAkB,GAAG,gCAAgC,GAAG,IAAI,CAE3G;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAsB/F;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,GAAG,cAAc,GAAG,IAAI,CAEvF"}
1
+ {"version":3,"file":"flightDocumentText.d.ts","sourceRoot":"","sources":["../src/flightDocumentText.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,kBAAkB,EAClB,cAAc,EASd,gCAAgC,EAKhC,kBAAkB,EASnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAoB,2BAA2B,EAAE,MAAM,0BAA0B,CAAC;AAKzF,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,kBAAkB,GAAG,gCAAgC,GAAG,IAAI,CAE3G;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAsB/F;AAED,wBAAgB,0CAA0C,CACxD,GAAG,EAAE,kBAAkB,CAAC,gCAAgC,CAAC,EACzD,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,KAAK,EAAE,MAAM,GAAG,IAAI,EACpB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,IAAI,EAAE,MAAM,GAAG,IAAI,EACnB,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,2BAA2B,EACnC,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,OAAO,EAAE,MAAM,GAAG,IAAI,GACrB,IAAI,CAaN;AAED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,kBAAkB,GAAG,cAAc,GAAG,IAAI,CAEvF"}
@@ -1,3 +1,5 @@
1
+ import { createOrthographicProjection, createPerspectiveProjection } from '@flighthq/camera/contract';
2
+ import { allocateEntity, finishEntity } from '@flighthq/entity/contract';
1
3
  import { createTransform3D } from '@flighthq/geometry/contract';
2
4
  import { EntityRuntimeKey, FlightDocumentRefusalReason } from '@flighthq/types/contract';
3
5
  import { createDocumentRefusal } from './sceneDocumentRefusal';
@@ -28,6 +30,20 @@ export function formatFlightDocumentText(document) {
28
30
  }
29
31
  return text;
30
32
  }
33
+ export function initializeFlightDocumentRefusalExplanation(out, actual, column, kind, limit, line, mode, offset, path, reason, resourceKey, tokenKey, version) {
34
+ out.actual = actual;
35
+ out.column = column;
36
+ out.kind = kind;
37
+ out.limit = limit;
38
+ out.line = line;
39
+ out.mode = mode;
40
+ out.offset = offset;
41
+ out.path = path;
42
+ out.reason = reason;
43
+ out.resourceKey = resourceKey;
44
+ out.tokenKey = tokenKey;
45
+ out.version = version;
46
+ }
31
47
  export function parseFlightDocumentText(text) {
32
48
  return readFlightDocumentText(text).document;
33
49
  }
@@ -88,6 +104,7 @@ function appendNode(lines, node, indent) {
88
104
  const prefix = ' '.repeat(indent);
89
105
  lines.push(prefix + 'kind: ' + formatString(node.kind));
90
106
  appendFields(lines, node.fields, indent, NODE_RESERVED_FIELDS);
107
+ appendNodeInteractiveMetadata(lines, node, indent);
91
108
  if (node.children.length > 0) {
92
109
  lines.push(prefix + 'children:');
93
110
  for (const child of node.children)
@@ -98,12 +115,67 @@ function appendNodeSequenceItem(lines, node, indent) {
98
115
  const prefix = ' '.repeat(indent);
99
116
  lines.push(prefix + '- kind: ' + formatString(node.kind));
100
117
  appendFields(lines, node.fields, indent + 2, NODE_RESERVED_FIELDS);
118
+ appendNodeInteractiveMetadata(lines, node, indent + 2);
101
119
  if (node.children.length > 0) {
102
120
  lines.push(prefix + ' children:');
103
121
  for (const child of node.children)
104
122
  appendNodeSequenceItem(lines, child, indent + 4);
105
123
  }
106
124
  }
125
+ function appendInteractiveState(lines, state, indent) {
126
+ const entries = getInteractiveStateEntries(state);
127
+ if (entries.length === 0 && state.extensions.length === 0) {
128
+ throw new TypeError('FlightDocument interactive state must not be empty');
129
+ }
130
+ for (const [property, value] of entries)
131
+ appendMappingEntry(lines, property, value, indent);
132
+ if (state.extensions.length === 0)
133
+ return;
134
+ lines.push(' '.repeat(indent) + 'extensions:');
135
+ for (const extension of state.extensions) {
136
+ lines.push(' '.repeat(indent + 2) + '- kind: ' + formatString(extension.kind));
137
+ appendFields(lines, extension.fields, indent + 4, DESCRIPTOR_RESERVED_FIELDS);
138
+ }
139
+ }
140
+ function appendNodeInteractiveMetadata(lines, node, indent) {
141
+ const prefix = ' '.repeat(indent);
142
+ if (node.interactiveStates == null) {
143
+ if (node.transition != null)
144
+ throw new TypeError('FlightDocument transition requires interactiveStates');
145
+ return;
146
+ }
147
+ const phases = getInteractiveStatePhases(node.interactiveStates);
148
+ if (phases.length === 0)
149
+ throw new TypeError('FlightDocument interactiveStates must not be empty');
150
+ lines.push(prefix + 'interactiveStates:');
151
+ for (const [phase, state] of phases) {
152
+ lines.push(prefix + ' ' + phase + ':');
153
+ appendInteractiveState(lines, state, indent + 4);
154
+ }
155
+ if (node.transition != null) {
156
+ lines.push(prefix + 'transition:');
157
+ lines.push(prefix + ' kind: ' + formatString(node.transition.kind));
158
+ appendFields(lines, node.transition.fields, indent + 2, DESCRIPTOR_RESERVED_FIELDS);
159
+ }
160
+ }
161
+ function getInteractiveStateEntries(state) {
162
+ const out = [];
163
+ for (const property of INTERACTIVE_STATE_PROPERTIES) {
164
+ const value = state[property];
165
+ if (value !== undefined)
166
+ out.push([property, value]);
167
+ }
168
+ return out;
169
+ }
170
+ function getInteractiveStatePhases(states) {
171
+ const out = [];
172
+ for (const phase of INTERACTIVE_STATE_PHASES) {
173
+ const state = states[phase];
174
+ if (state !== null)
175
+ out.push([phase, state]);
176
+ }
177
+ return out;
178
+ }
107
179
  function appendObjectEntries(lines, value, indent, reserved) {
108
180
  for (const [key, entry] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) {
109
181
  if (reserved.includes(key))
@@ -153,6 +225,60 @@ function appendScene(lines, scene) {
153
225
  }
154
226
  lines.push(' scene:');
155
227
  appendNode(lines, scene.scene, 6);
228
+ appendLayouts(lines, scene.layouts);
229
+ appendTokens(lines, scene.tokens);
230
+ }
231
+ function appendLayouts(lines, layouts) {
232
+ if (layouts.length === 0)
233
+ return;
234
+ const seenTargets = new Set();
235
+ lines.push(' layouts:');
236
+ for (const layout of layouts) {
237
+ const nodes = layout.tree.nodes;
238
+ if (nodes.length === 0)
239
+ throw new RangeError('FlightDocument layout tree must not be empty');
240
+ if (layout.targets.length !== nodes.length) {
241
+ throw new RangeError('FlightDocument layout targets must match its tree nodes index-for-index');
242
+ }
243
+ lines.push(' - targets:');
244
+ for (const target of layout.targets) {
245
+ if (target.length === 0)
246
+ throw new RangeError('FlightDocument layout target names must not be empty');
247
+ if (seenTargets.has(target))
248
+ throw new RangeError('FlightDocument layout targets must be unique per scene');
249
+ seenTargets.add(target);
250
+ lines.push(' - ' + formatString(target));
251
+ }
252
+ lines.push(' tree:');
253
+ lines.push(' nodes:');
254
+ for (let index = 0; index < nodes.length; index++)
255
+ appendLayoutNode(lines, nodes[index], index);
256
+ }
257
+ }
258
+ function appendLayoutNode(lines, node, index) {
259
+ if (node.kind.length === 0)
260
+ throw new RangeError('FlightDocument layout node kinds must not be empty');
261
+ if (!Number.isInteger(node.parentIndex) || node.parentIndex < -1 || node.parentIndex >= index) {
262
+ throw new RangeError('FlightDocument layout parentIndex must be -1 or precede its node');
263
+ }
264
+ lines.push(' - kind: ' + formatString(node.kind));
265
+ lines.push(' parentIndex: ' + String(node.parentIndex));
266
+ if (node.containerStyle !== null)
267
+ appendMappingEntry(lines, 'containerStyle', node.containerStyle, 14);
268
+ if (node.itemStyle !== null)
269
+ appendMappingEntry(lines, 'itemStyle', node.itemStyle, 14);
270
+ }
271
+ function appendTokens(lines, tokens) {
272
+ if (tokens.length === 0)
273
+ return;
274
+ lines.push(' tokens:');
275
+ for (const token of tokens) {
276
+ lines.push(' - kind: ' + formatString(token.kind));
277
+ lines.push(' key: ' + formatString(token.key));
278
+ // Mode names are sorted so a formatted document is stable, while the token ROWS keep the order
279
+ // they were authored in: row order is the author's, mode order is not meaningful.
280
+ appendFields(lines, token.values, 8, TOKEN_RESERVED_FIELDS);
281
+ }
156
282
  }
157
283
  function appendSequence(lines, values, indent) {
158
284
  const prefix = ' '.repeat(indent);
@@ -300,18 +426,11 @@ function readFlightDocumentText(text) {
300
426
  if (!parsed.ok) {
301
427
  return {
302
428
  document: null,
303
- refusal: {
304
- actual: parsed.actual,
305
- column: parsed.column,
306
- kind: null,
307
- limit: parsed.limit,
308
- line: parsed.line,
309
- offset: parsed.offset,
310
- path: '',
311
- reason: parsed.kind,
312
- resourceKey: null,
313
- version: null,
314
- },
429
+ refusal: (() => {
430
+ const out = allocateEntity();
431
+ initializeFlightDocumentRefusalExplanation(out, parsed.actual, parsed.column, null, parsed.limit, parsed.line, null, parsed.offset, '', parsed.kind, null, null, null);
432
+ return finishEntity(out);
433
+ })(),
315
434
  };
316
435
  }
317
436
  const context = { refusal: null };
@@ -409,7 +528,83 @@ function readLight(value, path, context) {
409
528
  light.node = node;
410
529
  return light;
411
530
  }
412
- function readNode(value, path, context) {
531
+ function readInteractiveState(value, path, dimension, context) {
532
+ if (!isMapping(value) || !hasOnlyKeys(value, INTERACTIVE_STATE_KEYS))
533
+ return refuse(context, path);
534
+ const state = { extensions: [] };
535
+ for (const property of INTERACTIVE_STATE_PROPERTIES) {
536
+ const propertyValue = value[property];
537
+ if (propertyValue === undefined)
538
+ continue;
539
+ if (property === 'visible') {
540
+ if (typeof propertyValue !== 'boolean')
541
+ return refuse(context, appendPath(path, property));
542
+ }
543
+ else if (typeof propertyValue !== 'number' || !Number.isFinite(propertyValue)) {
544
+ return refuse(context, appendPath(path, property));
545
+ }
546
+ if (dimension === 'Scene3D' && INTERACTIVE_STATE_2D_PROPERTIES.includes(property)) {
547
+ return refuse(context, appendPath(path, property));
548
+ }
549
+ Object.assign(state, { [property]: propertyValue });
550
+ }
551
+ const extensionsRaw = value['extensions'];
552
+ if (extensionsRaw !== undefined) {
553
+ if (!Array.isArray(extensionsRaw))
554
+ return refuse(context, appendPath(path, 'extensions'));
555
+ const kinds = new Set();
556
+ for (let i = 0; i < extensionsRaw.length; i++) {
557
+ const extensionPath = `${path}.extensions[${i}]`;
558
+ const extension = readInteractiveStateExtension(extensionsRaw[i], extensionPath, context);
559
+ if (extension === null)
560
+ return null;
561
+ if (kinds.has(extension.kind)) {
562
+ return refuseWithReason(context, FlightDocumentRefusalReason.InteractiveStateExtensionKindDuplicate, extensionPath, extension.kind);
563
+ }
564
+ kinds.add(extension.kind);
565
+ state.extensions.push(extension);
566
+ }
567
+ }
568
+ if (getInteractiveStateEntries(state).length === 0 && state.extensions.length === 0)
569
+ return refuse(context, path);
570
+ return state;
571
+ }
572
+ function readInteractiveStateExtension(value, path, context) {
573
+ if (!isMapping(value))
574
+ return refuse(context, path);
575
+ const kind = value['kind'];
576
+ if (typeof kind !== 'string')
577
+ return refuse(context, appendPath(path, 'kind'));
578
+ const fields = copyFlightDocumentFields(value, DESCRIPTOR_RESERVED_FIELDS, path, context);
579
+ return fields === null ? null : { fields, kind };
580
+ }
581
+ function readInteractiveStates(value, path, dimension, context) {
582
+ if (!isMapping(value) || !hasOnlyKeys(value, INTERACTIVE_STATE_PHASES))
583
+ return refuse(context, path);
584
+ const states = { disabled: null, hover: null, pressed: null };
585
+ let count = 0;
586
+ for (const phase of INTERACTIVE_STATE_PHASES) {
587
+ const stateRaw = value[phase];
588
+ if (stateRaw === undefined)
589
+ continue;
590
+ const state = readInteractiveState(stateRaw, appendPath(path, phase), dimension, context);
591
+ if (state === null)
592
+ return null;
593
+ states[phase] = state;
594
+ count++;
595
+ }
596
+ return count === 0 ? refuse(context, path) : states;
597
+ }
598
+ function readInteractiveStateTransition(value, path, context) {
599
+ if (!isMapping(value))
600
+ return refuse(context, path);
601
+ const kind = value['kind'];
602
+ if (typeof kind !== 'string')
603
+ return refuse(context, appendPath(path, 'kind'));
604
+ const fields = copyFlightDocumentFields(value, DESCRIPTOR_RESERVED_FIELDS, path, context);
605
+ return fields === null ? null : { fields, kind };
606
+ }
607
+ function readNode(value, path, dimension, context) {
413
608
  if (!isMapping(value))
414
609
  return refuse(context, path);
415
610
  const kind = value['kind'];
@@ -421,14 +616,30 @@ function readNode(value, path, context) {
421
616
  if (!Array.isArray(childrenRaw))
422
617
  return refuse(context, appendPath(path, 'children'));
423
618
  for (let index = 0; index < childrenRaw.length; index++) {
424
- const child = readNode(childrenRaw[index], `${path}.children[${index}]`, context);
619
+ const child = readNode(childrenRaw[index], `${path}.children[${index}]`, dimension, context);
425
620
  if (child === null)
426
621
  return null;
427
622
  children.push(child);
428
623
  }
429
624
  }
625
+ const interactiveStatesRaw = value['interactiveStates'];
626
+ let interactiveStates = null;
627
+ if (interactiveStatesRaw !== undefined) {
628
+ interactiveStates = readInteractiveStates(interactiveStatesRaw, appendPath(path, 'interactiveStates'), dimension, context);
629
+ if (interactiveStates === null)
630
+ return null;
631
+ }
632
+ const transitionRaw = value['transition'];
633
+ if (transitionRaw !== undefined && interactiveStates === null)
634
+ return refuse(context, appendPath(path, 'transition'));
635
+ let transition = null;
636
+ if (transitionRaw !== undefined) {
637
+ transition = readInteractiveStateTransition(transitionRaw, appendPath(path, 'transition'), context);
638
+ if (transition === null)
639
+ return null;
640
+ }
430
641
  const fields = copyFlightDocumentFields(value, NODE_RESERVED_FIELDS, path, context);
431
- return fields === null ? null : { children, fields, kind };
642
+ return fields === null ? null : { children, fields, interactiveStates, kind, transition };
432
643
  }
433
644
  function readOptionalIndex(value, path, context) {
434
645
  if (value === undefined)
@@ -451,7 +662,7 @@ function readProjection(value, path, context) {
451
662
  return refuse(context, appendPath(path, 'halfHeight'));
452
663
  if (typeof halfWidth !== 'number')
453
664
  return refuse(context, appendPath(path, 'halfWidth'));
454
- return { halfHeight, halfWidth, kind };
665
+ return createOrthographicProjection({ halfHeight, halfWidth });
455
666
  }
456
667
  if (kind === 'perspective') {
457
668
  if (!hasOnlyKeys(value, PERSPECTIVE_PROJECTION_KEYS))
@@ -462,7 +673,7 @@ function readProjection(value, path, context) {
462
673
  return refuse(context, appendPath(path, 'aspect'));
463
674
  if (typeof fovY !== 'number')
464
675
  return refuse(context, appendPath(path, 'fovY'));
465
- return { aspect, fovY, kind };
676
+ return createPerspectiveProjection({ aspect, fovY });
466
677
  }
467
678
  return refuse(context, appendPath(path, 'kind'));
468
679
  }
@@ -519,10 +730,16 @@ function readScene2D(mapping, path, context) {
519
730
  if (backgroundColor !== undefined && backgroundColor !== null && typeof backgroundColor !== 'number') {
520
731
  return refuse(context, appendPath(path, 'backgroundColor'));
521
732
  }
522
- const scene = readNode(mapping['scene'], appendPath(path, 'scene'), context);
733
+ const scene = readNode(mapping['scene'], appendPath(path, 'scene'), 'Scene2D', context);
523
734
  if (scene === null)
524
735
  return null;
525
- return { backgroundColor: backgroundColor ?? null, kind: 'Scene2D', scene };
736
+ const layouts = readLayouts(mapping['layouts'], path, context);
737
+ if (layouts === null)
738
+ return null;
739
+ const tokens = readTokens(mapping['tokens'], path, context);
740
+ if (tokens === null)
741
+ return null;
742
+ return { backgroundColor: backgroundColor ?? null, kind: 'Scene2D', layouts, scene, tokens };
526
743
  }
527
744
  function readScene3D(mapping, path, context) {
528
745
  if (!hasOnlyKeys(mapping, SCENE_3D_KEYS))
@@ -551,10 +768,134 @@ function readScene3D(mapping, path, context) {
551
768
  lights.push(light);
552
769
  }
553
770
  }
554
- const scene = readNode(mapping['scene'], appendPath(path, 'scene'), context);
771
+ const scene = readNode(mapping['scene'], appendPath(path, 'scene'), 'Scene3D', context);
555
772
  if (scene === null)
556
773
  return null;
557
- return { cameras, kind: 'Scene3D', lights, scene };
774
+ const layouts = readLayouts(mapping['layouts'], path, context);
775
+ if (layouts === null)
776
+ return null;
777
+ const tokens = readTokens(mapping['tokens'], path, context);
778
+ if (tokens === null)
779
+ return null;
780
+ return { cameras, kind: 'Scene3D', layouts, lights, scene, tokens };
781
+ }
782
+ function readLayouts(value, scenePath, context) {
783
+ if (value === undefined)
784
+ return [];
785
+ const layoutsPath = appendPath(scenePath, 'layouts');
786
+ if (!Array.isArray(value))
787
+ return refuse(context, layoutsPath);
788
+ const layouts = [];
789
+ const seenTargets = new Set();
790
+ for (let layoutIndex = 0; layoutIndex < value.length; layoutIndex++) {
791
+ const layoutPath = `${layoutsPath}[${layoutIndex}]`;
792
+ const raw = value[layoutIndex];
793
+ if (!isMapping(raw) || !hasOnlyKeys(raw, LAYOUT_KEYS))
794
+ return refuse(context, layoutPath);
795
+ const targetsPath = appendPath(layoutPath, 'targets');
796
+ const targetsRaw = raw['targets'];
797
+ if (!Array.isArray(targetsRaw) || targetsRaw.length === 0)
798
+ return refuse(context, targetsPath);
799
+ const targets = [];
800
+ for (let targetIndex = 0; targetIndex < targetsRaw.length; targetIndex++) {
801
+ const targetPath = `${targetsPath}[${targetIndex}]`;
802
+ const target = targetsRaw[targetIndex];
803
+ if (typeof target !== 'string' || target.length === 0 || seenTargets.has(target)) {
804
+ return refuse(context, targetPath);
805
+ }
806
+ seenTargets.add(target);
807
+ targets.push(target);
808
+ }
809
+ const treePath = appendPath(layoutPath, 'tree');
810
+ const treeRaw = raw['tree'];
811
+ if (!isMapping(treeRaw) || !hasOnlyKeys(treeRaw, LAYOUT_TREE_KEYS))
812
+ return refuse(context, treePath);
813
+ const nodesPath = appendPath(treePath, 'nodes');
814
+ const nodesRaw = treeRaw['nodes'];
815
+ if (!Array.isArray(nodesRaw) || nodesRaw.length === 0)
816
+ return refuse(context, nodesPath);
817
+ const nodes = [];
818
+ for (let nodeIndex = 0; nodeIndex < nodesRaw.length; nodeIndex++) {
819
+ const node = readLayoutNode(nodesRaw[nodeIndex], nodeIndex, `${nodesPath}[${nodeIndex}]`, context);
820
+ if (node === null)
821
+ return null;
822
+ nodes.push(node);
823
+ }
824
+ if (targets.length !== nodes.length)
825
+ return refuse(context, targetsPath);
826
+ layouts.push({ targets, tree: { nodes } });
827
+ }
828
+ return layouts;
829
+ }
830
+ function readLayoutNode(value, index, path, context) {
831
+ if (!isMapping(value) || !hasOnlyKeys(value, LAYOUT_NODE_KEYS))
832
+ return refuse(context, path);
833
+ const kind = value['kind'];
834
+ if (typeof kind !== 'string' || kind.length === 0)
835
+ return refuse(context, appendPath(path, 'kind'));
836
+ const parentIndex = value['parentIndex'];
837
+ if (!Number.isInteger(parentIndex) || parentIndex < -1 || parentIndex >= index) {
838
+ return refuse(context, appendPath(path, 'parentIndex'));
839
+ }
840
+ const containerStyle = readLayoutStyle(value['containerStyle'], appendPath(path, 'containerStyle'), context);
841
+ if (containerStyle === INVALID_LAYOUT_STYLE)
842
+ return null;
843
+ const itemStyle = readLayoutStyle(value['itemStyle'], appendPath(path, 'itemStyle'), context);
844
+ if (itemStyle === INVALID_LAYOUT_STYLE)
845
+ return null;
846
+ return { containerStyle, itemStyle, kind, parentIndex: parentIndex };
847
+ }
848
+ function readLayoutStyle(value, path, context) {
849
+ if (value === undefined || value === null)
850
+ return null;
851
+ if (!isMapping(value)) {
852
+ refuse(context, path);
853
+ return INVALID_LAYOUT_STYLE;
854
+ }
855
+ const fields = copyFlightDocumentFields(value, [], path, context);
856
+ return fields ?? INVALID_LAYOUT_STYLE;
857
+ }
858
+ function readTokens(value, path, context) {
859
+ if (value === undefined)
860
+ return [];
861
+ if (!Array.isArray(value))
862
+ return refuse(context, appendPath(path, 'tokens'));
863
+ const tokens = [];
864
+ const seen = new Set();
865
+ for (let index = 0; index < value.length; index++) {
866
+ const rowPath = `${path}.tokens[${index}]`;
867
+ const raw = value[index];
868
+ if (!isMapping(raw))
869
+ return refuse(context, rowPath);
870
+ const kind = raw['kind'];
871
+ const key = raw['key'];
872
+ if (typeof kind !== 'string')
873
+ return refuse(context, appendPath(rowPath, 'kind'));
874
+ if (typeof key !== 'string')
875
+ return refuse(context, appendPath(rowPath, 'key'));
876
+ if (!SAFE_PLAIN_SCALAR_PATTERN.test(key)) {
877
+ return refuseToken(context, FlightDocumentRefusalReason.TokenKeyInvalid, appendPath(rowPath, 'key'), key, null);
878
+ }
879
+ if (seen.has(key)) {
880
+ return refuseToken(context, FlightDocumentRefusalReason.TokenKeyDuplicate, appendPath(rowPath, 'key'), key, null);
881
+ }
882
+ seen.add(key);
883
+ const values = {};
884
+ for (const mode of Object.keys(raw)) {
885
+ if (TOKEN_RESERVED_FIELDS.includes(mode))
886
+ continue;
887
+ const modePath = appendPath(rowPath, mode);
888
+ if (!SAFE_PLAIN_SCALAR_PATTERN.test(mode)) {
889
+ return refuseToken(context, FlightDocumentRefusalReason.TokenModeInvalid, modePath, key, mode);
890
+ }
891
+ const modeValue = copyFlightDocumentValue(raw[mode], modePath, context);
892
+ if (modeValue === INVALID_FLIGHT_DOCUMENT_VALUE)
893
+ return null;
894
+ values[mode] = modeValue;
895
+ }
896
+ tokens.push({ key, kind, values });
897
+ }
898
+ return tokens;
558
899
  }
559
900
  function readTransform3D(value, path, context) {
560
901
  if (!isMapping(value) || !hasOnlyKeys(value, TRANSFORM_3D_KEYS))
@@ -592,6 +933,20 @@ function refuse(context, path) {
592
933
  context.refusal ??= createDocumentRefusal(FlightDocumentRefusalReason.StructureInvalid, path);
593
934
  return null;
594
935
  }
936
+ function refuseWithReason(context, reason, path, kind) {
937
+ if (context.refusal === null) {
938
+ context.refusal = createDocumentRefusal(reason, path);
939
+ context.refusal.kind = kind;
940
+ }
941
+ return null;
942
+ }
943
+ function refuseToken(context, reason, path, tokenKey, mode) {
944
+ const refusal = createDocumentRefusal(reason, path);
945
+ refusal.mode = mode;
946
+ refusal.tokenKey = tokenKey;
947
+ context.refusal ??= refusal;
948
+ return null;
949
+ }
595
950
  function refuseValue(context, reason, path) {
596
951
  context.refusal ??= createDocumentRefusal(reason, path);
597
952
  return INVALID_FLIGHT_DOCUMENT_VALUE;
@@ -600,18 +955,30 @@ function appendPath(path, key) {
600
955
  return path === '' ? key : path + '.' + key;
601
956
  }
602
957
  const INVALID_FLIGHT_DOCUMENT_VALUE = Symbol('invalid-flight-document-value');
958
+ const INVALID_LAYOUT_STYLE = Symbol('invalid-layout-style');
603
959
  const INVALID_OPTIONAL_INDEX = Symbol('invalid-flight-document-optional-index');
604
960
  const CAMERA_KEYS = ['far', 'name', 'near', 'node', 'projection', 'transform'];
961
+ const DESCRIPTOR_RESERVED_FIELDS = ['kind'];
605
962
  const DOCUMENT_KEYS = ['defaultScene', 'flight', 'resources', 'scenes'];
963
+ const INTERACTIVE_STATE_2D_PROPERTIES = ['scaleX', 'scaleY', 'x', 'y'];
964
+ const INTERACTIVE_STATE_KEYS = ['alpha', 'extensions', 'scaleX', 'scaleY', 'visible', 'x', 'y'];
965
+ const INTERACTIVE_STATE_PHASES = ['disabled', 'hover', 'pressed'];
966
+ const INTERACTIVE_STATE_PROPERTIES = ['alpha', 'scaleX', 'scaleY', 'visible', 'x', 'y'];
606
967
  const LIGHT_KEYS = ['descriptor', 'name', 'node', 'transform'];
607
- const NODE_RESERVED_FIELDS = ['children', 'kind'];
968
+ const LAYOUT_KEYS = ['targets', 'tree'];
969
+ const LAYOUT_NODE_KEYS = ['containerStyle', 'itemStyle', 'kind', 'parentIndex'];
970
+ const LAYOUT_TREE_KEYS = ['nodes'];
971
+ const NODE_RESERVED_FIELDS = ['children', 'interactiveStates', 'kind', 'transition'];
608
972
  const ORTHOGRAPHIC_PROJECTION_KEYS = ['halfHeight', 'halfWidth', 'kind'];
609
973
  const PERSPECTIVE_PROJECTION_KEYS = ['aspect', 'fovY', 'kind'];
610
974
  const QUATERNION_KEYS = ['w', 'x', 'y', 'z'];
611
975
  const RESOURCE_RESERVED_FIELDS = ['key', 'kind'];
612
976
  const SAFE_PLAIN_SCALAR_PATTERN = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
613
- const SCENE_2D_KEYS = ['backgroundColor', 'kind', 'scene'];
614
- const SCENE_3D_KEYS = ['cameras', 'kind', 'lights', 'scene'];
977
+ const SCENE_2D_KEYS = ['backgroundColor', 'kind', 'layouts', 'scene', 'tokens'];
978
+ const SCENE_3D_KEYS = ['cameras', 'kind', 'layouts', 'lights', 'scene', 'tokens'];
979
+ // A token row flattens its mode variants beside kind and key, exactly as a resource row flattens
980
+ // its fields, so these two names are structural and can never be authored as mode names.
981
+ const TOKEN_RESERVED_FIELDS = ['key', 'kind'];
615
982
  const TRANSFORM_3D_KEYS = ['position', 'rotation', 'scale'];
616
983
  const VECTOR_3_KEYS = ['x', 'y', 'z'];
617
984
  //# sourceMappingURL=flightDocumentText.js.map