@editframe/elements 0.35.0-beta → 0.36.0-beta
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/elements/EFImage.js +11 -2
- package/dist/elements/EFImage.js.map +1 -1
- package/dist/elements/EFTemporal.js +1 -0
- package/dist/elements/EFTemporal.js.map +1 -1
- package/dist/elements/EFTimegroup.d.ts +40 -6
- package/dist/elements/EFTimegroup.js +127 -8
- package/dist/elements/EFTimegroup.js.map +1 -1
- package/dist/elements/updateAnimations.js +38 -15
- package/dist/elements/updateAnimations.js.map +1 -1
- package/dist/gui/EFWorkbench.js +10 -12
- package/dist/gui/EFWorkbench.js.map +1 -1
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/TWMixin.js.map +1 -1
- package/dist/preview/FrameController.js +6 -1
- package/dist/preview/FrameController.js.map +1 -1
- package/dist/preview/encoding/canvasEncoder.js.map +1 -1
- package/dist/preview/encoding/mainThreadEncoder.js +3 -0
- package/dist/preview/encoding/mainThreadEncoder.js.map +1 -1
- package/dist/preview/renderTimegroupPreview.js +57 -55
- package/dist/preview/renderTimegroupPreview.js.map +1 -1
- package/dist/preview/renderTimegroupToCanvas.js +22 -23
- package/dist/preview/renderTimegroupToCanvas.js.map +1 -1
- package/dist/preview/renderTimegroupToVideo.d.ts +2 -1
- package/dist/preview/renderTimegroupToVideo.js +77 -40
- package/dist/preview/renderTimegroupToVideo.js.map +1 -1
- package/dist/preview/rendering/renderToImage.d.ts +1 -0
- package/dist/preview/rendering/renderToImage.js +1 -26
- package/dist/preview/rendering/renderToImage.js.map +1 -1
- package/dist/preview/rendering/renderToImageForeignObject.js +34 -6
- package/dist/preview/rendering/renderToImageForeignObject.js.map +1 -1
- package/dist/preview/rendering/serializeTimelineDirect.js +379 -0
- package/dist/preview/rendering/serializeTimelineDirect.js.map +1 -0
- package/dist/render/EFRenderAPI.js +45 -0
- package/dist/render/EFRenderAPI.js.map +1 -1
- package/dist/style.css +12 -0
- package/package.json +2 -2
|
@@ -8,7 +8,7 @@ import { efContext } from "../gui/efContext.js";
|
|
|
8
8
|
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
9
9
|
import { TWMixin } from "../gui/TWMixin2.js";
|
|
10
10
|
import { isTracingEnabled, withSpan } from "../otel/tracingHelpers.js";
|
|
11
|
-
import { FrameController } from "../preview/FrameController.js";
|
|
11
|
+
import { FrameController, PRIORITY_DEFAULT } from "../preview/FrameController.js";
|
|
12
12
|
import { renderTemporalAudio } from "./renderTemporalAudio.js";
|
|
13
13
|
import { EFTargetable } from "./TargetController.js";
|
|
14
14
|
import { deepGetMediaElements } from "./EFMedia.js";
|
|
@@ -251,6 +251,65 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
251
251
|
}
|
|
252
252
|
`;
|
|
253
253
|
}
|
|
254
|
+
#initializer;
|
|
255
|
+
/**
|
|
256
|
+
* Initializer function for setting up JavaScript behavior on this timegroup.
|
|
257
|
+
* This function is called ONCE per instance - on the prime timeline when first connected,
|
|
258
|
+
* and on each render clone when created.
|
|
259
|
+
*
|
|
260
|
+
* Use this to register frame callbacks, set up event listeners, or initialize state.
|
|
261
|
+
* The same initializer code runs on both prime and clones, eliminating duplication.
|
|
262
|
+
*
|
|
263
|
+
* CONSTRAINTS:
|
|
264
|
+
* - MUST be synchronous (no async/await, no Promise return)
|
|
265
|
+
* - MUST complete in <100ms (error thrown) or <10ms (warning logged)
|
|
266
|
+
* - Should only register callbacks and set up behavior, not do expensive work
|
|
267
|
+
*
|
|
268
|
+
* TIMING:
|
|
269
|
+
* - If set before element connects to DOM: runs automatically after connectedCallback
|
|
270
|
+
* - If set after element is connected: runs immediately
|
|
271
|
+
* - Clones automatically copy and run the initializer when created
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```javascript
|
|
275
|
+
* const tg = document.querySelector('ef-timegroup');
|
|
276
|
+
* tg.initializer = (instance) => {
|
|
277
|
+
* // Runs once on prime timeline, once on each clone
|
|
278
|
+
* instance.addFrameTask((info) => {
|
|
279
|
+
* // Update content based on time
|
|
280
|
+
* });
|
|
281
|
+
* };
|
|
282
|
+
* ```
|
|
283
|
+
* @public
|
|
284
|
+
*/
|
|
285
|
+
get initializer() {
|
|
286
|
+
return this.#initializer;
|
|
287
|
+
}
|
|
288
|
+
set initializer(fn) {
|
|
289
|
+
this.#initializer = fn;
|
|
290
|
+
if (fn && this.isConnected && !this.#initializerHasRun) this.#initializerComplete = this.updateComplete.then(() => {
|
|
291
|
+
this.#runInitializer();
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Track if initializer has run on this instance to prevent double execution.
|
|
296
|
+
* @internal
|
|
297
|
+
*/
|
|
298
|
+
#initializerHasRun = false;
|
|
299
|
+
/**
|
|
300
|
+
* Promise that resolves when initializer completes.
|
|
301
|
+
* Used by createRenderClone to ensure frame tasks are registered before rendering.
|
|
302
|
+
* @internal
|
|
303
|
+
*/
|
|
304
|
+
#initializerComplete;
|
|
305
|
+
/**
|
|
306
|
+
* Public accessor for initializer completion promise.
|
|
307
|
+
* Allows createRenderClone to wait for initializer before rendering.
|
|
308
|
+
* @internal
|
|
309
|
+
*/
|
|
310
|
+
get initializerComplete() {
|
|
311
|
+
return this.#initializerComplete;
|
|
312
|
+
}
|
|
254
313
|
attributeChangedCallback(name, old, value) {
|
|
255
314
|
if (name === "mode" && value) this.mode = value;
|
|
256
315
|
if (name === "overlap" && value) this.overlapMs = parseTimeToMs(value);
|
|
@@ -293,6 +352,35 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
293
352
|
return this.#frameController;
|
|
294
353
|
}
|
|
295
354
|
/**
|
|
355
|
+
* Query timegroup's readiness state for a given time.
|
|
356
|
+
* Timegroups are always ready (no async preparation needed).
|
|
357
|
+
* @public
|
|
358
|
+
*/
|
|
359
|
+
getFrameState(_timeMs) {
|
|
360
|
+
return {
|
|
361
|
+
needsPreparation: false,
|
|
362
|
+
isReady: true,
|
|
363
|
+
priority: PRIORITY_DEFAULT
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Async preparation phase (no-op for timegroups).
|
|
368
|
+
* Timegroups don't need preparation - they just coordinate child rendering.
|
|
369
|
+
* @public
|
|
370
|
+
*/
|
|
371
|
+
async prepareFrame(_timeMs, _signal) {}
|
|
372
|
+
/**
|
|
373
|
+
* Synchronous render phase - executes custom frame callbacks.
|
|
374
|
+
* Called by FrameController after all preparation is complete.
|
|
375
|
+
* Kicks off async frame callbacks without blocking (they run in background).
|
|
376
|
+
* @public
|
|
377
|
+
*/
|
|
378
|
+
renderFrame(_timeMs) {
|
|
379
|
+
if (this.#customFrameTasks.size > 0) this.#executeCustomFrameTasks().catch((error) => {
|
|
380
|
+
console.error("EFTimegroup custom frame task error:", error);
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
296
384
|
* Get the effective FPS for this timegroup.
|
|
297
385
|
* During rendering, uses the render options FPS if available.
|
|
298
386
|
* Otherwise uses the configured fps property.
|
|
@@ -566,6 +654,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
566
654
|
}
|
|
567
655
|
connectedCallback() {
|
|
568
656
|
super.connectedCallback();
|
|
657
|
+
this.updateComplete.then(() => {
|
|
658
|
+
this.#runInitializer();
|
|
659
|
+
});
|
|
569
660
|
requestAnimationFrame(() => {
|
|
570
661
|
requestAnimationFrame(() => {
|
|
571
662
|
if (this.parentTimegroup) new TimegroupController(this.parentTimegroup, this);
|
|
@@ -689,15 +780,16 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
689
780
|
}
|
|
690
781
|
/**
|
|
691
782
|
* Runs the initializer function with validation for synchronous execution and time budget.
|
|
692
|
-
*
|
|
783
|
+
* Only runs once per instance. Safe to call multiple times - will skip if already run.
|
|
693
784
|
* @throws Error if initializer returns a Promise (async not allowed)
|
|
694
785
|
* @throws Error if initializer takes more than INITIALIZER_ERROR_THRESHOLD_MS
|
|
695
786
|
* @internal
|
|
696
787
|
*/
|
|
697
|
-
#runInitializer(
|
|
698
|
-
if (!this.initializer) return;
|
|
788
|
+
#runInitializer() {
|
|
789
|
+
if (!this.initializer || this.#initializerHasRun) return;
|
|
790
|
+
this.#initializerHasRun = true;
|
|
699
791
|
const startTime = performance.now();
|
|
700
|
-
const result = this.initializer(
|
|
792
|
+
const result = this.initializer(this);
|
|
701
793
|
const elapsed = performance.now() - startTime;
|
|
702
794
|
if (result !== void 0 && result !== null && typeof result.then === "function") throw new Error("Timeline initializer must be synchronous. Do not return a Promise from the initializer function.");
|
|
703
795
|
if (elapsed > INITIALIZER_ERROR_THRESHOLD_MS) throw new Error(`Timeline initializer took ${elapsed.toFixed(1)}ms, exceeding the ${INITIALIZER_ERROR_THRESHOLD_MS}ms limit. Initializers must be fast - move expensive work outside the initializer.`);
|
|
@@ -742,9 +834,10 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
742
834
|
* so we must manually copy them to the cloned elements.
|
|
743
835
|
* @internal
|
|
744
836
|
*/
|
|
745
|
-
#copyTextSegmentData(original, clone) {
|
|
837
|
+
async #copyTextSegmentData(original, clone) {
|
|
746
838
|
const originalSegments = original.querySelectorAll("ef-text-segment");
|
|
747
839
|
const cloneSegments = clone.querySelectorAll("ef-text-segment");
|
|
840
|
+
const updatePromises = [];
|
|
748
841
|
for (let i = 0; i < originalSegments.length && i < cloneSegments.length; i++) {
|
|
749
842
|
const origSeg = originalSegments[i];
|
|
750
843
|
const cloneSeg = cloneSegments[i];
|
|
@@ -753,7 +846,9 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
753
846
|
if (origSeg.staggerOffsetMs !== void 0) cloneSeg.staggerOffsetMs = origSeg.staggerOffsetMs;
|
|
754
847
|
if (origSeg.segmentStartMs !== void 0) cloneSeg.segmentStartMs = origSeg.segmentStartMs;
|
|
755
848
|
if (origSeg.segmentEndMs !== void 0) cloneSeg.segmentEndMs = origSeg.segmentEndMs;
|
|
849
|
+
if (cloneSeg.updateComplete) updatePromises.push(cloneSeg.updateComplete);
|
|
756
850
|
}
|
|
851
|
+
await Promise.all(updatePromises);
|
|
757
852
|
}
|
|
758
853
|
/**
|
|
759
854
|
* Wait for all ef-captions elements to have their data loaded.
|
|
@@ -773,6 +868,29 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
773
868
|
if (waitPromises.length > 0) await Promise.all(waitPromises);
|
|
774
869
|
}
|
|
775
870
|
/**
|
|
871
|
+
* Copies initializers from original timegroup tree to cloned timegroup tree.
|
|
872
|
+
* Handles both the root timegroup and all nested timegroups recursively.
|
|
873
|
+
* @internal
|
|
874
|
+
*/
|
|
875
|
+
async #copyInitializersToClone(original, clone) {
|
|
876
|
+
const initializerPromises = [];
|
|
877
|
+
if (original.initializer) {
|
|
878
|
+
clone.initializer = original.initializer;
|
|
879
|
+
if (clone.initializerComplete) initializerPromises.push(clone.initializerComplete);
|
|
880
|
+
}
|
|
881
|
+
const originalNested = Array.from(original.querySelectorAll("ef-timegroup"));
|
|
882
|
+
const cloneNested = Array.from(clone.querySelectorAll("ef-timegroup"));
|
|
883
|
+
for (let i = 0; i < originalNested.length && i < cloneNested.length; i++) {
|
|
884
|
+
const origNested = originalNested[i];
|
|
885
|
+
const cloneNestedItem = cloneNested[i];
|
|
886
|
+
if (origNested.initializer) {
|
|
887
|
+
cloneNestedItem.initializer = origNested.initializer;
|
|
888
|
+
if (cloneNestedItem.initializerComplete) initializerPromises.push(cloneNestedItem.initializerComplete);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (initializerPromises.length > 0) await Promise.all(initializerPromises);
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
776
894
|
* Create an independent clone of this timegroup for rendering.
|
|
777
895
|
* The clone is a fully functional ef-timegroup with its own animations
|
|
778
896
|
* and time state, isolated from the original (Prime-timeline).
|
|
@@ -813,8 +931,8 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
813
931
|
} else container.appendChild(cloneEl);
|
|
814
932
|
document.body.appendChild(container);
|
|
815
933
|
await cloneEl.updateComplete;
|
|
816
|
-
this.#
|
|
817
|
-
this.#
|
|
934
|
+
await this.#copyInitializersToClone(this, cloneEl);
|
|
935
|
+
await this.#copyTextSegmentData(this, cloneEl);
|
|
818
936
|
let actualClone = container.querySelector("ef-timegroup");
|
|
819
937
|
if (!actualClone) throw new Error("No ef-timegroup found after initializer. Ensure your initializer renders a Timegroup (React) or does not remove the cloned element (vanilla JS).");
|
|
820
938
|
await customElements.whenDefined("ef-timegroup");
|
|
@@ -822,6 +940,7 @@ let EFTimegroup = class EFTimegroup$1 extends EFTargetable(EFTemporal(TWMixin(Li
|
|
|
822
940
|
actualClone = container.querySelector("ef-timegroup");
|
|
823
941
|
if (!actualClone) throw new Error("ef-timegroup element lost after upgrade");
|
|
824
942
|
await actualClone.updateComplete;
|
|
943
|
+
await this.#copyTextSegmentData(this, actualClone);
|
|
825
944
|
const setupParentChildRelationships = (parent, root) => {
|
|
826
945
|
for (const child of parent.children) if (child.tagName === "EF-TIMEGROUP") {
|
|
827
946
|
const childTG = child;
|