@flighthq/scene-document 0.5.1-next.658.9cb44d7 → 0.5.1-next.685.f72fc7f

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 (46) hide show
  1. package/README.md +2 -2
  2. package/dist/contract.d.ts +1 -0
  3. package/dist/contract.d.ts.map +1 -1
  4. package/dist/contract.js +1 -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.map +1 -1
  11. package/dist/flightDocumentText.js +370 -10
  12. package/dist/flightDocumentText.js.map +1 -1
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1 -0
  16. package/dist/index.js.map +1 -1
  17. package/dist/sceneDocumentInteractiveStateBindings.d.ts +11 -0
  18. package/dist/sceneDocumentInteractiveStateBindings.d.ts.map +1 -0
  19. package/dist/sceneDocumentInteractiveStateBindings.js +108 -0
  20. package/dist/sceneDocumentInteractiveStateBindings.js.map +1 -0
  21. package/dist/sceneDocumentLayoutBindings.d.ts +5 -0
  22. package/dist/sceneDocumentLayoutBindings.d.ts.map +1 -0
  23. package/dist/sceneDocumentLayoutBindings.js +207 -0
  24. package/dist/sceneDocumentLayoutBindings.js.map +1 -0
  25. package/dist/sceneDocumentRefusal.d.ts +4 -1
  26. package/dist/sceneDocumentRefusal.d.ts.map +1 -1
  27. package/dist/sceneDocumentRefusal.js +140 -0
  28. package/dist/sceneDocumentRefusal.js.map +1 -1
  29. package/dist/sceneDocumentScene2DMaterialization.d.ts +2 -2
  30. package/dist/sceneDocumentScene2DMaterialization.d.ts.map +1 -1
  31. package/dist/sceneDocumentScene2DMaterialization.js +73 -10
  32. package/dist/sceneDocumentScene2DMaterialization.js.map +1 -1
  33. package/dist/sceneDocumentScene3DMaterialization.d.ts +2 -2
  34. package/dist/sceneDocumentScene3DMaterialization.d.ts.map +1 -1
  35. package/dist/sceneDocumentScene3DMaterialization.js +71 -10
  36. package/dist/sceneDocumentScene3DMaterialization.js.map +1 -1
  37. package/package.json +10 -10
  38. package/src/flightDocumentResourceDependencies.test.ts +83 -0
  39. package/src/flightDocumentText.test.ts +271 -0
  40. package/src/sceneDocumentInteractiveStateBindings.test.ts +129 -0
  41. package/src/sceneDocumentLayoutBindings.test.ts +141 -0
  42. package/src/sceneDocumentMaterializationSelection.test.ts +4 -0
  43. package/src/sceneDocumentMultiSceneMaterialization.test.ts +6 -0
  44. package/src/sceneDocumentRefusal.test.ts +84 -0
  45. package/src/sceneDocumentScene2DMaterialization.test.ts +353 -1
  46. package/src/sceneDocumentScene3DMaterialization.test.ts +147 -4
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.5.1-next.658.9cb44d7](https://github.com/flighthq/flight/tree/9cb44d76b981adad2503d5e62207be99adb35c55/packages/scene-document)
17
- - [License](https://github.com/flighthq/flight/blob/9cb44d76b981adad2503d5e62207be99adb35c55/LICENSE.md)
16
+ - [Source for @flighthq/scene-document@0.5.1-next.685.f72fc7f](https://github.com/flighthq/flight/tree/f72fc7feaa9653d950f39c7d2fe6365f48e82e51/packages/scene-document)
17
+ - [License](https://github.com/flighthq/flight/blob/f72fc7feaa9653d950f39c7d2fe6365f48e82e51/LICENSE.md)
@@ -1,4 +1,5 @@
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';
4
5
  //# 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"}
package/dist/contract.js CHANGED
@@ -1,4 +1,5 @@
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';
4
5
  //# 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"}
@@ -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 +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":"AACA,OAAO,KAAK,EACV,cAAc,EASd,gCAAgC,EAOhC,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"}
@@ -88,6 +88,7 @@ function appendNode(lines, node, indent) {
88
88
  const prefix = ' '.repeat(indent);
89
89
  lines.push(prefix + 'kind: ' + formatString(node.kind));
90
90
  appendFields(lines, node.fields, indent, NODE_RESERVED_FIELDS);
91
+ appendNodeInteractiveMetadata(lines, node, indent);
91
92
  if (node.children.length > 0) {
92
93
  lines.push(prefix + 'children:');
93
94
  for (const child of node.children)
@@ -98,12 +99,67 @@ function appendNodeSequenceItem(lines, node, indent) {
98
99
  const prefix = ' '.repeat(indent);
99
100
  lines.push(prefix + '- kind: ' + formatString(node.kind));
100
101
  appendFields(lines, node.fields, indent + 2, NODE_RESERVED_FIELDS);
102
+ appendNodeInteractiveMetadata(lines, node, indent + 2);
101
103
  if (node.children.length > 0) {
102
104
  lines.push(prefix + ' children:');
103
105
  for (const child of node.children)
104
106
  appendNodeSequenceItem(lines, child, indent + 4);
105
107
  }
106
108
  }
109
+ function appendInteractiveState(lines, state, indent) {
110
+ const entries = getInteractiveStateEntries(state);
111
+ if (entries.length === 0 && state.extensions.length === 0) {
112
+ throw new TypeError('FlightDocument interactive state must not be empty');
113
+ }
114
+ for (const [property, value] of entries)
115
+ appendMappingEntry(lines, property, value, indent);
116
+ if (state.extensions.length === 0)
117
+ return;
118
+ lines.push(' '.repeat(indent) + 'extensions:');
119
+ for (const extension of state.extensions) {
120
+ lines.push(' '.repeat(indent + 2) + '- kind: ' + formatString(extension.kind));
121
+ appendFields(lines, extension.fields, indent + 4, DESCRIPTOR_RESERVED_FIELDS);
122
+ }
123
+ }
124
+ function appendNodeInteractiveMetadata(lines, node, indent) {
125
+ const prefix = ' '.repeat(indent);
126
+ if (node.interactiveStates == null) {
127
+ if (node.transition != null)
128
+ throw new TypeError('FlightDocument transition requires interactiveStates');
129
+ return;
130
+ }
131
+ const phases = getInteractiveStatePhases(node.interactiveStates);
132
+ if (phases.length === 0)
133
+ throw new TypeError('FlightDocument interactiveStates must not be empty');
134
+ lines.push(prefix + 'interactiveStates:');
135
+ for (const [phase, state] of phases) {
136
+ lines.push(prefix + ' ' + phase + ':');
137
+ appendInteractiveState(lines, state, indent + 4);
138
+ }
139
+ if (node.transition != null) {
140
+ lines.push(prefix + 'transition:');
141
+ lines.push(prefix + ' kind: ' + formatString(node.transition.kind));
142
+ appendFields(lines, node.transition.fields, indent + 2, DESCRIPTOR_RESERVED_FIELDS);
143
+ }
144
+ }
145
+ function getInteractiveStateEntries(state) {
146
+ const out = [];
147
+ for (const property of INTERACTIVE_STATE_PROPERTIES) {
148
+ const value = state[property];
149
+ if (value !== undefined)
150
+ out.push([property, value]);
151
+ }
152
+ return out;
153
+ }
154
+ function getInteractiveStatePhases(states) {
155
+ const out = [];
156
+ for (const phase of INTERACTIVE_STATE_PHASES) {
157
+ const state = states[phase];
158
+ if (state !== null)
159
+ out.push([phase, state]);
160
+ }
161
+ return out;
162
+ }
107
163
  function appendObjectEntries(lines, value, indent, reserved) {
108
164
  for (const [key, entry] of Object.entries(value).sort(([left], [right]) => left.localeCompare(right))) {
109
165
  if (reserved.includes(key))
@@ -153,6 +209,60 @@ function appendScene(lines, scene) {
153
209
  }
154
210
  lines.push(' scene:');
155
211
  appendNode(lines, scene.scene, 6);
212
+ appendLayouts(lines, scene.layouts);
213
+ appendTokens(lines, scene.tokens);
214
+ }
215
+ function appendLayouts(lines, layouts) {
216
+ if (layouts.length === 0)
217
+ return;
218
+ const seenTargets = new Set();
219
+ lines.push(' layouts:');
220
+ for (const layout of layouts) {
221
+ const nodes = layout.tree.nodes;
222
+ if (nodes.length === 0)
223
+ throw new RangeError('FlightDocument layout tree must not be empty');
224
+ if (layout.targets.length !== nodes.length) {
225
+ throw new RangeError('FlightDocument layout targets must match its tree nodes index-for-index');
226
+ }
227
+ lines.push(' - targets:');
228
+ for (const target of layout.targets) {
229
+ if (target.length === 0)
230
+ throw new RangeError('FlightDocument layout target names must not be empty');
231
+ if (seenTargets.has(target))
232
+ throw new RangeError('FlightDocument layout targets must be unique per scene');
233
+ seenTargets.add(target);
234
+ lines.push(' - ' + formatString(target));
235
+ }
236
+ lines.push(' tree:');
237
+ lines.push(' nodes:');
238
+ for (let index = 0; index < nodes.length; index++)
239
+ appendLayoutNode(lines, nodes[index], index);
240
+ }
241
+ }
242
+ function appendLayoutNode(lines, node, index) {
243
+ if (node.kind.length === 0)
244
+ throw new RangeError('FlightDocument layout node kinds must not be empty');
245
+ if (!Number.isInteger(node.parentIndex) || node.parentIndex < -1 || node.parentIndex >= index) {
246
+ throw new RangeError('FlightDocument layout parentIndex must be -1 or precede its node');
247
+ }
248
+ lines.push(' - kind: ' + formatString(node.kind));
249
+ lines.push(' parentIndex: ' + String(node.parentIndex));
250
+ if (node.containerStyle !== null)
251
+ appendMappingEntry(lines, 'containerStyle', node.containerStyle, 14);
252
+ if (node.itemStyle !== null)
253
+ appendMappingEntry(lines, 'itemStyle', node.itemStyle, 14);
254
+ }
255
+ function appendTokens(lines, tokens) {
256
+ if (tokens.length === 0)
257
+ return;
258
+ lines.push(' tokens:');
259
+ for (const token of tokens) {
260
+ lines.push(' - kind: ' + formatString(token.kind));
261
+ lines.push(' key: ' + formatString(token.key));
262
+ // Mode names are sorted so a formatted document is stable, while the token ROWS keep the order
263
+ // they were authored in: row order is the author's, mode order is not meaningful.
264
+ appendFields(lines, token.values, 8, TOKEN_RESERVED_FIELDS);
265
+ }
156
266
  }
157
267
  function appendSequence(lines, values, indent) {
158
268
  const prefix = ' '.repeat(indent);
@@ -306,10 +416,12 @@ function readFlightDocumentText(text) {
306
416
  kind: null,
307
417
  limit: parsed.limit,
308
418
  line: parsed.line,
419
+ mode: null,
309
420
  offset: parsed.offset,
310
421
  path: '',
311
422
  reason: parsed.kind,
312
423
  resourceKey: null,
424
+ tokenKey: null,
313
425
  version: null,
314
426
  },
315
427
  };
@@ -409,7 +521,83 @@ function readLight(value, path, context) {
409
521
  light.node = node;
410
522
  return light;
411
523
  }
412
- function readNode(value, path, context) {
524
+ function readInteractiveState(value, path, dimension, context) {
525
+ if (!isMapping(value) || !hasOnlyKeys(value, INTERACTIVE_STATE_KEYS))
526
+ return refuse(context, path);
527
+ const state = { extensions: [] };
528
+ for (const property of INTERACTIVE_STATE_PROPERTIES) {
529
+ const propertyValue = value[property];
530
+ if (propertyValue === undefined)
531
+ continue;
532
+ if (property === 'visible') {
533
+ if (typeof propertyValue !== 'boolean')
534
+ return refuse(context, appendPath(path, property));
535
+ }
536
+ else if (typeof propertyValue !== 'number' || !Number.isFinite(propertyValue)) {
537
+ return refuse(context, appendPath(path, property));
538
+ }
539
+ if (dimension === 'Scene3D' && INTERACTIVE_STATE_2D_PROPERTIES.includes(property)) {
540
+ return refuse(context, appendPath(path, property));
541
+ }
542
+ Object.assign(state, { [property]: propertyValue });
543
+ }
544
+ const extensionsRaw = value['extensions'];
545
+ if (extensionsRaw !== undefined) {
546
+ if (!Array.isArray(extensionsRaw))
547
+ return refuse(context, appendPath(path, 'extensions'));
548
+ const kinds = new Set();
549
+ for (let i = 0; i < extensionsRaw.length; i++) {
550
+ const extensionPath = `${path}.extensions[${i}]`;
551
+ const extension = readInteractiveStateExtension(extensionsRaw[i], extensionPath, context);
552
+ if (extension === null)
553
+ return null;
554
+ if (kinds.has(extension.kind)) {
555
+ return refuseWithReason(context, FlightDocumentRefusalReason.InteractiveStateExtensionKindDuplicate, extensionPath, extension.kind);
556
+ }
557
+ kinds.add(extension.kind);
558
+ state.extensions.push(extension);
559
+ }
560
+ }
561
+ if (getInteractiveStateEntries(state).length === 0 && state.extensions.length === 0)
562
+ return refuse(context, path);
563
+ return state;
564
+ }
565
+ function readInteractiveStateExtension(value, path, context) {
566
+ if (!isMapping(value))
567
+ return refuse(context, path);
568
+ const kind = value['kind'];
569
+ if (typeof kind !== 'string')
570
+ return refuse(context, appendPath(path, 'kind'));
571
+ const fields = copyFlightDocumentFields(value, DESCRIPTOR_RESERVED_FIELDS, path, context);
572
+ return fields === null ? null : { fields, kind };
573
+ }
574
+ function readInteractiveStates(value, path, dimension, context) {
575
+ if (!isMapping(value) || !hasOnlyKeys(value, INTERACTIVE_STATE_PHASES))
576
+ return refuse(context, path);
577
+ const states = { disabled: null, hover: null, pressed: null };
578
+ let count = 0;
579
+ for (const phase of INTERACTIVE_STATE_PHASES) {
580
+ const stateRaw = value[phase];
581
+ if (stateRaw === undefined)
582
+ continue;
583
+ const state = readInteractiveState(stateRaw, appendPath(path, phase), dimension, context);
584
+ if (state === null)
585
+ return null;
586
+ states[phase] = state;
587
+ count++;
588
+ }
589
+ return count === 0 ? refuse(context, path) : states;
590
+ }
591
+ function readInteractiveStateTransition(value, path, context) {
592
+ if (!isMapping(value))
593
+ return refuse(context, path);
594
+ const kind = value['kind'];
595
+ if (typeof kind !== 'string')
596
+ return refuse(context, appendPath(path, 'kind'));
597
+ const fields = copyFlightDocumentFields(value, DESCRIPTOR_RESERVED_FIELDS, path, context);
598
+ return fields === null ? null : { fields, kind };
599
+ }
600
+ function readNode(value, path, dimension, context) {
413
601
  if (!isMapping(value))
414
602
  return refuse(context, path);
415
603
  const kind = value['kind'];
@@ -421,14 +609,30 @@ function readNode(value, path, context) {
421
609
  if (!Array.isArray(childrenRaw))
422
610
  return refuse(context, appendPath(path, 'children'));
423
611
  for (let index = 0; index < childrenRaw.length; index++) {
424
- const child = readNode(childrenRaw[index], `${path}.children[${index}]`, context);
612
+ const child = readNode(childrenRaw[index], `${path}.children[${index}]`, dimension, context);
425
613
  if (child === null)
426
614
  return null;
427
615
  children.push(child);
428
616
  }
429
617
  }
618
+ const interactiveStatesRaw = value['interactiveStates'];
619
+ let interactiveStates = null;
620
+ if (interactiveStatesRaw !== undefined) {
621
+ interactiveStates = readInteractiveStates(interactiveStatesRaw, appendPath(path, 'interactiveStates'), dimension, context);
622
+ if (interactiveStates === null)
623
+ return null;
624
+ }
625
+ const transitionRaw = value['transition'];
626
+ if (transitionRaw !== undefined && interactiveStates === null)
627
+ return refuse(context, appendPath(path, 'transition'));
628
+ let transition = null;
629
+ if (transitionRaw !== undefined) {
630
+ transition = readInteractiveStateTransition(transitionRaw, appendPath(path, 'transition'), context);
631
+ if (transition === null)
632
+ return null;
633
+ }
430
634
  const fields = copyFlightDocumentFields(value, NODE_RESERVED_FIELDS, path, context);
431
- return fields === null ? null : { children, fields, kind };
635
+ return fields === null ? null : { children, fields, interactiveStates, kind, transition };
432
636
  }
433
637
  function readOptionalIndex(value, path, context) {
434
638
  if (value === undefined)
@@ -519,10 +723,16 @@ function readScene2D(mapping, path, context) {
519
723
  if (backgroundColor !== undefined && backgroundColor !== null && typeof backgroundColor !== 'number') {
520
724
  return refuse(context, appendPath(path, 'backgroundColor'));
521
725
  }
522
- const scene = readNode(mapping['scene'], appendPath(path, 'scene'), context);
726
+ const scene = readNode(mapping['scene'], appendPath(path, 'scene'), 'Scene2D', context);
523
727
  if (scene === null)
524
728
  return null;
525
- return { backgroundColor: backgroundColor ?? null, kind: 'Scene2D', scene };
729
+ const layouts = readLayouts(mapping['layouts'], path, context);
730
+ if (layouts === null)
731
+ return null;
732
+ const tokens = readTokens(mapping['tokens'], path, context);
733
+ if (tokens === null)
734
+ return null;
735
+ return { backgroundColor: backgroundColor ?? null, kind: 'Scene2D', layouts, scene, tokens };
526
736
  }
527
737
  function readScene3D(mapping, path, context) {
528
738
  if (!hasOnlyKeys(mapping, SCENE_3D_KEYS))
@@ -551,10 +761,134 @@ function readScene3D(mapping, path, context) {
551
761
  lights.push(light);
552
762
  }
553
763
  }
554
- const scene = readNode(mapping['scene'], appendPath(path, 'scene'), context);
764
+ const scene = readNode(mapping['scene'], appendPath(path, 'scene'), 'Scene3D', context);
555
765
  if (scene === null)
556
766
  return null;
557
- return { cameras, kind: 'Scene3D', lights, scene };
767
+ const layouts = readLayouts(mapping['layouts'], path, context);
768
+ if (layouts === null)
769
+ return null;
770
+ const tokens = readTokens(mapping['tokens'], path, context);
771
+ if (tokens === null)
772
+ return null;
773
+ return { cameras, kind: 'Scene3D', layouts, lights, scene, tokens };
774
+ }
775
+ function readLayouts(value, scenePath, context) {
776
+ if (value === undefined)
777
+ return [];
778
+ const layoutsPath = appendPath(scenePath, 'layouts');
779
+ if (!Array.isArray(value))
780
+ return refuse(context, layoutsPath);
781
+ const layouts = [];
782
+ const seenTargets = new Set();
783
+ for (let layoutIndex = 0; layoutIndex < value.length; layoutIndex++) {
784
+ const layoutPath = `${layoutsPath}[${layoutIndex}]`;
785
+ const raw = value[layoutIndex];
786
+ if (!isMapping(raw) || !hasOnlyKeys(raw, LAYOUT_KEYS))
787
+ return refuse(context, layoutPath);
788
+ const targetsPath = appendPath(layoutPath, 'targets');
789
+ const targetsRaw = raw['targets'];
790
+ if (!Array.isArray(targetsRaw) || targetsRaw.length === 0)
791
+ return refuse(context, targetsPath);
792
+ const targets = [];
793
+ for (let targetIndex = 0; targetIndex < targetsRaw.length; targetIndex++) {
794
+ const targetPath = `${targetsPath}[${targetIndex}]`;
795
+ const target = targetsRaw[targetIndex];
796
+ if (typeof target !== 'string' || target.length === 0 || seenTargets.has(target)) {
797
+ return refuse(context, targetPath);
798
+ }
799
+ seenTargets.add(target);
800
+ targets.push(target);
801
+ }
802
+ const treePath = appendPath(layoutPath, 'tree');
803
+ const treeRaw = raw['tree'];
804
+ if (!isMapping(treeRaw) || !hasOnlyKeys(treeRaw, LAYOUT_TREE_KEYS))
805
+ return refuse(context, treePath);
806
+ const nodesPath = appendPath(treePath, 'nodes');
807
+ const nodesRaw = treeRaw['nodes'];
808
+ if (!Array.isArray(nodesRaw) || nodesRaw.length === 0)
809
+ return refuse(context, nodesPath);
810
+ const nodes = [];
811
+ for (let nodeIndex = 0; nodeIndex < nodesRaw.length; nodeIndex++) {
812
+ const node = readLayoutNode(nodesRaw[nodeIndex], nodeIndex, `${nodesPath}[${nodeIndex}]`, context);
813
+ if (node === null)
814
+ return null;
815
+ nodes.push(node);
816
+ }
817
+ if (targets.length !== nodes.length)
818
+ return refuse(context, targetsPath);
819
+ layouts.push({ targets, tree: { nodes } });
820
+ }
821
+ return layouts;
822
+ }
823
+ function readLayoutNode(value, index, path, context) {
824
+ if (!isMapping(value) || !hasOnlyKeys(value, LAYOUT_NODE_KEYS))
825
+ return refuse(context, path);
826
+ const kind = value['kind'];
827
+ if (typeof kind !== 'string' || kind.length === 0)
828
+ return refuse(context, appendPath(path, 'kind'));
829
+ const parentIndex = value['parentIndex'];
830
+ if (!Number.isInteger(parentIndex) || parentIndex < -1 || parentIndex >= index) {
831
+ return refuse(context, appendPath(path, 'parentIndex'));
832
+ }
833
+ const containerStyle = readLayoutStyle(value['containerStyle'], appendPath(path, 'containerStyle'), context);
834
+ if (containerStyle === INVALID_LAYOUT_STYLE)
835
+ return null;
836
+ const itemStyle = readLayoutStyle(value['itemStyle'], appendPath(path, 'itemStyle'), context);
837
+ if (itemStyle === INVALID_LAYOUT_STYLE)
838
+ return null;
839
+ return { containerStyle, itemStyle, kind, parentIndex: parentIndex };
840
+ }
841
+ function readLayoutStyle(value, path, context) {
842
+ if (value === undefined || value === null)
843
+ return null;
844
+ if (!isMapping(value)) {
845
+ refuse(context, path);
846
+ return INVALID_LAYOUT_STYLE;
847
+ }
848
+ const fields = copyFlightDocumentFields(value, [], path, context);
849
+ return fields ?? INVALID_LAYOUT_STYLE;
850
+ }
851
+ function readTokens(value, path, context) {
852
+ if (value === undefined)
853
+ return [];
854
+ if (!Array.isArray(value))
855
+ return refuse(context, appendPath(path, 'tokens'));
856
+ const tokens = [];
857
+ const seen = new Set();
858
+ for (let index = 0; index < value.length; index++) {
859
+ const rowPath = `${path}.tokens[${index}]`;
860
+ const raw = value[index];
861
+ if (!isMapping(raw))
862
+ return refuse(context, rowPath);
863
+ const kind = raw['kind'];
864
+ const key = raw['key'];
865
+ if (typeof kind !== 'string')
866
+ return refuse(context, appendPath(rowPath, 'kind'));
867
+ if (typeof key !== 'string')
868
+ return refuse(context, appendPath(rowPath, 'key'));
869
+ if (!SAFE_PLAIN_SCALAR_PATTERN.test(key)) {
870
+ return refuseToken(context, FlightDocumentRefusalReason.TokenKeyInvalid, appendPath(rowPath, 'key'), key, null);
871
+ }
872
+ if (seen.has(key)) {
873
+ return refuseToken(context, FlightDocumentRefusalReason.TokenKeyDuplicate, appendPath(rowPath, 'key'), key, null);
874
+ }
875
+ seen.add(key);
876
+ const values = {};
877
+ for (const mode of Object.keys(raw)) {
878
+ if (TOKEN_RESERVED_FIELDS.includes(mode))
879
+ continue;
880
+ const modePath = appendPath(rowPath, mode);
881
+ if (!SAFE_PLAIN_SCALAR_PATTERN.test(mode)) {
882
+ return refuseToken(context, FlightDocumentRefusalReason.TokenModeInvalid, modePath, key, mode);
883
+ }
884
+ const modeValue = copyFlightDocumentValue(raw[mode], modePath, context);
885
+ if (modeValue === INVALID_FLIGHT_DOCUMENT_VALUE)
886
+ return null;
887
+ values[mode] = modeValue;
888
+ }
889
+ tokens.push({ key, kind, values });
890
+ }
891
+ return tokens;
558
892
  }
559
893
  function readTransform3D(value, path, context) {
560
894
  if (!isMapping(value) || !hasOnlyKeys(value, TRANSFORM_3D_KEYS))
@@ -592,6 +926,20 @@ function refuse(context, path) {
592
926
  context.refusal ??= createDocumentRefusal(FlightDocumentRefusalReason.StructureInvalid, path);
593
927
  return null;
594
928
  }
929
+ function refuseWithReason(context, reason, path, kind) {
930
+ if (context.refusal === null) {
931
+ context.refusal = createDocumentRefusal(reason, path);
932
+ context.refusal.kind = kind;
933
+ }
934
+ return null;
935
+ }
936
+ function refuseToken(context, reason, path, tokenKey, mode) {
937
+ const refusal = createDocumentRefusal(reason, path);
938
+ refusal.mode = mode;
939
+ refusal.tokenKey = tokenKey;
940
+ context.refusal ??= refusal;
941
+ return null;
942
+ }
595
943
  function refuseValue(context, reason, path) {
596
944
  context.refusal ??= createDocumentRefusal(reason, path);
597
945
  return INVALID_FLIGHT_DOCUMENT_VALUE;
@@ -600,18 +948,30 @@ function appendPath(path, key) {
600
948
  return path === '' ? key : path + '.' + key;
601
949
  }
602
950
  const INVALID_FLIGHT_DOCUMENT_VALUE = Symbol('invalid-flight-document-value');
951
+ const INVALID_LAYOUT_STYLE = Symbol('invalid-layout-style');
603
952
  const INVALID_OPTIONAL_INDEX = Symbol('invalid-flight-document-optional-index');
604
953
  const CAMERA_KEYS = ['far', 'name', 'near', 'node', 'projection', 'transform'];
954
+ const DESCRIPTOR_RESERVED_FIELDS = ['kind'];
605
955
  const DOCUMENT_KEYS = ['defaultScene', 'flight', 'resources', 'scenes'];
956
+ const INTERACTIVE_STATE_2D_PROPERTIES = ['scaleX', 'scaleY', 'x', 'y'];
957
+ const INTERACTIVE_STATE_KEYS = ['alpha', 'extensions', 'scaleX', 'scaleY', 'visible', 'x', 'y'];
958
+ const INTERACTIVE_STATE_PHASES = ['disabled', 'hover', 'pressed'];
959
+ const INTERACTIVE_STATE_PROPERTIES = ['alpha', 'scaleX', 'scaleY', 'visible', 'x', 'y'];
606
960
  const LIGHT_KEYS = ['descriptor', 'name', 'node', 'transform'];
607
- const NODE_RESERVED_FIELDS = ['children', 'kind'];
961
+ const LAYOUT_KEYS = ['targets', 'tree'];
962
+ const LAYOUT_NODE_KEYS = ['containerStyle', 'itemStyle', 'kind', 'parentIndex'];
963
+ const LAYOUT_TREE_KEYS = ['nodes'];
964
+ const NODE_RESERVED_FIELDS = ['children', 'interactiveStates', 'kind', 'transition'];
608
965
  const ORTHOGRAPHIC_PROJECTION_KEYS = ['halfHeight', 'halfWidth', 'kind'];
609
966
  const PERSPECTIVE_PROJECTION_KEYS = ['aspect', 'fovY', 'kind'];
610
967
  const QUATERNION_KEYS = ['w', 'x', 'y', 'z'];
611
968
  const RESOURCE_RESERVED_FIELDS = ['key', 'kind'];
612
969
  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'];
970
+ const SCENE_2D_KEYS = ['backgroundColor', 'kind', 'layouts', 'scene', 'tokens'];
971
+ const SCENE_3D_KEYS = ['cameras', 'kind', 'layouts', 'lights', 'scene', 'tokens'];
972
+ // A token row flattens its mode variants beside kind and key, exactly as a resource row flattens
973
+ // its fields, so these two names are structural and can never be authored as mode names.
974
+ const TOKEN_RESERVED_FIELDS = ['key', 'kind'];
615
975
  const TRANSFORM_3D_KEYS = ['position', 'rotation', 'scale'];
616
976
  const VECTOR_3_KEYS = ['x', 'y', 'z'];
617
977
  //# sourceMappingURL=flightDocumentText.js.map