@unseenco/theatre-core 0.4.3 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1927 @@
1
+ import { Pointer, PointerType, Prism } from '@unseenco/theatre-dataverse';
2
+
3
+ /**
4
+ * Using a symbol, we can sort of add unique properties to arbitrary other types.
5
+ * So, we use this to our advantage to add a "marker" of information to strings using
6
+ * the {@link Nominal} type.
7
+ *
8
+ * Can be used with keys in pointers.
9
+ * This identifier shows in the expanded {@link Nominal} as `string & {[nominal]:"SequenceTrackId"}`,
10
+ * So, we're opting to keeping the identifier short.
11
+ */
12
+ declare const nominal: unique symbol;
13
+ /**
14
+ * This creates an "opaque"/"nominal" type.
15
+ *
16
+ * Our primary use case is to be able to use with keys in pointers.
17
+ *
18
+ * Numbers cannot be added together if they are "nominal"
19
+ *
20
+ * See {@link nominal} for more details.
21
+ */
22
+ type Nominal<N extends string> = string & {
23
+ [nominal]: N;
24
+ };
25
+ declare global {
26
+ interface ObjectConstructor {
27
+ /** Nominal: Extension to the Object prototype definition to properly manage {@link Nominal} keyed records */
28
+ keys<T extends Record<Nominal<string>, any>>(obj: T): any extends T ? never[] : Extract<keyof T, string>[];
29
+ /** Nominal: Extension to the Object prototype definition to properly manage {@link Nominal} keyed records */
30
+ entries<T extends Record<Nominal<string>, any>>(obj: T): any extends T ? [never, never][] : Array<{
31
+ [P in keyof T]: [P, T[P]];
32
+ }[Extract<keyof T, string>]>;
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Addresses are used to identify projects, sheets, objects, and other things.
38
+ *
39
+ * For example, a project's address looks like `{projectId: 'my-project'}`, and a sheet's
40
+ * address looks like `{projectId: 'my-project', sheetId: 'my-sheet'}`.
41
+ *
42
+ * As you see, a Sheet's address is a superset of a Project's address. This is so that we can
43
+ * use the same address type for both. All addresses follow the same rule. An object's address
44
+ * extends its sheet's address, which extends its project's address.
45
+ *
46
+ * For example, generating an object's address from a sheet's address is as simple as `{...sheetAddress, objectId: 'my-object'}`.
47
+ *
48
+ * Also, if you need the projectAddress of an object, you can just re-use the object's address:
49
+ * `aFunctionThatRequiresProjectAddress(objectAddress)`.
50
+ */
51
+ /**
52
+ * Represents the address to a project
53
+ */
54
+ interface ProjectAddress {
55
+ projectId: ProjectId;
56
+ }
57
+ /**
58
+ * Represents the address to a specific instance of a Sheet
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * const sheet = project.sheet('a sheet', 'some instance id')
63
+ * sheet.address.sheetId === 'a sheet'
64
+ * sheet.address.sheetInstanceId === 'sheetInstanceId'
65
+ * ```
66
+ *
67
+ * See {@link WithoutSheetInstance} for a type that doesn't include the sheet instance id.
68
+ */
69
+ interface SheetAddress extends ProjectAddress {
70
+ sheetId: SheetId;
71
+ sheetInstanceId: SheetInstanceId;
72
+ }
73
+ /**
74
+ * Represents the address to a Sheet's Object.
75
+ *
76
+ * It includes the sheetInstance, so it's specific to a single instance of a sheet. If you
77
+ * would like an address that doesn't include the sheetInstance, use `WithoutSheetInstance<SheetObjectAddress>`.
78
+ */
79
+ interface SheetObjectAddress extends SheetAddress {
80
+ /**
81
+ * The key of the object.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * const obj = sheet.object('foo', {})
86
+ * obj.address.objectKey === 'foo'
87
+ * ```
88
+ */
89
+ objectKey: ObjectAddressKey;
90
+ }
91
+ /**
92
+ * Just like {@link PathToProp}, but encoded as a string. Since this type is nominal,
93
+ * it can only be generated using {@link encodePathToProp}.
94
+ */
95
+ type PathToProp_Encoded = Nominal<'PathToProp_Encoded'>;
96
+
97
+ type Asset = {
98
+ type: 'image';
99
+ id: string | undefined;
100
+ };
101
+ type File = {
102
+ type: 'file';
103
+ id: string | undefined;
104
+ };
105
+
106
+ type VoidFn = () => void;
107
+ /**
108
+ * A `SerializableMap` is a plain JS object that can be safely serialized to JSON.
109
+ */
110
+ type SerializableMap<Primitives extends SerializablePrimitive = SerializablePrimitive> = {
111
+ [Key in string]?: SerializableValue<Primitives>;
112
+ };
113
+ type SerializablePrimitive = string | number | boolean | {
114
+ r: number;
115
+ g: number;
116
+ b: number;
117
+ a: number;
118
+ } | Asset;
119
+ /**
120
+ * This type represents all values that can be safely serialized.
121
+ * Also, it's notable that this type is compatible for dataverse pointer traversal (everything
122
+ * is path accessible [e.g. `a.b.c`]).
123
+ *
124
+ * One example usage is for keyframe values or static overrides such as `Rgba`, `string`, `number`, and "compound values".
125
+ */
126
+ type SerializableValue<Primitives extends SerializablePrimitive = SerializablePrimitive> = Primitives | SerializableMap;
127
+ type DeepPartialOfSerializableValue<T extends SerializableValue> = T extends SerializableMap ? {
128
+ [K in keyof T]?: DeepPartialOfSerializableValue<Exclude<T[K], undefined>>;
129
+ } : T;
130
+ /**
131
+ * This is equivalent to `Partial<Record<Key, V>>` being used to describe a sort of Map
132
+ * where the keys might not have values.
133
+ *
134
+ * We do not use `Map`s or `Set`s, because they add complexity with converting to
135
+ * `JSON.stringify` + pointer types.
136
+ */
137
+ type StrictRecord<Key extends string, V> = {
138
+ [K in Key]?: V;
139
+ };
140
+ /** For `any`s that we don't care about */
141
+ type $IntentionalAny = any;
142
+
143
+ interface SheetState_Historic {
144
+ /**
145
+ * @remarks
146
+ * Notes for when we implement FSMs:
147
+ *
148
+ * Each FSM state will have overrides of its own. Since a state could be a descendant
149
+ * of another state, it will be able to inherit the overrides from ancestor states.
150
+ */
151
+ staticOverrides: {
152
+ byObject: StrictRecord<ObjectAddressKey, SerializableMap>;
153
+ };
154
+ /**
155
+ * Per-variant static overrides. The `default` variant uses `staticOverrides.byObject`
156
+ * for backward compatibility. Non-default variants store only their own overrides here
157
+ * and inherit from `staticOverrides.byObject` at read time.
158
+ */
159
+ staticOverridesByVariant?: StrictRecord<string, {
160
+ byObject: StrictRecord<ObjectAddressKey, SerializableMap>;
161
+ }>;
162
+ /**
163
+ * @deprecated Use `sequencesById` instead. Kept for backward compatibility with
164
+ * project states saved before sequence variants were introduced.
165
+ */
166
+ sequence?: HistoricPositionalSequence;
167
+ /**
168
+ * Each variant has its own sequence data (tracks, length, etc.), allowing the same
169
+ * sheet properties to be animated differently per variant (e.g. mobile vs desktop).
170
+ */
171
+ sequencesById?: StrictRecord<string, HistoricPositionalSequence>;
172
+ /**
173
+ * Sheet objects explicitly opted into a non-default sequence variant for editing
174
+ * variant-specific overrides in the outline. All objects inherit default static
175
+ * and sequence data on every variant unless overridden here.
176
+ */
177
+ variantObjectOverrides?: StrictRecord<string, ObjectAddressKey[]>;
178
+ }
179
+ type HistoricPositionalSequence = {
180
+ type: 'PositionalSequence';
181
+ /**
182
+ * This is the length of the sequence in unit position. If the sequence
183
+ * is interpreted in seconds, then a length=2 means the sequence is two
184
+ * seconds long.
185
+ *
186
+ * Note that if there are keyframes sitting after sequence.length, they don't
187
+ * get truncated, but calling sequence.play() will play until it reaches the
188
+ * length of the sequence.
189
+ */
190
+ length: number;
191
+ /**
192
+ * Given the most common case of tracking a sequence against time (where 1 second = position 1),
193
+ * If set to, say, 30, then the keyframe editor will try to snap all keyframes
194
+ * to a 30fps grid
195
+ */
196
+ subUnitsPerUnit: number;
197
+ tracksByObject: StrictRecord<ObjectAddressKey, {
198
+ trackIdByPropPath: StrictRecord<PathToProp_Encoded, SequenceTrackId>;
199
+ /**
200
+ * Props on this variant that should not inherit default-variant sequences.
201
+ * Used when a prop is made static while inheriting from the default variant.
202
+ */
203
+ unsequencedPropPaths?: PathToProp_Encoded[];
204
+ /**
205
+ * A flat record of SequenceTrackId to TrackData. It's better
206
+ * that only its sub-props are observed (say via val(pointer(...))),
207
+ * rather than the object as a whole.
208
+ */
209
+ trackData: StrictRecord<SequenceTrackId, TrackData>;
210
+ }>;
211
+ };
212
+ /**
213
+ * Discriminated union of sequence track kinds.
214
+ *
215
+ * Future: Other types of tracks can be added in, such as `MixedTrack` which would
216
+ * look like `[keyframes, expression, moreKeyframes, anotherExpression, …]`.
217
+ */
218
+ type TrackData = BasicKeyframedTrack | GsapClipTrack;
219
+ type KeyframeType = 'bezier' | 'hold';
220
+ type Keyframe = {
221
+ id: KeyframeId;
222
+ /** The `value` is the raw value type such as `Rgba` or `number`. See {@link SerializableValue} */
223
+ value: SerializableValue;
224
+ position: number;
225
+ handles: [leftX: number, leftY: number, rightX: number, rightY: number];
226
+ connectedRight: boolean;
227
+ type?: KeyframeType;
228
+ /**
229
+ * Optional label for the tween to the right of this keyframe.
230
+ * Only meaningful when {@link connectedRight} is true.
231
+ */
232
+ tweenLabel?: string;
233
+ };
234
+ type TrackDataCommon<TypeName extends string> = {
235
+ type: TypeName;
236
+ /**
237
+ * Initial name of the track for debugging purposes. In the future, let's
238
+ * strip this value from `studio.createContentOfSaveFile()` Could also be
239
+ * useful for users who manually edit the project state.
240
+ */
241
+ __debugName?: string;
242
+ };
243
+ type BasicKeyframedTrack = TrackDataCommon<'BasicKeyframedTrack'> & {
244
+ /**
245
+ * {@link Keyframe} is not provided an explicit generic value `T`, because
246
+ * a single track can technically have multiple different types for each keyframe.
247
+ */
248
+ keyframes: Keyframe[];
249
+ };
250
+ /**
251
+ * A GSAP tween segment on the Theatre sequence timeline, bridged at runtime
252
+ * via `@unseenco/theatre-gsap`.
253
+ */
254
+ type GsapTimelineChildClip = {
255
+ /** Stable id for this child tween within the parent timeline clip. */
256
+ childId: string;
257
+ /** Display label in the sequence editor. */
258
+ label: string;
259
+ /** Start time in seconds on the parent GSAP timeline (at progress 0). */
260
+ localStart: number;
261
+ /** Duration in seconds on the parent GSAP timeline. */
262
+ localDuration: number;
263
+ };
264
+ /** Timing snapshot captured when a GSAP clip is first added to the sequence. */
265
+ type GsapClipBaselineTiming = {
266
+ duration: number;
267
+ timelineSpan?: number;
268
+ timelineChildren?: GsapTimelineChildClip[];
269
+ };
270
+ type GsapClipTrack = TrackDataCommon<'GsapClipTrack'> & {
271
+ /**
272
+ * Registry animation id for this clip. Defaults to the sanitised GSAP sheet
273
+ * object key (e.g. `GSAP / Panel show`) when set via Studio; may differ if
274
+ * {@link registerGsapAnimation} was called with an explicit `id` override.
275
+ */
276
+ gsapAnimationId: string;
277
+ /** Sequence position where the clip starts (same units as the sequence). */
278
+ start: number;
279
+ /** Clip length on the sequence timeline. Must be &gt; 0. */
280
+ duration: number;
281
+ /**
282
+ * One-level child tweens when the registered animation is a GSAP timeline.
283
+ * Omitted for single tweens.
284
+ */
285
+ timelineChildren?: GsapTimelineChildClip[];
286
+ /**
287
+ * Parent timeline total duration in seconds when {@link timelineChildren} was
288
+ * last synced (used to map child local times into sequence space).
289
+ */
290
+ timelineSpan?: number;
291
+ /**
292
+ * Default clip timing before Studio edits; used by "Reset to original state".
293
+ */
294
+ baselineTiming?: GsapClipBaselineTiming;
295
+ };
296
+
297
+ type SequenceVariantId = string;
298
+
299
+ type Rgba = {
300
+ r: number;
301
+ g: number;
302
+ b: number;
303
+ a: number;
304
+ };
305
+
306
+ declare const propTypeSymbol: unique symbol;
307
+ type UnknownValidCompoundProps = {
308
+ [K in string]: PropTypeConfig;
309
+ };
310
+ /**
311
+ *
312
+ * This does not include Rgba since Rgba does not have a predictable
313
+ * object shape. We prefer to infer that compound props are described as
314
+ * `Record<string, IShorthandProp>` for now.
315
+ *
316
+ * In the future, it might be reasonable to wrap these types up into something
317
+ * which would allow us to differentiate between values at runtime
318
+ * (e.g. `val.type = "Rgba"` vs `val.type = "Compound"` etc)
319
+ */
320
+ type UnknownShorthandProp = string | number | boolean | PropTypeConfig | UnknownShorthandCompoundProps;
321
+ /** Given an object like this, we have enough info to predict the compound prop */
322
+ type UnknownShorthandCompoundProps = {
323
+ [K in string]: UnknownShorthandProp;
324
+ };
325
+ type ShorthandPropToLonghandProp<P extends UnknownShorthandProp> = P extends string ? PropTypeConfig_String : P extends number ? PropTypeConfig_Number : P extends boolean ? PropTypeConfig_Boolean : P extends PropTypeConfig ? P : P extends UnknownShorthandCompoundProps ? PropTypeConfig_Compound<ShorthandCompoundPropsToLonghandCompoundProps<P>> : never;
326
+ type LonghandCompoundPropsToInitialValue<P extends UnknownValidCompoundProps> = {
327
+ [K in keyof P]: P[K]['valueType'];
328
+ };
329
+ type PropsValue<P> = P extends UnknownValidCompoundProps ? LonghandCompoundPropsToInitialValue<P> : P extends UnknownShorthandCompoundProps ? LonghandCompoundPropsToInitialValue<ShorthandCompoundPropsToLonghandCompoundProps<P>> : never;
330
+ type ShorthandCompoundPropsToLonghandCompoundProps<P extends UnknownShorthandCompoundProps> = {
331
+ [K in keyof P]: ShorthandPropToLonghandProp<P[K]>;
332
+ };
333
+
334
+ /**
335
+ * A compound prop type (basically a JS object).
336
+ *
337
+ * @example
338
+ * Usage:
339
+ * ```ts
340
+ * // shorthand
341
+ * const position = {
342
+ * x: 0,
343
+ * y: 0
344
+ * }
345
+ * assert(sheet.object('some object', position).value.x === 0)
346
+ *
347
+ * // nesting
348
+ * const foo = {bar: {baz: {quo: 0}}}
349
+ * assert(sheet.object('some object', foo).value.bar.baz.quo === 0)
350
+ *
351
+ * // With additional options:
352
+ * const position = t.compound(
353
+ * {x: 0, y: 0},
354
+ * // a custom label for the prop:
355
+ * {label: "Position"}
356
+ * )
357
+ * ```
358
+ *
359
+ */
360
+ declare function compoundFromSanitizedProps<Props extends UnknownValidCompoundProps>(sanitizedProps: Props, opts?: CommonOpts): PropTypeConfig_Compound<Props>;
361
+ /**
362
+ * Shorthand compound prop type: plain object literals are sanitized into longhand prop configs.
363
+ *
364
+ * @example
365
+ * ```ts
366
+ * sheet.object('obj', types.compound({x: 0, y: 0}))
367
+ * ```
368
+ */
369
+ declare const compound: <Props extends UnknownShorthandCompoundProps>(props: Props, opts?: CommonOpts) => PropTypeConfig_Compound<ShorthandCompoundPropsToLonghandCompoundProps<Props>>;
370
+ /**
371
+ * A file prop type
372
+ *
373
+ * @example
374
+ * Usage:
375
+ * ```ts
376
+ *
377
+ * // with a label:
378
+ * const obj = sheet.object('key', {
379
+ * url: t.file('My file.glb', {
380
+ * label: 'Model'
381
+ * })
382
+ * })
383
+ * ```
384
+ *
385
+ * @param opts - Options (See usage examples)
386
+ */
387
+ declare const file: (defaultValue: File['id'], opts?: {
388
+ label?: string;
389
+ interpolate?: Interpolator<File['id']>;
390
+ }) => PropTypeConfig_File;
391
+ /**
392
+ * An image prop type
393
+ *
394
+ * @example
395
+ * Usage:
396
+ * ```ts
397
+ *
398
+ * // with a label:
399
+ * const obj = sheet.object('key', {
400
+ * url: t.image('My image.png', {
401
+ * label: 'texture'
402
+ * })
403
+ * })
404
+ * ```
405
+ *
406
+ * @param opts - Options (See usage examples)
407
+ */
408
+ declare const image: (defaultValue: Asset['id'], opts?: {
409
+ label?: string;
410
+ interpolate?: Interpolator<Asset['id']>;
411
+ /**
412
+ * When `false`, values edited in Studio are kept in memory for the
413
+ * current session only and are not written to persisted project state.
414
+ * Defaults to `true`.
415
+ */
416
+ persist?: boolean;
417
+ }) => PropTypeConfig_Image;
418
+ /**
419
+ * A number prop type.
420
+ *
421
+ * @example
422
+ * Usage
423
+ * ```ts
424
+ * // shorthand:
425
+ * const obj = sheet.object('key', {x: 0})
426
+ *
427
+ * // With options (equal to above)
428
+ * const obj = sheet.object('key', {
429
+ * x: t.number(0)
430
+ * })
431
+ *
432
+ * // With a range (note that opts.range is just a visual guide, not a validation rule)
433
+ * const x = t.number(0, {range: [0, 10]}) // limited to 0 and 10
434
+ *
435
+ * // With custom nudging
436
+ * const x = t.number(0, {nudgeMultiplier: 0.1}) // nudging will happen in 0.1 increments
437
+ *
438
+ * // With custom precision (decimal places shown/stored in Studio)
439
+ * const x = t.number(0, {precision: 2})
440
+ *
441
+ * // With custom nudging function
442
+ * const x = t.number({
443
+ * nudgeFn: (
444
+ * // the mouse movement (in pixels)
445
+ * deltaX: number,
446
+ * // the movement as a fraction of the width of the number editor's input
447
+ * deltaFraction: number,
448
+ * // A multiplier that's usually 1, but might be another number if user wants to nudge slower/faster
449
+ * magnitude: number,
450
+ * // the configuration of the number
451
+ * config: {nudgeMultiplier?: number; range?: [number, number]},
452
+ * ): number => {
453
+ * return deltaX * magnitude
454
+ * },
455
+ * })
456
+ * ```
457
+ *
458
+ * @param defaultValue - The default value (Must be a finite number)
459
+ * @param opts - The options (See usage examples)
460
+ * @returns A number prop config
461
+ */
462
+ declare const number: (defaultValue: number, opts?: {
463
+ nudgeFn?: PropTypeConfig_Number['nudgeFn'];
464
+ range?: PropTypeConfig_Number['range'];
465
+ nudgeMultiplier?: number;
466
+ precision?: number;
467
+ label?: string;
468
+ }) => PropTypeConfig_Number;
469
+ /**
470
+ * RGBA color prop type factory.
471
+ *
472
+ * @param defaultValue - Initial color; defaults to opaque black
473
+ * @param opts - Optional `{ label }` for the Studio
474
+ */
475
+ declare const rgba: (defaultValue?: Rgba, opts?: CommonOpts) => PropTypeConfig_Rgba;
476
+ /**
477
+ * A boolean prop type
478
+ *
479
+ * @example
480
+ * Usage:
481
+ * ```ts
482
+ * // shorthand:
483
+ * const obj = sheet.object('key', {isOn: true})
484
+ *
485
+ * // with a label:
486
+ * const obj = sheet.object('key', {
487
+ * isOn: t.boolean(true, {
488
+ * label: 'Enabled'
489
+ * })
490
+ * })
491
+ * ```
492
+ *
493
+ * @param defaultValue - The default value (must be a boolean)
494
+ * @param opts - Options (See usage examples)
495
+ */
496
+ declare const boolean: (defaultValue: boolean, opts?: {
497
+ label?: string;
498
+ interpolate?: Interpolator<boolean>;
499
+ }) => PropTypeConfig_Boolean;
500
+ /**
501
+ * A string prop type
502
+ *
503
+ * @example
504
+ * Usage:
505
+ * ```ts
506
+ * // shorthand:
507
+ * const obj = sheet.object('key', {message: "Animation loading"})
508
+ *
509
+ * // with a label:
510
+ * const obj = sheet.object('key', {
511
+ * message: t.string("Animation Loading", {
512
+ * label: 'The Message'
513
+ * })
514
+ * })
515
+ * ```
516
+ *
517
+ * @param defaultValue - The default value (must be a string)
518
+ * @param opts - The options (See usage examples)
519
+ * @returns A string prop type
520
+ */
521
+ declare const string: (defaultValue: string, opts?: {
522
+ label?: string;
523
+ interpolate?: Interpolator<string>;
524
+ }) => PropTypeConfig_String;
525
+ /**
526
+ * A stringLiteral prop type, useful for building menus or radio buttons.
527
+ *
528
+ * @example
529
+ * Usage:
530
+ * ```ts
531
+ * // Basic usage
532
+ * const obj = sheet.object('key', {
533
+ * light: t.stringLiteral("r", {r: "Red", "g": "Green"})
534
+ * })
535
+ *
536
+ * // Shown as a radio switch with a custom label
537
+ * const obj = sheet.object('key', {
538
+ * light: t.stringLiteral("r", {r: "Red", "g": "Green"})
539
+ * }, {as: "switch", label: "Street Light"})
540
+ * ```
541
+ *
542
+ * @returns A stringLiteral prop type
543
+ *
544
+ */
545
+ declare function stringLiteral<ValuesAndLabels extends {
546
+ [key in string]: string;
547
+ }>(
548
+ /**
549
+ * Default value (a string that equals one of the options)
550
+ */
551
+ defaultValue: Extract<keyof ValuesAndLabels, string>,
552
+ /**
553
+ * The options. Use the `"value": "Label"` format.
554
+ *
555
+ * An object like `{[value]: Label}`. Example: `{r: "Red", "g": "Green"}`
556
+ */
557
+ valuesAndLabels: ValuesAndLabels,
558
+ /**
559
+ * opts.as Determines if editor is shown as a menu or a switch. Either 'menu' or 'switch'. Default: 'menu'
560
+ */
561
+ opts?: {
562
+ as?: 'menu' | 'switch';
563
+ label?: string;
564
+ interpolate?: Interpolator<Extract<keyof ValuesAndLabels, string>>;
565
+ }): PropTypeConfig_StringLiteral<Extract<keyof ValuesAndLabels, string>>;
566
+ /**
567
+ * A linear interpolator for a certain value type.
568
+ *
569
+ * @param left - the value to interpolate from (beginning)
570
+ * @param right - the value to interpolate to (end)
571
+ * @param progression - the amount of progression. Starts at 0 and ends at 1. But could overshoot in either direction
572
+ *
573
+ * @example
574
+ * ```ts
575
+ * const numberInterpolator: Interpolator<number> = (left, right, progression) => left + progression * (right - left)
576
+ *
577
+ * numberInterpolator(-50, 50, 0.5) === 0
578
+ * numberInterpolator(-50, 50, 0) === -50
579
+ * numberInterpolator(-50, 50, 1) === 50
580
+ * numberInterpolator(-50, 50, 2) === 150 // overshoot
581
+ * ```
582
+ */
583
+ type Interpolator<T> = (left: T, right: T, progression: number) => T;
584
+ /** Base shape shared by all Theatre prop type configurations. */
585
+ interface IBasePropType<LiteralIdentifier extends string, ValueType, DeserializeType = ValueType> {
586
+ /**
587
+ * Each prop config has a string literal identifying it. For example,
588
+ * `assert.equal(t.number(10).type, 'number')`
589
+ */
590
+ type: LiteralIdentifier;
591
+ /**
592
+ * the `valueType` is only used by typescript. It won't be present in runtime.
593
+ */
594
+ valueType: ValueType;
595
+ /** Internal marker distinguishing Theatre prop configs from plain values. */
596
+ [propTypeSymbol]: 'TheatrePropType';
597
+ /**
598
+ * Each prop type may be given a custom label instead of the name of the sub-prop
599
+ * it is in.
600
+ *
601
+ * @example
602
+ * ```ts
603
+ * const position = {
604
+ * x: t.number(0), // label would be 'x'
605
+ * y: t.number(0, {label: 'top'}) // label would be 'top'
606
+ * }
607
+ * ```
608
+ */
609
+ label: string | undefined;
610
+ /** Default value used when the prop has no override or keyframes. */
611
+ default: ValueType;
612
+ /**
613
+ * Each prop config has a `deserializeAndSanitize()` function that deserializes and sanitizes
614
+ * any js value into one that is acceptable by this prop config, or `undefined`.
615
+ *
616
+ * As a rule, the value returned by this function should not hold any reference to `json` or any
617
+ * other value referenced by the descendent props of `json`. This is to ensure that json values
618
+ * controlled by the user can never change the values in the store. See `deserializeAndSanitize()` in
619
+ * `t.compound()` or `t.rgba()` as examples.
620
+ *
621
+ * The `DeserializeType` is usually equal to `ValueType`. That is the case with
622
+ * all simple prop configs, such as `number`, `string`, or `rgba`. However, composite
623
+ * configs such as `compound` or `enum` may deserialize+sanitize into a partial value. For example,
624
+ * a prop config of `t.compound({x: t.number(0), y: t.number(0)})` may deserialize+sanitize into `{x: 10}`.
625
+ * This behavior is used by {@link SheetObject.getValues} to replace the missing sub-props
626
+ * with their default value.
627
+ *
628
+ * Admittedly, this partial deserialization behavior is not what the word "deserialize"
629
+ * typically implies in most codebases, so feel free to change this name into a more
630
+ * appropriate one.
631
+ *
632
+ * Additionally, returning an `undefined` allows {@link SheetObject.getValues} to
633
+ * replace the `undefined` with the default value of that prop.
634
+ */
635
+ deserializeAndSanitize: (json: unknown) => undefined | DeserializeType;
636
+ }
637
+ interface ISimplePropType<LiteralIdentifier extends string, ValueType> extends IBasePropType<LiteralIdentifier, ValueType, ValueType> {
638
+ interpolate: Interpolator<ValueType>;
639
+ }
640
+ /** Number prop type configuration. */
641
+ interface PropTypeConfig_Number extends ISimplePropType<'number', number> {
642
+ /** Optional min/max shown in the Studio number editor (not a runtime clamp). */
643
+ range?: [min: number, max: number];
644
+ /** Custom nudging behavior when dragging the number in the Studio. */
645
+ nudgeFn: NumberNudgeFn;
646
+ /**
647
+ * See {@link defaultNumberNudgeFn} to see how `nudgeMultiplier` is treated.
648
+ */
649
+ nudgeMultiplier: number | undefined;
650
+ /**
651
+ * The number of decimal places to use for this prop in the Studio.
652
+ * Falls back to the project's `numberPrecision` config, then to 3.
653
+ */
654
+ precision?: number;
655
+ }
656
+ /** Function that computes a new number when the user nudges a number prop in the Studio. */
657
+ type NumberNudgeFn = (p: {
658
+ deltaX: number;
659
+ deltaFraction: number;
660
+ magnitude: number;
661
+ config: PropTypeConfig_Number;
662
+ }) => number;
663
+ /** Boolean prop type configuration. */
664
+ interface PropTypeConfig_Boolean extends ISimplePropType<'boolean', boolean> {
665
+ }
666
+ type CommonOpts = {
667
+ /**
668
+ * Each prop type may be given a custom label instead of the name of the sub-prop
669
+ * it is in.
670
+ *
671
+ * @example
672
+ * ```ts
673
+ * const position = {
674
+ * x: t.number(0), // label would be 'x'
675
+ * y: t.number(0, {label: 'top'}) // label would be 'top'
676
+ * }
677
+ * ```
678
+ */
679
+ label?: string;
680
+ };
681
+ /** String prop type configuration. */
682
+ interface PropTypeConfig_String extends ISimplePropType<'string', string> {
683
+ }
684
+ /** String-literal prop type configuration (menu or switch UI in the Studio). */
685
+ interface PropTypeConfig_StringLiteral<T extends string> extends ISimplePropType<'stringLiteral', T> {
686
+ /** Map of allowed values to human-readable labels in the Studio. */
687
+ valuesAndLabels: Record<T, string>;
688
+ /** Whether the Studio renders a dropdown menu or a switch control. */
689
+ as: 'menu' | 'switch';
690
+ }
691
+ /** RGBA color prop type configuration. */
692
+ interface PropTypeConfig_Rgba extends ISimplePropType<'rgba', Rgba> {
693
+ }
694
+ /** Image (texture) prop type configuration. */
695
+ interface PropTypeConfig_Image extends ISimplePropType<'image', Asset> {
696
+ /**
697
+ * When `false`, Studio edits are session-only and not written to persisted
698
+ * project state. Defaults to `true`.
699
+ */
700
+ persist: boolean;
701
+ }
702
+ /** File asset prop type configuration. */
703
+ interface PropTypeConfig_File extends ISimplePropType<'file', File> {
704
+ }
705
+ type DeepPartialCompound<Props extends UnknownValidCompoundProps> = {
706
+ [K in keyof Props]?: DeepPartial<Props[K]>;
707
+ };
708
+ type DeepPartial<Conf extends PropTypeConfig> = Conf extends PropTypeConfig_AllSimples ? Conf['valueType'] : Conf extends PropTypeConfig_Compound<infer T> ? DeepPartialCompound<T> : never;
709
+ /** Compound (nested object) prop type configuration. */
710
+ interface PropTypeConfig_Compound<Props extends UnknownValidCompoundProps> extends IBasePropType<'compound', {
711
+ [K in keyof Props]: Props[K]['valueType'];
712
+ }, DeepPartialCompound<Props>> {
713
+ /** Child prop configurations keyed by sub-prop name. */
714
+ props: Record<keyof Props, PropTypeConfig>;
715
+ }
716
+ /** Enum (discriminated) prop type configuration with named cases. */
717
+ interface PropTypeConfig_Enum extends IBasePropType<'enum', {}> {
718
+ /** Prop configs for each enum case name. */
719
+ cases: Record<string, PropTypeConfig>;
720
+ /** Case name used when no value is set. */
721
+ defaultCase: string;
722
+ }
723
+ /** Union of all simple (non-compound, non-enum) prop type configurations. */
724
+ type PropTypeConfig_AllSimples = PropTypeConfig_Number | PropTypeConfig_Boolean | PropTypeConfig_String | PropTypeConfig_StringLiteral<$IntentionalAny> | PropTypeConfig_Rgba | PropTypeConfig_Image | PropTypeConfig_File;
725
+ /** Any Theatre prop type configuration (simple, compound, or enum). */
726
+ type PropTypeConfig = PropTypeConfig_AllSimples | PropTypeConfig_Compound<$IntentionalAny> | PropTypeConfig_Enum;
727
+
728
+ type index_d_UnknownShorthandCompoundProps = UnknownShorthandCompoundProps;
729
+ declare const index_d_compoundFromSanitizedProps: typeof compoundFromSanitizedProps;
730
+ declare const index_d_compound: typeof compound;
731
+ declare const index_d_file: typeof file;
732
+ declare const index_d_image: typeof image;
733
+ declare const index_d_number: typeof number;
734
+ declare const index_d_rgba: typeof rgba;
735
+ declare const index_d_boolean: typeof boolean;
736
+ declare const index_d_string: typeof string;
737
+ declare const index_d_stringLiteral: typeof stringLiteral;
738
+ type index_d_Interpolator<_0> = Interpolator<_0>;
739
+ type index_d_IBasePropType<_0, _1, _2> = IBasePropType<_0, _1, _2>;
740
+ type index_d_PropTypeConfig_Number = PropTypeConfig_Number;
741
+ type index_d_NumberNudgeFn = NumberNudgeFn;
742
+ type index_d_PropTypeConfig_Boolean = PropTypeConfig_Boolean;
743
+ type index_d_PropTypeConfig_String = PropTypeConfig_String;
744
+ type index_d_PropTypeConfig_StringLiteral<_0> = PropTypeConfig_StringLiteral<_0>;
745
+ type index_d_PropTypeConfig_Rgba = PropTypeConfig_Rgba;
746
+ type index_d_PropTypeConfig_Image = PropTypeConfig_Image;
747
+ type index_d_PropTypeConfig_File = PropTypeConfig_File;
748
+ type index_d_PropTypeConfig_Compound<_0> = PropTypeConfig_Compound<_0>;
749
+ type index_d_PropTypeConfig_Enum = PropTypeConfig_Enum;
750
+ type index_d_PropTypeConfig_AllSimples = PropTypeConfig_AllSimples;
751
+ type index_d_PropTypeConfig = PropTypeConfig;
752
+ declare namespace index_d {
753
+ export {
754
+ index_d_UnknownShorthandCompoundProps as UnknownShorthandCompoundProps,
755
+ index_d_compoundFromSanitizedProps as compoundFromSanitizedProps,
756
+ index_d_compound as compound,
757
+ index_d_file as file,
758
+ index_d_image as image,
759
+ index_d_number as number,
760
+ index_d_rgba as rgba,
761
+ index_d_boolean as boolean,
762
+ index_d_string as string,
763
+ index_d_stringLiteral as stringLiteral,
764
+ index_d_Interpolator as Interpolator,
765
+ index_d_IBasePropType as IBasePropType,
766
+ index_d_PropTypeConfig_Number as PropTypeConfig_Number,
767
+ index_d_NumberNudgeFn as NumberNudgeFn,
768
+ index_d_PropTypeConfig_Boolean as PropTypeConfig_Boolean,
769
+ index_d_PropTypeConfig_String as PropTypeConfig_String,
770
+ index_d_PropTypeConfig_StringLiteral as PropTypeConfig_StringLiteral,
771
+ index_d_PropTypeConfig_Rgba as PropTypeConfig_Rgba,
772
+ index_d_PropTypeConfig_Image as PropTypeConfig_Image,
773
+ index_d_PropTypeConfig_File as PropTypeConfig_File,
774
+ index_d_PropTypeConfig_Compound as PropTypeConfig_Compound,
775
+ index_d_PropTypeConfig_Enum as PropTypeConfig_Enum,
776
+ index_d_PropTypeConfig_AllSimples as PropTypeConfig_AllSimples,
777
+ index_d_PropTypeConfig as PropTypeConfig,
778
+ };
779
+ }
780
+
781
+ type TransientPropPath = string | readonly (string | number)[];
782
+ /** Same path format as {@link TransientPropPath}. */
783
+ type StaticPropPath = TransientPropPath;
784
+
785
+ /** How the sheet sequence maps to the sequencer UI and playback driver. */
786
+ type SheetSequenceMode = 'time' | 'page';
787
+ /** Fixed sequence length in page mode (= 100% scroll range). */
788
+ declare const PAGE_MODE_SEQUENCE_LENGTH = 100;
789
+ /** Sub-units per unit in page mode: 10 → 0.1% snap steps. */
790
+ declare const PAGE_MODE_SUB_UNITS_PER_UNIT = 10;
791
+
792
+ /** Maps page scroll progress (0–1) to sequence position and back. */
793
+ type ScrollDriver = {
794
+ getProgress(): number;
795
+ setProgress(progress: number): void;
796
+ subscribe(onChange: (progress: number) => void): () => void;
797
+ };
798
+ declare function getSheetScrollDriver(sheet: ISheet): ScrollDriver | undefined;
799
+ /** Native `window` / `documentElement` vertical scroll. */
800
+ declare function createNativeDocumentScrollDriver(): ScrollDriver;
801
+ /** Native `window` / `documentElement` horizontal scroll. */
802
+ declare function createNativeDocumentHorizontalScrollDriver(): ScrollDriver;
803
+ /** Vertical scroll on a custom overflow element. */
804
+ declare function createElementScrollDriver(element: HTMLElement): ScrollDriver;
805
+ /** Horizontal scroll on a custom overflow element. */
806
+ declare function createElementHorizontalScrollDriver(element: HTMLElement): ScrollDriver;
807
+ /**
808
+ * Keeps `sheet.sequence.position` aligned with scroll progress in page mode.
809
+ * Progress maps to `[0, sequence.length]` (length is 100 in page mode).
810
+ */
811
+ declare function attachSheetScrollDriver(sheet: ISheet, driver?: ScrollDriver): () => void;
812
+ declare function pageScrollProgressFromSequence(sheet: ISheet): number;
813
+ declare function setPageScrollProgress(sheet: ISheet, progress: number): void;
814
+ /** Scroll to match the current sequence playhead using the sheet's active driver. */
815
+ declare function syncPageScrollToSequencePosition(sheet: ISheet, driver?: ScrollDriver): void;
816
+ /** Scroll the native document to match the current sequence playhead (page mode UI). */
817
+ declare function syncNativeDocumentScrollToSequencePosition(sheet: ISheet): void;
818
+
819
+ /** Public API for a custom requestAnimationFrame driver that advances Theatre's core ticker. */
820
+ interface IRafDriver {
821
+ /**
822
+ * All raf derivers have have `driver.type === 'Theatre_RafDriver_PublicAPI'`
823
+ */
824
+ readonly type: 'Theatre_RafDriver_PublicAPI';
825
+ /**
826
+ * The name of the driver. This is used for debugging purposes.
827
+ */
828
+ name: string;
829
+ /**
830
+ * The id of the driver. This is used for debugging purposes.
831
+ * It's guaranteed to be unique.
832
+ */
833
+ id: number;
834
+ /**
835
+ * This is called by the driver when it's time to tick forward.
836
+ * The time param is of the same type returned by `performance.now()`.
837
+ */
838
+ tick: (time: number) => void;
839
+ }
840
+ /**
841
+ * Creates a custom raf driver.
842
+ * `rafDriver`s allow you to control when and how often computations in Theatre tick forward. (raf stands for [`requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)).
843
+ * The default `rafDriver` in Theatre creates a `raf` loop and ticks forward on each frame. You can create your own `rafDriver`, which enables the following use-cases:
844
+ *
845
+ * 1. When using Theatre.js alongside other animation libs (`gsap`/`lenis`/`etc`), you'd want all animation libs to use a single `raf` loop to keep the libraries in sync and also to get better performance.
846
+ * 2. In XR sessions, you'd want Theatre to tick forward using [`xr.requestAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/XRSession/requestAnimationFrame).
847
+ * 3. In some advanced cases, you'd just want to manually tick forward (many ticks per frame, or skipping many frames, etc). This is useful for recording an animation, rendering to a file, testing an animation, running benchmarks, etc.
848
+ *
849
+ * Here is how you'd create a custom `rafDriver`:
850
+ *
851
+ * ```js
852
+ * import { createRafDriver } from '@unseenco/theatre-core'
853
+ *
854
+ * const rafDriver = createRafDriver({ name: 'a custom 5fps raf driver' })
855
+ *
856
+ * setInterval(() => {
857
+ * rafDriver.tick(performance.now())
858
+ * }, 200)
859
+ * ```
860
+ *
861
+ * Now, any time you set up an `onChange()` listener, pass your custom `rafDriver`:
862
+ *
863
+ * ```js
864
+ * import { onChange } from '@unseenco/theatre-core'
865
+ *
866
+ * onChange(
867
+ * // let's say object is a Theatre object, the one returned from calling `sheet.object()`
868
+ * object.props,
869
+ * // this callback will now only be called at 5fps (and won't be called if there are no new values)
870
+ * // even if `sequence.play()` updates `object.props` at 60fps, this listener is called a maximum of 5fps
871
+ * (propValues) => {
872
+ * console.log(propValues)
873
+ * },
874
+ * rafDriver,
875
+ * )
876
+ *
877
+ * // this will update the values of `object.props` at 60fps, but the listener above will still get called a maximum of 5fps
878
+ * sheet.sequence.play()
879
+ *
880
+ * // we can also customize at what resolution the sequence's playhead moves forward
881
+ * sheet.sequence.play({ rafDriver }) // the playhead will move forward at 5fps
882
+ * ```
883
+ *
884
+ * You can optionally make studio use this `rafDriver`. This means the parts of the studio that tick based on raf, will now tick at 5fps. This is only useful if you're doing something crazy like running the studio (and not the core) in an XR frame.
885
+ *
886
+ * ```js
887
+ * studio.initialize({
888
+ * __experimental_rafDriver: rafDriver,
889
+ * })
890
+ * ```
891
+ *
892
+ * `rafDriver`s can optionally provide a `start/stop` callback. Theatre will call `start()` when it actually has computations scheduled, and will call `stop` if there is nothing to update after a few ticks:
893
+ *
894
+ * ```js
895
+ * import { createRafDriver } from '@unseenco/theatre-core'
896
+ * import type { IRafDriver } from '@theare/core'
897
+ *
898
+ * function createBasicRafDriver(): IRafDriver {
899
+ * let rafId: number | null = null
900
+ * const start = (): void => {
901
+ * if (typeof window !== 'undefined') {
902
+ * const onAnimationFrame = (t: number) => {
903
+ * driver.tick(t)
904
+ * rafId = window.requestAnimationFrame(onAnimationFrame)
905
+ * }
906
+ * rafId = window.requestAnimationFrame(onAnimationFrame)
907
+ * } else {
908
+ * driver.tick(0)
909
+ * setTimeout(() => driver.tick(1), 0)
910
+ * }
911
+ * }
912
+ *
913
+ * const stop = (): void => {
914
+ * if (typeof window !== 'undefined') {
915
+ * if (rafId !== null) {
916
+ * window.cancelAnimationFrame(rafId)
917
+ * }
918
+ * } else {
919
+ * // nothing to do in SSR
920
+ * }
921
+ * }
922
+ *
923
+ * const driver = createRafDriver({ name: 'DefaultCoreRafDriver', start, stop })
924
+ *
925
+ * return driver
926
+ * }
927
+ * ```
928
+ */
929
+ declare function createRafDriver(conf?: {
930
+ name?: string;
931
+ start?: () => void;
932
+ stop?: () => void;
933
+ }): IRafDriver;
934
+
935
+ type SheetObjectValuesChangeMeta = {
936
+ /**
937
+ * The sequence variant whose values are being applied.
938
+ * This reflects the sheet's active sequence variant.
939
+ */
940
+ variant: SequenceVariantId;
941
+ };
942
+ /** Public API for a Theatre.js sheet object (animated props and Studio integration). */
943
+ interface ISheetObject<Props extends UnknownShorthandCompoundProps = UnknownShorthandCompoundProps> {
944
+ /**
945
+ * All Objects will have `object.type === 'Theatre_SheetObject_PublicAPI'`
946
+ */
947
+ readonly type: 'Theatre_SheetObject_PublicAPI';
948
+ /**
949
+ * The current values of the props.
950
+ *
951
+ * @example
952
+ * Usage:
953
+ * ```ts
954
+ * const obj = sheet.object("obj", {x: 0})
955
+ * console.log(obj.value.x) // prints 0 or the current numeric value
956
+ * ```
957
+ *
958
+ * Future: Notice that if the user actually changes the Props config for one of the
959
+ * properties, then this type can't be guaranteed accurrate.
960
+ * * Right now the user can't change prop configs, but we'll probably enable that
961
+ * functionality later via (`object.overrideConfig()`). We need to educate the
962
+ * user that they can't rely on static types to know the type of object.value.
963
+ */
964
+ readonly value: PropsValue<Props>;
965
+ /**
966
+ * A Pointer to the props of the object.
967
+ *
968
+ * More documentation soon.
969
+ */
970
+ readonly props: Pointer<this['value']>;
971
+ /**
972
+ * The instance of Sheet the Object belongs to
973
+ */
974
+ readonly sheet: ISheet;
975
+ /**
976
+ * The Project the project belongs to
977
+ */
978
+ readonly project: IProject;
979
+ /**
980
+ * An object representing the address of the Object
981
+ */
982
+ readonly address: SheetObjectAddress;
983
+ /**
984
+ * Calls `fn` every time the value of the props change.
985
+ *
986
+ * @param fn - The callback is called every time the value of the props change, plus once at the beginning.
987
+ * @param rafDriver - (optional) The `rafDriver` to use. Learn how to use `rafDriver`s [from the docs](https://unseen-theatre.netlify.app/docs/guide/manual/advanced#rafdrivers).
988
+ * @returns an Unsubscribe function
989
+ *
990
+ * @example
991
+ * Usage:
992
+ * ```ts
993
+ * const obj = sheet.object("Box", {position: {x: 0, y: 0}})
994
+ * const div = document.getElementById("box")
995
+ *
996
+ * const unsubscribe = obj.onValuesChange((newValues, {variant}) => {
997
+ * div.style.left = newValues.position.x + 'px'
998
+ * div.style.top = newValues.position.y + 'px'
999
+ * console.log('Active variant:', variant)
1000
+ * })
1001
+ *
1002
+ * // you can call unsubscribe() to stop listening to changes
1003
+ * ```
1004
+ */
1005
+ onValuesChange(fn: (values: this['value'], meta: SheetObjectValuesChangeMeta) => void, rafDriver?: IRafDriver): VoidFn;
1006
+ /**
1007
+ * Sets the initial value of the object. This value overrides the default
1008
+ * values defined in the prop types, but would itself be overridden if the user
1009
+ * overrides it in the UI with a static or animated value.
1010
+ *
1011
+ * @example
1012
+ * Usage:
1013
+ * ```ts
1014
+ * const obj = sheet.object("obj", {position: {x: 0, y: 0}})
1015
+ *
1016
+ * obj.value // {position: {x: 0, y: 0}}
1017
+ *
1018
+ * // here, we only override position.x
1019
+ * obj.initialValue = {position: {x: 2}}
1020
+ *
1021
+ * obj.value // {position: {x: 2, y: 0}}
1022
+ * ```
1023
+ */
1024
+ set initialValue(value: DeepPartialOfSerializableValue<this['value']>);
1025
+ /**
1026
+ * Show another object's props in this object's Studio details pane.
1027
+ * Runtime-only (not persisted). Edits and sequencing still target the
1028
+ * source objects. Pass `[]` to clear.
1029
+ *
1030
+ * @example
1031
+ * ```ts
1032
+ * const appearance = sheet.object('Appearance', {color: types.rgba()})
1033
+ * const box = sheet.object('Box', {x: 0})
1034
+ * box.showPropsOf([appearance])
1035
+ * ```
1036
+ */
1037
+ showPropsOf(objects: ISheetObject<any>[]): void;
1038
+ /**
1039
+ * Returns the objects currently linked via {@link showPropsOf}.
1040
+ */
1041
+ getShowPropsOf(): ISheetObject<any>[];
1042
+ /**
1043
+ * Replace this object's prop config. Props removed from `config` disappear
1044
+ * from the Studio details pane; historic statics/tracks for those paths are
1045
+ * stripped. Same effect as `sheet.object(key, config, {reconfigure: true})`.
1046
+ */
1047
+ reconfigure(config: UnknownShorthandCompoundProps, opts?: {
1048
+ transient?: readonly TransientPropPath[];
1049
+ static?: readonly StaticPropPath[];
1050
+ }): void;
1051
+ /**
1052
+ * Add new props to this object's config. Existing props are preserved; only
1053
+ * the keys in `config` are added. Throws if a key already exists on the object.
1054
+ *
1055
+ * @example
1056
+ * ```ts
1057
+ * const obj = sheet.object('Box', {x: 0})
1058
+ * obj.addProps({y: 0})
1059
+ * // obj now has props x and y
1060
+ * ```
1061
+ */
1062
+ addProps(config: UnknownShorthandCompoundProps, opts?: {
1063
+ transient?: readonly TransientPropPath[];
1064
+ static?: readonly StaticPropPath[];
1065
+ }): void;
1066
+ }
1067
+
1068
+ type KeyframeId = Nominal<'KeyframeId'>;
1069
+ type ProjectId = Nominal<'ProjectId'>;
1070
+ type SheetId = Nominal<'SheetId'>;
1071
+ type SheetInstanceId = Nominal<'SheetInstanceId'>;
1072
+ type SequenceTrackId = Nominal<'SequenceTrackId'>;
1073
+ type ObjectAddressKey = Nominal<'ObjectAddressKey'>;
1074
+
1075
+ /**
1076
+ * This is the state of each project that is consumable by `@unseenco/theatre-core`.
1077
+ * If the studio is present, this part of the state joins the studio's historic state,
1078
+ * at {@link StudioHistoricState.coreByProject}
1079
+ */
1080
+ interface ProjectState_Historic {
1081
+ sheetsById: StrictRecord<SheetId, SheetState_Historic>;
1082
+ /**
1083
+ * The last 50 revision IDs this state is based on, starting with the most recent one.
1084
+ * The most recent one is the revision ID of this state
1085
+ */
1086
+ revisionHistory: string[];
1087
+ definitionVersion: string;
1088
+ }
1089
+ interface OnDiskState extends ProjectState_Historic {
1090
+ }
1091
+
1092
+ type IPlaybackRange = [from: number, to: number];
1093
+ type IPlaybackDirection = 'normal' | 'reverse' | 'alternate' | 'alternateReverse';
1094
+
1095
+ interface IAttachAudioArgs {
1096
+ /**
1097
+ * Either a URL to the audio file (eg "http://localhost:3000/audio.mp3") or an instance of AudioBuffer
1098
+ */
1099
+ source: string | AudioBuffer;
1100
+ /**
1101
+ * An optional AudioContext. If not provided, one will be created.
1102
+ */
1103
+ audioContext?: AudioContext;
1104
+ /**
1105
+ * An AudioNode to feed the audio into. Will use audioContext.destination if not provided.
1106
+ */
1107
+ destinationNode?: AudioNode;
1108
+ }
1109
+ /** Public API for a sheet's animation sequence (playback, playhead, and audio). */
1110
+ interface ISequence {
1111
+ /** Discriminator for Theatre.js public sequence instances. */
1112
+ readonly type: 'Theatre_Sequence_PublicAPI';
1113
+ /**
1114
+ * Starts playback of a sequence.
1115
+ * Returns a promise that either resolves to true when the playback completes,
1116
+ * or resolves to false if playback gets interrupted (for example by calling sequence.pause())
1117
+ *
1118
+ * @returns A promise that resolves when the playback is finished, or rejects if interruped
1119
+ *
1120
+ * @example
1121
+ * Usage:
1122
+ * ```ts
1123
+ * // plays the sequence from the current position to sequence.length
1124
+ * sheet.sequence.play()
1125
+ *
1126
+ * // plays the sequence at 2.4x speed
1127
+ * sheet.sequence.play({rate: 2.4})
1128
+ *
1129
+ * // plays the sequence from second 1 to 4
1130
+ * sheet.sequence.play({range: [1, 4]})
1131
+ *
1132
+ * // plays the sequence 4 times
1133
+ * sheet.sequence.play({iterationCount: 4})
1134
+ *
1135
+ * // plays the sequence in reverse
1136
+ * sheet.sequence.play({direction: 'reverse'})
1137
+ *
1138
+ * // plays the sequence back and forth forever (until interrupted)
1139
+ * sheet.sequence.play({iterationCount: Infinity, direction: 'alternateReverse})
1140
+ *
1141
+ * // plays the sequence and logs "done" once playback is finished
1142
+ * sheet.sequence.play().then(() => console.log('done'))
1143
+ * ```
1144
+ */
1145
+ play(conf?: {
1146
+ /**
1147
+ * The number of times the animation must run. Must be an integer larger
1148
+ * than 0. Defaults to 1. Pick Infinity to run forever
1149
+ */
1150
+ iterationCount?: number;
1151
+ /**
1152
+ * Limits the range to be played. Default is [0, sequence.length]
1153
+ */
1154
+ range?: IPlaybackRange;
1155
+ /**
1156
+ * The playback rate. Defaults to 1. Choosing 2 would play the animation
1157
+ * at twice the speed.
1158
+ */
1159
+ rate?: number;
1160
+ /**
1161
+ * The direction of the playback. Similar to CSS's animation-direction
1162
+ */
1163
+ direction?: IPlaybackDirection;
1164
+ /**
1165
+ * Optionally provide a rafDriver to use for the playback. It'll default to
1166
+ * the core driver if not provided, which is a `requestAnimationFrame()` driver.
1167
+ * Learn how to use `rafDriver`s [from the docs](https://unseen-theatre.netlify.app/docs/guide/manual/advanced#rafdrivers).
1168
+ */
1169
+ rafDriver?: IRafDriver;
1170
+ }): Promise<boolean>;
1171
+ /**
1172
+ * Pauses the currently playing animation
1173
+ */
1174
+ pause(): void;
1175
+ /**
1176
+ * The current position of the playhead.
1177
+ * In a time-based sequence, this represents the current time in seconds.
1178
+ */
1179
+ position: number;
1180
+ /**
1181
+ * A Pointer to the sequence's inner state.
1182
+ *
1183
+ * @remarks
1184
+ * As with any Pointer, you can use this with {@link onChange | onChange()} to listen to its value changes
1185
+ * or with {@link val | val()} to read its current value.
1186
+ *
1187
+ * @example Usage
1188
+ * ```ts
1189
+ * import {onChange, val} from '@unseenco/theatre-core'
1190
+ *
1191
+ * // let's assume `sheet` is a sheet
1192
+ * const sequence = sheet.sequence
1193
+ *
1194
+ * onChange(sequence.pointer.length, (len) => {
1195
+ * console.log("Length of the sequence changed to:", len)
1196
+ * })
1197
+ *
1198
+ * onChange(sequence.pointer.position, (position) => {
1199
+ * console.log("Position of the sequence changed to:", position)
1200
+ * })
1201
+ *
1202
+ * onChange(sequence.pointer.playing, (playing) => {
1203
+ * console.log(playing ? 'playing' : 'paused')
1204
+ * })
1205
+ *
1206
+ * // we can also read the current value of the pointer
1207
+ * console.log('current length is', val(sequence.pointer.length))
1208
+ * ```
1209
+ */
1210
+ pointer: Pointer<{
1211
+ playing: boolean;
1212
+ length: number;
1213
+ position: number;
1214
+ }>;
1215
+ /**
1216
+ * Given a property, returns a list of keyframes that affect that property.
1217
+ *
1218
+ * @example
1219
+ * Usage:
1220
+ * ```ts
1221
+ * // let's assume `sheet` is a sheet and obj is one of its objects
1222
+ * const keyframes = sheet.sequence.__experimental_getKeyframes(obj.pointer.x)
1223
+ * console.log(keyframes) // an array of keyframes
1224
+ * ```
1225
+ */
1226
+ __experimental_getKeyframes(prop: Pointer<{}>): Keyframe[];
1227
+ /**
1228
+ * Returns GSAP clip tracks on the active sequence variant for all objects.
1229
+ *
1230
+ * @experimental
1231
+ */
1232
+ __experimental_getGsapClips(): Array<{
1233
+ objectKey: string;
1234
+ trackId: SequenceTrackId;
1235
+ clip: GsapClipTrack;
1236
+ }>;
1237
+ /**
1238
+ * Runtime ScrollTrigger registrations for page-mode visualization (read-only in Studio).
1239
+ *
1240
+ * @experimental
1241
+ */
1242
+ __experimental_getGsapScrollTriggers(): Array<{
1243
+ objectKey: string;
1244
+ scrollTriggerId: string;
1245
+ label: string;
1246
+ layout: {
1247
+ start: number;
1248
+ duration: number;
1249
+ };
1250
+ kind: 'tween' | 'timeline';
1251
+ animationSpanSeconds: number;
1252
+ timelineChildren: GsapTimelineChildClip[];
1253
+ }>;
1254
+ /**
1255
+ * Attaches an audio source to the sequence. Playing the sequence automatically
1256
+ * plays the audio source and their times are kept in sync.
1257
+ *
1258
+ * @returns A promise that resolves once the audio source is loaded and decoded
1259
+ *
1260
+ * Learn more [here](https://unseen-theatre.netlify.app/docs/guide/manual/audio).
1261
+ *
1262
+ * @example
1263
+ * Usage:
1264
+ * ```ts
1265
+ * // Loads and decodes audio from the URL and then attaches it to the sequence
1266
+ * await sheet.sequence.attachAudio({source: "http://localhost:3000/audio.mp3"})
1267
+ * sheet.sequence.play()
1268
+ *
1269
+ * // Providing your own AudioAPI Context, destination, etc
1270
+ * const audioContext: AudioContext = {...} // create an AudioContext using the Audio API
1271
+ * const audioBuffer: AudioBuffer = {...} // create an AudioBuffer
1272
+ * const destinationNode = audioContext.destination
1273
+ *
1274
+ * await sheet.sequence.attachAudio({source: audioBuffer, audioContext, destinationNode})
1275
+ * ```
1276
+ *
1277
+ * Note: It's better to provide the `audioContext` rather than allow Theatre.js to create it.
1278
+ * That's because some browsers [suspend the audioContext](https://developer.chrome.com/blog/autoplay/#webaudio)
1279
+ * unless it's initiated by a user gesture, like a click. If that happens, Theatre.js will
1280
+ * wait for a user gesture to resume the audioContext. But that's probably not an
1281
+ * optimal user experience. It is better to provide a button or some other UI element
1282
+ * to communicate to the user that they have to initiate the animation.
1283
+ *
1284
+ * @example
1285
+ * Example:
1286
+ * ```ts
1287
+ * // html: <button id="#start">start</button>
1288
+ * const button = document.getElementById('start')
1289
+ *
1290
+ * button.addEventListener('click', async () => {
1291
+ * const audioContext = ...
1292
+ * await sheet.sequence.attachAudio({audioContext, source: '...'})
1293
+ * sheet.sequence.play()
1294
+ * })
1295
+ * ```
1296
+ */
1297
+ attachAudio(args: IAttachAudioArgs): Promise<{
1298
+ /**
1299
+ * An {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer | AudioBuffer}.
1300
+ * If `args.source` is a URL, then `decodedBuffer` would be the result
1301
+ * of {@link https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData | audioContext.decodeAudioData()}
1302
+ * on the audio file at that URL.
1303
+ *
1304
+ * If `args.source` is an `AudioBuffer`, then `decodedBuffer` would be equal to `args.source`
1305
+ */
1306
+ decodedBuffer: AudioBuffer;
1307
+ /**
1308
+ * The `AudioContext`. It is either equal to `source.audioContext` if it is provided, or
1309
+ * one that's created on the fly.
1310
+ */
1311
+ audioContext: AudioContext;
1312
+ /**
1313
+ * Equals to either `args.destinationNode`, or if none is provided, it equals `audioContext.destinationNode`.
1314
+ *
1315
+ * See `gainNode` for more info.
1316
+ */
1317
+ destinationNode: AudioNode;
1318
+ /**
1319
+ * This is an intermediate GainNode that Theatre.js feeds its audio to. It is by default
1320
+ * connected to destinationNode, but you can disconnect the gainNode and feed it to your own graph.
1321
+ *
1322
+ * @example
1323
+ * For example:
1324
+ * ```ts
1325
+ * const {gainNode, audioContext} = await sequence.attachAudio({source: '/audio.mp3'})
1326
+ * // disconnect the gainNode (at this point, the sequence's audio track won't be audible)
1327
+ * gainNode.disconnect()
1328
+ * // create our own gain node
1329
+ * const lowerGain = audioContext.createGain()
1330
+ * // lower its volume to 10%
1331
+ * lowerGain.gain.setValueAtTime(0.1, audioContext.currentTime)
1332
+ * // feed the sequence's audio to our lowered gainNode
1333
+ * gainNode.connect(lowerGain)
1334
+ * // feed the lowered gainNode to the audioContext's destination
1335
+ * lowerGain.connect(audioContext.destination)
1336
+ * // now audio will be audible, with 10% the volume
1337
+ * ```
1338
+ */
1339
+ gainNode: GainNode;
1340
+ }>;
1341
+ }
1342
+
1343
+ type SheetObjectAction = (object: ISheetObject) => void;
1344
+ type SheetObjectActionsConfig = Record<string, SheetObjectAction>;
1345
+ /** Options for {@link ISheet.object} beyond prop definitions (visibility, showPropsOf, etc.). */
1346
+ type ISheetObjectOptions = {
1347
+ reconfigure?: boolean;
1348
+ /**
1349
+ * Whether the object appears in the Studio outline panel. Defaults to `true`.
1350
+ */
1351
+ visible?: boolean;
1352
+ /**
1353
+ * Other sheet objects whose props are shown in this object's Studio details
1354
+ * pane. Runtime-only (not persisted). Edits and sequencing still target the
1355
+ * source objects. Same-sheet only; cannot include the host object.
1356
+ *
1357
+ * @example
1358
+ * ```ts
1359
+ * const appearance = sheet.object('Appearance', {color: types.rgba()})
1360
+ * sheet.object('Box', {x: 0}, {showPropsOf: [appearance]})
1361
+ * ```
1362
+ */
1363
+ showPropsOf?: ISheetObject<any>[];
1364
+ /**
1365
+ * Prop paths that are excluded from exported project state JSON.
1366
+ * Values are stored in ahistoric static overrides (persist across Studio
1367
+ * reloads) but never written to historic state or sequence tracks.
1368
+ *
1369
+ * Paths accept dot notation (`'foo.bar'`) or arrays (`['foo', 'bar']`).
1370
+ * A prefix path like `'foo'` marks the entire subtree as transient.
1371
+ */
1372
+ transient?: readonly TransientPropPath[];
1373
+ /**
1374
+ * Prop paths that cannot be sequenced but are saved to exported project state.
1375
+ *
1376
+ * Paths accept dot notation (`'foo.bar'`) or arrays (`['foo', 'bar']`).
1377
+ * A prefix path like `'foo'` marks the entire subtree as static.
1378
+ */
1379
+ static?: readonly StaticPropPath[];
1380
+ __actions__THIS_API_IS_UNSTABLE_AND_WILL_CHANGE_IN_THE_NEXT_VERSION?: SheetObjectActionsConfig;
1381
+ };
1382
+ type ISheetPropsOptions = Omit<ISheetObjectOptions, 'visible' | 'showPropsOf'>;
1383
+ /** Public API for a Theatre.js sheet (objects, sequence, and runtime lifecycle). */
1384
+ interface ISheet {
1385
+ /**
1386
+ * All sheets have `sheet.type === 'Theatre_Sheet_PublicAPI'`
1387
+ */
1388
+ readonly type: 'Theatre_Sheet_PublicAPI';
1389
+ /**
1390
+ * The Project this Sheet belongs to
1391
+ */
1392
+ readonly project: IProject;
1393
+ /**
1394
+ * The address of the Sheet
1395
+ */
1396
+ readonly address: SheetAddress;
1397
+ /**
1398
+ * Creates a child object for the sheet
1399
+ *
1400
+ * **Docs: https://unseen-theatre.netlify.app/docs/guide/manual/objects**
1401
+ *
1402
+ * @param key - Each object is identified by a key, which is a non-empty string
1403
+ * @param props - The props of the object. See examples
1404
+ * @param options - (Optional) Provide `{reconfigure: true}` to reconfigure an existing object, `{visible: false}` to hide it from the Studio outline panel, or `{actions: { ... }}` to add custom buttons to the UI. Read the example below for details.
1405
+ *
1406
+ * @returns An Object
1407
+ *
1408
+ * @example
1409
+ * Usage:
1410
+ * ```ts
1411
+ * // Create an object named "a unique key" with no props
1412
+ * const obj = sheet.object("a unique key", {})
1413
+ * obj.address.objectKey // "a unique key"
1414
+ *
1415
+ *
1416
+ * // Create an object with {x: 0}
1417
+ * const obj = sheet.object("obj", {x: 0})
1418
+ * obj.value.x // returns 0 or the current number that the user has set
1419
+ *
1420
+ * // Create an object with nested props
1421
+ * const obj = sheet.object("obj", {position: {x: 0, y: 0}})
1422
+ * obj.value.position // {x: 0, y: 0}
1423
+ *
1424
+ * // you can also reconfigure an existing object:
1425
+ * const obj = sheet.object("obj", {foo: 0})
1426
+ * console.log(object.value.foo) // prints 0
1427
+ *
1428
+ * const obj2 = sheet.object("obj", {bar: 0}, {reconfigure: true})
1429
+ * console.log(object.value.foo) // prints undefined, since we've removed this prop via reconfiguring the object
1430
+ * console.log(object.value.bar) // prints 0, since we've introduced this prop by reconfiguring the object
1431
+ *
1432
+ * assert(obj === obj2) // passes, because reconfiguring the object returns the same object
1433
+ *
1434
+ * // you can add custom actions to an object:
1435
+ * const obj = sheet.object("obj", {foo: 0}, {
1436
+ * actions: {
1437
+ * // This will display a button in the UI that will reset the value of `foo` to 0
1438
+ * Reset: () => {
1439
+ * studio.transaction((api) => {
1440
+ * api.set(obj.props.foo, 0)
1441
+ * })
1442
+ * }
1443
+ * }
1444
+ * })
1445
+ *
1446
+ * // you can mark props as transient (excluded from exported state JSON):
1447
+ * const obj = sheet.object("Camera", {
1448
+ * fov: 50,
1449
+ * orbitEnabled: false,
1450
+ * }, {
1451
+ * transient: ['orbitEnabled']
1452
+ * })
1453
+ *
1454
+ * // static props are saved to state but cannot be sequenced:
1455
+ * const obj = sheet.object("Camera", { fov: 50, zoom: 1 }, {
1456
+ * static: ['zoom']
1457
+ * })
1458
+ * ```
1459
+ */
1460
+ object<Props extends UnknownShorthandCompoundProps>(key: string, props: Props, options?: ISheetObjectOptions): ISheetObject<Props>;
1461
+ /**
1462
+ * Declares props scoped to the sheet (not a scene object). The returned handle
1463
+ * behaves like a sheet object for `props`, `value`, `onValuesChange`, and
1464
+ * Studio editing, but is hidden from {@link ISheet.getObjects} and the outline.
1465
+ *
1466
+ * Sheet props are shared across all sequence variants (static and sequenced).
1467
+ */
1468
+ props<Props extends UnknownShorthandCompoundProps>(config: Props, options?: ISheetPropsOptions): ISheetObject<Props>;
1469
+ /**
1470
+ * Detaches a previously created child object from the sheet.
1471
+ *
1472
+ * If you call `sheet.object(key)` again with the same `key`, the object's values of the object's
1473
+ * props WILL NOT be reset to their initial values.
1474
+ *
1475
+ * @param key - The `key` of the object previously given to `sheet.object(key, ...)`.
1476
+ */
1477
+ detachObject(key: string): void;
1478
+ /**
1479
+ * Returns all currently attached objects on this sheet.
1480
+ *
1481
+ * Use with {@link ISheet.detachObject} to detach individual objects.
1482
+ */
1483
+ getObjects(): ISheetObject[];
1484
+ /**
1485
+ * Unloads this sheet instance from memory: detaches all objects, pauses
1486
+ * sequences, and removes the sheet from the project.
1487
+ *
1488
+ * Runtime-only: persisted prop overrides and sequence data are kept. Calling
1489
+ * `project.sheet` again with the same id recreates the sheet.
1490
+ */
1491
+ unload(): void;
1492
+ /**
1493
+ * Declares an outline namespace folder in the Studio. The folder appears in
1494
+ * the outline panel even before any sheet objects are added under it.
1495
+ *
1496
+ * You can also use this to set the default collapsed state for a namespace
1497
+ * folder. The default only applies when the user has not manually expanded
1498
+ * or collapsed the folder yet.
1499
+ *
1500
+ * This method is part of `@unseenco/theatre-core` so you can configure outline folders
1501
+ * without importing `@unseenco/theatre-studio`.
1502
+ *
1503
+ * @param namespacePath - The namespace path, e.g. `"My Folder"` or `"My Folder / Subfolder"`
1504
+ * @param opts - Optional configuration for the namespace folder
1505
+ *
1506
+ * @example
1507
+ * ```ts
1508
+ * const sheet = project.sheet('Scene')
1509
+ *
1510
+ * // Create an empty folder ahead of time, collapsed by default
1511
+ * sheet.declareOutlineNamespace('Props', {collapsed: true})
1512
+ *
1513
+ * // Later, add objects under that folder
1514
+ * sheet.object('Props / Chair', {x: 0})
1515
+ * sheet.object('Props / Table', {x: 0})
1516
+ * ```
1517
+ */
1518
+ declareOutlineNamespace(namespacePath: string, opts?: {
1519
+ collapsed?: boolean;
1520
+ }): void;
1521
+ /**
1522
+ * Sets whether a namespace folder in the Studio outline panel is collapsed.
1523
+ *
1524
+ * Call this on load to force a folder closed every time your app starts.
1525
+ * Unlike `declareOutlineNamespace()`, this overrides any previous user
1526
+ * preference for the current session's initial render.
1527
+ *
1528
+ * @param namespacePath - The namespace path, e.g. `"My Folder"` or `"My Folder / Subfolder"`
1529
+ * @param collapsed - Whether the folder should be collapsed
1530
+ *
1531
+ * @example
1532
+ * ```ts
1533
+ * const sheet = project.sheet('Scene')
1534
+ *
1535
+ * // Force a folder closed every time your app loads
1536
+ * sheet.setOutlineNamespaceCollapsed('Props', true)
1537
+ * ```
1538
+ */
1539
+ setOutlineNamespaceCollapsed(namespacePath: string, collapsed: boolean): void;
1540
+ /**
1541
+ * The Sequence of this Sheet (uses the currently active sequence variant)
1542
+ */
1543
+ readonly sequence: ISequence;
1544
+ /**
1545
+ * Declares the sequence variants available on this sheet. Each variant has its own
1546
+ * independent sequence data, allowing the same properties to be animated differently
1547
+ * per variant (e.g. mobile vs desktop).
1548
+ *
1549
+ * The `"default"` variant is always required and is included automatically if omitted.
1550
+ *
1551
+ * @param variants - An array of variant names (each at least 3 characters long)
1552
+ *
1553
+ * @example
1554
+ * ```ts
1555
+ * const sheet = project.sheet('Scene')
1556
+ * sheet.declareSequenceVariants(['default', 'mobile', 'desktop'])
1557
+ * sheet.setActiveSequenceVariant('mobile')
1558
+ * ```
1559
+ */
1560
+ declareSequenceVariants(variants: SequenceVariantId[]): void;
1561
+ /**
1562
+ * Sets which sequence variant is currently active. The active variant determines
1563
+ * which sequence's keyframes are used when computing prop values, and which sequence
1564
+ * `sheet.sequence` refers to.
1565
+ */
1566
+ setActiveSequenceVariant(variant: SequenceVariantId): void;
1567
+ /**
1568
+ * Returns the currently active sequence variant name.
1569
+ */
1570
+ getActiveSequenceVariant(): SequenceVariantId;
1571
+ /**
1572
+ * Whether the sequence uses wall-clock time (default) or page-scroll percent units.
1573
+ * Page mode is runtime-only and not persisted in project state.
1574
+ */
1575
+ getSequenceMode(): 'time' | 'page';
1576
+ /**
1577
+ * Switches sequence units: `time` (seconds) or `page` (0–100 = scroll percent).
1578
+ * In page mode, length is fixed at 100 and snap grid uses 0.1% steps.
1579
+ */
1580
+ setSequenceMode(mode: 'time' | 'page'): void;
1581
+ /**
1582
+ * Overrides the scroll driver used in page mode (e.g. Lenis). Pass `undefined` to
1583
+ * restore native document scroll. Re-attaches the scroll → playhead listener.
1584
+ */
1585
+ setPageScrollDriver(driver: ScrollDriver | undefined): void;
1586
+ }
1587
+
1588
+ /**
1589
+ * A project's config object (currently the only point of configuration is the project's state)
1590
+ */
1591
+ type ISheetOptions = {
1592
+ /**
1593
+ * Whether the sheet appears in the Studio outline panel. Defaults to `true`.
1594
+ */
1595
+ visible?: boolean;
1596
+ /**
1597
+ * How the sheet sequence maps to the sequencer UI and playback driver.
1598
+ * In **`page`** mode the sequence length is fixed at **100** (percent scroll) with **0.1%** snap steps;
1599
+ * native document scroll keeps the playhead in sync.
1600
+ *
1601
+ * @defaultValue `'time'`
1602
+ */
1603
+ sequenceMode?: SheetSequenceMode;
1604
+ /**
1605
+ * When `true`, enables the GSAP sequence bridge for this sheet (required for `@unseenco/theatre-gsap`).
1606
+ */
1607
+ gsap?: boolean;
1608
+ /**
1609
+ * Custom scroll driver for **`page`** mode (Lenis, overflow element, etc.).
1610
+ * When omitted in page mode, native document scroll is used.
1611
+ */
1612
+ scrollDriver?: ScrollDriver;
1613
+ };
1614
+ /** Options passed to {@link getProject} when creating or attaching to a project. */
1615
+ type IProjectConfig = {
1616
+ /**
1617
+ * The state of the project, as [exported](https://unseen-theatre.netlify.app/docs/guide/manual/projects#state) by the studio.
1618
+ */
1619
+ state?: $IntentionalAny;
1620
+ assets?: {
1621
+ baseUrl?: string;
1622
+ };
1623
+ /**
1624
+ * The default number of decimal places to use for number props in the Studio.
1625
+ * Can be overridden per prop via `types.number(defaultValue, {precision})`.
1626
+ *
1627
+ * @defaultValue 3
1628
+ */
1629
+ numberPrecision?: number;
1630
+ };
1631
+ /**
1632
+ * A Theatre.js project
1633
+ */
1634
+ interface IProject {
1635
+ /** Discriminator for Theatre.js public project instances. */
1636
+ readonly type: 'Theatre_Project_PublicAPI';
1637
+ /**
1638
+ * If `@unseenco/theatre-studio` is used, this promise would resolve when studio has loaded
1639
+ * the state of the project into memory.
1640
+ *
1641
+ * If `@unseenco/theatre-studio` is not used, this promise is already resolved.
1642
+ */
1643
+ readonly ready: Promise<void>;
1644
+ /**
1645
+ * Shows whether the project is ready to be used.
1646
+ * Better to use {@link IProject.ready}, which is a promise that would
1647
+ * resolve when the project is ready.
1648
+ */
1649
+ readonly isReady: boolean;
1650
+ /**
1651
+ * The project's address
1652
+ */
1653
+ readonly address: ProjectAddress;
1654
+ /**
1655
+ * Creates a Sheet under the project
1656
+ * @param sheetId - Sheets are identified by their `sheetId`, which must be a string longer than 3 characters
1657
+ * @param instanceIdOrOpts - Optionally provide an `instanceId`, or pass sheet options (e.g. `{ sequenceMode: 'page', gsap: true }`)
1658
+ * @param opts - Sheet options such as `{ visible: false }`, `sequenceMode`, or `gsap`
1659
+ * @returns The newly created Sheet
1660
+ *
1661
+ * **Docs: https://unseen-theatre.netlify.app/docs/guide/manual/sheets**
1662
+ */
1663
+ sheet(sheetId: string, instanceIdOrOpts?: string | ISheetOptions): ISheet;
1664
+ /**
1665
+ * Creates a Sheet under the project (overload with explicit `instanceId`).
1666
+ *
1667
+ * @param sheetId - Sheets are identified by their `sheetId`, which must be a string longer than 3 characters
1668
+ * @param instanceId - Instance id when creating multiple instances of the same sheet
1669
+ * @param opts - Sheet options such as `{ visible: false }`, `sequenceMode`, or `gsap`
1670
+ * @returns The newly created Sheet
1671
+ */
1672
+ sheet(sheetId: string, instanceId: string, opts?: ISheetOptions): ISheet;
1673
+ /**
1674
+ * Returns all currently loaded sheet instances under this project.
1675
+ *
1676
+ * Use with {@link ISheet.detachObject} or {@link ISheet.unload} to tear down
1677
+ * individual sheets/objects at runtime. Persisted project state is not cleared.
1678
+ */
1679
+ getSheets(): ISheet[];
1680
+ /**
1681
+ * Unloads one sheet and its attached objects from memory.
1682
+ *
1683
+ * Runtime-only: persisted prop overrides and sequence data are kept. Calling
1684
+ * {@link IProject.sheet} again with the same `sheetId` recreates the sheet.
1685
+ *
1686
+ * @param sheetId - The sheet id previously given to {@link IProject.sheet}
1687
+ * @param instanceId - If provided, only that instance is unloaded. If omitted,
1688
+ * all loaded instances of `sheetId` are unloaded.
1689
+ */
1690
+ unloadSheet(sheetId: string, instanceId?: string): void;
1691
+ /**
1692
+ * Unloads every currently loaded sheet and its objects from memory.
1693
+ *
1694
+ * Runtime-only: persisted project state is kept.
1695
+ */
1696
+ unloadSheets(): void;
1697
+ /**
1698
+ * Returns the URL for an asset.
1699
+ *
1700
+ * @param asset - The asset to get the URL for
1701
+ * @returns The URL for the asset, or `undefined` if the asset is not found
1702
+ */
1703
+ getAssetUrl(asset: Asset | File): string | undefined;
1704
+ }
1705
+
1706
+ type Notify = (
1707
+ /**
1708
+ * The title of the notification.
1709
+ */
1710
+ title: string,
1711
+ /**
1712
+ * The message of the notification.
1713
+ */
1714
+ message: string,
1715
+ /**
1716
+ * An array of doc pages to link to.
1717
+ */
1718
+ docs?: {
1719
+ url: string;
1720
+ title: string;
1721
+ }[],
1722
+ /**
1723
+ * Whether duplicate notifications should be allowed.
1724
+ */
1725
+ allowDuplicates?: boolean) => void;
1726
+ type Notifiers = {
1727
+ /**
1728
+ * Show a success notification.
1729
+ */
1730
+ success: Notify;
1731
+ /**
1732
+ * Show a warning notification.
1733
+ *
1734
+ * Say what happened in the title.
1735
+ * In the message, start with 1) a reassurance, then 2) explain why it happened, and 3) what the user can do about it.
1736
+ */
1737
+ warning: Notify;
1738
+ /**
1739
+ * Show an info notification.
1740
+ */
1741
+ info: Notify;
1742
+ /**
1743
+ * Show an error notification.
1744
+ */
1745
+ error: Notify;
1746
+ };
1747
+ /** User-facing notification helpers (success, warning, info, error) used by Theatre.js runtimes. */
1748
+ declare const notify: Notifiers;
1749
+
1750
+ /**
1751
+ * Sets the `rafDriver` that Theatre's core uses internally to tick forward.
1752
+ *
1753
+ * Call this **before** any other `@unseenco/theatre-core` API that would trigger tick creation
1754
+ * (e.g. `onChange`, `sequence.play`, `val`). Calling it after the core ticker has
1755
+ * already been initialised will throw.
1756
+ *
1757
+ * This is the recommended way to drive Theatre from your own
1758
+ * `requestAnimationFrame` loop — for example when integrating with
1759
+ * `gsap`, `lenis`, or an XR session:
1760
+ *
1761
+ * ```ts
1762
+ * import { createRafDriver, setCoreRafDriver } from '@unseenco/theatre-core'
1763
+ *
1764
+ * const driver = createRafDriver({ name: 'MyRafDriver' })
1765
+ * setCoreRafDriver(driver)
1766
+ *
1767
+ * function myLoop(time: number) {
1768
+ * driver.tick(time)
1769
+ * requestAnimationFrame(myLoop)
1770
+ * }
1771
+ * requestAnimationFrame(myLoop)
1772
+ * ```
1773
+ *
1774
+ * Because you hold the `driver` reference you created, you do not need a separate
1775
+ * `getCoreRafDriver()` call — just keep the reference around and call
1776
+ * `driver.tick(time)` from your loop.
1777
+ */
1778
+ declare function setCoreRafDriver(driver: IRafDriver): void;
1779
+
1780
+ /**
1781
+ * A window is considered the "remote editor" for a project when its URL
1782
+ * contains the `editor` hash, e.g. `https://myapp.com/#editor`. Every other
1783
+ * window is a listener that mirrors whatever the editor window broadcasts.
1784
+ *
1785
+ * This convention is shared with `@unseenco/theatre-studio`'s "Open remote editor
1786
+ * window" toolbar button, which opens a popup at the current URL with this
1787
+ * hash set.
1788
+ */
1789
+ declare function isRemoteEditorWindow(): boolean;
1790
+
1791
+ /**
1792
+ * Drives registered GSAP animations from the sheet sequence playhead.
1793
+ *
1794
+ * @returns Disposer — call to detach the bridge.
1795
+ */
1796
+ declare function attachGsapSequenceBridge(sheet: ISheet): () => void;
1797
+
1798
+ /** `null` = native document scroll (window / documentElement). */
1799
+ type PageScrollScroller = Window | Element | null;
1800
+ type PageScrollAxis = 'vertical' | 'horizontal';
1801
+ type PageScrollContext = {
1802
+ scroller: PageScrollScroller;
1803
+ /** Scroll axis for page mode and ScrollTrigger registration (default vertical). */
1804
+ axis?: PageScrollAxis;
1805
+ };
1806
+ declare const defaultPageScrollContext: PageScrollContext;
1807
+ declare function setActivePageScrollContext(context: PageScrollContext): void;
1808
+ declare function getActivePageScrollContext(): PageScrollContext;
1809
+ declare function resolvePageScrollAxis(context?: PageScrollContext): PageScrollAxis;
1810
+ declare function resolvePageScrollScroller(st: unknown, defaultsScroller?: PageScrollScroller): PageScrollScroller;
1811
+ declare function isNativeDocumentScroller(scroller: PageScrollScroller): boolean;
1812
+ /** Same scroller target for page-mode scroll registration (reference equality or both native doc). */
1813
+ declare function pageScrollScrollersMatch(a: PageScrollScroller, b: PageScrollScroller): boolean;
1814
+ /**
1815
+ * True when ST scroller and horizontal flag match the configured page scroll context.
1816
+ */
1817
+ declare function isPageScrollTrigger(st: unknown, context?: PageScrollContext): boolean;
1818
+ declare function isVerticalPageScrollTrigger(st: unknown, context?: PageScrollContext): boolean;
1819
+
1820
+ type TheatrePageScrollConfig = {
1821
+ /** Default scroller for page-mode layout helpers; `null` = native document. */
1822
+ scroller?: PageScrollScroller;
1823
+ /** Scroll axis (default vertical). */
1824
+ axis?: PageScrollAxis;
1825
+ };
1826
+ declare function configureTheatrePageScroll(config: TheatrePageScrollConfig): {
1827
+ reset: () => void;
1828
+ };
1829
+ declare function getTheatrePageScrollConfig(): TheatrePageScrollConfig;
1830
+ /** Active page scroll context from {@link configureTheatrePageScroll}. */
1831
+ declare function getTheatrePageScrollContext(): PageScrollContext;
1832
+ declare function createDefaultPageScrollDriver(scroller?: PageScrollScroller, axis?: PageScrollAxis): ScrollDriver;
1833
+ type AttachTheatrePageScrollOptions = {
1834
+ driver?: ScrollDriver;
1835
+ };
1836
+ /**
1837
+ * Wires page-mode scroll → sequence sync using {@link configureTheatrePageScroll}
1838
+ * or an explicit driver (Lenis, overflow containers, etc.).
1839
+ */
1840
+ declare function attachTheatrePageScroll(sheet: ISheet, options?: AttachTheatrePageScrollOptions): () => void;
1841
+
1842
+ /**
1843
+ * Returns a project of the given id, or creates one if it doesn't already exist.
1844
+ *
1845
+ * @remarks
1846
+ * If \@unseenco/theatre-studio is also loaded, then the state of the project will be managed by the studio.
1847
+ *
1848
+ * [Learn more about exporting](https://unseen-theatre.netlify.app/docs/guide/manual/projects#state)
1849
+ *
1850
+ * @example
1851
+ * Usage:
1852
+ * ```ts
1853
+ * import {getProject} from '@unseenco/theatre-core'
1854
+ * const config = {} // the config can be empty when starting a new project
1855
+ * const project = getProject("a-unique-id", config)
1856
+ * ```
1857
+ *
1858
+ * @example
1859
+ * Usage with an explicit state:
1860
+ * ```ts
1861
+ * import {getProject} from '@unseenco/theatre-core'
1862
+ * import state from './saved-state.json'
1863
+ * const config = {state} // here the config contains our saved state
1864
+ * const project = getProject("a-unique-id", config)
1865
+ * ```
1866
+ */
1867
+ declare function getProject(id: string, config?: IProjectConfig): IProject;
1868
+ /**
1869
+ * Calls `callback` every time the pointed value of `pointer` changes.
1870
+ *
1871
+ * @param pointer - A Pointer (like `object.props.x`)
1872
+ * @param callback - The callback is called every time the value of pointer changes
1873
+ * @param rafDriver - (optional) The `rafDriver` to use. Learn how to use `rafDriver`s [from the docs](https://unseen-theatre.netlify.app/docs/guide/manual/advanced#rafdrivers).
1874
+ * @returns An unsubscribe function
1875
+ *
1876
+ * @example
1877
+ * Usage:
1878
+ * ```ts
1879
+ * import {getProject, onChange} from '@unseenco/theatre-core'
1880
+ *
1881
+ * const obj = getProject("A project").sheet("Scene").object("Box", {position: {x: 0}})
1882
+ *
1883
+ * const usubscribe = onChange(obj.props.position.x, (x) => {
1884
+ * console.log('position.x changed to:', x)
1885
+ * })
1886
+ *
1887
+ * setTimeout(usubscribe, 10000) // stop listening to changes after 10 seconds
1888
+ * ```
1889
+ */
1890
+ declare function onChange<P extends PointerType<$IntentionalAny> | Prism<$IntentionalAny>>(pointer: P, callback: (value: P extends PointerType<infer T> ? T : P extends Prism<infer T> ? T : unknown) => void, rafDriver?: IRafDriver): VoidFn;
1891
+ /**
1892
+ * Takes a Pointer and returns the value it points to.
1893
+ *
1894
+ * @param pointer - A pointer (like `object.props.x`)
1895
+ * @returns The value the pointer points to
1896
+ *
1897
+ * @example
1898
+ *
1899
+ * Usage
1900
+ * ```ts
1901
+ * import {val, getProject} from '@unseenco/theatre-core'
1902
+ *
1903
+ * const obj = getProject("A project").sheet("Scene").object("Box", {position: {x: 0}})
1904
+ *
1905
+ * console.log(val(obj.props.position.x)) // logs the value of obj.props.x
1906
+ * ```
1907
+ */
1908
+ declare function val<T>(pointer: PointerType<T>): T;
1909
+
1910
+ /**
1911
+ * The library providing the runtime functionality of Theatre.js.
1912
+ *
1913
+ * @packageDocumentation
1914
+ */
1915
+
1916
+ /**
1917
+ * NOTE: **INTERNAL and UNSTABLE** - This _WILL_ break between minor versions.
1918
+ *
1919
+ * This type represents the object returned by `studio.createContnentOfSaveFile()`. It's
1920
+ * meant for advanced users who want to interact with the state of projects. In the vast
1921
+ * majority of cases, you __should not__ use this type. Either an API for your use-case
1922
+ * already exists, or you should open an issue on GitHub: https://github.com/craftedbygc/theatre/issues
1923
+ *
1924
+ */
1925
+ type __UNSTABLE_Project_OnDiskState = OnDiskState;
1926
+
1927
+ export { AttachTheatrePageScrollOptions, IProject, IProjectConfig, IRafDriver, ISequence, ISheet, ISheetObject, ISheetObjectOptions, ISheetOptions, PAGE_MODE_SEQUENCE_LENGTH, PAGE_MODE_SUB_UNITS_PER_UNIT, PageScrollAxis, PageScrollContext, PageScrollScroller, ScrollDriver, SheetSequenceMode, TheatrePageScrollConfig, UnknownShorthandCompoundProps, __UNSTABLE_Project_OnDiskState, attachGsapSequenceBridge, attachSheetScrollDriver, attachTheatrePageScroll, configureTheatrePageScroll, createDefaultPageScrollDriver, createElementHorizontalScrollDriver, createElementScrollDriver, createNativeDocumentHorizontalScrollDriver, createNativeDocumentScrollDriver, createRafDriver, defaultPageScrollContext, getActivePageScrollContext, getProject, getSheetScrollDriver, getTheatrePageScrollConfig, getTheatrePageScrollContext, isNativeDocumentScroller, isPageScrollTrigger, isRemoteEditorWindow, isVerticalPageScrollTrigger, notify, onChange, pageScrollProgressFromSequence, pageScrollScrollersMatch, resolvePageScrollAxis, resolvePageScrollScroller, setActivePageScrollContext, setCoreRafDriver, setPageScrollProgress, syncNativeDocumentScrollToSequencePosition, syncPageScrollToSequencePosition, index_d as types, val };