@unseenco/theatre-core 0.4.3 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -239,17 +239,44 @@ var require_propose = __commonJS({
239
239
  // core/src/coreExports.ts
240
240
  var coreExports_exports = {};
241
241
  __export(coreExports_exports, {
242
+ PAGE_MODE_SEQUENCE_LENGTH: () => PAGE_MODE_SEQUENCE_LENGTH,
243
+ PAGE_MODE_SUB_UNITS_PER_UNIT: () => PAGE_MODE_SUB_UNITS_PER_UNIT,
242
244
  attachGsapSequenceBridge: () => attachGsapSequenceBridge,
245
+ attachSheetScrollDriver: () => attachSheetScrollDriver,
246
+ attachTheatrePageScroll: () => attachTheatrePageScroll,
247
+ configureTheatrePageScroll: () => configureTheatrePageScroll,
248
+ createDefaultPageScrollDriver: () => createDefaultPageScrollDriver,
249
+ createElementHorizontalScrollDriver: () => createElementHorizontalScrollDriver,
250
+ createElementScrollDriver: () => createElementScrollDriver,
251
+ createNativeDocumentHorizontalScrollDriver: () => createNativeDocumentHorizontalScrollDriver,
252
+ createNativeDocumentScrollDriver: () => createNativeDocumentScrollDriver,
243
253
  createRafDriver: () => createRafDriver,
254
+ defaultPageScrollContext: () => defaultPageScrollContext,
255
+ getActivePageScrollContext: () => getActivePageScrollContext,
244
256
  getProject: () => getProject,
257
+ getSheetScrollDriver: () => getSheetScrollDriver,
258
+ getTheatreCoreRafDriver: () => getTheatreCoreRafDriver,
259
+ getTheatrePageScrollConfig: () => getTheatrePageScrollConfig,
260
+ getTheatrePageScrollContext: () => getTheatrePageScrollContext,
261
+ isNativeDocumentScroller: () => isNativeDocumentScroller,
262
+ isPageScrollTrigger: () => isPageScrollTrigger,
245
263
  isRemoteEditorWindow: () => isRemoteEditorWindow,
264
+ isVerticalPageScrollTrigger: () => isVerticalPageScrollTrigger,
246
265
  notify: () => notify,
247
266
  onChange: () => onChange,
267
+ pageScrollProgressFromSequence: () => pageScrollProgressFromSequence,
268
+ pageScrollScrollersMatch: () => pageScrollScrollersMatch,
248
269
  privateAPI: () => privateAPI,
270
+ resolvePageScrollAxis: () => resolvePageScrollAxis,
271
+ resolvePageScrollScroller: () => resolvePageScrollScroller,
272
+ setActivePageScrollContext: () => setActivePageScrollContext,
249
273
  setCoreRafDriver: () => setCoreRafDriver,
274
+ setPageScrollProgress: () => setPageScrollProgress,
250
275
  setPrivateAPI: () => setPrivateAPI,
276
+ syncNativeDocumentScrollToSequencePosition: () => syncNativeDocumentScrollToSequencePosition,
277
+ syncPageScrollToSequencePosition: () => syncPageScrollToSequencePosition,
251
278
  types: () => propTypes_exports,
252
- val: () => val11
279
+ val: () => val12
253
280
  });
254
281
 
255
282
  // core/src/projects/projectsSingleton.ts
@@ -274,6 +301,82 @@ var ProjectsSingleton = class {
274
301
  var singleton = new ProjectsSingleton();
275
302
  var projectsSingleton_default = singleton;
276
303
 
304
+ // core/src/rafDrivers.ts
305
+ import { Ticker } from "@unseenco/theatre-dataverse";
306
+ var lastDriverId = 0;
307
+ function createRafDriver(conf) {
308
+ const tick = (time) => {
309
+ ticker.tick(time);
310
+ };
311
+ const ticker = new Ticker({
312
+ onActive() {
313
+ conf?.start?.();
314
+ },
315
+ onDormant() {
316
+ conf?.stop?.();
317
+ }
318
+ });
319
+ const driverPublicApi = {
320
+ tick,
321
+ id: lastDriverId++,
322
+ name: conf?.name ?? "CustomRafDriver-".concat(lastDriverId),
323
+ type: "Theatre_RafDriver_PublicAPI"
324
+ };
325
+ const driverPrivateApi = {
326
+ type: "Theatre_RafDriver_PrivateAPI",
327
+ publicApi: driverPublicApi,
328
+ ticker,
329
+ start: conf?.start,
330
+ stop: conf?.stop
331
+ };
332
+ setPrivateAPI(driverPublicApi, driverPrivateApi);
333
+ return driverPublicApi;
334
+ }
335
+
336
+ // core/src/coreTicker.ts
337
+ function createBasicRafDriver() {
338
+ let rafId = null;
339
+ const start = () => {
340
+ if (typeof window !== "undefined") {
341
+ const onAnimationFrame = (t2) => {
342
+ driver.tick(t2);
343
+ rafId = window.requestAnimationFrame(onAnimationFrame);
344
+ };
345
+ rafId = window.requestAnimationFrame(onAnimationFrame);
346
+ } else {
347
+ driver.tick(0);
348
+ setTimeout(() => driver.tick(1), 0);
349
+ }
350
+ };
351
+ const stop = () => {
352
+ if (typeof window !== "undefined") {
353
+ if (rafId !== null) {
354
+ window.cancelAnimationFrame(rafId);
355
+ }
356
+ } else {
357
+ }
358
+ };
359
+ const driver = createRafDriver({ name: "DefaultCoreRafDriver", start, stop });
360
+ return driver;
361
+ }
362
+ var coreRafDriver;
363
+ function getCoreRafDriver() {
364
+ if (!coreRafDriver) {
365
+ setCoreRafDriver(createBasicRafDriver());
366
+ }
367
+ return coreRafDriver;
368
+ }
369
+ function getCoreTicker() {
370
+ return getCoreRafDriver().ticker;
371
+ }
372
+ function setCoreRafDriver(driver) {
373
+ if (coreRafDriver) {
374
+ throw new Error("`setCoreRafDriver()` is already called.");
375
+ }
376
+ const driverPrivateApi = privateAPI(driver);
377
+ coreRafDriver = driverPrivateApi;
378
+ }
379
+
277
380
  // core/src/privateAPIs.ts
278
381
  var publicAPIToPrivateAPIMap = /* @__PURE__ */ new WeakMap();
279
382
  function privateAPI(pub) {
@@ -282,6 +385,23 @@ function privateAPI(pub) {
282
385
  function setPrivateAPI(pub, priv) {
283
386
  publicAPIToPrivateAPIMap.set(pub, priv);
284
387
  }
388
+ function getTheatreCoreRafDriver() {
389
+ return getCoreRafDriver().publicApi;
390
+ }
391
+
392
+ // core/src/sheets/applySheetRuntimeOptions.ts
393
+ function applySheetRuntimeOptions(sheet, opts) {
394
+ if (!opts) return;
395
+ if (opts.sequenceMode !== void 0) {
396
+ sheet.setSequenceMode(opts.sequenceMode);
397
+ }
398
+ if (opts.scrollDriver !== void 0) {
399
+ sheet.setPageScrollDriver(opts.scrollDriver);
400
+ }
401
+ if (opts.gsap === true) {
402
+ sheet.enableGsapSequenceBridge();
403
+ }
404
+ }
285
405
 
286
406
  // shared/src/utils/index.ts
287
407
  var emptyArray = [];
@@ -3223,11 +3343,11 @@ function sanitizeCompoundProps(props) {
3223
3343
  );
3224
3344
  }
3225
3345
  }
3226
- const val12 = props[key];
3227
- if (isLonghandPropType(val12)) {
3228
- sanitizedProps[key] = val12;
3346
+ const val13 = props[key];
3347
+ if (isLonghandPropType(val13)) {
3348
+ sanitizedProps[key] = val13;
3229
3349
  } else {
3230
- sanitizedProps[key] = toLonghandProp(val12);
3350
+ sanitizedProps[key] = toLonghandProp(val13);
3231
3351
  }
3232
3352
  }
3233
3353
  return sanitizedProps;
@@ -3325,15 +3445,15 @@ var file = (defaultValue, opts = {}) => {
3325
3445
  deserializeAndSanitize: _ensureFile
3326
3446
  };
3327
3447
  };
3328
- var _ensureFile = (val12) => {
3329
- if (!val12) return void 0;
3448
+ var _ensureFile = (val13) => {
3449
+ if (!val13) return void 0;
3330
3450
  let valid = true;
3331
- if (typeof val12.id !== "string" && ![null, void 0].includes(val12.id)) {
3451
+ if (typeof val13.id !== "string" && ![null, void 0].includes(val13.id)) {
3332
3452
  valid = false;
3333
3453
  }
3334
- if (val12.type !== "file") valid = false;
3454
+ if (val13.type !== "file") valid = false;
3335
3455
  if (!valid) return void 0;
3336
- return val12;
3456
+ return val13;
3337
3457
  };
3338
3458
  var image = (defaultValue, opts = {}) => {
3339
3459
  if (process.env.NODE_ENV !== "production") {
@@ -3357,15 +3477,15 @@ var image = (defaultValue, opts = {}) => {
3357
3477
  deserializeAndSanitize: _ensureImage
3358
3478
  };
3359
3479
  };
3360
- var _ensureImage = (val12) => {
3361
- if (!val12) return void 0;
3480
+ var _ensureImage = (val13) => {
3481
+ if (!val13) return void 0;
3362
3482
  let valid = true;
3363
- if (typeof val12.id !== "string" && ![null, void 0].includes(val12.id)) {
3483
+ if (typeof val13.id !== "string" && ![null, void 0].includes(val13.id)) {
3364
3484
  valid = false;
3365
3485
  }
3366
- if (val12.type !== "image") valid = false;
3486
+ if (val13.type !== "image") valid = false;
3367
3487
  if (!valid) return void 0;
3368
- return val12;
3488
+ return val13;
3369
3489
  };
3370
3490
  var number = (defaultValue, opts = {}) => {
3371
3491
  if (process.env.NODE_ENV !== "production") {
@@ -3488,11 +3608,11 @@ var rgba = (defaultValue = { r: 0, g: 0, b: 0, a: 1 }, opts = {}) => {
3488
3608
  deserializeAndSanitize: _sanitizeRgba
3489
3609
  };
3490
3610
  };
3491
- var _sanitizeRgba = (val12) => {
3492
- if (!val12) return void 0;
3611
+ var _sanitizeRgba = (val13) => {
3612
+ if (!val13) return void 0;
3493
3613
  let valid = true;
3494
3614
  for (const c2 of ["r", "g", "b", "a"]) {
3495
- if (!Object.prototype.hasOwnProperty.call(val12, c2) || typeof val12[c2] !== "number") {
3615
+ if (!Object.prototype.hasOwnProperty.call(val13, c2) || typeof val13[c2] !== "number") {
3496
3616
  valid = false;
3497
3617
  }
3498
3618
  }
@@ -3501,7 +3621,7 @@ var _sanitizeRgba = (val12) => {
3501
3621
  for (const c2 of ["r", "g", "b", "a"]) {
3502
3622
  ;
3503
3623
  sanitized[c2] = Math.min(
3504
- Math.max(val12[c2], 0),
3624
+ Math.max(val13[c2], 0),
3505
3625
  1
3506
3626
  );
3507
3627
  }
@@ -3540,8 +3660,8 @@ var boolean = (defaultValue, opts = {}) => {
3540
3660
  deserializeAndSanitize: _ensureBoolean
3541
3661
  };
3542
3662
  };
3543
- var _ensureBoolean = (val12) => {
3544
- return typeof val12 === "boolean" ? val12 : void 0;
3663
+ var _ensureBoolean = (val13) => {
3664
+ return typeof val13 === "boolean" ? val13 : void 0;
3545
3665
  };
3546
3666
  function leftInterpolate(left) {
3547
3667
  return left;
@@ -3748,8 +3868,8 @@ var TheatreSheetObject = class {
3748
3868
  }
3749
3869
  return der.getValue();
3750
3870
  }
3751
- set initialValue(val12) {
3752
- privateAPI(this).setInitialValue(val12);
3871
+ set initialValue(val13) {
3872
+ privateAPI(this).setInitialValue(val13);
3753
3873
  }
3754
3874
  showPropsOf(objects) {
3755
3875
  const host = privateAPI(this);
@@ -4517,9 +4637,9 @@ var SheetObject = class {
4517
4637
  }
4518
4638
  validateValue(pointer3, value) {
4519
4639
  }
4520
- setInitialValue(val12) {
4521
- this.validateValue(this.propsP, val12);
4522
- this._initialValue.set(val12);
4640
+ setInitialValue(val13) {
4641
+ this.validateValue(this.propsP, val13);
4642
+ this._initialValue.set(val13);
4523
4643
  }
4524
4644
  setSessionOverride(path, value) {
4525
4645
  const propConfig = getPropConfigByPath(this.template.staticConfig, path);
@@ -4539,8 +4659,8 @@ var SheetObject = class {
4539
4659
  * Internal: called by `RemoteSync` when a value update for this object
4540
4660
  * arrives from a remote editor window. Not exposed on the public API.
4541
4661
  */
4542
- setRemoteOverride(val12) {
4543
- this._remoteOverride.set(val12);
4662
+ setRemoteOverride(val13) {
4663
+ this._remoteOverride.set(val13);
4544
4664
  }
4545
4665
  /**
4546
4666
  * Internal: used by `RemoteSync` to subscribe to this object's fully
@@ -5520,7 +5640,7 @@ function parsePathToProp(pathToPropInString) {
5520
5640
  }
5521
5641
 
5522
5642
  // core/src/sheets/SheetTemplate.ts
5523
- import { Atom as Atom9 } from "@unseenco/theatre-dataverse";
5643
+ import { Atom as Atom10 } from "@unseenco/theatre-dataverse";
5524
5644
 
5525
5645
  // shared/src/utils/didYouMean.ts
5526
5646
  var import_propose = __toESM(require_propose());
@@ -5537,7 +5657,7 @@ function didYouMean(str, dictionary, prepend = "Did you mean ", append = "?") {
5537
5657
 
5538
5658
  // core/src/sequences/Sequence.ts
5539
5659
  import { getPointerParts as getPointerParts3 } from "@unseenco/theatre-dataverse";
5540
- import { Atom as Atom7 } from "@unseenco/theatre-dataverse";
5660
+ import { Atom as Atom8 } from "@unseenco/theatre-dataverse";
5541
5661
  import { pointer as pointer2 } from "@unseenco/theatre-dataverse";
5542
5662
  import { prism as prism6, val as val6 } from "@unseenco/theatre-dataverse";
5543
5663
 
@@ -5918,11 +6038,11 @@ var AudioPlaybackController = class {
5918
6038
  "The sequence will still play, but only 1000 times. The `iterationCount: ".concat(iterationCount, "` provided to `sequence.play()`\nis too high for a sequence with audio.\n\nTo fix this, either set `iterationCount` to a lower value, or remove the audio from the sequence."),
5919
6039
  [
5920
6040
  {
5921
- url: "https://www.theatrejs.com/docs/latest/manual/audio",
6041
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/audio",
5922
6042
  title: "Using Audio"
5923
6043
  },
5924
6044
  {
5925
- url: "https://www.theatrejs.com/docs/latest/api/core#sequence.attachaudio",
6045
+ url: "https://unseen-theatre.netlify.app/docs/api/theatre-core#sequence.attachaudio",
5926
6046
  title: "Audio API"
5927
6047
  }
5928
6048
  ]
@@ -5975,82 +6095,6 @@ var AudioPlaybackController = class {
5975
6095
  }
5976
6096
  };
5977
6097
 
5978
- // core/src/rafDrivers.ts
5979
- import { Ticker } from "@unseenco/theatre-dataverse";
5980
- var lastDriverId = 0;
5981
- function createRafDriver(conf) {
5982
- const tick = (time) => {
5983
- ticker.tick(time);
5984
- };
5985
- const ticker = new Ticker({
5986
- onActive() {
5987
- conf?.start?.();
5988
- },
5989
- onDormant() {
5990
- conf?.stop?.();
5991
- }
5992
- });
5993
- const driverPublicApi = {
5994
- tick,
5995
- id: lastDriverId++,
5996
- name: conf?.name ?? "CustomRafDriver-".concat(lastDriverId),
5997
- type: "Theatre_RafDriver_PublicAPI"
5998
- };
5999
- const driverPrivateApi = {
6000
- type: "Theatre_RafDriver_PrivateAPI",
6001
- publicApi: driverPublicApi,
6002
- ticker,
6003
- start: conf?.start,
6004
- stop: conf?.stop
6005
- };
6006
- setPrivateAPI(driverPublicApi, driverPrivateApi);
6007
- return driverPublicApi;
6008
- }
6009
-
6010
- // core/src/coreTicker.ts
6011
- function createBasicRafDriver() {
6012
- let rafId = null;
6013
- const start = () => {
6014
- if (typeof window !== "undefined") {
6015
- const onAnimationFrame = (t2) => {
6016
- driver.tick(t2);
6017
- rafId = window.requestAnimationFrame(onAnimationFrame);
6018
- };
6019
- rafId = window.requestAnimationFrame(onAnimationFrame);
6020
- } else {
6021
- driver.tick(0);
6022
- setTimeout(() => driver.tick(1), 0);
6023
- }
6024
- };
6025
- const stop = () => {
6026
- if (typeof window !== "undefined") {
6027
- if (rafId !== null) {
6028
- window.cancelAnimationFrame(rafId);
6029
- }
6030
- } else {
6031
- }
6032
- };
6033
- const driver = createRafDriver({ name: "DefaultCoreRafDriver", start, stop });
6034
- return driver;
6035
- }
6036
- var coreRafDriver;
6037
- function getCoreRafDriver() {
6038
- if (!coreRafDriver) {
6039
- setCoreRafDriver(createBasicRafDriver());
6040
- }
6041
- return coreRafDriver;
6042
- }
6043
- function getCoreTicker() {
6044
- return getCoreRafDriver().ticker;
6045
- }
6046
- function setCoreRafDriver(driver) {
6047
- if (coreRafDriver) {
6048
- throw new Error("`setCoreRafDriver()` is already called.");
6049
- }
6050
- const driverPrivateApi = privateAPI(driver);
6051
- coreRafDriver = driverPrivateApi;
6052
- }
6053
-
6054
6098
  // core/src/sequences/TheatreSequence.ts
6055
6099
  var TheatreSequence = class {
6056
6100
  get type() {
@@ -6074,7 +6118,7 @@ var TheatreSequence = class {
6074
6118
  "You seem to have called `sequence.play()` before the project has finished loading.\n\nThis would **not** a problem in production when using `@unseenco/theatre-core`, since Theatre.js loads instantly in core mode. However, when using `@unseenco/theatre-studio`, it takes a few milliseconds for it to load your project's state, before which your sequences cannot start playing.\n\nTo fix this, simply defer calling `sequence.play()` until after the project is loaded, like this:\n\n```\nproject.ready.then(() => {\n sequence.play()\n})\n```",
6075
6119
  [
6076
6120
  {
6077
- url: "https://www.theatrejs.com/docs/0.5/api/core#project.ready",
6121
+ url: "https://unseen-theatre.netlify.app/docs/api/theatre-core#project.ready",
6078
6122
  title: "Project.ready"
6079
6123
  }
6080
6124
  ]
@@ -6100,14 +6144,27 @@ var TheatreSequence = class {
6100
6144
  __experimental_getGsapClips() {
6101
6145
  return privateAPI(this).getGsapClips();
6102
6146
  }
6147
+ __experimental_getGsapScrollTriggers() {
6148
+ return privateAPI(this).getGsapScrollTriggers();
6149
+ }
6103
6150
  async attachAudio(args) {
6151
+ const priv = privateAPI(this);
6152
+ if (priv._sheet.getSequenceMode() === "page") {
6153
+ notify.warning(
6154
+ "Audio is not supported in page mode",
6155
+ "Sequence audio expects time in seconds. Use time mode for attachAudio(), or scrub scroll-driven animation without audio."
6156
+ );
6157
+ return Promise.reject(
6158
+ new Error("sequence.attachAudio() is not supported in page mode")
6159
+ );
6160
+ }
6104
6161
  const { audioContext, destinationNode, decodedBuffer, gainNode } = await resolveAudioBuffer(args);
6105
6162
  const playbackController = new AudioPlaybackController(
6106
6163
  decodedBuffer,
6107
6164
  audioContext,
6108
6165
  gainNode
6109
6166
  );
6110
- privateAPI(this).replacePlaybackController(playbackController);
6167
+ priv.replacePlaybackController(playbackController);
6111
6168
  return { audioContext, destinationNode, decodedBuffer, gainNode };
6112
6169
  }
6113
6170
  get pointer() {
@@ -6205,6 +6262,31 @@ async function resolveAudioBuffer(args) {
6205
6262
  };
6206
6263
  }
6207
6264
 
6265
+ // shared/src/gsap/gsapStudioRegistryRevision.ts
6266
+ import { Atom as Atom7 } from "@unseenco/theatre-dataverse";
6267
+ var revisionAtom = new Atom7(0);
6268
+ var gsapStudioRegistryRevisionPointer = revisionAtom.pointer;
6269
+
6270
+ // shared/src/gsap/scrollTriggerRegistry.ts
6271
+ var DEFAULT_SHEET_INSTANCE_ID = "default";
6272
+ var REGISTRY_KEY = "__unseenco_theatre_gsap_scrollTriggerRegistry__";
6273
+ function sheetAddressKey(address) {
6274
+ const sheetInstanceId = address.sheetInstanceId ?? DEFAULT_SHEET_INSTANCE_ID;
6275
+ return "".concat(address.projectId, "|").concat(address.sheetId, "|").concat(sheetInstanceId);
6276
+ }
6277
+ function getStore() {
6278
+ const g2 = globalThis;
6279
+ if (!g2[REGISTRY_KEY]) {
6280
+ g2[REGISTRY_KEY] = { bySheetAddress: /* @__PURE__ */ new Map() };
6281
+ }
6282
+ return g2[REGISTRY_KEY];
6283
+ }
6284
+ function listScrollTriggerEntriesForSheet(sheetKey) {
6285
+ const map = getStore().bySheetAddress.get(sheetKey);
6286
+ if (!map) return [];
6287
+ return [...map.values()].sort((a2, b2) => a2.layout.start - b2.layout.start);
6288
+ }
6289
+
6208
6290
  // shared/src/sequence/trackData.ts
6209
6291
  function isBasicKeyframedTrack(track) {
6210
6292
  return track.type === "BasicKeyframedTrack";
@@ -6260,7 +6342,7 @@ var Sequence = class {
6260
6342
  this._logger = _project._logger.named("Sheet", _sheet.address.sheetId).named("Instance", _sheet.address.sheetInstanceId).named("Variant", _sequenceVariant);
6261
6343
  this.address = { ...this._sheet.address, sequenceName: _sequenceVariant };
6262
6344
  this.publicApi = new TheatreSequence(this);
6263
- this._playbackControllerBox = new Atom7(
6345
+ this._playbackControllerBox = new Atom8(
6264
6346
  playbackController ?? new DefaultPlaybackController()
6265
6347
  );
6266
6348
  this._prismOfStatePointer = prism6(
@@ -6272,6 +6354,9 @@ var Sequence = class {
6272
6354
  });
6273
6355
  this._positionFormatterD = prism6(() => {
6274
6356
  const subUnitsPerUnit = val6(this._subUnitsPerUnitD);
6357
+ if (this._sheet.getSequenceMode() === "page") {
6358
+ return new PercentBasedPositionFormatter(subUnitsPerUnit);
6359
+ }
6275
6360
  return new TimeBasedPositionFormatter(subUnitsPerUnit);
6276
6361
  });
6277
6362
  }
@@ -6377,6 +6462,21 @@ var Sequence = class {
6377
6462
  }
6378
6463
  return result.sort((a2, b2) => a2.clip.start - b2.clip.start);
6379
6464
  }
6465
+ getGsapScrollTriggers() {
6466
+ if (this._sheet.getSequenceMode() !== "page") {
6467
+ return [];
6468
+ }
6469
+ const key = sheetAddressKey(this._sheet.address);
6470
+ return listScrollTriggerEntriesForSheet(key).filter((entry) => entry.sheetObject != null).map((entry) => ({
6471
+ objectKey: entry.sheetObject.address.objectKey,
6472
+ scrollTriggerId: entry.id,
6473
+ label: entry.label,
6474
+ layout: entry.layout,
6475
+ kind: entry.kind,
6476
+ animationSpanSeconds: entry.animationSpanSeconds,
6477
+ timelineChildren: entry.timelineChildren
6478
+ }));
6479
+ }
6380
6480
  get positionFormatter() {
6381
6481
  return this._positionFormatterD.getValue();
6382
6482
  }
@@ -6447,6 +6547,13 @@ var Sequence = class {
6447
6547
  return this._playbackControllerBox.get().playDynamicRange(rangeD, ticker);
6448
6548
  }
6449
6549
  async play(conf, ticker) {
6550
+ if (this._sheet.getSequenceMode() === "page") {
6551
+ notify.warning(
6552
+ "Playback is disabled in page mode",
6553
+ "The sequence is driven by page scroll. Scrub the playhead or scroll the page instead of calling sequence.play()."
6554
+ );
6555
+ return false;
6556
+ }
6450
6557
  const sequenceDuration = this.length;
6451
6558
  const range = conf && conf.range ? conf.range : [0, sequenceDuration];
6452
6559
  if (process.env.NODE_ENV !== "production") {
@@ -6479,11 +6586,11 @@ var Sequence = class {
6479
6586
  ), "s) is longer than the duration of the sequence (").concat(sequenceDuration, "s).\n\nTo fix this, either set `conf.range[1]` to be less the duration of the sequence, or adjust the sequence duration in the UI."),
6480
6587
  [
6481
6588
  {
6482
- url: "https://www.theatrejs.com/docs/latest/manual/sequences",
6589
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/sequences",
6483
6590
  title: "Sequences"
6484
6591
  },
6485
6592
  {
6486
- url: "https://www.theatrejs.com/docs/latest/manual/sequences",
6593
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/sequences",
6487
6594
  title: "Playback API"
6488
6595
  }
6489
6596
  ]
@@ -6637,6 +6744,33 @@ var TimeBasedPositionFormatter = class {
6637
6744
  return posInUnitSpace.toFixed(2) + "s";
6638
6745
  }
6639
6746
  };
6747
+ var PercentBasedPositionFormatter = class {
6748
+ constructor(_subUnitsPerUnit) {
6749
+ this._subUnitsPerUnit = _subUnitsPerUnit;
6750
+ }
6751
+ formatSubUnitForGrid(posInUnitSpace) {
6752
+ const subUnitSize = 1 / this._subUnitsPerUnit;
6753
+ const withinUnit = posInUnitSpace % 1;
6754
+ const tenths = Math.round(withinUnit / subUnitSize);
6755
+ return tenths + "t";
6756
+ }
6757
+ formatFullUnitForGrid(posInUnitSpace) {
6758
+ return Math.round(posInUnitSpace) + "%";
6759
+ }
6760
+ formatForPlayhead(posInUnitSpace) {
6761
+ const whole = Math.floor(posInUnitSpace);
6762
+ const subUnitSize = 1 / this._subUnitsPerUnit;
6763
+ const withinUnit = posInUnitSpace - whole;
6764
+ const tenths = Math.round(withinUnit / subUnitSize);
6765
+ if (tenths <= 0) {
6766
+ return whole + "%";
6767
+ }
6768
+ return whole + "." + tenths + "%";
6769
+ }
6770
+ formatBasic(posInUnitSpace) {
6771
+ return posInUnitSpace.toFixed(1) + "%";
6772
+ }
6773
+ };
6640
6774
  var second = 1;
6641
6775
  var minute = second * 60;
6642
6776
  var hour = minute * 60;
@@ -6671,11 +6805,11 @@ function validateAndSanitiseSlashedPathOrThrow(unsanitisedPath, fnName) {
6671
6805
  "The path in `".concat(fnName, '("').concat(unsanitisedPath, '")` was sanitized to `"').concat(sanitisedPath, '"`.\n\n') + "Please replace the path with the sanitized one, otherwise it will likely break in the future.",
6672
6806
  [
6673
6807
  {
6674
- url: "https://www.theatrejs.com/docs/latest/manual/objects#creating-sheet-objects",
6808
+ url: "https://unseen-theatre.netlify.app/docs/guide/manual/objects#creating-objects",
6675
6809
  title: "Sheet Objects"
6676
6810
  },
6677
6811
  {
6678
- url: "https://www.theatrejs.com/docs/latest/api/core#sheet.object",
6812
+ url: "https://unseen-theatre.netlify.app/docs/api/theatre-core#sheet.object",
6679
6813
  title: "API"
6680
6814
  }
6681
6815
  ]
@@ -6890,6 +7024,15 @@ var TheatreSheet = class {
6890
7024
  getActiveSequenceVariant() {
6891
7025
  return privateAPI(this).getActiveSequenceVariant();
6892
7026
  }
7027
+ getSequenceMode() {
7028
+ return privateAPI(this).getSequenceMode();
7029
+ }
7030
+ setSequenceMode(mode) {
7031
+ privateAPI(this).setSequenceMode(mode);
7032
+ }
7033
+ setPageScrollDriver(driver) {
7034
+ privateAPI(this).setPageScrollDriver(driver);
7035
+ }
6893
7036
  get project() {
6894
7037
  return privateAPI(this).project.publicApi;
6895
7038
  }
@@ -6947,47 +7090,411 @@ var TheatreSheet = class {
6947
7090
  };
6948
7091
 
6949
7092
  // core/src/sheets/Sheet.ts
6950
- import { Atom as Atom8, prism as prism7, val as val7 } from "@unseenco/theatre-dataverse";
6951
- var Sheet = class {
6952
- constructor(template, instanceId) {
6953
- this.template = template;
6954
- this.instanceId = instanceId;
6955
- __publicField(this, "_objects", new Atom8({}));
6956
- __publicField(this, "_sequences", {});
6957
- __publicField(this, "_activeSequenceVariant", new Atom8(
6958
- DEFAULT_SEQUENCE_VARIANT
6959
- ));
6960
- /**
6961
- * When Studio is open, it sets this to control which variant is used for
6962
- * value resolution (preview). User code can still update `_activeSequenceVariant`
6963
- * via `setActiveSequenceVariant()` without affecting the Studio preview.
6964
- */
6965
- __publicField(this, "_studioPreviewVariantOverride", new Atom8(void 0));
6966
- __publicField(this, "activeSequenceVariantP", this._activeSequenceVariant.pointer);
6967
- __publicField(this, "effectiveActiveSequenceVariantD");
6968
- __publicField(this, "address");
6969
- __publicField(this, "publicApi");
6970
- __publicField(this, "project");
6971
- __publicField(this, "objectsP", this._objects.pointer);
6972
- __publicField(this, "type", "Theatre_Sheet");
6973
- __publicField(this, "_logger");
6974
- this._logger = template.project._logger.named("Sheet", instanceId);
6975
- this._logger._trace("creating sheet");
6976
- this.project = template.project;
6977
- this.address = {
6978
- ...template.address,
6979
- sheetInstanceId: this.instanceId
6980
- };
6981
- this.publicApi = new TheatreSheet(this);
6982
- this.effectiveActiveSequenceVariantD = prism7(() => {
6983
- const studioOverride = val7(this._studioPreviewVariantOverride.pointer);
6984
- if (studioOverride !== void 0) {
6985
- return studioOverride;
6986
- }
6987
- return val7(this._activeSequenceVariant.pointer);
6988
- });
7093
+ import { Atom as Atom9, prism as prism8, val as val9 } from "@unseenco/theatre-dataverse";
7094
+
7095
+ // core/src/sheets/sheetSequenceMode.ts
7096
+ var PAGE_MODE_SEQUENCE_LENGTH = 100;
7097
+ var PAGE_MODE_SUB_UNITS_PER_UNIT = 10;
7098
+
7099
+ // shared/src/gsap/applyTimelineChildTiming.ts
7100
+ function applyTimelineChildTimingToGsap(rootAnimation, timelineChildren, childById, onRebuild) {
7101
+ if (!timelineChildren.length) return true;
7102
+ let ok = applyTimelineChildTimingToGsapInner(
7103
+ rootAnimation,
7104
+ timelineChildren,
7105
+ childById
7106
+ );
7107
+ if (!ok && onRebuild) {
7108
+ const rebuilt = onRebuild();
7109
+ if (rebuilt) {
7110
+ ok = applyTimelineChildTimingToGsapInner(
7111
+ rebuilt,
7112
+ timelineChildren,
7113
+ childById
7114
+ );
7115
+ }
6989
7116
  }
6990
- /**
7117
+ return ok;
7118
+ }
7119
+ function applyTimelineChildTimingToGsapInner(rootAnimation, timelineChildren, childById) {
7120
+ if (!childById || timelineChildren.length === 0) return true;
7121
+ let ok = true;
7122
+ for (const childState of timelineChildren) {
7123
+ const tween = childById.get(childState.childId);
7124
+ if (!tween) continue;
7125
+ try {
7126
+ const t2 = tween;
7127
+ if (typeof t2.startTime === "function") {
7128
+ t2.startTime(childState.localStart);
7129
+ }
7130
+ if (typeof t2.duration === "function") {
7131
+ t2.duration(childState.localDuration);
7132
+ }
7133
+ } catch {
7134
+ ok = false;
7135
+ }
7136
+ }
7137
+ return ok;
7138
+ }
7139
+
7140
+ // shared/src/gsap/syncGsapClipProgress.ts
7141
+ function getGsapAnimationTargetKey(animation, fallbackAnimationId) {
7142
+ const tween = animation;
7143
+ const targets = tween.targets?.();
7144
+ if (targets && targets.length > 0) {
7145
+ return targets[0];
7146
+ }
7147
+ return fallbackAnimationId;
7148
+ }
7149
+ function syncRegisteredGsapAnimationsForClips(sequencePosition, clips) {
7150
+ const enriched = [];
7151
+ for (const clip of clips) {
7152
+ const entry = getAnimationEntryBySheetAddressKey(
7153
+ clip.sheetObjectAddressKey,
7154
+ clip.gsapAnimationId
7155
+ );
7156
+ if (!entry?.animation) continue;
7157
+ enriched.push({
7158
+ ...clip,
7159
+ entry,
7160
+ targetKey: getGsapAnimationTargetKey(
7161
+ entry.animation,
7162
+ clip.gsapAnimationId
7163
+ )
7164
+ });
7165
+ }
7166
+ const byTarget = /* @__PURE__ */ new Map();
7167
+ for (const item of enriched) {
7168
+ const list = byTarget.get(item.targetKey) ?? [];
7169
+ list.push(item);
7170
+ byTarget.set(item.targetKey, list);
7171
+ }
7172
+ for (const group of byTarget.values()) {
7173
+ const sorted = [...group].sort((a2, b2) => a2.start - b2.start);
7174
+ for (const clip of sorted) {
7175
+ const progress = gsapClipSyncProgress(sequencePosition, clip);
7176
+ const entry = clip.entry;
7177
+ const animation = entry.animation;
7178
+ if (entry.kind === "timeline" && clip.timelineChildren && clip.timelineChildren.length > 0) {
7179
+ applyTimelineChildTimingToGsap(
7180
+ entry.animation,
7181
+ clip.timelineChildren,
7182
+ entry.timelineChildById,
7183
+ entry.onRebuildTimeline
7184
+ );
7185
+ const span = clip.timelineSpan ?? readGsapTweenTimelineDuration(entry.animation);
7186
+ const t2 = progress * Math.max(span, 0.01);
7187
+ if (typeof animation.time === "function") {
7188
+ animation.time(t2, true);
7189
+ } else {
7190
+ animation.progress?.(progress, true);
7191
+ }
7192
+ } else {
7193
+ animation.progress?.(progress, true);
7194
+ }
7195
+ }
7196
+ }
7197
+ }
7198
+ function readGsapTweenTimelineDuration(animation) {
7199
+ const tween = animation;
7200
+ const total = tween.totalDuration?.();
7201
+ if (typeof total === "number" && total > 0) return total;
7202
+ const d2 = tween.duration();
7203
+ return d2 > 0 ? d2 : 1;
7204
+ }
7205
+
7206
+ // shared/src/gsap/gsapAnimationRegistry.ts
7207
+ var DEFAULT_SHEET_INSTANCE_ID2 = "default";
7208
+ var REGISTRY_KEY2 = "__unseenco_theatre_gsap_animationRegistry__";
7209
+ function sheetObjectAddressKeyFromParts(address) {
7210
+ const sheetInstanceId = address.sheetInstanceId ?? DEFAULT_SHEET_INSTANCE_ID2;
7211
+ return "".concat(address.projectId, "|").concat(address.sheetId, "|").concat(sheetInstanceId, "|").concat(address.objectKey);
7212
+ }
7213
+ function getStore2() {
7214
+ const g2 = globalThis;
7215
+ if (!g2[REGISTRY_KEY2]) {
7216
+ g2[REGISTRY_KEY2] = {
7217
+ bySheetAddress: /* @__PURE__ */ new Map()
7218
+ };
7219
+ }
7220
+ return g2[REGISTRY_KEY2];
7221
+ }
7222
+ function getAnimationEntryBySheetAddressKey(sheetObjectAddressKey, animationId) {
7223
+ return getStore2().bySheetAddress.get(sheetObjectAddressKey)?.get(animationId);
7224
+ }
7225
+
7226
+ // shared/src/gsap/subscribeGsapClipSyncAtPlayhead.ts
7227
+ import { pointerToPrism, prism as prism7, val as val7 } from "@unseenco/theatre-dataverse";
7228
+
7229
+ // shared/src/gsap/syncGsapClipsAtSequencePosition.ts
7230
+ function syncGsapClipsAtSequencePosition(position, clips) {
7231
+ syncRegisteredGsapAnimationsForClips(
7232
+ position,
7233
+ clips
7234
+ );
7235
+ }
7236
+
7237
+ // shared/src/gsap/subscribeGsapClipSyncAtPlayhead.ts
7238
+ function subscribeGsapClipSyncAtPlayhead(source) {
7239
+ const positionPointer = source.pointer.position;
7240
+ const positionPrism = pointerToPrism(positionPointer);
7241
+ const clipsPrism = prism7(() => source.getGsapClipTimings());
7242
+ const syncNow = () => {
7243
+ const clips = clipsPrism.getValue();
7244
+ if (clips.length === 0) return;
7245
+ syncGsapClipsAtSequencePosition(val7(positionPointer), clips);
7246
+ };
7247
+ const untapPosition = positionPrism.onStale(syncNow);
7248
+ const untapClips = clipsPrism.onStale(syncNow);
7249
+ syncNow();
7250
+ return () => {
7251
+ untapPosition();
7252
+ untapClips();
7253
+ };
7254
+ }
7255
+
7256
+ // core/src/gsap/attachGsapSequenceBridge.ts
7257
+ function attachGsapSequenceBridge(sheet) {
7258
+ const sequence = sheet.sequence;
7259
+ const sheetAddress = privateAPI(sheet).address;
7260
+ return subscribeGsapClipSyncAtPlayhead({
7261
+ pointer: sequence.pointer,
7262
+ getGsapClipTimings: () => sequence.__experimental_getGsapClips().map(({ objectKey, clip }) => ({
7263
+ sheetObjectAddressKey: sheetObjectAddressKeyFromParts({
7264
+ projectId: sheetAddress.projectId,
7265
+ sheetId: sheetAddress.sheetId,
7266
+ sheetInstanceId: sheetAddress.sheetInstanceId,
7267
+ objectKey
7268
+ }),
7269
+ gsapAnimationId: clip.gsapAnimationId,
7270
+ start: clip.start,
7271
+ duration: clip.duration,
7272
+ timelineChildren: clip.timelineChildren,
7273
+ timelineSpan: clip.timelineSpan
7274
+ }))
7275
+ });
7276
+ }
7277
+
7278
+ // core/src/sheets/attachSheetScrollDriver.ts
7279
+ import { val as val8 } from "@unseenco/theatre-dataverse";
7280
+ var sheetScrollDrivers = /* @__PURE__ */ new WeakMap();
7281
+ var pageScrollDrivenSequencePositionListeners = /* @__PURE__ */ new Set();
7282
+ function onPageScrollDrivenSequencePosition(listener) {
7283
+ pageScrollDrivenSequencePositionListeners.add(listener);
7284
+ return () => {
7285
+ pageScrollDrivenSequencePositionListeners.delete(listener);
7286
+ };
7287
+ }
7288
+ function notifyPageScrollDrivenSequencePosition(sheet, position) {
7289
+ for (const listener of pageScrollDrivenSequencePositionListeners) {
7290
+ listener(sheet, position);
7291
+ }
7292
+ }
7293
+ function getSheetScrollDriver(sheet) {
7294
+ return sheetScrollDrivers.get(sheet);
7295
+ }
7296
+ function rememberSheetScrollDriver(sheet, driver) {
7297
+ sheetScrollDrivers.set(sheet, driver);
7298
+ }
7299
+ function progressFromScrollPosition(position, max) {
7300
+ if (max <= 0) return 0;
7301
+ if (position <= 0) return 0;
7302
+ if (position >= max - 1) return 1;
7303
+ return position / max;
7304
+ }
7305
+ function getNativeDocumentScrollingElement() {
7306
+ return document.scrollingElement ?? document.documentElement;
7307
+ }
7308
+ function addNativeDocumentScrollListener(handler) {
7309
+ document.addEventListener("scroll", handler, { passive: true, capture: true });
7310
+ return () => document.removeEventListener("scroll", handler, { capture: true });
7311
+ }
7312
+ function createScrollDriverFromElement(getMaxScroll, getScrollPos, setScrollPos, addScrollListener) {
7313
+ let suppressScrollEvents = false;
7314
+ let suppressGeneration = 0;
7315
+ const getProgress = () => {
7316
+ const max = getMaxScroll();
7317
+ return progressFromScrollPosition(getScrollPos(), max);
7318
+ };
7319
+ const setProgress = (progress) => {
7320
+ const max = getMaxScroll();
7321
+ const clamped = Math.max(0, Math.min(1, progress));
7322
+ const gen = ++suppressGeneration;
7323
+ suppressScrollEvents = true;
7324
+ setScrollPos(clamped * max);
7325
+ const release = () => {
7326
+ if (gen === suppressGeneration) {
7327
+ suppressScrollEvents = false;
7328
+ }
7329
+ };
7330
+ requestAnimationFrame(() => requestAnimationFrame(release));
7331
+ };
7332
+ return {
7333
+ getProgress,
7334
+ setProgress,
7335
+ subscribe(onProgressChange) {
7336
+ let rafId = 0;
7337
+ const flush = () => {
7338
+ rafId = 0;
7339
+ if (suppressScrollEvents) return;
7340
+ onProgressChange(getProgress());
7341
+ };
7342
+ const scheduleFlush = () => {
7343
+ if (rafId !== 0) return;
7344
+ rafId = requestAnimationFrame(flush);
7345
+ };
7346
+ const handler = () => scheduleFlush();
7347
+ const removeScroll = addScrollListener(handler);
7348
+ const onResize = () => scheduleFlush();
7349
+ window.addEventListener("resize", onResize);
7350
+ return () => {
7351
+ removeScroll();
7352
+ window.removeEventListener("resize", onResize);
7353
+ if (rafId !== 0) cancelAnimationFrame(rafId);
7354
+ };
7355
+ }
7356
+ };
7357
+ }
7358
+ function createNativeDocumentScrollDriver() {
7359
+ const getMaxScroll = () => {
7360
+ const el = document.documentElement;
7361
+ return Math.max(0, el.scrollHeight - window.innerHeight);
7362
+ };
7363
+ return createScrollDriverFromElement(
7364
+ getMaxScroll,
7365
+ () => window.scrollY,
7366
+ (y2) => window.scrollTo({ top: y2, behavior: "instant" }),
7367
+ addNativeDocumentScrollListener
7368
+ );
7369
+ }
7370
+ function createNativeDocumentHorizontalScrollDriver() {
7371
+ const getMaxScroll = () => {
7372
+ const el = getNativeDocumentScrollingElement();
7373
+ return Math.max(0, el.scrollWidth - el.clientWidth);
7374
+ };
7375
+ const getScrollLeft = () => {
7376
+ const el = getNativeDocumentScrollingElement();
7377
+ return el.scrollLeft;
7378
+ };
7379
+ return createScrollDriverFromElement(
7380
+ getMaxScroll,
7381
+ getScrollLeft,
7382
+ (x2) => {
7383
+ getNativeDocumentScrollingElement().scrollLeft = x2;
7384
+ },
7385
+ addNativeDocumentScrollListener
7386
+ );
7387
+ }
7388
+ function createElementScrollDriver(element) {
7389
+ const getMaxScroll = () => Math.max(0, element.scrollHeight - element.clientHeight);
7390
+ return createScrollDriverFromElement(
7391
+ getMaxScroll,
7392
+ () => element.scrollTop,
7393
+ (y2) => {
7394
+ element.scrollTop = y2;
7395
+ },
7396
+ (handler) => {
7397
+ element.addEventListener("scroll", handler, { passive: true });
7398
+ return () => element.removeEventListener("scroll", handler);
7399
+ }
7400
+ );
7401
+ }
7402
+ function createElementHorizontalScrollDriver(element) {
7403
+ const getMaxScroll = () => Math.max(0, element.scrollWidth - element.clientWidth);
7404
+ return createScrollDriverFromElement(
7405
+ getMaxScroll,
7406
+ () => element.scrollLeft,
7407
+ (x2) => {
7408
+ element.scrollLeft = x2;
7409
+ },
7410
+ (handler) => {
7411
+ element.addEventListener("scroll", handler, { passive: true });
7412
+ return () => element.removeEventListener("scroll", handler);
7413
+ }
7414
+ );
7415
+ }
7416
+ function attachSheetScrollDriver(sheet, driver = createNativeDocumentScrollDriver()) {
7417
+ rememberSheetScrollDriver(sheet, driver);
7418
+ const sequence = sheet.sequence;
7419
+ const syncPositionFromScroll = (progress) => {
7420
+ const length = val8(sequence.pointer.length);
7421
+ if (length <= 0) return;
7422
+ sequence.position = progress * length;
7423
+ };
7424
+ const untapScroll = driver.subscribe((progress) => {
7425
+ syncPositionFromScroll(progress);
7426
+ notifyPageScrollDrivenSequencePosition(sheet, sequence.position);
7427
+ });
7428
+ syncPositionFromScroll(driver.getProgress());
7429
+ return () => {
7430
+ untapScroll();
7431
+ };
7432
+ }
7433
+ function pageScrollProgressFromSequence(sheet) {
7434
+ const length = val8(sheet.sequence.pointer.length);
7435
+ if (length <= 0) return 0;
7436
+ return sheet.sequence.position / length;
7437
+ }
7438
+ function setPageScrollProgress(sheet, progress) {
7439
+ const length = val8(sheet.sequence.pointer.length);
7440
+ if (length <= 0) return;
7441
+ const clamped = Math.max(0, Math.min(1, progress));
7442
+ sheet.sequence.position = clamped * length;
7443
+ }
7444
+ function syncPageScrollToSequencePosition(sheet, driver = getSheetScrollDriver(sheet) ?? createNativeDocumentScrollDriver()) {
7445
+ const progress = pageScrollProgressFromSequence(sheet);
7446
+ driver.setProgress(progress);
7447
+ }
7448
+ function syncNativeDocumentScrollToSequencePosition(sheet) {
7449
+ syncPageScrollToSequencePosition(sheet, createNativeDocumentScrollDriver());
7450
+ }
7451
+
7452
+ // core/src/sheets/Sheet.ts
7453
+ var Sheet = class {
7454
+ constructor(template, instanceId) {
7455
+ this.template = template;
7456
+ this.instanceId = instanceId;
7457
+ __publicField(this, "_objects", new Atom9({}));
7458
+ __publicField(this, "_sequences", {});
7459
+ __publicField(this, "_activeSequenceVariant", new Atom9(
7460
+ DEFAULT_SEQUENCE_VARIANT
7461
+ ));
7462
+ /**
7463
+ * When Studio is open, it sets this to control which variant is used for
7464
+ * value resolution (preview). User code can still update `_activeSequenceVariant`
7465
+ * via `setActiveSequenceVariant()` without affecting the Studio preview.
7466
+ */
7467
+ __publicField(this, "_studioPreviewVariantOverride", new Atom9(void 0));
7468
+ __publicField(this, "_sequenceMode", new Atom9("time"));
7469
+ __publicField(this, "sequenceModeP", this._sequenceMode.pointer);
7470
+ __publicField(this, "_pageScrollDisposer");
7471
+ __publicField(this, "_customPageScrollDriver");
7472
+ __publicField(this, "_gsapBridgeDisposer");
7473
+ __publicField(this, "activeSequenceVariantP", this._activeSequenceVariant.pointer);
7474
+ __publicField(this, "effectiveActiveSequenceVariantD");
7475
+ __publicField(this, "address");
7476
+ __publicField(this, "publicApi");
7477
+ __publicField(this, "project");
7478
+ __publicField(this, "objectsP", this._objects.pointer);
7479
+ __publicField(this, "type", "Theatre_Sheet");
7480
+ __publicField(this, "_logger");
7481
+ this._logger = template.project._logger.named("Sheet", instanceId);
7482
+ this._logger._trace("creating sheet");
7483
+ this.project = template.project;
7484
+ this.address = {
7485
+ ...template.address,
7486
+ sheetInstanceId: this.instanceId
7487
+ };
7488
+ this.publicApi = new TheatreSheet(this);
7489
+ this.effectiveActiveSequenceVariantD = prism8(() => {
7490
+ const studioOverride = val9(this._studioPreviewVariantOverride.pointer);
7491
+ if (studioOverride !== void 0) {
7492
+ return studioOverride;
7493
+ }
7494
+ return val9(this._activeSequenceVariant.pointer);
7495
+ });
7496
+ }
7497
+ /**
6991
7498
  * @remarks At some point, we have to reconcile the concept of "an object"
6992
7499
  * with that of "an element."
6993
7500
  */
@@ -7035,6 +7542,7 @@ var Sheet = class {
7035
7542
  * this sheet instance from the project. Persisted state is kept.
7036
7543
  */
7037
7544
  unload() {
7545
+ this.disposeRuntimeIntegrations();
7038
7546
  for (const sequence of Object.values(this._sequences)) {
7039
7547
  sequence.pause();
7040
7548
  }
@@ -7046,10 +7554,13 @@ var Sheet = class {
7046
7554
  this.project._unloadSheetInstance(this);
7047
7555
  }
7048
7556
  getSequence(variant) {
7049
- const variantId = variant ?? val7(this._activeSequenceVariant.pointer);
7557
+ const variantId = variant ?? val9(this._activeSequenceVariant.pointer);
7050
7558
  if (!this._sequences[variantId]) {
7051
- const lengthD = prism7(() => {
7052
- const sheetState = val7(
7559
+ const lengthD = prism8(() => {
7560
+ if (val9(this._sequenceMode.pointer) === "page") {
7561
+ return PAGE_MODE_SEQUENCE_LENGTH;
7562
+ }
7563
+ const sheetState = val9(
7053
7564
  this.project.pointers.historic.sheetsById[this.address.sheetId]
7054
7565
  );
7055
7566
  const unsanitized = getSequenceStateFromSheet(
@@ -7058,8 +7569,11 @@ var Sheet = class {
7058
7569
  )?.length;
7059
7570
  return sanitizeSequenceLength(unsanitized);
7060
7571
  });
7061
- const subUnitsPerUnitD = prism7(() => {
7062
- const sheetState = val7(
7572
+ const subUnitsPerUnitD = prism8(() => {
7573
+ if (val9(this._sequenceMode.pointer) === "page") {
7574
+ return PAGE_MODE_SUB_UNITS_PER_UNIT;
7575
+ }
7576
+ const sheetState = val9(
7063
7577
  this.project.pointers.historic.sheetsById[this.address.sheetId]
7064
7578
  );
7065
7579
  const unsanitized = getSequenceStateFromSheet(
@@ -7111,6 +7625,44 @@ var Sheet = class {
7111
7625
  }
7112
7626
  this._studioPreviewVariantOverride.set(variantId);
7113
7627
  }
7628
+ getSequenceMode() {
7629
+ return this._sequenceMode.get();
7630
+ }
7631
+ setSequenceMode(mode) {
7632
+ if (this._sequenceMode.get() === mode) return;
7633
+ this._sequenceMode.set(mode);
7634
+ this.syncPageScrollDriver();
7635
+ if (this._gsapBridgeDisposer) {
7636
+ this._gsapBridgeDisposer();
7637
+ this._gsapBridgeDisposer = attachGsapSequenceBridge(this.publicApi);
7638
+ }
7639
+ }
7640
+ enableGsapSequenceBridge() {
7641
+ if (this._gsapBridgeDisposer) return;
7642
+ this._gsapBridgeDisposer = attachGsapSequenceBridge(this.publicApi);
7643
+ this.syncPageScrollDriver();
7644
+ }
7645
+ disposeRuntimeIntegrations() {
7646
+ this._pageScrollDisposer?.();
7647
+ this._pageScrollDisposer = void 0;
7648
+ this._gsapBridgeDisposer?.();
7649
+ this._gsapBridgeDisposer = void 0;
7650
+ }
7651
+ syncPageScrollDriver() {
7652
+ this._pageScrollDisposer?.();
7653
+ this._pageScrollDisposer = void 0;
7654
+ if (this.getSequenceMode() === "page") {
7655
+ const driver = this._customPageScrollDriver ?? createNativeDocumentScrollDriver();
7656
+ this._pageScrollDisposer = attachSheetScrollDriver(this.publicApi, driver);
7657
+ }
7658
+ }
7659
+ setPageScrollDriver(driver) {
7660
+ this._customPageScrollDriver = driver;
7661
+ this.syncPageScrollDriver();
7662
+ }
7663
+ getPageScrollDriver() {
7664
+ return this._customPageScrollDriver;
7665
+ }
7114
7666
  };
7115
7667
  var sanitizeSequenceLength = (len) => typeof len === "number" && isFinite(len) && len > 0 ? len : 10;
7116
7668
  var sanitizeSequenceSubUnitsPerUnit = (subs) => typeof subs === "number" && isInteger_default(subs) && subs >= 1 && subs <= 1e3 ? subs : 30;
@@ -7121,13 +7673,13 @@ var SheetTemplate = class {
7121
7673
  this.project = project;
7122
7674
  __publicField(this, "type", "Theatre_SheetTemplate");
7123
7675
  __publicField(this, "address");
7124
- __publicField(this, "_instances", new Atom9({}));
7676
+ __publicField(this, "_instances", new Atom10({}));
7125
7677
  __publicField(this, "instancesP", this._instances.pointer);
7126
- __publicField(this, "_objectTemplates", new Atom9({}));
7678
+ __publicField(this, "_objectTemplates", new Atom10({}));
7127
7679
  __publicField(this, "objectTemplatesP", this._objectTemplates.pointer);
7128
7680
  __publicField(this, "_pendingOutlineNamespaces", {});
7129
7681
  __publicField(this, "_sequenceVariants", [DEFAULT_SEQUENCE_VARIANT]);
7130
- __publicField(this, "_visibleInOutline", new Atom9(true));
7682
+ __publicField(this, "_visibleInOutline", new Atom10(true));
7131
7683
  __publicField(this, "visibleInOutlineP", this._visibleInOutline.pointer);
7132
7684
  this.address = { ...project.address, sheetId };
7133
7685
  }
@@ -7210,7 +7762,7 @@ var SheetTemplate = class {
7210
7762
 
7211
7763
  // core/src/projects/Project.ts
7212
7764
  import { PointerProxy } from "@unseenco/theatre-dataverse";
7213
- import { Atom as Atom10 } from "@unseenco/theatre-dataverse";
7765
+ import { Atom as Atom11 } from "@unseenco/theatre-dataverse";
7214
7766
 
7215
7767
  // shared/src/utils/delay.ts
7216
7768
  var delay = (dur) => new Promise((resolve) => setTimeout(resolve, dur));
@@ -7645,11 +8197,168 @@ async function initialiseProjectState(studio, project, onDiskState) {
7645
8197
  }
7646
8198
  }
7647
8199
  }
7648
- });
8200
+ });
8201
+ }
8202
+
8203
+ // shared/src/sheets/pageScrollContext.ts
8204
+ var defaultPageScrollContext = {
8205
+ scroller: null,
8206
+ axis: "vertical"
8207
+ };
8208
+ var activePageScrollContext = defaultPageScrollContext;
8209
+ function setActivePageScrollContext(context) {
8210
+ activePageScrollContext = {
8211
+ scroller: context.scroller,
8212
+ axis: context.axis ?? "vertical"
8213
+ };
8214
+ }
8215
+ function getActivePageScrollContext() {
8216
+ return activePageScrollContext;
8217
+ }
8218
+ function resolvePageScrollAxis(context = defaultPageScrollContext) {
8219
+ return context.axis ?? "vertical";
8220
+ }
8221
+ function resolvePageScrollScroller(st, defaultsScroller) {
8222
+ const surface = st;
8223
+ const fromSt = surface.scroller ?? surface.vars?.scroller;
8224
+ if (fromSt != null) {
8225
+ return fromSt;
8226
+ }
8227
+ if (defaultsScroller !== void 0) {
8228
+ return defaultsScroller;
8229
+ }
8230
+ return null;
8231
+ }
8232
+ function isWindowLike(value) {
8233
+ return typeof Window !== "undefined" && (value === window || value instanceof Window);
8234
+ }
8235
+ function isNativeDocumentScroller(scroller) {
8236
+ if (scroller == null) return true;
8237
+ if (isWindowLike(scroller)) return true;
8238
+ if (typeof document === "undefined") return false;
8239
+ return scroller === document.documentElement || scroller === document.body;
8240
+ }
8241
+ function pageScrollScrollersMatch(a2, b2) {
8242
+ if (a2 === b2) return true;
8243
+ if (isNativeDocumentScroller(a2) && isNativeDocumentScroller(b2)) return true;
8244
+ return false;
8245
+ }
8246
+ function isScrollTriggerHorizontal(st) {
8247
+ const surface = st;
8248
+ return surface.horizontal === true || surface.vars?.horizontal === true;
8249
+ }
8250
+ function isPageScrollTrigger(st, context = defaultPageScrollContext) {
8251
+ const axis = resolvePageScrollAxis(context);
8252
+ const stHorizontal = isScrollTriggerHorizontal(st);
8253
+ if (stHorizontal !== (axis === "horizontal")) {
8254
+ return false;
8255
+ }
8256
+ const stScroller = resolvePageScrollScroller(st, context.scroller);
8257
+ return pageScrollScrollersMatch(stScroller, context.scroller);
8258
+ }
8259
+ function isVerticalPageScrollTrigger(st, context = defaultPageScrollContext) {
8260
+ return isPageScrollTrigger(st, {
8261
+ scroller: context.scroller,
8262
+ axis: "vertical"
8263
+ });
8264
+ }
8265
+
8266
+ // shared/src/gsap/scrollTriggerLayout.ts
8267
+ function getNativeDocumentMaxScroll(axis = "vertical") {
8268
+ if (typeof document === "undefined" || typeof window === "undefined") {
8269
+ return 0;
8270
+ }
8271
+ const el = document.documentElement;
8272
+ if (axis === "horizontal") {
8273
+ const scrolling = document.scrollingElement ?? el;
8274
+ return Math.max(0, scrolling.scrollWidth - scrolling.clientWidth);
8275
+ }
8276
+ return Math.max(0, el.scrollHeight - window.innerHeight);
8277
+ }
8278
+ function getMaxScrollForScroller(scroller, axis = "vertical") {
8279
+ if (typeof document === "undefined" || typeof window === "undefined") {
8280
+ return 0;
8281
+ }
8282
+ if (isNativeDocumentScroller(scroller)) {
8283
+ return getNativeDocumentMaxScroll(axis);
8284
+ }
8285
+ const el = scroller;
8286
+ if (axis === "horizontal") {
8287
+ return Math.max(0, el.scrollWidth - el.clientWidth);
8288
+ }
8289
+ return Math.max(0, el.scrollHeight - el.clientHeight);
8290
+ }
8291
+ function getMaxScrollForPageScrollContext(scroller, axis = "vertical") {
8292
+ return getMaxScrollForScroller(scroller, axis);
8293
+ }
8294
+
8295
+ // shared/src/gsap/domElementHighlightTarget.ts
8296
+ function resolveDomElementHighlightTarget(target) {
8297
+ if (typeof document === "undefined") {
8298
+ return null;
8299
+ }
8300
+ if (target.type === "id") {
8301
+ return document.getElementById(target.id);
8302
+ }
8303
+ try {
8304
+ const match = document.querySelector(target.selector);
8305
+ return match instanceof Element ? match : null;
8306
+ } catch {
8307
+ return null;
8308
+ }
8309
+ }
8310
+
8311
+ // shared/src/sheets/remoteDomElementHighlight.ts
8312
+ var listeners = /* @__PURE__ */ new Set();
8313
+ function setRemoteDomElementHighlight(element) {
8314
+ for (const listener of listeners) {
8315
+ listener(element);
8316
+ }
8317
+ }
8318
+
8319
+ // shared/src/sheets/remotePageScrollMetrics.ts
8320
+ var remoteMetrics;
8321
+ var listeners2 = /* @__PURE__ */ new Set();
8322
+ function setRemotePageScrollMetrics(metrics) {
8323
+ const prev = remoteMetrics;
8324
+ remoteMetrics = metrics;
8325
+ if (prev?.maxScroll !== metrics?.maxScroll || prev?.axis !== metrics?.axis) {
8326
+ for (const listener of listeners2) {
8327
+ listener();
8328
+ }
8329
+ }
7649
8330
  }
7650
8331
 
7651
8332
  // core/src/internal/RemoteSync.ts
7652
- import { val as val8 } from "@unseenco/theatre-dataverse";
8333
+ import { pointerToPrism as pointerToPrism2, val as val10 } from "@unseenco/theatre-dataverse";
8334
+
8335
+ // core/src/internal/createDebouncedCallback.ts
8336
+ function createDebouncedCallback(fn2, delayMs) {
8337
+ let timeoutId;
8338
+ const clearPending = () => {
8339
+ if (timeoutId !== void 0) {
8340
+ clearTimeout(timeoutId);
8341
+ timeoutId = void 0;
8342
+ }
8343
+ };
8344
+ return {
8345
+ schedule() {
8346
+ clearPending();
8347
+ timeoutId = setTimeout(() => {
8348
+ timeoutId = void 0;
8349
+ fn2();
8350
+ }, delayMs);
8351
+ },
8352
+ flush() {
8353
+ if (timeoutId === void 0) return;
8354
+ clearPending();
8355
+ fn2();
8356
+ },
8357
+ cancel() {
8358
+ clearPending();
8359
+ }
8360
+ };
8361
+ }
7653
8362
 
7654
8363
  // core/src/internal/remoteEditor.ts
7655
8364
  function isRemoteEditorWindow() {
@@ -7657,6 +8366,10 @@ function isRemoteEditorWindow() {
7657
8366
  }
7658
8367
 
7659
8368
  // core/src/internal/RemoteSync.ts
8369
+ var REMOTE_HISTORIC_SYNC_DEBOUNCE_MS = 100;
8370
+ function shouldSyncPageScrollWhenApplyingTimelineUpdate(isEditor, sequenceMode) {
8371
+ return !isEditor && sequenceMode === "page";
8372
+ }
7660
8373
  var RemoteSync = class {
7661
8374
  constructor(project) {
7662
8375
  this.project = project;
@@ -7667,34 +8380,37 @@ var RemoteSync = class {
7667
8380
  __publicField(this, "objectUnsubs", /* @__PURE__ */ new Map());
7668
8381
  __publicField(this, "studio");
7669
8382
  __publicField(this, "activeSheet");
8383
+ __publicField(this, "historicSyncDebounce");
8384
+ __publicField(this, "lastBroadcastHistoricFingerprint");
8385
+ __publicField(this, "historicSyncUnsubs", []);
8386
+ __publicField(this, "remoteEditorActive", false);
8387
+ __publicField(this, "suppressTimelineBroadcast", false);
8388
+ __publicField(this, "listenerTimelineUnsub");
8389
+ __publicField(this, "listenerMetricsUnsubs", []);
8390
+ __publicField(this, "broadcastPageScrollMetrics");
7670
8391
  if (typeof BroadcastChannel === "undefined") return;
7671
8392
  this.channel = new BroadcastChannel(
7672
8393
  "theatre-remote:".concat(project.address.projectId)
7673
8394
  );
8395
+ this.channel.onmessage = (event) => {
8396
+ this._handleIncoming(event.data);
8397
+ };
7674
8398
  if (!this.isEditor) {
7675
- this.channel.onmessage = (event) => {
7676
- this._handleIncoming(event.data);
7677
- };
8399
+ this.listenerTimelineUnsub = onPageScrollDrivenSequencePosition(
8400
+ (sheet, position) => {
8401
+ this._broadcastTimelineFromListener(sheet, position);
8402
+ }
8403
+ );
7678
8404
  } else {
7679
8405
  const channel = this.channel;
7680
8406
  window.addEventListener("pagehide", () => {
7681
- const data = {};
7682
- if (this.studio) {
7683
- const projectId = this.project.address.projectId;
7684
- const historic = val8(
7685
- this.studio.atomP.historic.coreByProject[projectId]
7686
- );
7687
- const ahistoric = val8(
7688
- this.studio.atomP.ahistoric.coreByProject[projectId]
7689
- );
7690
- if (historic) {
7691
- data.historic = JSON.parse(JSON.stringify(historic));
7692
- if (ahistoric) {
7693
- data.ahistoric = JSON.parse(JSON.stringify(ahistoric));
7694
- }
7695
- }
8407
+ this.historicSyncDebounce?.flush();
8408
+ const data = this._readHistoricSnapshotFromStudio();
8409
+ if (data && this.lastBroadcastHistoricFingerprint === fingerprintHistoricSnapshot(data)) {
8410
+ channel.postMessage({ event: "disconnect", data: {} });
8411
+ return;
7696
8412
  }
7697
- channel.postMessage({ event: "disconnect", data });
8413
+ channel.postMessage({ event: "disconnect", data: data ?? {} });
7698
8414
  });
7699
8415
  }
7700
8416
  }
@@ -7745,8 +8461,13 @@ var RemoteSync = class {
7745
8461
  }
7746
8462
  attachStudio(studio) {
7747
8463
  this.studio = studio;
7748
- if (!this.isEditor || !this.channel) return;
8464
+ if (!this.channel) return;
8465
+ if (!this.isEditor) {
8466
+ this._attachListenerPageScrollMetricsBroadcast();
8467
+ return;
8468
+ }
7749
8469
  const channel = this.channel;
8470
+ channel.postMessage({ event: "editorHello", data: {} });
7750
8471
  const projectId = this.project.address.projectId;
7751
8472
  studio.publicApi.onSelectionChange((selection) => {
7752
8473
  for (const item of selection) {
@@ -7771,7 +8492,7 @@ var RemoteSync = class {
7771
8492
  let lastPosition;
7772
8493
  const ticker = getCoreTicker();
7773
8494
  const pollTimelinePosition = () => {
7774
- if (this.activeSheet) {
8495
+ if (this.activeSheet && !this.suppressTimelineBroadcast) {
7775
8496
  const position = this.activeSheet.publicApi.sequence.position;
7776
8497
  if (position !== lastPosition) {
7777
8498
  lastPosition = position;
@@ -7785,9 +8506,117 @@ var RemoteSync = class {
7785
8506
  ticker.onNextTick(pollTimelinePosition);
7786
8507
  };
7787
8508
  ticker.onNextTick(pollTimelinePosition);
8509
+ this.historicSyncDebounce = createDebouncedCallback(() => {
8510
+ this._broadcastHistoricSnapshot();
8511
+ }, REMOTE_HISTORIC_SYNC_DEBOUNCE_MS);
8512
+ const scheduleHistoricSync = () => {
8513
+ this.historicSyncDebounce?.schedule();
8514
+ };
8515
+ void studio.initialized.then(() => {
8516
+ this.historicSyncUnsubs.push(
8517
+ pointerToPrism2(studio.atomP.historic.coreByProject[projectId]).onChange(
8518
+ studio.ticker,
8519
+ scheduleHistoricSync,
8520
+ true
8521
+ ),
8522
+ pointerToPrism2(
8523
+ studio.atomP.ahistoric.coreByProject[projectId]
8524
+ ).onChange(studio.ticker, scheduleHistoricSync, true)
8525
+ );
8526
+ });
8527
+ }
8528
+ _readHistoricSnapshotFromStudio() {
8529
+ if (!this.studio) return void 0;
8530
+ const projectId = this.project.address.projectId;
8531
+ const historic = val10(
8532
+ this.studio.atomP.historic.coreByProject[projectId]
8533
+ );
8534
+ if (!historic) return void 0;
8535
+ const data = {
8536
+ historic: JSON.parse(JSON.stringify(historic))
8537
+ };
8538
+ const ahistoric = val10(
8539
+ this.studio.atomP.ahistoric.coreByProject[projectId]
8540
+ );
8541
+ if (ahistoric) {
8542
+ data.ahistoric = JSON.parse(JSON.stringify(ahistoric));
8543
+ }
8544
+ return data;
8545
+ }
8546
+ _broadcastHistoricSnapshot(options) {
8547
+ if (!this.channel) return;
8548
+ const data = this._readHistoricSnapshotFromStudio();
8549
+ if (!data) return;
8550
+ const fingerprint = fingerprintHistoricSnapshot(data);
8551
+ if (!options?.force && fingerprint === this.lastBroadcastHistoricFingerprint) {
8552
+ return;
8553
+ }
8554
+ this.lastBroadcastHistoricFingerprint = fingerprint;
8555
+ const message = { event: "updateHistoric", data };
8556
+ this.channel.postMessage(message);
8557
+ }
8558
+ _applyHistoricSnapshot(historic, ahistoric) {
8559
+ if (!this.studio) return;
8560
+ const projectId = this.project.address.projectId;
8561
+ this.studio.transaction(({ drafts }) => {
8562
+ drafts.historic.coreByProject[projectId] = historic;
8563
+ if (ahistoric) {
8564
+ drafts.ahistoric.coreByProject[projectId] = ahistoric;
8565
+ }
8566
+ drafts.ephemeral.coreByProject[projectId].loadingState = {
8567
+ type: "loaded"
8568
+ };
8569
+ });
8570
+ }
8571
+ _broadcastTimelineFromListener(sheet, position) {
8572
+ if (this.isEditor || !this.channel || !this.remoteEditorActive || this.suppressTimelineBroadcast) {
8573
+ return;
8574
+ }
8575
+ const registered = this.sheets.get(sheet.address.sheetId);
8576
+ if (!registered || registered.getSequenceMode() !== "page") return;
8577
+ const message = {
8578
+ event: "updateTimeline",
8579
+ data: { sheet: sheet.address.sheetId, position }
8580
+ };
8581
+ this.channel.postMessage(message);
8582
+ }
8583
+ _postPageScrollMetrics() {
8584
+ if (!this.remoteEditorActive || !this.channel) return;
8585
+ const ctx = getActivePageScrollContext();
8586
+ const axis = resolvePageScrollAxis(ctx);
8587
+ const maxScroll = getMaxScrollForPageScrollContext(ctx.scroller, axis);
8588
+ const message = {
8589
+ event: "pageScrollMetrics",
8590
+ data: { maxScroll, axis }
8591
+ };
8592
+ this.channel.postMessage(message);
8593
+ }
8594
+ _attachListenerPageScrollMetricsBroadcast() {
8595
+ if (this.isEditor || !this.channel) return;
8596
+ const broadcast = () => this._postPageScrollMetrics();
8597
+ this.broadcastPageScrollMetrics = broadcast;
8598
+ const onScroll = () => broadcast();
8599
+ document.addEventListener("scroll", onScroll, { passive: true, capture: true });
8600
+ const onResize = () => broadcast();
8601
+ window.addEventListener("resize", onResize);
8602
+ const ScrollTrigger = globalThis.ScrollTrigger;
8603
+ ScrollTrigger?.addEventListener?.("refresh", broadcast);
8604
+ broadcast();
8605
+ this.listenerMetricsUnsubs.push(() => {
8606
+ document.removeEventListener("scroll", onScroll, { capture: true });
8607
+ window.removeEventListener("resize", onResize);
8608
+ ScrollTrigger?.removeEventListener?.("refresh", broadcast);
8609
+ });
7788
8610
  }
7789
8611
  _handleIncoming(msg) {
7790
8612
  switch (msg.event) {
8613
+ case "editorHello": {
8614
+ if (this.isEditor) break;
8615
+ this.remoteEditorActive = true;
8616
+ this.broadcastPageScrollMetrics?.();
8617
+ this._broadcastHistoricSnapshot({ force: true });
8618
+ break;
8619
+ }
7791
8620
  case "setSheet": {
7792
8621
  const sheet = this.sheets.get(msg.data.sheet);
7793
8622
  if (sheet && this.studio) {
@@ -7812,32 +8641,67 @@ var RemoteSync = class {
7812
8641
  const sheet = this.sheets.get(msg.data.sheet);
7813
8642
  if (sheet) {
7814
8643
  this.activeSheet = sheet;
8644
+ this.suppressTimelineBroadcast = true;
7815
8645
  sheet.publicApi.sequence.position = msg.data.position;
8646
+ if (shouldSyncPageScrollWhenApplyingTimelineUpdate(
8647
+ this.isEditor,
8648
+ sheet.getSequenceMode()
8649
+ )) {
8650
+ syncPageScrollToSequencePosition(sheet.publicApi);
8651
+ }
8652
+ requestAnimationFrame(() => {
8653
+ this.suppressTimelineBroadcast = false;
8654
+ });
8655
+ }
8656
+ break;
8657
+ }
8658
+ case "pageScrollMetrics": {
8659
+ if (!this.isEditor) break;
8660
+ const { maxScroll, axis } = msg.data;
8661
+ setRemotePageScrollMetrics({ maxScroll, axis });
8662
+ break;
8663
+ }
8664
+ case "highlightDomTarget": {
8665
+ if (this.isEditor || !this.remoteEditorActive) break;
8666
+ const target = msg.data.target;
8667
+ const element = resolveDomElementHighlightTarget(target);
8668
+ setRemoteDomElementHighlight(element);
8669
+ break;
8670
+ }
8671
+ case "clearDomHighlight": {
8672
+ if (this.isEditor || !this.remoteEditorActive) break;
8673
+ setRemoteDomElementHighlight(null);
8674
+ break;
8675
+ }
8676
+ case "updateHistoric": {
8677
+ const { historic, ahistoric } = msg.data;
8678
+ if (historic) {
8679
+ this._applyHistoricSnapshot(historic, ahistoric);
7816
8680
  }
7817
8681
  break;
7818
8682
  }
7819
8683
  case "disconnect": {
7820
8684
  const { historic, ahistoric } = msg.data;
7821
- if (historic && this.studio) {
7822
- const projectId = this.project.address.projectId;
7823
- this.studio.transaction(({ drafts }) => {
7824
- drafts.historic.coreByProject[projectId] = historic;
7825
- if (ahistoric) {
7826
- drafts.ahistoric.coreByProject[projectId] = ahistoric;
7827
- }
7828
- drafts.ephemeral.coreByProject[projectId].loadingState = {
7829
- type: "loaded"
7830
- };
7831
- });
8685
+ if (historic) {
8686
+ this._applyHistoricSnapshot(historic, ahistoric);
7832
8687
  }
7833
8688
  for (const obj of this.objects.values()) {
7834
8689
  obj.setRemoteOverride({});
7835
8690
  }
8691
+ if (!this.isEditor) {
8692
+ this.remoteEditorActive = false;
8693
+ setRemoteDomElementHighlight(null);
8694
+ } else {
8695
+ setRemotePageScrollMetrics(void 0);
8696
+ }
7836
8697
  break;
7837
8698
  }
7838
8699
  }
7839
8700
  }
7840
8701
  };
8702
+ function fingerprintHistoricSnapshot(data) {
8703
+ return JSON.stringify(data);
8704
+ }
7841
8705
 
7842
8706
  // core/src/_coreLogger.ts
7843
8707
  function noop2() {
@@ -7864,7 +8728,7 @@ function _coreLogger(config) {
7864
8728
  }
7865
8729
 
7866
8730
  // shared/src/utils/assets.ts
7867
- import { val as val9 } from "@unseenco/theatre-dataverse";
8731
+ import { val as val11 } from "@unseenco/theatre-dataverse";
7868
8732
 
7869
8733
  // shared/src/utils/forEachDeep.ts
7870
8734
  function forEachPropDeep(m, fn2, startingPath = []) {
@@ -7934,7 +8798,7 @@ var Project = class {
7934
8798
  __publicField(this, "_studioReadyDeferred");
7935
8799
  __publicField(this, "_assetStorageReadyDeferred");
7936
8800
  __publicField(this, "_readyPromise");
7937
- __publicField(this, "_sheetTemplates", new Atom10({}));
8801
+ __publicField(this, "_sheetTemplates", new Atom11({}));
7938
8802
  __publicField(this, "sheetTemplatesP", this._sheetTemplates.pointer);
7939
8803
  __publicField(this, "_studio");
7940
8804
  __publicField(this, "_onDiskStateAtom");
@@ -7947,7 +8811,7 @@ var Project = class {
7947
8811
  this._logger.traceDev("creating project");
7948
8812
  this.address = { projectId: id };
7949
8813
  this._remoteSync = new RemoteSync(this);
7950
- const onDiskStateAtom = new Atom10({
8814
+ const onDiskStateAtom = new Atom11({
7951
8815
  ahistoric: {
7952
8816
  ahistoricStuff: ""
7953
8817
  },
@@ -8002,14 +8866,14 @@ var Project = class {
8002
8866
  if (typeof window === "undefined") {
8003
8867
  if (process.env.NODE_ENV === "production") {
8004
8868
  console.error(
8005
- 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. ') + "You can safely ignore this message if you're developing a Next.js/Remix project in development mode. But if you are shipping to your end-users, then you need to set config.state, otherwise your project's state will be empty and nothing will animate. Learn more at https://www.theatrejs.com/docs/latest/manual/projects#state"
8869
+ 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. ') + "You can safely ignore this message if you're developing a Next.js/Remix project in development mode. But if you are shipping to your end-users, then you need to set config.state, otherwise your project's state will be empty and nothing will animate. Learn more at https://unseen-theatre.netlify.app/docs/guide/manual/projects#state"
8006
8870
  );
8007
8871
  }
8008
8872
  } else {
8009
8873
  setTimeout(() => {
8010
8874
  if (!this._studio) {
8011
8875
  throw new Error(
8012
- 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. This is fine ') + "while you are using @unseenco/theatre-core along with @unseenco/theatre-studio. But since @unseenco/theatre-studio " + 'is not loaded, the state of project "'.concat(id, '" will be empty.\n\n') + "To fix this, you need to add @unseenco/theatre-studio into the bundle and export the project's state. Learn how to do that at https://www.theatrejs.com/docs/latest/manual/projects#state\n"
8876
+ 'Argument config.state in Theatre.getProject("'.concat(id, '", config) is empty. This is fine ') + "while you are using @unseenco/theatre-core along with @unseenco/theatre-studio. But since @unseenco/theatre-studio " + 'is not loaded, the state of project "'.concat(id, '" will be empty.\n\n') + "To fix this, you need to add @unseenco/theatre-studio into the bundle and export the project's state. Learn how to do that at https://unseen-theatre.netlify.app/docs/guide/manual/projects#state\n"
8013
8877
  );
8014
8878
  }
8015
8879
  }, 1e3);
@@ -8073,6 +8937,7 @@ var Project = class {
8073
8937
  template.setVisibleInOutline(opts.visible);
8074
8938
  }
8075
8939
  const sheet = template.getInstance(instanceId);
8940
+ applySheetRuntimeOptions(sheet, opts);
8076
8941
  this._remoteSync.registerSheet(sheet);
8077
8942
  return sheet;
8078
8943
  }
@@ -8353,13 +9218,13 @@ var Project = class {
8353
9218
  };
8354
9219
 
8355
9220
  // shared/src/utils/sanitizers.ts
8356
- var _validateSym = (val12, thingy, range) => {
8357
- if (typeof val12 !== "string") {
8358
- return "".concat(thingy, " must be a string. ").concat(userReadableTypeOfValue_default(val12), " given.");
8359
- } else if (val12.trim().length !== val12.length) {
8360
- return "".concat(thingy, " must not have leading or trailing spaces. '").concat(val12, "' given.");
8361
- } else if (val12.length < range[0] || val12.length > range[1]) {
8362
- return "".concat(thingy, " must have between ").concat(range[0], " and ").concat(range[1], " characters. '").concat(val12, "' given.");
9221
+ var _validateSym = (val13, thingy, range) => {
9222
+ if (typeof val13 !== "string") {
9223
+ return "".concat(thingy, " must be a string. ").concat(userReadableTypeOfValue_default(val13), " given.");
9224
+ } else if (val13.trim().length !== val13.length) {
9225
+ return "".concat(thingy, " must not have leading or trailing spaces. '").concat(val13, "' given.");
9226
+ } else if (val13.length < range[0] || val13.length > range[1]) {
9227
+ return "".concat(thingy, " must have between ").concat(range[0], " and ").concat(range[1], " characters. '").concat(val13, "' given.");
8363
9228
  }
8364
9229
  };
8365
9230
  var validateName = (name, thingy, shouldThrow = false) => {
@@ -8476,192 +9341,60 @@ var TheatreProject = class {
8476
9341
  // core/src/coreExports.ts
8477
9342
  var import_fast_deep_equal3 = __toESM(require_fast_deep_equal());
8478
9343
  import { isPointer } from "@unseenco/theatre-dataverse";
8479
- import { isPrism, pointerToPrism as pointerToPrism2 } from "@unseenco/theatre-dataverse";
8480
-
8481
- // shared/src/gsap/applyTimelineChildTiming.ts
8482
- function applyTimelineChildTimingToGsap(rootAnimation, timelineChildren, childById, onRebuild) {
8483
- if (!timelineChildren.length) return true;
8484
- let ok = applyTimelineChildTimingToGsapInner(
8485
- rootAnimation,
8486
- timelineChildren,
8487
- childById
8488
- );
8489
- if (!ok && onRebuild) {
8490
- const rebuilt = onRebuild();
8491
- if (rebuilt) {
8492
- ok = applyTimelineChildTimingToGsapInner(
8493
- rebuilt,
8494
- timelineChildren,
8495
- childById
8496
- );
8497
- }
8498
- }
8499
- return ok;
8500
- }
8501
- function applyTimelineChildTimingToGsapInner(rootAnimation, timelineChildren, childById) {
8502
- if (!childById || timelineChildren.length === 0) return true;
8503
- let ok = true;
8504
- for (const childState of timelineChildren) {
8505
- const tween = childById.get(childState.childId);
8506
- if (!tween) continue;
8507
- try {
8508
- const t2 = tween;
8509
- if (typeof t2.startTime === "function") {
8510
- t2.startTime(childState.localStart);
8511
- }
8512
- if (typeof t2.duration === "function") {
8513
- t2.duration(childState.localDuration);
8514
- }
8515
- } catch {
8516
- ok = false;
8517
- }
8518
- }
8519
- return ok;
8520
- }
9344
+ import { isPrism, pointerToPrism as pointerToPrism3 } from "@unseenco/theatre-dataverse";
8521
9345
 
8522
- // shared/src/gsap/syncGsapClipProgress.ts
8523
- function getGsapAnimationTargetKey(animation, fallbackAnimationId) {
8524
- const tween = animation;
8525
- const targets = tween.targets?.();
8526
- if (targets && targets.length > 0) {
8527
- return targets[0];
8528
- }
8529
- return fallbackAnimationId;
9346
+ // core/src/sheets/theatrePageScroll.ts
9347
+ var pageScrollConfig = {};
9348
+ function buildPageScrollContextFromConfig() {
9349
+ return {
9350
+ scroller: pageScrollConfig.scroller ?? defaultPageScrollContext.scroller,
9351
+ axis: pageScrollConfig.axis ?? defaultPageScrollContext.axis
9352
+ };
8530
9353
  }
8531
- function syncRegisteredGsapAnimationsForClips(sequencePosition, clips) {
8532
- const enriched = [];
8533
- for (const clip of clips) {
8534
- const entry = getAnimationEntryBySheetAddressKey(
8535
- clip.sheetObjectAddressKey,
8536
- clip.gsapAnimationId
8537
- );
8538
- if (!entry?.animation) continue;
8539
- enriched.push({
8540
- ...clip,
8541
- entry,
8542
- targetKey: getGsapAnimationTargetKey(
8543
- entry.animation,
8544
- clip.gsapAnimationId
8545
- )
8546
- });
8547
- }
8548
- const byTarget = /* @__PURE__ */ new Map();
8549
- for (const item of enriched) {
8550
- const list = byTarget.get(item.targetKey) ?? [];
8551
- list.push(item);
8552
- byTarget.set(item.targetKey, list);
9354
+ function configureTheatrePageScroll(config) {
9355
+ const prev = pageScrollConfig;
9356
+ pageScrollConfig = {
9357
+ scroller: config.scroller ?? prev.scroller,
9358
+ axis: config.axis ?? prev.axis
9359
+ };
9360
+ if (config.scroller !== void 0 || config.axis !== void 0) {
9361
+ setActivePageScrollContext(buildPageScrollContextFromConfig());
8553
9362
  }
8554
- for (const group of byTarget.values()) {
8555
- const sorted = [...group].sort((a2, b2) => a2.start - b2.start);
8556
- for (const clip of sorted) {
8557
- const progress = gsapClipSyncProgress(sequencePosition, clip);
8558
- const entry = clip.entry;
8559
- const animation = entry.animation;
8560
- if (entry.kind === "timeline" && clip.timelineChildren && clip.timelineChildren.length > 0) {
8561
- applyTimelineChildTimingToGsap(
8562
- entry.animation,
8563
- clip.timelineChildren,
8564
- entry.timelineChildById,
8565
- entry.onRebuildTimeline
8566
- );
8567
- const span = clip.timelineSpan ?? readGsapTweenTimelineDuration(entry.animation);
8568
- const t2 = progress * Math.max(span, 0.01);
8569
- if (typeof animation.time === "function") {
8570
- animation.time(t2, true);
8571
- } else {
8572
- animation.progress?.(progress, true);
8573
- }
8574
- } else {
8575
- animation.progress?.(progress, true);
8576
- }
9363
+ return {
9364
+ reset() {
9365
+ pageScrollConfig = prev;
9366
+ setActivePageScrollContext(
9367
+ prev.scroller !== void 0 || prev.axis !== void 0 ? {
9368
+ scroller: prev.scroller ?? defaultPageScrollContext.scroller,
9369
+ axis: prev.axis ?? defaultPageScrollContext.axis
9370
+ } : defaultPageScrollContext
9371
+ );
8577
9372
  }
8578
- }
9373
+ };
8579
9374
  }
8580
- function readGsapTweenTimelineDuration(animation) {
8581
- const tween = animation;
8582
- const total = tween.totalDuration?.();
8583
- if (typeof total === "number" && total > 0) return total;
8584
- const d2 = tween.duration();
8585
- return d2 > 0 ? d2 : 1;
9375
+ function getTheatrePageScrollConfig() {
9376
+ return pageScrollConfig;
8586
9377
  }
8587
-
8588
- // shared/src/gsap/gsapStudioRegistryRevision.ts
8589
- import { Atom as Atom11 } from "@unseenco/theatre-dataverse";
8590
- var revisionAtom = new Atom11(0);
8591
- var gsapStudioRegistryRevisionPointer = revisionAtom.pointer;
8592
-
8593
- // shared/src/gsap/gsapAnimationRegistry.ts
8594
- var DEFAULT_SHEET_INSTANCE_ID = "default";
8595
- var REGISTRY_KEY = "__unseenco_theatre_gsap_animationRegistry__";
8596
- function sheetObjectAddressKeyFromParts(address) {
8597
- const sheetInstanceId = address.sheetInstanceId ?? DEFAULT_SHEET_INSTANCE_ID;
8598
- return "".concat(address.projectId, "|").concat(address.sheetId, "|").concat(sheetInstanceId, "|").concat(address.objectKey);
9378
+ function getTheatrePageScrollContext() {
9379
+ const ctx = buildPageScrollContextFromConfig();
9380
+ setActivePageScrollContext(ctx);
9381
+ return ctx;
8599
9382
  }
8600
- function getStore() {
8601
- const g2 = globalThis;
8602
- if (!g2[REGISTRY_KEY]) {
8603
- g2[REGISTRY_KEY] = {
8604
- bySheetAddress: /* @__PURE__ */ new Map()
8605
- };
9383
+ function createDefaultPageScrollDriver(scroller = getTheatrePageScrollContext().scroller, axis = resolvePageScrollAxis(getTheatrePageScrollContext())) {
9384
+ if (typeof document !== "undefined" && scroller != null && !isNativeDocumentScroller(scroller) && scroller instanceof HTMLElement) {
9385
+ return axis === "horizontal" ? createElementHorizontalScrollDriver(scroller) : createElementScrollDriver(scroller);
8606
9386
  }
8607
- return g2[REGISTRY_KEY];
9387
+ return axis === "horizontal" ? createNativeDocumentHorizontalScrollDriver() : createNativeDocumentScrollDriver();
8608
9388
  }
8609
- function getAnimationEntryBySheetAddressKey(sheetObjectAddressKey, animationId) {
8610
- return getStore().bySheetAddress.get(sheetObjectAddressKey)?.get(animationId);
8611
- }
8612
-
8613
- // shared/src/gsap/subscribeGsapClipSyncAtPlayhead.ts
8614
- import { pointerToPrism, prism as prism8, val as val10 } from "@unseenco/theatre-dataverse";
8615
-
8616
- // shared/src/gsap/syncGsapClipsAtSequencePosition.ts
8617
- function syncGsapClipsAtSequencePosition(position, clips) {
8618
- syncRegisteredGsapAnimationsForClips(
8619
- position,
8620
- clips
8621
- );
8622
- }
8623
-
8624
- // shared/src/gsap/subscribeGsapClipSyncAtPlayhead.ts
8625
- function subscribeGsapClipSyncAtPlayhead(source) {
8626
- const positionPointer = source.pointer.position;
8627
- const positionPrism = pointerToPrism(positionPointer);
8628
- const clipsPrism = prism8(() => source.getGsapClipTimings());
8629
- const syncNow = () => {
8630
- const clips = clipsPrism.getValue();
8631
- if (clips.length === 0) return;
8632
- syncGsapClipsAtSequencePosition(val10(positionPointer), clips);
8633
- };
8634
- const untapPosition = positionPrism.onStale(syncNow);
8635
- const untapClips = clipsPrism.onStale(syncNow);
8636
- syncNow();
9389
+ function attachTheatrePageScroll(sheet, options = {}) {
9390
+ const ctx = getTheatrePageScrollContext();
9391
+ const driver = options.driver ?? createDefaultPageScrollDriver(ctx.scroller, resolvePageScrollAxis(ctx));
9392
+ sheet.setPageScrollDriver(driver);
8637
9393
  return () => {
8638
- untapPosition();
8639
- untapClips();
9394
+ sheet.setPageScrollDriver(void 0);
8640
9395
  };
8641
9396
  }
8642
9397
 
8643
- // core/src/gsap/attachGsapSequenceBridge.ts
8644
- function attachGsapSequenceBridge(sheet) {
8645
- const sequence = sheet.sequence;
8646
- const sheetAddress = privateAPI(sheet).address;
8647
- return subscribeGsapClipSyncAtPlayhead({
8648
- pointer: sequence.pointer,
8649
- getGsapClipTimings: () => sequence.__experimental_getGsapClips().map(({ objectKey, clip }) => ({
8650
- sheetObjectAddressKey: sheetObjectAddressKeyFromParts({
8651
- projectId: sheetAddress.projectId,
8652
- sheetId: sheetAddress.sheetId,
8653
- sheetInstanceId: sheetAddress.sheetInstanceId,
8654
- objectKey
8655
- }),
8656
- gsapAnimationId: clip.gsapAnimationId,
8657
- start: clip.start,
8658
- duration: clip.duration,
8659
- timelineChildren: clip.timelineChildren,
8660
- timelineSpan: clip.timelineSpan
8661
- }))
8662
- });
8663
- }
8664
-
8665
9398
  // core/src/coreExports.ts
8666
9399
  function getProject(id, config = {}) {
8667
9400
  const existingProject = projectsSingleton_default.get(id);
@@ -8703,7 +9436,7 @@ var shallowValidateOnDiskState = (projectId, s2) => {
8703
9436
  throw new InvalidArgumentError(
8704
9437
  "Error validating conf.state in Theatre.getProject(".concat(JSON.stringify(
8705
9438
  projectId
8706
- ), ", conf). The state seems to be formatted in a way that is unreadable to Theatre.js. Read more at https://www.theatrejs.com/docs/latest/manual/projects#state")
9439
+ ), ", conf). The state seems to be formatted in a way that is unreadable to Theatre.js. Read more at https://unseen-theatre.netlify.app/docs/guide/manual/projects#state")
8707
9440
  );
8708
9441
  }
8709
9442
  };
@@ -8743,7 +9476,7 @@ var validateProjectIdOrThrow = (value) => {
8743
9476
  function onChange(pointer3, callback, rafDriver) {
8744
9477
  const ticker = rafDriver ? privateAPI(rafDriver).ticker : getCoreTicker();
8745
9478
  if (isPointer(pointer3)) {
8746
- const pr = pointerToPrism2(pointer3);
9479
+ const pr = pointerToPrism3(pointer3);
8747
9480
  return pr.onChange(ticker, callback, true);
8748
9481
  } else if (isPrism(pointer3)) {
8749
9482
  return pointer3.onChange(ticker, callback, true);
@@ -8753,9 +9486,9 @@ function onChange(pointer3, callback, rafDriver) {
8753
9486
  );
8754
9487
  }
8755
9488
  }
8756
- function val11(pointer3) {
9489
+ function val12(pointer3) {
8757
9490
  if (isPointer(pointer3)) {
8758
- return pointerToPrism2(pointer3).getValue();
9491
+ return pointerToPrism3(pointer3).getValue();
8759
9492
  } else {
8760
9493
  throw new Error("Called val(p) where p is not a pointer.");
8761
9494
  }
@@ -8770,7 +9503,7 @@ var CoreBundle = class {
8770
9503
  return "Theatre_CoreBundle";
8771
9504
  }
8772
9505
  get version() {
8773
- return "0.4.3";
9506
+ return "0.5.0";
8774
9507
  }
8775
9508
  getBitsForStudio(studio, callback) {
8776
9509
  if (this._studio) {
@@ -8819,17 +9552,44 @@ function registerCoreBundle() {
8819
9552
  }
8820
9553
  }
8821
9554
  export {
9555
+ PAGE_MODE_SEQUENCE_LENGTH,
9556
+ PAGE_MODE_SUB_UNITS_PER_UNIT,
8822
9557
  attachGsapSequenceBridge,
9558
+ attachSheetScrollDriver,
9559
+ attachTheatrePageScroll,
9560
+ configureTheatrePageScroll,
9561
+ createDefaultPageScrollDriver,
9562
+ createElementHorizontalScrollDriver,
9563
+ createElementScrollDriver,
9564
+ createNativeDocumentHorizontalScrollDriver,
9565
+ createNativeDocumentScrollDriver,
8823
9566
  createRafDriver,
9567
+ defaultPageScrollContext,
9568
+ getActivePageScrollContext,
8824
9569
  getProject,
9570
+ getSheetScrollDriver,
9571
+ getTheatreCoreRafDriver,
9572
+ getTheatrePageScrollConfig,
9573
+ getTheatrePageScrollContext,
9574
+ isNativeDocumentScroller,
9575
+ isPageScrollTrigger,
8825
9576
  isRemoteEditorWindow,
9577
+ isVerticalPageScrollTrigger,
8826
9578
  notify,
8827
9579
  onChange,
9580
+ pageScrollProgressFromSequence,
9581
+ pageScrollScrollersMatch,
8828
9582
  privateAPI,
9583
+ resolvePageScrollAxis,
9584
+ resolvePageScrollScroller,
9585
+ setActivePageScrollContext,
8829
9586
  setCoreRafDriver,
9587
+ setPageScrollProgress,
8830
9588
  setPrivateAPI,
9589
+ syncNativeDocumentScrollToSequencePosition,
9590
+ syncPageScrollToSequencePosition,
8831
9591
  propTypes_exports as types,
8832
- val11 as val
9592
+ val12 as val
8833
9593
  };
8834
9594
  /*! Bundled license information:
8835
9595