@unseenco/theatre-core 0.1.14 → 0.1.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,10 +1,1620 @@
1
- export * from './coreExports';
2
- export { IProject, IProjectConfig, ISheetOptions } from './projects/TheatreProject';
3
- export { ISequence } from './sequences/TheatreSequence';
4
- export { ISheetObject } from './sheetObjects/TheatreSheetObject';
5
- export { ISheet, ISheetObjectOptions } from './sheets/TheatreSheet';
6
- export { UnknownShorthandCompoundProps } from './propTypes/index';
7
- import { OnDiskState } from './projects/store/storeTypes';
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
+ * Currently just {@link BasicKeyframedTrack}.
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;
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
+ type TrackDataCommon<TypeName extends string> = {
230
+ type: TypeName;
231
+ /**
232
+ * Initial name of the track for debugging purposes. In the future, let's
233
+ * strip this value from `studio.createContentOfSaveFile()` Could also be
234
+ * useful for users who manually edit the project state.
235
+ */
236
+ __debugName?: string;
237
+ };
238
+ type BasicKeyframedTrack = TrackDataCommon<'BasicKeyframedTrack'> & {
239
+ /**
240
+ * {@link Keyframe} is not provided an explicit generic value `T`, because
241
+ * a single track can technically have multiple different types for each keyframe.
242
+ */
243
+ keyframes: Keyframe[];
244
+ };
245
+
246
+ type SequenceVariantId = string;
247
+
248
+ type Rgba = {
249
+ r: number;
250
+ g: number;
251
+ b: number;
252
+ a: number;
253
+ };
254
+
255
+ declare const propTypeSymbol: unique symbol;
256
+ type UnknownValidCompoundProps = {
257
+ [K in string]: PropTypeConfig;
258
+ };
259
+ /**
260
+ *
261
+ * This does not include Rgba since Rgba does not have a predictable
262
+ * object shape. We prefer to infer that compound props are described as
263
+ * `Record<string, IShorthandProp>` for now.
264
+ *
265
+ * In the future, it might be reasonable to wrap these types up into something
266
+ * which would allow us to differentiate between values at runtime
267
+ * (e.g. `val.type = "Rgba"` vs `val.type = "Compound"` etc)
268
+ */
269
+ type UnknownShorthandProp = string | number | boolean | PropTypeConfig | UnknownShorthandCompoundProps;
270
+ /** Given an object like this, we have enough info to predict the compound prop */
271
+ type UnknownShorthandCompoundProps = {
272
+ [K in string]: UnknownShorthandProp;
273
+ };
274
+ 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;
275
+ type LonghandCompoundPropsToInitialValue<P extends UnknownValidCompoundProps> = {
276
+ [K in keyof P]: P[K]['valueType'];
277
+ };
278
+ type PropsValue<P> = P extends UnknownValidCompoundProps ? LonghandCompoundPropsToInitialValue<P> : P extends UnknownShorthandCompoundProps ? LonghandCompoundPropsToInitialValue<ShorthandCompoundPropsToLonghandCompoundProps<P>> : never;
279
+ type ShorthandCompoundPropsToLonghandCompoundProps<P extends UnknownShorthandCompoundProps> = {
280
+ [K in keyof P]: ShorthandPropToLonghandProp<P[K]>;
281
+ };
282
+
283
+ /**
284
+ * A compound prop type (basically a JS object).
285
+ *
286
+ * @example
287
+ * Usage:
288
+ * ```ts
289
+ * // shorthand
290
+ * const position = {
291
+ * x: 0,
292
+ * y: 0
293
+ * }
294
+ * assert(sheet.object('some object', position).value.x === 0)
295
+ *
296
+ * // nesting
297
+ * const foo = {bar: {baz: {quo: 0}}}
298
+ * assert(sheet.object('some object', foo).value.bar.baz.quo === 0)
299
+ *
300
+ * // With additional options:
301
+ * const position = t.compound(
302
+ * {x: 0, y: 0},
303
+ * // a custom label for the prop:
304
+ * {label: "Position"}
305
+ * )
306
+ * ```
307
+ *
308
+ */
309
+ declare const compound: <Props extends UnknownShorthandCompoundProps>(props: Props, opts?: CommonOpts) => PropTypeConfig_Compound<ShorthandCompoundPropsToLonghandCompoundProps<Props>>;
310
+ /**
311
+ * A file prop type
312
+ *
313
+ * @example
314
+ * Usage:
315
+ * ```ts
316
+ *
317
+ * // with a label:
318
+ * const obj = sheet.object('key', {
319
+ * url: t.file('My file.glb', {
320
+ * label: 'Model'
321
+ * })
322
+ * })
323
+ * ```
324
+ *
325
+ * @param opts - Options (See usage examples)
326
+ */
327
+ declare const file: (defaultValue: File['id'], opts?: {
328
+ label?: string;
329
+ interpolate?: Interpolator<File['id']>;
330
+ }) => PropTypeConfig_File;
331
+ /**
332
+ * An image prop type
333
+ *
334
+ * @example
335
+ * Usage:
336
+ * ```ts
337
+ *
338
+ * // with a label:
339
+ * const obj = sheet.object('key', {
340
+ * url: t.image('My image.png', {
341
+ * label: 'texture'
342
+ * })
343
+ * })
344
+ * ```
345
+ *
346
+ * @param opts - Options (See usage examples)
347
+ */
348
+ declare const image: (defaultValue: Asset['id'], opts?: {
349
+ label?: string;
350
+ interpolate?: Interpolator<Asset['id']>;
351
+ /**
352
+ * When `false`, values edited in Studio are kept in memory for the
353
+ * current session only and are not written to persisted project state.
354
+ * Defaults to `true`.
355
+ */
356
+ persist?: boolean;
357
+ }) => PropTypeConfig_Image;
358
+ /**
359
+ * A number prop type.
360
+ *
361
+ * @example
362
+ * Usage
363
+ * ```ts
364
+ * // shorthand:
365
+ * const obj = sheet.object('key', {x: 0})
366
+ *
367
+ * // With options (equal to above)
368
+ * const obj = sheet.object('key', {
369
+ * x: t.number(0)
370
+ * })
371
+ *
372
+ * // With a range (note that opts.range is just a visual guide, not a validation rule)
373
+ * const x = t.number(0, {range: [0, 10]}) // limited to 0 and 10
374
+ *
375
+ * // With custom nudging
376
+ * const x = t.number(0, {nudgeMultiplier: 0.1}) // nudging will happen in 0.1 increments
377
+ *
378
+ * // With custom nudging function
379
+ * const x = t.number({
380
+ * nudgeFn: (
381
+ * // the mouse movement (in pixels)
382
+ * deltaX: number,
383
+ * // the movement as a fraction of the width of the number editor's input
384
+ * deltaFraction: number,
385
+ * // A multiplier that's usually 1, but might be another number if user wants to nudge slower/faster
386
+ * magnitude: number,
387
+ * // the configuration of the number
388
+ * config: {nudgeMultiplier?: number; range?: [number, number]},
389
+ * ): number => {
390
+ * return deltaX * magnitude
391
+ * },
392
+ * })
393
+ * ```
394
+ *
395
+ * @param defaultValue - The default value (Must be a finite number)
396
+ * @param opts - The options (See usage examples)
397
+ * @returns A number prop config
398
+ */
399
+ declare const number: (defaultValue: number, opts?: {
400
+ nudgeFn?: PropTypeConfig_Number['nudgeFn'];
401
+ range?: PropTypeConfig_Number['range'];
402
+ nudgeMultiplier?: number;
403
+ label?: string;
404
+ }) => PropTypeConfig_Number;
405
+ declare const rgba: (defaultValue?: Rgba, opts?: CommonOpts) => PropTypeConfig_Rgba;
406
+ /**
407
+ * A boolean prop type
408
+ *
409
+ * @example
410
+ * Usage:
411
+ * ```ts
412
+ * // shorthand:
413
+ * const obj = sheet.object('key', {isOn: true})
414
+ *
415
+ * // with a label:
416
+ * const obj = sheet.object('key', {
417
+ * isOn: t.boolean(true, {
418
+ * label: 'Enabled'
419
+ * })
420
+ * })
421
+ * ```
422
+ *
423
+ * @param defaultValue - The default value (must be a boolean)
424
+ * @param opts - Options (See usage examples)
425
+ */
426
+ declare const boolean: (defaultValue: boolean, opts?: {
427
+ label?: string;
428
+ interpolate?: Interpolator<boolean>;
429
+ }) => PropTypeConfig_Boolean;
430
+ /**
431
+ * A string prop type
432
+ *
433
+ * @example
434
+ * Usage:
435
+ * ```ts
436
+ * // shorthand:
437
+ * const obj = sheet.object('key', {message: "Animation loading"})
438
+ *
439
+ * // with a label:
440
+ * const obj = sheet.object('key', {
441
+ * message: t.string("Animation Loading", {
442
+ * label: 'The Message'
443
+ * })
444
+ * })
445
+ * ```
446
+ *
447
+ * @param defaultValue - The default value (must be a string)
448
+ * @param opts - The options (See usage examples)
449
+ * @returns A string prop type
450
+ */
451
+ declare const string: (defaultValue: string, opts?: {
452
+ label?: string;
453
+ interpolate?: Interpolator<string>;
454
+ }) => PropTypeConfig_String;
455
+ /**
456
+ * A stringLiteral prop type, useful for building menus or radio buttons.
457
+ *
458
+ * @example
459
+ * Usage:
460
+ * ```ts
461
+ * // Basic usage
462
+ * const obj = sheet.object('key', {
463
+ * light: t.stringLiteral("r", {r: "Red", "g": "Green"})
464
+ * })
465
+ *
466
+ * // Shown as a radio switch with a custom label
467
+ * const obj = sheet.object('key', {
468
+ * light: t.stringLiteral("r", {r: "Red", "g": "Green"})
469
+ * }, {as: "switch", label: "Street Light"})
470
+ * ```
471
+ *
472
+ * @returns A stringLiteral prop type
473
+ *
474
+ */
475
+ declare function stringLiteral<ValuesAndLabels extends {
476
+ [key in string]: string;
477
+ }>(
478
+ /**
479
+ * Default value (a string that equals one of the options)
480
+ */
481
+ defaultValue: Extract<keyof ValuesAndLabels, string>,
482
+ /**
483
+ * The options. Use the `"value": "Label"` format.
484
+ *
485
+ * An object like `{[value]: Label}`. Example: `{r: "Red", "g": "Green"}`
486
+ */
487
+ valuesAndLabels: ValuesAndLabels,
488
+ /**
489
+ * opts.as Determines if editor is shown as a menu or a switch. Either 'menu' or 'switch'. Default: 'menu'
490
+ */
491
+ opts?: {
492
+ as?: 'menu' | 'switch';
493
+ label?: string;
494
+ interpolate?: Interpolator<Extract<keyof ValuesAndLabels, string>>;
495
+ }): PropTypeConfig_StringLiteral<Extract<keyof ValuesAndLabels, string>>;
496
+ /**
497
+ * A linear interpolator for a certain value type.
498
+ *
499
+ * @param left - the value to interpolate from (beginning)
500
+ * @param right - the value to interpolate to (end)
501
+ * @param progression - the amount of progression. Starts at 0 and ends at 1. But could overshoot in either direction
502
+ *
503
+ * @example
504
+ * ```ts
505
+ * const numberInterpolator: Interpolator<number> = (left, right, progression) => left + progression * (right - left)
506
+ *
507
+ * numberInterpolator(-50, 50, 0.5) === 0
508
+ * numberInterpolator(-50, 50, 0) === -50
509
+ * numberInterpolator(-50, 50, 1) === 50
510
+ * numberInterpolator(-50, 50, 2) === 150 // overshoot
511
+ * ```
512
+ */
513
+ type Interpolator<T> = (left: T, right: T, progression: number) => T;
514
+ interface IBasePropType<LiteralIdentifier extends string, ValueType, DeserializeType = ValueType> {
515
+ /**
516
+ * Each prop config has a string literal identifying it. For example,
517
+ * `assert.equal(t.number(10).type, 'number')`
518
+ */
519
+ type: LiteralIdentifier;
520
+ /**
521
+ * the `valueType` is only used by typescript. It won't be present in runtime.
522
+ */
523
+ valueType: ValueType;
524
+ [propTypeSymbol]: 'TheatrePropType';
525
+ /**
526
+ * Each prop type may be given a custom label instead of the name of the sub-prop
527
+ * it is in.
528
+ *
529
+ * @example
530
+ * ```ts
531
+ * const position = {
532
+ * x: t.number(0), // label would be 'x'
533
+ * y: t.number(0, {label: 'top'}) // label would be 'top'
534
+ * }
535
+ * ```
536
+ */
537
+ label: string | undefined;
538
+ default: ValueType;
539
+ /**
540
+ * Each prop config has a `deserializeAndSanitize()` function that deserializes and sanitizes
541
+ * any js value into one that is acceptable by this prop config, or `undefined`.
542
+ *
543
+ * As a rule, the value returned by this function should not hold any reference to `json` or any
544
+ * other value referenced by the descendent props of `json`. This is to ensure that json values
545
+ * controlled by the user can never change the values in the store. See `deserializeAndSanitize()` in
546
+ * `t.compound()` or `t.rgba()` as examples.
547
+ *
548
+ * The `DeserializeType` is usually equal to `ValueType`. That is the case with
549
+ * all simple prop configs, such as `number`, `string`, or `rgba`. However, composite
550
+ * configs such as `compound` or `enum` may deserialize+sanitize into a partial value. For example,
551
+ * a prop config of `t.compound({x: t.number(0), y: t.number(0)})` may deserialize+sanitize into `{x: 10}`.
552
+ * This behavior is used by {@link SheetObject.getValues} to replace the missing sub-props
553
+ * with their default value.
554
+ *
555
+ * Admittedly, this partial deserialization behavior is not what the word "deserialize"
556
+ * typically implies in most codebases, so feel free to change this name into a more
557
+ * appropriate one.
558
+ *
559
+ * Additionally, returning an `undefined` allows {@link SheetObject.getValues} to
560
+ * replace the `undefined` with the default value of that prop.
561
+ */
562
+ deserializeAndSanitize: (json: unknown) => undefined | DeserializeType;
563
+ }
564
+ interface ISimplePropType<LiteralIdentifier extends string, ValueType> extends IBasePropType<LiteralIdentifier, ValueType, ValueType> {
565
+ interpolate: Interpolator<ValueType>;
566
+ }
567
+ interface PropTypeConfig_Number extends ISimplePropType<'number', number> {
568
+ range?: [min: number, max: number];
569
+ nudgeFn: NumberNudgeFn;
570
+ /**
571
+ * See {@link defaultNumberNudgeFn} to see how `nudgeMultiplier` is treated.
572
+ */
573
+ nudgeMultiplier: number | undefined;
574
+ }
575
+ type NumberNudgeFn = (p: {
576
+ deltaX: number;
577
+ deltaFraction: number;
578
+ magnitude: number;
579
+ config: PropTypeConfig_Number;
580
+ }) => number;
581
+ interface PropTypeConfig_Boolean extends ISimplePropType<'boolean', boolean> {
582
+ }
583
+ type CommonOpts = {
584
+ /**
585
+ * Each prop type may be given a custom label instead of the name of the sub-prop
586
+ * it is in.
587
+ *
588
+ * @example
589
+ * ```ts
590
+ * const position = {
591
+ * x: t.number(0), // label would be 'x'
592
+ * y: t.number(0, {label: 'top'}) // label would be 'top'
593
+ * }
594
+ * ```
595
+ */
596
+ label?: string;
597
+ };
598
+ interface PropTypeConfig_String extends ISimplePropType<'string', string> {
599
+ }
600
+ interface PropTypeConfig_StringLiteral<T extends string> extends ISimplePropType<'stringLiteral', T> {
601
+ valuesAndLabels: Record<T, string>;
602
+ as: 'menu' | 'switch';
603
+ }
604
+ interface PropTypeConfig_Rgba extends ISimplePropType<'rgba', Rgba> {
605
+ }
606
+ interface PropTypeConfig_Image extends ISimplePropType<'image', Asset> {
607
+ /**
608
+ * When `false`, Studio edits are session-only and not written to persisted
609
+ * project state. Defaults to `true`.
610
+ */
611
+ persist: boolean;
612
+ }
613
+ interface PropTypeConfig_File extends ISimplePropType<'file', File> {
614
+ }
615
+ type DeepPartialCompound<Props extends UnknownValidCompoundProps> = {
616
+ [K in keyof Props]?: DeepPartial<Props[K]>;
617
+ };
618
+ type DeepPartial<Conf extends PropTypeConfig> = Conf extends PropTypeConfig_AllSimples ? Conf['valueType'] : Conf extends PropTypeConfig_Compound<infer T> ? DeepPartialCompound<T> : never;
619
+ interface PropTypeConfig_Compound<Props extends UnknownValidCompoundProps> extends IBasePropType<'compound', {
620
+ [K in keyof Props]: Props[K]['valueType'];
621
+ }, DeepPartialCompound<Props>> {
622
+ props: Record<keyof Props, PropTypeConfig>;
623
+ }
624
+ interface PropTypeConfig_Enum extends IBasePropType<'enum', {}> {
625
+ cases: Record<string, PropTypeConfig>;
626
+ defaultCase: string;
627
+ }
628
+ type PropTypeConfig_AllSimples = PropTypeConfig_Number | PropTypeConfig_Boolean | PropTypeConfig_String | PropTypeConfig_StringLiteral<$IntentionalAny> | PropTypeConfig_Rgba | PropTypeConfig_Image | PropTypeConfig_File;
629
+ type PropTypeConfig = PropTypeConfig_AllSimples | PropTypeConfig_Compound<$IntentionalAny> | PropTypeConfig_Enum;
630
+
631
+ type index_d_UnknownShorthandCompoundProps = UnknownShorthandCompoundProps;
632
+ declare const index_d_compound: typeof compound;
633
+ declare const index_d_file: typeof file;
634
+ declare const index_d_image: typeof image;
635
+ declare const index_d_number: typeof number;
636
+ declare const index_d_rgba: typeof rgba;
637
+ declare const index_d_boolean: typeof boolean;
638
+ declare const index_d_string: typeof string;
639
+ declare const index_d_stringLiteral: typeof stringLiteral;
640
+ type index_d_Interpolator<_0> = Interpolator<_0>;
641
+ type index_d_IBasePropType<_0, _1, _2> = IBasePropType<_0, _1, _2>;
642
+ type index_d_PropTypeConfig_Number = PropTypeConfig_Number;
643
+ type index_d_NumberNudgeFn = NumberNudgeFn;
644
+ type index_d_PropTypeConfig_Boolean = PropTypeConfig_Boolean;
645
+ type index_d_PropTypeConfig_String = PropTypeConfig_String;
646
+ type index_d_PropTypeConfig_StringLiteral<_0> = PropTypeConfig_StringLiteral<_0>;
647
+ type index_d_PropTypeConfig_Rgba = PropTypeConfig_Rgba;
648
+ type index_d_PropTypeConfig_Image = PropTypeConfig_Image;
649
+ type index_d_PropTypeConfig_File = PropTypeConfig_File;
650
+ type index_d_PropTypeConfig_Compound<_0> = PropTypeConfig_Compound<_0>;
651
+ type index_d_PropTypeConfig_Enum = PropTypeConfig_Enum;
652
+ type index_d_PropTypeConfig_AllSimples = PropTypeConfig_AllSimples;
653
+ type index_d_PropTypeConfig = PropTypeConfig;
654
+ declare namespace index_d {
655
+ export {
656
+ index_d_UnknownShorthandCompoundProps as UnknownShorthandCompoundProps,
657
+ index_d_compound as compound,
658
+ index_d_file as file,
659
+ index_d_image as image,
660
+ index_d_number as number,
661
+ index_d_rgba as rgba,
662
+ index_d_boolean as boolean,
663
+ index_d_string as string,
664
+ index_d_stringLiteral as stringLiteral,
665
+ index_d_Interpolator as Interpolator,
666
+ index_d_IBasePropType as IBasePropType,
667
+ index_d_PropTypeConfig_Number as PropTypeConfig_Number,
668
+ index_d_NumberNudgeFn as NumberNudgeFn,
669
+ index_d_PropTypeConfig_Boolean as PropTypeConfig_Boolean,
670
+ index_d_PropTypeConfig_String as PropTypeConfig_String,
671
+ index_d_PropTypeConfig_StringLiteral as PropTypeConfig_StringLiteral,
672
+ index_d_PropTypeConfig_Rgba as PropTypeConfig_Rgba,
673
+ index_d_PropTypeConfig_Image as PropTypeConfig_Image,
674
+ index_d_PropTypeConfig_File as PropTypeConfig_File,
675
+ index_d_PropTypeConfig_Compound as PropTypeConfig_Compound,
676
+ index_d_PropTypeConfig_Enum as PropTypeConfig_Enum,
677
+ index_d_PropTypeConfig_AllSimples as PropTypeConfig_AllSimples,
678
+ index_d_PropTypeConfig as PropTypeConfig,
679
+ };
680
+ }
681
+
682
+ type TransientPropPath = string | readonly (string | number)[];
683
+ /** Same path format as {@link TransientPropPath}. */
684
+ type StaticPropPath = TransientPropPath;
685
+
686
+ interface IRafDriver {
687
+ /**
688
+ * All raf derivers have have `driver.type === 'Theatre_RafDriver_PublicAPI'`
689
+ */
690
+ readonly type: 'Theatre_RafDriver_PublicAPI';
691
+ /**
692
+ * The name of the driver. This is used for debugging purposes.
693
+ */
694
+ name: string;
695
+ /**
696
+ * The id of the driver. This is used for debugging purposes.
697
+ * It's guaranteed to be unique.
698
+ */
699
+ id: number;
700
+ /**
701
+ * This is called by the driver when it's time to tick forward.
702
+ * The time param is of the same type returned by `performance.now()`.
703
+ */
704
+ tick: (time: number) => void;
705
+ }
706
+ /**
707
+ * Creates a custom raf driver.
708
+ * `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)).
709
+ * 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:
710
+ *
711
+ * 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.
712
+ * 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).
713
+ * 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.
714
+ *
715
+ * Here is how you'd create a custom `rafDriver`:
716
+ *
717
+ * ```js
718
+ * import { createRafDriver } from '@unseenco/theatre-core'
719
+ *
720
+ * const rafDriver = createRafDriver({ name: 'a custom 5fps raf driver' })
721
+ *
722
+ * setInterval(() => {
723
+ * rafDriver.tick(performance.now())
724
+ * }, 200)
725
+ * ```
726
+ *
727
+ * Now, any time you set up an `onChange()` listener, pass your custom `rafDriver`:
728
+ *
729
+ * ```js
730
+ * import { onChange } from '@unseenco/theatre-core'
731
+ *
732
+ * onChange(
733
+ * // let's say object is a Theatre object, the one returned from calling `sheet.object()`
734
+ * object.props,
735
+ * // this callback will now only be called at 5fps (and won't be called if there are no new values)
736
+ * // even if `sequence.play()` updates `object.props` at 60fps, this listener is called a maximum of 5fps
737
+ * (propValues) => {
738
+ * console.log(propValues)
739
+ * },
740
+ * rafDriver,
741
+ * )
742
+ *
743
+ * // this will update the values of `object.props` at 60fps, but the listener above will still get called a maximum of 5fps
744
+ * sheet.sequence.play()
745
+ *
746
+ * // we can also customize at what resolution the sequence's playhead moves forward
747
+ * sheet.sequence.play({ rafDriver }) // the playhead will move forward at 5fps
748
+ * ```
749
+ *
750
+ * 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.
751
+ *
752
+ * ```js
753
+ * studio.initialize({
754
+ * __experimental_rafDriver: rafDriver,
755
+ * })
756
+ * ```
757
+ *
758
+ * `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:
759
+ *
760
+ * ```js
761
+ * import { createRafDriver } from '@unseenco/theatre-core'
762
+ * import type { IRafDriver } from '@theare/core'
763
+ *
764
+ * function createBasicRafDriver(): IRafDriver {
765
+ * let rafId: number | null = null
766
+ * const start = (): void => {
767
+ * if (typeof window !== 'undefined') {
768
+ * const onAnimationFrame = (t: number) => {
769
+ * driver.tick(t)
770
+ * rafId = window.requestAnimationFrame(onAnimationFrame)
771
+ * }
772
+ * rafId = window.requestAnimationFrame(onAnimationFrame)
773
+ * } else {
774
+ * driver.tick(0)
775
+ * setTimeout(() => driver.tick(1), 0)
776
+ * }
777
+ * }
778
+ *
779
+ * const stop = (): void => {
780
+ * if (typeof window !== 'undefined') {
781
+ * if (rafId !== null) {
782
+ * window.cancelAnimationFrame(rafId)
783
+ * }
784
+ * } else {
785
+ * // nothing to do in SSR
786
+ * }
787
+ * }
788
+ *
789
+ * const driver = createRafDriver({ name: 'DefaultCoreRafDriver', start, stop })
790
+ *
791
+ * return driver
792
+ * }
793
+ * ```
794
+ */
795
+ declare function createRafDriver(conf?: {
796
+ name?: string;
797
+ start?: () => void;
798
+ stop?: () => void;
799
+ }): IRafDriver;
800
+
801
+ type SheetObjectValuesChangeMeta = {
802
+ /**
803
+ * The sequence variant whose values are being applied.
804
+ * This reflects the sheet's active sequence variant.
805
+ */
806
+ variant: SequenceVariantId;
807
+ };
808
+ interface ISheetObject<Props extends UnknownShorthandCompoundProps = UnknownShorthandCompoundProps> {
809
+ /**
810
+ * All Objects will have `object.type === 'Theatre_SheetObject_PublicAPI'`
811
+ */
812
+ readonly type: 'Theatre_SheetObject_PublicAPI';
813
+ /**
814
+ * The current values of the props.
815
+ *
816
+ * @example
817
+ * Usage:
818
+ * ```ts
819
+ * const obj = sheet.object("obj", {x: 0})
820
+ * console.log(obj.value.x) // prints 0 or the current numeric value
821
+ * ```
822
+ *
823
+ * Future: Notice that if the user actually changes the Props config for one of the
824
+ * properties, then this type can't be guaranteed accurrate.
825
+ * * Right now the user can't change prop configs, but we'll probably enable that
826
+ * functionality later via (`object.overrideConfig()`). We need to educate the
827
+ * user that they can't rely on static types to know the type of object.value.
828
+ */
829
+ readonly value: PropsValue<Props>;
830
+ /**
831
+ * A Pointer to the props of the object.
832
+ *
833
+ * More documentation soon.
834
+ */
835
+ readonly props: Pointer<this['value']>;
836
+ /**
837
+ * The instance of Sheet the Object belongs to
838
+ */
839
+ readonly sheet: ISheet;
840
+ /**
841
+ * The Project the project belongs to
842
+ */
843
+ readonly project: IProject;
844
+ /**
845
+ * An object representing the address of the Object
846
+ */
847
+ readonly address: SheetObjectAddress;
848
+ /**
849
+ * Calls `fn` every time the value of the props change.
850
+ *
851
+ * @param fn - The callback is called every time the value of the props change, plus once at the beginning.
852
+ * @param rafDriver - (optional) The `rafDriver` to use. Learn how to use `rafDriver`s [from the docs](https://www.theatrejs.com/docs/latest/manual/advanced#rafdrivers).
853
+ * @returns an Unsubscribe function
854
+ *
855
+ * @example
856
+ * Usage:
857
+ * ```ts
858
+ * const obj = sheet.object("Box", {position: {x: 0, y: 0}})
859
+ * const div = document.getElementById("box")
860
+ *
861
+ * const unsubscribe = obj.onValuesChange((newValues, {variant}) => {
862
+ * div.style.left = newValues.position.x + 'px'
863
+ * div.style.top = newValues.position.y + 'px'
864
+ * console.log('Active variant:', variant)
865
+ * })
866
+ *
867
+ * // you can call unsubscribe() to stop listening to changes
868
+ * ```
869
+ */
870
+ onValuesChange(fn: (values: this['value'], meta: SheetObjectValuesChangeMeta) => void, rafDriver?: IRafDriver): VoidFn;
871
+ /**
872
+ * Sets the initial value of the object. This value overrides the default
873
+ * values defined in the prop types, but would itself be overridden if the user
874
+ * overrides it in the UI with a static or animated value.
875
+ *
876
+ * @example
877
+ * Usage:
878
+ * ```ts
879
+ * const obj = sheet.object("obj", {position: {x: 0, y: 0}})
880
+ *
881
+ * obj.value // {position: {x: 0, y: 0}}
882
+ *
883
+ * // here, we only override position.x
884
+ * obj.initialValue = {position: {x: 2}}
885
+ *
886
+ * obj.value // {position: {x: 2, y: 0}}
887
+ * ```
888
+ */
889
+ set initialValue(value: DeepPartialOfSerializableValue<this['value']>);
890
+ /**
891
+ * Show another object's props in this object's Studio details pane.
892
+ * Runtime-only (not persisted). Edits and sequencing still target the
893
+ * source objects. Pass `[]` to clear.
894
+ *
895
+ * @example
896
+ * ```ts
897
+ * const appearance = sheet.object('Appearance', {color: types.rgba()})
898
+ * const box = sheet.object('Box', {x: 0})
899
+ * box.showPropsOf([appearance])
900
+ * ```
901
+ */
902
+ showPropsOf(objects: ISheetObject<any>[]): void;
903
+ /**
904
+ * Returns the objects currently linked via {@link showPropsOf}.
905
+ */
906
+ getShowPropsOf(): ISheetObject<any>[];
907
+ /**
908
+ * Replace this object's prop config. Props removed from `config` disappear
909
+ * from the Studio details pane; historic statics/tracks for those paths are
910
+ * stripped. Same effect as `sheet.object(key, config, {reconfigure: true})`.
911
+ */
912
+ reconfigure(config: UnknownShorthandCompoundProps, opts?: {
913
+ transient?: readonly TransientPropPath[];
914
+ static?: readonly StaticPropPath[];
915
+ }): void;
916
+ }
917
+
918
+ type KeyframeId = Nominal<'KeyframeId'>;
919
+ type ProjectId = Nominal<'ProjectId'>;
920
+ type SheetId = Nominal<'SheetId'>;
921
+ type SheetInstanceId = Nominal<'SheetInstanceId'>;
922
+ type SequenceTrackId = Nominal<'SequenceTrackId'>;
923
+ type ObjectAddressKey = Nominal<'ObjectAddressKey'>;
924
+
925
+ /**
926
+ * This is the state of each project that is consumable by `@unseenco/theatre-core`.
927
+ * If the studio is present, this part of the state joins the studio's historic state,
928
+ * at {@link StudioHistoricState.coreByProject}
929
+ */
930
+ interface ProjectState_Historic {
931
+ sheetsById: StrictRecord<SheetId, SheetState_Historic>;
932
+ /**
933
+ * The last 50 revision IDs this state is based on, starting with the most recent one.
934
+ * The most recent one is the revision ID of this state
935
+ */
936
+ revisionHistory: string[];
937
+ definitionVersion: string;
938
+ }
939
+ interface OnDiskState extends ProjectState_Historic {
940
+ }
941
+
942
+ type IPlaybackRange = [from: number, to: number];
943
+ type IPlaybackDirection = 'normal' | 'reverse' | 'alternate' | 'alternateReverse';
944
+
945
+ interface IAttachAudioArgs {
946
+ /**
947
+ * Either a URL to the audio file (eg "http://localhost:3000/audio.mp3") or an instance of AudioBuffer
948
+ */
949
+ source: string | AudioBuffer;
950
+ /**
951
+ * An optional AudioContext. If not provided, one will be created.
952
+ */
953
+ audioContext?: AudioContext;
954
+ /**
955
+ * An AudioNode to feed the audio into. Will use audioContext.destination if not provided.
956
+ */
957
+ destinationNode?: AudioNode;
958
+ }
959
+ interface ISequence {
960
+ readonly type: 'Theatre_Sequence_PublicAPI';
961
+ /**
962
+ * Starts playback of a sequence.
963
+ * Returns a promise that either resolves to true when the playback completes,
964
+ * or resolves to false if playback gets interrupted (for example by calling sequence.pause())
965
+ *
966
+ * @returns A promise that resolves when the playback is finished, or rejects if interruped
967
+ *
968
+ * @example
969
+ * Usage:
970
+ * ```ts
971
+ * // plays the sequence from the current position to sequence.length
972
+ * sheet.sequence.play()
973
+ *
974
+ * // plays the sequence at 2.4x speed
975
+ * sheet.sequence.play({rate: 2.4})
976
+ *
977
+ * // plays the sequence from second 1 to 4
978
+ * sheet.sequence.play({range: [1, 4]})
979
+ *
980
+ * // plays the sequence 4 times
981
+ * sheet.sequence.play({iterationCount: 4})
982
+ *
983
+ * // plays the sequence in reverse
984
+ * sheet.sequence.play({direction: 'reverse'})
985
+ *
986
+ * // plays the sequence back and forth forever (until interrupted)
987
+ * sheet.sequence.play({iterationCount: Infinity, direction: 'alternateReverse})
988
+ *
989
+ * // plays the sequence and logs "done" once playback is finished
990
+ * sheet.sequence.play().then(() => console.log('done'))
991
+ * ```
992
+ */
993
+ play(conf?: {
994
+ /**
995
+ * The number of times the animation must run. Must be an integer larger
996
+ * than 0. Defaults to 1. Pick Infinity to run forever
997
+ */
998
+ iterationCount?: number;
999
+ /**
1000
+ * Limits the range to be played. Default is [0, sequence.length]
1001
+ */
1002
+ range?: IPlaybackRange;
1003
+ /**
1004
+ * The playback rate. Defaults to 1. Choosing 2 would play the animation
1005
+ * at twice the speed.
1006
+ */
1007
+ rate?: number;
1008
+ /**
1009
+ * The direction of the playback. Similar to CSS's animation-direction
1010
+ */
1011
+ direction?: IPlaybackDirection;
1012
+ /**
1013
+ * Optionally provide a rafDriver to use for the playback. It'll default to
1014
+ * the core driver if not provided, which is a `requestAnimationFrame()` driver.
1015
+ * Learn how to use `rafDriver`s [from the docs](https://www.theatrejs.com/docs/latest/manual/advanced#rafdrivers).
1016
+ */
1017
+ rafDriver?: IRafDriver;
1018
+ }): Promise<boolean>;
1019
+ /**
1020
+ * Pauses the currently playing animation
1021
+ */
1022
+ pause(): void;
1023
+ /**
1024
+ * The current position of the playhead.
1025
+ * In a time-based sequence, this represents the current time in seconds.
1026
+ */
1027
+ position: number;
1028
+ /**
1029
+ * A Pointer to the sequence's inner state.
1030
+ *
1031
+ * @remarks
1032
+ * As with any Pointer, you can use this with {@link onChange | onChange()} to listen to its value changes
1033
+ * or with {@link val | val()} to read its current value.
1034
+ *
1035
+ * @example Usage
1036
+ * ```ts
1037
+ * import {onChange, val} from '@unseenco/theatre-core'
1038
+ *
1039
+ * // let's assume `sheet` is a sheet
1040
+ * const sequence = sheet.sequence
1041
+ *
1042
+ * onChange(sequence.pointer.length, (len) => {
1043
+ * console.log("Length of the sequence changed to:", len)
1044
+ * })
1045
+ *
1046
+ * onChange(sequence.pointer.position, (position) => {
1047
+ * console.log("Position of the sequence changed to:", position)
1048
+ * })
1049
+ *
1050
+ * onChange(sequence.pointer.playing, (playing) => {
1051
+ * console.log(playing ? 'playing' : 'paused')
1052
+ * })
1053
+ *
1054
+ * // we can also read the current value of the pointer
1055
+ * console.log('current length is', val(sequence.pointer.length))
1056
+ * ```
1057
+ */
1058
+ pointer: Pointer<{
1059
+ playing: boolean;
1060
+ length: number;
1061
+ position: number;
1062
+ }>;
1063
+ /**
1064
+ * Given a property, returns a list of keyframes that affect that property.
1065
+ *
1066
+ * @example
1067
+ * Usage:
1068
+ * ```ts
1069
+ * // let's assume `sheet` is a sheet and obj is one of its objects
1070
+ * const keyframes = sheet.sequence.__experimental_getKeyframes(obj.pointer.x)
1071
+ * console.log(keyframes) // an array of keyframes
1072
+ * ```
1073
+ */
1074
+ __experimental_getKeyframes(prop: Pointer<{}>): Keyframe[];
1075
+ /**
1076
+ * Attaches an audio source to the sequence. Playing the sequence automatically
1077
+ * plays the audio source and their times are kept in sync.
1078
+ *
1079
+ * @returns A promise that resolves once the audio source is loaded and decoded
1080
+ *
1081
+ * Learn more [here](https://www.theatrejs.com/docs/latest/manual/audio).
1082
+ *
1083
+ * @example
1084
+ * Usage:
1085
+ * ```ts
1086
+ * // Loads and decodes audio from the URL and then attaches it to the sequence
1087
+ * await sheet.sequence.attachAudio({source: "http://localhost:3000/audio.mp3"})
1088
+ * sheet.sequence.play()
1089
+ *
1090
+ * // Providing your own AudioAPI Context, destination, etc
1091
+ * const audioContext: AudioContext = {...} // create an AudioContext using the Audio API
1092
+ * const audioBuffer: AudioBuffer = {...} // create an AudioBuffer
1093
+ * const destinationNode = audioContext.destination
1094
+ *
1095
+ * await sheet.sequence.attachAudio({source: audioBuffer, audioContext, destinationNode})
1096
+ * ```
1097
+ *
1098
+ * Note: It's better to provide the `audioContext` rather than allow Theatre.js to create it.
1099
+ * That's because some browsers [suspend the audioContext](https://developer.chrome.com/blog/autoplay/#webaudio)
1100
+ * unless it's initiated by a user gesture, like a click. If that happens, Theatre.js will
1101
+ * wait for a user gesture to resume the audioContext. But that's probably not an
1102
+ * optimal user experience. It is better to provide a button or some other UI element
1103
+ * to communicate to the user that they have to initiate the animation.
1104
+ *
1105
+ * @example
1106
+ * Example:
1107
+ * ```ts
1108
+ * // html: <button id="#start">start</button>
1109
+ * const button = document.getElementById('start')
1110
+ *
1111
+ * button.addEventListener('click', async () => {
1112
+ * const audioContext = ...
1113
+ * await sheet.sequence.attachAudio({audioContext, source: '...'})
1114
+ * sheet.sequence.play()
1115
+ * })
1116
+ * ```
1117
+ */
1118
+ attachAudio(args: IAttachAudioArgs): Promise<{
1119
+ /**
1120
+ * An {@link https://developer.mozilla.org/en-US/docs/Web/API/AudioBuffer | AudioBuffer}.
1121
+ * If `args.source` is a URL, then `decodedBuffer` would be the result
1122
+ * of {@link https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/decodeAudioData | audioContext.decodeAudioData()}
1123
+ * on the audio file at that URL.
1124
+ *
1125
+ * If `args.source` is an `AudioBuffer`, then `decodedBuffer` would be equal to `args.source`
1126
+ */
1127
+ decodedBuffer: AudioBuffer;
1128
+ /**
1129
+ * The `AudioContext`. It is either equal to `source.audioContext` if it is provided, or
1130
+ * one that's created on the fly.
1131
+ */
1132
+ audioContext: AudioContext;
1133
+ /**
1134
+ * Equals to either `args.destinationNode`, or if none is provided, it equals `audioContext.destinationNode`.
1135
+ *
1136
+ * See `gainNode` for more info.
1137
+ */
1138
+ destinationNode: AudioNode;
1139
+ /**
1140
+ * This is an intermediate GainNode that Theatre.js feeds its audio to. It is by default
1141
+ * connected to destinationNode, but you can disconnect the gainNode and feed it to your own graph.
1142
+ *
1143
+ * @example
1144
+ * For example:
1145
+ * ```ts
1146
+ * const {gainNode, audioContext} = await sequence.attachAudio({source: '/audio.mp3'})
1147
+ * // disconnect the gainNode (at this point, the sequence's audio track won't be audible)
1148
+ * gainNode.disconnect()
1149
+ * // create our own gain node
1150
+ * const lowerGain = audioContext.createGain()
1151
+ * // lower its volume to 10%
1152
+ * lowerGain.gain.setValueAtTime(0.1, audioContext.currentTime)
1153
+ * // feed the sequence's audio to our lowered gainNode
1154
+ * gainNode.connect(lowerGain)
1155
+ * // feed the lowered gainNode to the audioContext's destination
1156
+ * lowerGain.connect(audioContext.destination)
1157
+ * // now audio will be audible, with 10% the volume
1158
+ * ```
1159
+ */
1160
+ gainNode: GainNode;
1161
+ }>;
1162
+ }
1163
+
1164
+ type SheetObjectAction = (object: ISheetObject) => void;
1165
+ type SheetObjectActionsConfig = Record<string, SheetObjectAction>;
1166
+ type ISheetObjectOptions = {
1167
+ reconfigure?: boolean;
1168
+ /**
1169
+ * Whether the object appears in the Studio outline panel. Defaults to `true`.
1170
+ */
1171
+ visible?: boolean;
1172
+ /**
1173
+ * Other sheet objects whose props are shown in this object's Studio details
1174
+ * pane. Runtime-only (not persisted). Edits and sequencing still target the
1175
+ * source objects. Same-sheet only; cannot include the host object.
1176
+ *
1177
+ * @example
1178
+ * ```ts
1179
+ * const appearance = sheet.object('Appearance', {color: types.rgba()})
1180
+ * sheet.object('Box', {x: 0}, {showPropsOf: [appearance]})
1181
+ * ```
1182
+ */
1183
+ showPropsOf?: ISheetObject<any>[];
1184
+ /**
1185
+ * Prop paths that are excluded from exported project state JSON.
1186
+ * Values are stored in ahistoric static overrides (persist across Studio
1187
+ * reloads) but never written to historic state or sequence tracks.
1188
+ *
1189
+ * Paths accept dot notation (`'foo.bar'`) or arrays (`['foo', 'bar']`).
1190
+ * A prefix path like `'foo'` marks the entire subtree as transient.
1191
+ */
1192
+ transient?: readonly TransientPropPath[];
1193
+ /**
1194
+ * Prop paths that cannot be sequenced but are saved to exported project state.
1195
+ *
1196
+ * Paths accept dot notation (`'foo.bar'`) or arrays (`['foo', 'bar']`).
1197
+ * A prefix path like `'foo'` marks the entire subtree as static.
1198
+ */
1199
+ static?: readonly StaticPropPath[];
1200
+ __actions__THIS_API_IS_UNSTABLE_AND_WILL_CHANGE_IN_THE_NEXT_VERSION?: SheetObjectActionsConfig;
1201
+ };
1202
+ interface ISheet {
1203
+ /**
1204
+ * All sheets have `sheet.type === 'Theatre_Sheet_PublicAPI'`
1205
+ */
1206
+ readonly type: 'Theatre_Sheet_PublicAPI';
1207
+ /**
1208
+ * The Project this Sheet belongs to
1209
+ */
1210
+ readonly project: IProject;
1211
+ /**
1212
+ * The address of the Sheet
1213
+ */
1214
+ readonly address: SheetAddress;
1215
+ /**
1216
+ * Creates a child object for the sheet
1217
+ *
1218
+ * **Docs: https://www.theatrejs.com/docs/latest/manual/objects**
1219
+ *
1220
+ * @param key - Each object is identified by a key, which is a non-empty string
1221
+ * @param props - The props of the object. See examples
1222
+ * @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.
1223
+ *
1224
+ * @returns An Object
1225
+ *
1226
+ * @example
1227
+ * Usage:
1228
+ * ```ts
1229
+ * // Create an object named "a unique key" with no props
1230
+ * const obj = sheet.object("a unique key", {})
1231
+ * obj.address.objectKey // "a unique key"
1232
+ *
1233
+ *
1234
+ * // Create an object with {x: 0}
1235
+ * const obj = sheet.object("obj", {x: 0})
1236
+ * obj.value.x // returns 0 or the current number that the user has set
1237
+ *
1238
+ * // Create an object with nested props
1239
+ * const obj = sheet.object("obj", {position: {x: 0, y: 0}})
1240
+ * obj.value.position // {x: 0, y: 0}
1241
+ *
1242
+ * // you can also reconfigure an existing object:
1243
+ * const obj = sheet.object("obj", {foo: 0})
1244
+ * console.log(object.value.foo) // prints 0
1245
+ *
1246
+ * const obj2 = sheet.object("obj", {bar: 0}, {reconfigure: true})
1247
+ * console.log(object.value.foo) // prints undefined, since we've removed this prop via reconfiguring the object
1248
+ * console.log(object.value.bar) // prints 0, since we've introduced this prop by reconfiguring the object
1249
+ *
1250
+ * assert(obj === obj2) // passes, because reconfiguring the object returns the same object
1251
+ *
1252
+ * // you can add custom actions to an object:
1253
+ * const obj = sheet.object("obj", {foo: 0}, {
1254
+ * actions: {
1255
+ * // This will display a button in the UI that will reset the value of `foo` to 0
1256
+ * Reset: () => {
1257
+ * studio.transaction((api) => {
1258
+ * api.set(obj.props.foo, 0)
1259
+ * })
1260
+ * }
1261
+ * }
1262
+ * })
1263
+ *
1264
+ * // you can mark props as transient (excluded from exported state JSON):
1265
+ * const obj = sheet.object("Camera", {
1266
+ * fov: 50,
1267
+ * orbitEnabled: false,
1268
+ * }, {
1269
+ * transient: ['orbitEnabled']
1270
+ * })
1271
+ *
1272
+ * // static props are saved to state but cannot be sequenced:
1273
+ * const obj = sheet.object("Camera", { fov: 50, zoom: 1 }, {
1274
+ * static: ['zoom']
1275
+ * })
1276
+ * ```
1277
+ */
1278
+ object<Props extends UnknownShorthandCompoundProps>(key: string, props: Props, options?: ISheetObjectOptions): ISheetObject<Props>;
1279
+ /**
1280
+ * Detaches a previously created child object from the sheet.
1281
+ *
1282
+ * If you call `sheet.object(key)` again with the same `key`, the object's values of the object's
1283
+ * props WILL NOT be reset to their initial values.
1284
+ *
1285
+ * @param key - The `key` of the object previously given to `sheet.object(key, ...)`.
1286
+ */
1287
+ detachObject(key: string): void;
1288
+ /**
1289
+ * Returns all currently attached objects on this sheet.
1290
+ *
1291
+ * Use with {@link ISheet.detachObject} to detach individual objects.
1292
+ */
1293
+ getObjects(): ISheetObject[];
1294
+ /**
1295
+ * Unloads this sheet instance from memory: detaches all objects, pauses
1296
+ * sequences, and removes the sheet from the project.
1297
+ *
1298
+ * Runtime-only: persisted prop overrides and sequence data are kept. Calling
1299
+ * `project.sheet` again with the same id recreates the sheet.
1300
+ */
1301
+ unload(): void;
1302
+ /**
1303
+ * Declares an outline namespace folder in the Studio. The folder appears in
1304
+ * the outline panel even before any sheet objects are added under it.
1305
+ *
1306
+ * You can also use this to set the default collapsed state for a namespace
1307
+ * folder. The default only applies when the user has not manually expanded
1308
+ * or collapsed the folder yet.
1309
+ *
1310
+ * This method is part of `@unseenco/theatre-core` so you can configure outline folders
1311
+ * without importing `@unseenco/theatre-studio`.
1312
+ *
1313
+ * @param namespacePath - The namespace path, e.g. `"My Folder"` or `"My Folder / Subfolder"`
1314
+ * @param opts - Optional configuration for the namespace folder
1315
+ *
1316
+ * @example
1317
+ * ```ts
1318
+ * const sheet = project.sheet('Scene')
1319
+ *
1320
+ * // Create an empty folder ahead of time, collapsed by default
1321
+ * sheet.declareOutlineNamespace('Props', {collapsed: true})
1322
+ *
1323
+ * // Later, add objects under that folder
1324
+ * sheet.object('Props / Chair', {x: 0})
1325
+ * sheet.object('Props / Table', {x: 0})
1326
+ * ```
1327
+ */
1328
+ declareOutlineNamespace(namespacePath: string, opts?: {
1329
+ collapsed?: boolean;
1330
+ }): void;
1331
+ /**
1332
+ * Sets whether a namespace folder in the Studio outline panel is collapsed.
1333
+ *
1334
+ * Call this on load to force a folder closed every time your app starts.
1335
+ * Unlike `declareOutlineNamespace()`, this overrides any previous user
1336
+ * preference for the current session's initial render.
1337
+ *
1338
+ * @param namespacePath - The namespace path, e.g. `"My Folder"` or `"My Folder / Subfolder"`
1339
+ * @param collapsed - Whether the folder should be collapsed
1340
+ *
1341
+ * @example
1342
+ * ```ts
1343
+ * const sheet = project.sheet('Scene')
1344
+ *
1345
+ * // Force a folder closed every time your app loads
1346
+ * sheet.setOutlineNamespaceCollapsed('Props', true)
1347
+ * ```
1348
+ */
1349
+ setOutlineNamespaceCollapsed(namespacePath: string, collapsed: boolean): void;
1350
+ /**
1351
+ * The Sequence of this Sheet (uses the currently active sequence variant)
1352
+ */
1353
+ readonly sequence: ISequence;
1354
+ /**
1355
+ * Declares the sequence variants available on this sheet. Each variant has its own
1356
+ * independent sequence data, allowing the same properties to be animated differently
1357
+ * per variant (e.g. mobile vs desktop).
1358
+ *
1359
+ * The `"default"` variant is always required and is included automatically if omitted.
1360
+ *
1361
+ * @param variants - An array of variant names (each at least 3 characters long)
1362
+ *
1363
+ * @example
1364
+ * ```ts
1365
+ * const sheet = project.sheet('Scene')
1366
+ * sheet.declareSequenceVariants(['default', 'mobile', 'desktop'])
1367
+ * sheet.setActiveSequenceVariant('mobile')
1368
+ * ```
1369
+ */
1370
+ declareSequenceVariants(variants: SequenceVariantId[]): void;
1371
+ /**
1372
+ * Sets which sequence variant is currently active. The active variant determines
1373
+ * which sequence's keyframes are used when computing prop values, and which sequence
1374
+ * `sheet.sequence` refers to.
1375
+ */
1376
+ setActiveSequenceVariant(variant: SequenceVariantId): void;
1377
+ /**
1378
+ * Returns the currently active sequence variant name.
1379
+ */
1380
+ getActiveSequenceVariant(): SequenceVariantId;
1381
+ }
1382
+
1383
+ /**
1384
+ * A project's config object (currently the only point of configuration is the project's state)
1385
+ */
1386
+ type ISheetOptions = {
1387
+ /**
1388
+ * Whether the sheet appears in the Studio outline panel. Defaults to `true`.
1389
+ */
1390
+ visible?: boolean;
1391
+ };
1392
+ type IProjectConfig = {
1393
+ /**
1394
+ * The state of the project, as [exported](https://www.theatrejs.com/docs/latest/manual/projects#state) by the studio.
1395
+ */
1396
+ state?: $IntentionalAny;
1397
+ assets?: {
1398
+ baseUrl?: string;
1399
+ };
1400
+ };
1401
+ /**
1402
+ * A Theatre.js project
1403
+ */
1404
+ interface IProject {
1405
+ readonly type: 'Theatre_Project_PublicAPI';
1406
+ /**
1407
+ * If `@unseenco/theatre-studio` is used, this promise would resolve when studio has loaded
1408
+ * the state of the project into memory.
1409
+ *
1410
+ * If `@unseenco/theatre-studio` is not used, this promise is already resolved.
1411
+ */
1412
+ readonly ready: Promise<void>;
1413
+ /**
1414
+ * Shows whether the project is ready to be used.
1415
+ * Better to use {@link IProject.ready}, which is a promise that would
1416
+ * resolve when the project is ready.
1417
+ */
1418
+ readonly isReady: boolean;
1419
+ /**
1420
+ * The project's address
1421
+ */
1422
+ readonly address: ProjectAddress;
1423
+ /**
1424
+ * Creates a Sheet under the project
1425
+ * @param sheetId - Sheets are identified by their `sheetId`, which must be a string longer than 3 characters
1426
+ * @param instanceIdOrOpts - Optionally provide an `instanceId` if you want to create multiple instances of the same Sheet, or pass `{ visible: false }` to hide the sheet from the Studio outline panel
1427
+ * @param opts - Optionally provide `{ visible: false }` to hide the sheet from the Studio outline panel
1428
+ * @returns The newly created Sheet
1429
+ *
1430
+ * **Docs: https://www.theatrejs.com/docs/latest/manual/sheets**
1431
+ */
1432
+ sheet(sheetId: string, instanceIdOrOpts?: string | ISheetOptions): ISheet;
1433
+ sheet(sheetId: string, instanceId: string, opts?: ISheetOptions): ISheet;
1434
+ /**
1435
+ * Returns all currently loaded sheet instances under this project.
1436
+ *
1437
+ * Use with {@link ISheet.detachObject} or {@link ISheet.unload} to tear down
1438
+ * individual sheets/objects at runtime. Persisted project state is not cleared.
1439
+ */
1440
+ getSheets(): ISheet[];
1441
+ /**
1442
+ * Unloads one sheet and its attached objects from memory.
1443
+ *
1444
+ * Runtime-only: persisted prop overrides and sequence data are kept. Calling
1445
+ * {@link IProject.sheet} again with the same `sheetId` recreates the sheet.
1446
+ *
1447
+ * @param sheetId - The sheet id previously given to {@link IProject.sheet}
1448
+ * @param instanceId - If provided, only that instance is unloaded. If omitted,
1449
+ * all loaded instances of `sheetId` are unloaded.
1450
+ */
1451
+ unloadSheet(sheetId: string, instanceId?: string): void;
1452
+ /**
1453
+ * Unloads every currently loaded sheet and its objects from memory.
1454
+ *
1455
+ * Runtime-only: persisted project state is kept.
1456
+ */
1457
+ unloadSheets(): void;
1458
+ /**
1459
+ * Returns the URL for an asset.
1460
+ *
1461
+ * @param asset - The asset to get the URL for
1462
+ * @returns The URL for the asset, or `undefined` if the asset is not found
1463
+ */
1464
+ getAssetUrl(asset: Asset | File): string | undefined;
1465
+ }
1466
+
1467
+ type Notify = (
1468
+ /**
1469
+ * The title of the notification.
1470
+ */
1471
+ title: string,
1472
+ /**
1473
+ * The message of the notification.
1474
+ */
1475
+ message: string,
1476
+ /**
1477
+ * An array of doc pages to link to.
1478
+ */
1479
+ docs?: {
1480
+ url: string;
1481
+ title: string;
1482
+ }[],
1483
+ /**
1484
+ * Whether duplicate notifications should be allowed.
1485
+ */
1486
+ allowDuplicates?: boolean) => void;
1487
+ type Notifiers = {
1488
+ /**
1489
+ * Show a success notification.
1490
+ */
1491
+ success: Notify;
1492
+ /**
1493
+ * Show a warning notification.
1494
+ *
1495
+ * Say what happened in the title.
1496
+ * In the message, start with 1) a reassurance, then 2) explain why it happened, and 3) what the user can do about it.
1497
+ */
1498
+ warning: Notify;
1499
+ /**
1500
+ * Show an info notification.
1501
+ */
1502
+ info: Notify;
1503
+ /**
1504
+ * Show an error notification.
1505
+ */
1506
+ error: Notify;
1507
+ };
1508
+ declare const notify: Notifiers;
1509
+
1510
+ /**
1511
+ * Sets the `rafDriver` that Theatre's core uses internally to tick forward.
1512
+ *
1513
+ * Call this **before** any other `@unseenco/theatre-core` API that would trigger tick creation
1514
+ * (e.g. `onChange`, `sequence.play`, `val`). Calling it after the core ticker has
1515
+ * already been initialised will throw.
1516
+ *
1517
+ * This is the recommended way to drive Theatre from your own
1518
+ * `requestAnimationFrame` loop — for example when integrating with
1519
+ * `gsap`, `lenis`, or an XR session:
1520
+ *
1521
+ * ```ts
1522
+ * import { createRafDriver, setCoreRafDriver } from '@unseenco/theatre-core'
1523
+ *
1524
+ * const driver = createRafDriver({ name: 'MyRafDriver' })
1525
+ * setCoreRafDriver(driver)
1526
+ *
1527
+ * function myLoop(time: number) {
1528
+ * driver.tick(time)
1529
+ * requestAnimationFrame(myLoop)
1530
+ * }
1531
+ * requestAnimationFrame(myLoop)
1532
+ * ```
1533
+ *
1534
+ * Because you hold the `driver` reference you created, you do not need a separate
1535
+ * `getCoreRafDriver()` call — just keep the reference around and call
1536
+ * `driver.tick(time)` from your loop.
1537
+ */
1538
+ declare function setCoreRafDriver(driver: IRafDriver): void;
1539
+
1540
+ /**
1541
+ * A window is considered the "remote editor" for a project when its URL
1542
+ * contains the `editor` hash, e.g. `https://myapp.com/#editor`. Every other
1543
+ * window is a listener that mirrors whatever the editor window broadcasts.
1544
+ *
1545
+ * This convention is shared with `@unseenco/theatre-studio`'s "Open remote editor
1546
+ * window" toolbar button, which opens a popup at the current URL with this
1547
+ * hash set.
1548
+ */
1549
+ declare function isRemoteEditorWindow(): boolean;
1550
+
1551
+ /**
1552
+ * Returns a project of the given id, or creates one if it doesn't already exist.
1553
+ *
1554
+ * @remarks
1555
+ * If \@unseenco/theatre-studio is also loaded, then the state of the project will be managed by the studio.
1556
+ *
1557
+ * [Learn more about exporting](https://www.theatrejs.com/docs/latest/manual/projects#state)
1558
+ *
1559
+ * @example
1560
+ * Usage:
1561
+ * ```ts
1562
+ * import {getProject} from '@unseenco/theatre-core'
1563
+ * const config = {} // the config can be empty when starting a new project
1564
+ * const project = getProject("a-unique-id", config)
1565
+ * ```
1566
+ *
1567
+ * @example
1568
+ * Usage with an explicit state:
1569
+ * ```ts
1570
+ * import {getProject} from '@unseenco/theatre-core'
1571
+ * import state from './saved-state.json'
1572
+ * const config = {state} // here the config contains our saved state
1573
+ * const project = getProject("a-unique-id", config)
1574
+ * ```
1575
+ */
1576
+ declare function getProject(id: string, config?: IProjectConfig): IProject;
1577
+ /**
1578
+ * Calls `callback` every time the pointed value of `pointer` changes.
1579
+ *
1580
+ * @param pointer - A Pointer (like `object.props.x`)
1581
+ * @param callback - The callback is called every time the value of pointer changes
1582
+ * @param rafDriver - (optional) The `rafDriver` to use. Learn how to use `rafDriver`s [from the docs](https://www.theatrejs.com/docs/latest/manual/advanced#rafdrivers).
1583
+ * @returns An unsubscribe function
1584
+ *
1585
+ * @example
1586
+ * Usage:
1587
+ * ```ts
1588
+ * import {getProject, onChange} from '@unseenco/theatre-core'
1589
+ *
1590
+ * const obj = getProject("A project").sheet("Scene").object("Box", {position: {x: 0}})
1591
+ *
1592
+ * const usubscribe = onChange(obj.props.position.x, (x) => {
1593
+ * console.log('position.x changed to:', x)
1594
+ * })
1595
+ *
1596
+ * setTimeout(usubscribe, 10000) // stop listening to changes after 10 seconds
1597
+ * ```
1598
+ */
1599
+ 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;
1600
+ /**
1601
+ * Takes a Pointer and returns the value it points to.
1602
+ *
1603
+ * @param pointer - A pointer (like `object.props.x`)
1604
+ * @returns The value the pointer points to
1605
+ *
1606
+ * @example
1607
+ *
1608
+ * Usage
1609
+ * ```ts
1610
+ * import {val, getProject} from '@unseenco/theatre-core'
1611
+ *
1612
+ * const obj = getProject("A project").sheet("Scene").object("Box", {position: {x: 0}})
1613
+ *
1614
+ * console.log(val(obj.props.position.x)) // logs the value of obj.props.x
1615
+ * ```
1616
+ */
1617
+ declare function val<T>(pointer: PointerType<T>): T;
8
1618
 
9
1619
  /**
10
1620
  * The library providing the runtime functionality of Theatre.js.
@@ -23,4 +1633,4 @@ import { OnDiskState } from './projects/store/storeTypes';
23
1633
  */
24
1634
  type __UNSTABLE_Project_OnDiskState = OnDiskState;
25
1635
 
26
- export { __UNSTABLE_Project_OnDiskState };
1636
+ export { IProject, IProjectConfig, IRafDriver, ISequence, ISheet, ISheetObject, ISheetObjectOptions, ISheetOptions, UnknownShorthandCompoundProps, __UNSTABLE_Project_OnDiskState, createRafDriver, getProject, isRemoteEditorWindow, notify, onChange, setCoreRafDriver, index_d as types, val };