@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 +1618 -8
- package/dist/index.js +5 -4
- package/dist/index.js.map +2 -2
- package/dist/index.mjs +5 -4
- package/dist/index.mjs.map +2 -2
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,1620 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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 };
|