@twick/video-editor 0.15.26 → 0.15.28

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
@@ -3,8 +3,9 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import { jsxs, jsx } from "react/jsx-runtime";
5
5
  import { useLivePlayerContext, PLAYER_STATE, LivePlayer } from "@twick/live-player";
6
- import { ImageElement as ImageElement$1, AudioElement, VideoElement as VideoElement$1, useTimelineContext, TIMELINE_ACTION, ElementDeserializer, getCurrentElements, CaptionElement as CaptionElement$1, TIMELINE_ELEMENT_TYPE, getDecimalNumber, resolveIds, TrackElement, TRACK_TYPES, resolveId, Track, getElementIdsInRange, ValidationError, VALIDATION_ERROR_CODE, formatTimeWithFrames } from "@twick/timeline";
6
+ import { ImageElement as ImageElement$1, AudioElement, VideoElement as VideoElement$1, useTimelineContext, TIMELINE_ACTION, ElementDeserializer, getCurrentElements, CaptionElement as CaptionElement$1, canSplitElement, TIMELINE_ELEMENT_TYPE, getDecimalNumber, resolveIds, TrackElement, TRACK_TYPES, Track, resolveId, getElementIdsInRange, ValidationError, VALIDATION_ERROR_CODE, formatTimeWithFrames } from "@twick/timeline";
7
7
  import React, { useState, useRef, useCallback, useEffect, useMemo, forwardRef, createElement, createContext, useContext, useId, useLayoutEffect, useInsertionEffect, Fragment, Component } from "react";
8
+ import { createPortal } from "react-dom";
8
9
  function t(t2, e3, s2) {
9
10
  return (e3 = function(t3) {
10
11
  var e4 = function(t4, e5) {
@@ -5943,6 +5944,9 @@ function ea() {
5943
5944
  function sa() {
5944
5945
  return !ta && (!(arguments.length > 0 && void 0 !== arguments[0]) || arguments[0]) && (ta = ea()), ta;
5945
5946
  }
5947
+ function ia(t2) {
5948
+ ta = t2;
5949
+ }
5946
5950
  const ra = ["filters", "resizeFilter", "src", "crossOrigin", "type"], na = ["cropX", "cropY"];
5947
5951
  class oa extends Li {
5948
5952
  static getDefaults() {
@@ -6470,13 +6474,7 @@ function Za(e3, s2) {
6470
6474
  return tt.setClass(r2, e3), r2;
6471
6475
  }
6472
6476
  t(Qa, "type", "ColorMatrix"), t(Qa, "defaults", Ja), t(Qa, "uniformLocations", ["uColorMatrix", "uConstants"]), tt.setClass(Qa);
6473
- Za("Brownie", [0.5997, 0.34553, -0.27082, 0, 0.186, -0.0377, 0.86095, 0.15059, 0, -0.1449, 0.24113, -0.07441, 0.44972, 0, -0.02965, 0, 0, 0, 1, 0]);
6474
- Za("Vintage", [0.62793, 0.32021, -0.03965, 0, 0.03784, 0.02578, 0.64411, 0.03259, 0, 0.02926, 0.0466, -0.08512, 0.52416, 0, 0.02023, 0, 0, 0, 1, 0]);
6475
- Za("Kodachrome", [1.12855, -0.39673, -0.03992, 0, 0.24991, -0.16404, 1.08352, -0.05498, 0, 0.09698, -0.16786, -0.56034, 1.60148, 0, 0.13972, 0, 0, 0, 1, 0]);
6476
- Za("Technicolor", [1.91252, -0.85453, -0.09155, 0, 0.04624, -0.30878, 1.76589, -0.10601, 0, -0.27589, -0.2311, -0.75018, 1.84759, 0, 0.12137, 0, 0, 0, 1, 0]);
6477
- Za("Polaroid", [1.438, -0.062, -0.062, 0, 0, -0.122, 1.378, -0.122, 0, 0, -0.016, -0.016, 1.483, 0, 0, 0, 0, 0, 1, 0]);
6478
- Za("Sepia", [0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]);
6479
- Za("BlackWhite", [1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 0, 0, 0, 1, 0]);
6477
+ const $a = Za("Brownie", [0.5997, 0.34553, -0.27082, 0, 0.186, -0.0377, 0.86095, 0.15059, 0, -0.1449, 0.24113, -0.07441, 0.44972, 0, -0.02965, 0, 0, 0, 1, 0]), th = Za("Vintage", [0.62793, 0.32021, -0.03965, 0, 0.03784, 0.02578, 0.64411, 0.03259, 0, 0.02926, 0.0466, -0.08512, 0.52416, 0, 0.02023, 0, 0, 0, 1, 0]), eh = Za("Kodachrome", [1.12855, -0.39673, -0.03992, 0, 0.24991, -0.16404, 1.08352, -0.05498, 0, 0.09698, -0.16786, -0.56034, 1.60148, 0, 0.13972, 0, 0, 0, 1, 0]), sh = Za("Technicolor", [1.91252, -0.85453, -0.09155, 0, 0.04624, -0.30878, 1.76589, -0.10601, 0, -0.27589, -0.2311, -0.75018, 1.84759, 0, 0.12137, 0, 0, 0, 1, 0]), ih = Za("Polaroid", [1.438, -0.062, -0.062, 0, 0, -0.122, 1.378, -0.122, 0, 0, -0.016, -0.016, 1.483, 0, 0, 0, 0, 0, 1, 0]), rh = Za("Sepia", [0.393, 0.769, 0.189, 0, 0, 0.349, 0.686, 0.168, 0, 0, 0.272, 0.534, 0.131, 0, 0, 0, 0, 0, 1, 0]), nh = Za("BlackWhite", [1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 1.5, 1.5, 1.5, 0, -1, 0, 0, 0, 1, 0]);
6480
6478
  class oh extends Va {
6481
6479
  constructor() {
6482
6480
  let t2 = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
@@ -6839,6 +6837,7 @@ class bh extends Va {
6839
6837
  }
6840
6838
  }
6841
6839
  t(bh, "type", "Vibrance"), t(bh, "defaults", { vibrance: 0 }), t(bh, "uniformLocations", ["uVibrance"]), tt.setClass(bh);
6840
+ var Sh = Object.freeze({ __proto__: null, BaseFilter: Va, BlackWhite: nh, BlendColor: Ga, BlendImage: Ua, Blur: qa, Brightness: Ka, Brownie: $a, ColorMatrix: Qa, Composed: oh, Contrast: ah, Convolute: ch, Gamma: uh, Grayscale: gh, HueRotation: ph, Invert: mh, Kodachrome: eh, Noise: vh$1, Pixelate: yh, Polaroid: ih, RemoveColor: _h, Resize: xh, Saturation: Ch, Sepia: rh, Technicolor: sh, Vibrance: bh, Vintage: th });
6842
6841
  var __defProp2 = Object.defineProperty;
6843
6842
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6844
6843
  var __publicField2 = (obj, key, value) => __defNormalProp2(obj, key + "", value);
@@ -6899,6 +6898,8 @@ const ELEMENT_TYPES = {
6899
6898
  RECT: "rect",
6900
6899
  /** Circle element type */
6901
6900
  CIRCLE: "circle",
6901
+ /** Emoji sticker element type */
6902
+ EMOJI: "emoji",
6902
6903
  /** Arrow annotation element type */
6903
6904
  ARROW: "arrow",
6904
6905
  /** Line annotation / shape element type */
@@ -7147,6 +7148,24 @@ const rotateControl = new ai({
7147
7148
  /** Whether to show connection line */
7148
7149
  withConnection: true
7149
7150
  });
7151
+ const COLOR_FILTERS = {
7152
+ SATURATED: "saturated",
7153
+ BRIGHT: "bright",
7154
+ VIBRANT: "vibrant",
7155
+ RETRO: "retro",
7156
+ BLACK_WHITE: "blackWhite",
7157
+ SEPIA: "sepia",
7158
+ COOL: "cool",
7159
+ WARM: "warm",
7160
+ CINEMATIC: "cinematic",
7161
+ SOFT_GLOW: "softGlow",
7162
+ MOODY: "moody",
7163
+ DREAMY: "dreamy",
7164
+ INVERTED: "inverted",
7165
+ VINTAGE: "vintage",
7166
+ DRAMATIC: "dramatic",
7167
+ FADED: "faded"
7168
+ };
7150
7169
  class LRUCache {
7151
7170
  constructor(maxSize = 100) {
7152
7171
  if (maxSize <= 0) {
@@ -7530,6 +7549,275 @@ const getObjectFitSize = (objectFit, elementSize, containerSize) => {
7530
7549
  };
7531
7550
  }
7532
7551
  };
7552
+ const {
7553
+ Blur,
7554
+ Brightness,
7555
+ ColorMatrix,
7556
+ Contrast,
7557
+ Grayscale,
7558
+ HueRotation,
7559
+ Invert,
7560
+ Saturation,
7561
+ Sepia
7562
+ } = Sh;
7563
+ let canvas2dFilterBackendInstalled = false;
7564
+ function ensureCanvas2dImageFilterBackend() {
7565
+ if (canvas2dFilterBackendInstalled) return;
7566
+ canvas2dFilterBackendInstalled = true;
7567
+ ia(new Zo());
7568
+ }
7569
+ function getSourceBitmapSize(img) {
7570
+ const el = img.getElement();
7571
+ if (!el) return null;
7572
+ const w2 = "naturalWidth" in el && el.naturalWidth > 0 ? el.naturalWidth : el.width;
7573
+ const h2 = "naturalHeight" in el && el.naturalHeight > 0 ? el.naturalHeight : el.height;
7574
+ if (!w2 || !h2) return null;
7575
+ return { w: w2, h: h2 };
7576
+ }
7577
+ function applyFiltersSafe(img) {
7578
+ ensureCanvas2dImageFilterBackend();
7579
+ img.applyFilters();
7580
+ }
7581
+ const bright = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.48));
7582
+ const contr = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.55));
7583
+ const sat = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.72));
7584
+ const IDENTITY = [
7585
+ 1,
7586
+ 0,
7587
+ 0,
7588
+ 0,
7589
+ 0,
7590
+ 0,
7591
+ 1,
7592
+ 0,
7593
+ 0,
7594
+ 0,
7595
+ 0,
7596
+ 0,
7597
+ 1,
7598
+ 0,
7599
+ 0,
7600
+ 0,
7601
+ 0,
7602
+ 0,
7603
+ 1,
7604
+ 0
7605
+ ];
7606
+ const SEPIA_MATRIX = [
7607
+ 0.393,
7608
+ 0.769,
7609
+ 0.189,
7610
+ 0,
7611
+ 0,
7612
+ 0.349,
7613
+ 0.686,
7614
+ 0.168,
7615
+ 0,
7616
+ 0,
7617
+ 0.272,
7618
+ 0.534,
7619
+ 0.131,
7620
+ 0,
7621
+ 0,
7622
+ 0,
7623
+ 0,
7624
+ 0,
7625
+ 1,
7626
+ 0
7627
+ ];
7628
+ function sepiaMix(strength) {
7629
+ const t2 = Math.min(1, Math.max(0, strength));
7630
+ const m2 = IDENTITY.map((v2, i2) => v2 + (SEPIA_MATRIX[i2] - v2) * t2);
7631
+ return new ColorMatrix({ matrix: m2, colorsOnly: true });
7632
+ }
7633
+ const twickBlurToFabric = (v2) => Math.min(0.22, Math.max(0, v2 * 0.045));
7634
+ function buildFilters(filterType) {
7635
+ switch (filterType) {
7636
+ case COLOR_FILTERS.SATURATED:
7637
+ return {
7638
+ filters: [
7639
+ new Saturation({ saturation: sat(1.4) }),
7640
+ new Contrast({ contrast: contr(1.1) })
7641
+ ],
7642
+ opacityFactor: 1
7643
+ };
7644
+ case COLOR_FILTERS.BRIGHT:
7645
+ return {
7646
+ filters: [
7647
+ new Brightness({ brightness: bright(1.3) }),
7648
+ new Contrast({ contrast: contr(1.05) })
7649
+ ],
7650
+ opacityFactor: 1
7651
+ };
7652
+ case COLOR_FILTERS.VIBRANT:
7653
+ return {
7654
+ filters: [
7655
+ new Saturation({ saturation: sat(1.6) }),
7656
+ new Brightness({ brightness: bright(1.15) }),
7657
+ new Contrast({ contrast: contr(1.1) })
7658
+ ],
7659
+ opacityFactor: 1
7660
+ };
7661
+ case COLOR_FILTERS.RETRO:
7662
+ return {
7663
+ filters: [
7664
+ sepiaMix(0.8),
7665
+ new Contrast({ contrast: contr(1.3) }),
7666
+ new Brightness({ brightness: bright(0.85) }),
7667
+ new Saturation({ saturation: sat(0.8) })
7668
+ ],
7669
+ opacityFactor: 1
7670
+ };
7671
+ case COLOR_FILTERS.BLACK_WHITE:
7672
+ return {
7673
+ filters: [
7674
+ new Grayscale(),
7675
+ new Contrast({ contrast: contr(1.25) }),
7676
+ new Brightness({ brightness: bright(1.05) })
7677
+ ],
7678
+ opacityFactor: 1
7679
+ };
7680
+ case COLOR_FILTERS.SEPIA:
7681
+ return {
7682
+ filters: [
7683
+ new Sepia(),
7684
+ new Contrast({ contrast: contr(1.08) })
7685
+ ],
7686
+ opacityFactor: 1
7687
+ };
7688
+ case COLOR_FILTERS.COOL:
7689
+ return {
7690
+ filters: [
7691
+ new HueRotation({ rotation: 15 / 180 }),
7692
+ new Brightness({ brightness: bright(1.1) }),
7693
+ new Saturation({ saturation: sat(1.3) }),
7694
+ new Contrast({ contrast: contr(1.05) })
7695
+ ],
7696
+ opacityFactor: 1
7697
+ };
7698
+ case COLOR_FILTERS.WARM:
7699
+ return {
7700
+ filters: [
7701
+ new HueRotation({ rotation: -15 / 180 }),
7702
+ new Brightness({ brightness: bright(1.15) }),
7703
+ new Saturation({ saturation: sat(1.3) }),
7704
+ new Contrast({ contrast: contr(1.05) })
7705
+ ],
7706
+ opacityFactor: 1
7707
+ };
7708
+ case COLOR_FILTERS.CINEMATIC:
7709
+ return {
7710
+ filters: [
7711
+ new Contrast({ contrast: contr(1.4) }),
7712
+ new Brightness({ brightness: bright(0.95) }),
7713
+ new Saturation({ saturation: sat(0.85) }),
7714
+ sepiaMix(0.2)
7715
+ ],
7716
+ opacityFactor: 1
7717
+ };
7718
+ case COLOR_FILTERS.SOFT_GLOW:
7719
+ return {
7720
+ filters: [
7721
+ new Brightness({ brightness: bright(1.2) }),
7722
+ new Contrast({ contrast: contr(0.95) }),
7723
+ new Blur({ blur: twickBlurToFabric(1.2) }),
7724
+ new Saturation({ saturation: sat(1.1) })
7725
+ ],
7726
+ opacityFactor: 1
7727
+ };
7728
+ case COLOR_FILTERS.MOODY:
7729
+ return {
7730
+ filters: [
7731
+ new Brightness({ brightness: bright(1.05) }),
7732
+ new Contrast({ contrast: contr(1.4) }),
7733
+ new Saturation({ saturation: sat(0.65) }),
7734
+ sepiaMix(0.2)
7735
+ ],
7736
+ opacityFactor: 1
7737
+ };
7738
+ case COLOR_FILTERS.DREAMY:
7739
+ return {
7740
+ filters: [
7741
+ new Brightness({ brightness: bright(1.3) }),
7742
+ new Blur({ blur: twickBlurToFabric(2) }),
7743
+ new Saturation({ saturation: sat(1.4) }),
7744
+ new Contrast({ contrast: contr(0.95) })
7745
+ ],
7746
+ opacityFactor: 1
7747
+ };
7748
+ case COLOR_FILTERS.INVERTED:
7749
+ return {
7750
+ filters: [
7751
+ new Invert({ invert: true, alpha: false }),
7752
+ new HueRotation({ rotation: 1 })
7753
+ ],
7754
+ opacityFactor: 1
7755
+ };
7756
+ case COLOR_FILTERS.VINTAGE:
7757
+ return {
7758
+ filters: [
7759
+ sepiaMix(0.4),
7760
+ new Saturation({ saturation: sat(1.4) }),
7761
+ new Contrast({ contrast: contr(1.2) }),
7762
+ new Brightness({ brightness: bright(1.1) })
7763
+ ],
7764
+ opacityFactor: 1
7765
+ };
7766
+ case COLOR_FILTERS.DRAMATIC:
7767
+ return {
7768
+ filters: [
7769
+ new Contrast({ contrast: contr(1.5) }),
7770
+ new Brightness({ brightness: bright(0.9) }),
7771
+ new Saturation({ saturation: sat(1.2) })
7772
+ ],
7773
+ opacityFactor: 1
7774
+ };
7775
+ case COLOR_FILTERS.FADED:
7776
+ return {
7777
+ filters: [
7778
+ new Brightness({ brightness: bright(1.2) }),
7779
+ new Saturation({ saturation: sat(0.8) }),
7780
+ new Contrast({ contrast: contr(0.9) })
7781
+ ],
7782
+ opacityFactor: 0.9
7783
+ };
7784
+ default:
7785
+ return { filters: [], opacityFactor: 1 };
7786
+ }
7787
+ }
7788
+ function applyFabricMediaColorFilters(img, mediaFilter, elementOpacity) {
7789
+ const key = (mediaFilter == null ? void 0 : mediaFilter.trim()) || "none";
7790
+ if (key === "none") {
7791
+ img.filters = [];
7792
+ img.set("opacity", elementOpacity);
7793
+ applyFiltersSafe(img);
7794
+ return;
7795
+ }
7796
+ const { filters: filterList, opacityFactor } = buildFilters(key);
7797
+ if (filterList.length === 0) {
7798
+ img.filters = [];
7799
+ img.set("opacity", elementOpacity);
7800
+ applyFiltersSafe(img);
7801
+ return;
7802
+ }
7803
+ if (!getSourceBitmapSize(img)) {
7804
+ img.filters = [];
7805
+ img.set("opacity", elementOpacity);
7806
+ return;
7807
+ }
7808
+ img.filters = filterList;
7809
+ img.set("opacity", elementOpacity * opacityFactor);
7810
+ try {
7811
+ applyFiltersSafe(img);
7812
+ } catch {
7813
+ img.filters = [];
7814
+ img.set("opacity", elementOpacity);
7815
+ try {
7816
+ applyFiltersSafe(img);
7817
+ } catch {
7818
+ }
7819
+ }
7820
+ }
7533
7821
  const MARGIN = 10;
7534
7822
  const addTextElement = ({
7535
7823
  element,
@@ -7620,7 +7908,7 @@ const setImageProps = ({
7620
7908
  canvasMetadata,
7621
7909
  lockAspectRatio = true
7622
7910
  }) => {
7623
- var _a, _b, _c, _d, _e2;
7911
+ var _a, _b, _c, _d, _e2, _f;
7624
7912
  const width = (((_a = element.props) == null ? void 0 : _a.width) || 0) * canvasMetadata.scaleX || canvasMetadata.width;
7625
7913
  const height = (((_b = element.props) == null ? void 0 : _b.height) || 0) * canvasMetadata.scaleY || canvasMetadata.height;
7626
7914
  const { x: x2, y: y2 } = convertToCanvasPosition(
@@ -7634,11 +7922,15 @@ const setImageProps = ({
7634
7922
  img.set("height", height);
7635
7923
  img.set("left", x2);
7636
7924
  img.set("top", y2);
7637
- img.set("opacity", ((_e2 = element.props) == null ? void 0 : _e2.opacity) ?? 1);
7638
7925
  img.set("selectable", true);
7639
7926
  img.set("hasControls", true);
7640
7927
  img.set("touchAction", "all");
7641
7928
  img.set("lockUniScaling", lockAspectRatio);
7929
+ applyFabricMediaColorFilters(
7930
+ img,
7931
+ (_e2 = element.props) == null ? void 0 : _e2.mediaFilter,
7932
+ ((_f = element.props) == null ? void 0 : _f.opacity) ?? 1
7933
+ );
7642
7934
  };
7643
7935
  const addCaptionElement = ({
7644
7936
  element,
@@ -7648,48 +7940,52 @@ const addCaptionElement = ({
7648
7940
  canvasMetadata,
7649
7941
  lockAspectRatio = false
7650
7942
  }) => {
7651
- var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2, _o2, _p, _q, _r2, _s2, _t2, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L;
7652
- const applyToAll = (captionProps == null ? void 0 : captionProps.applyToAll) ?? false;
7653
- const captionTextColor = ((_a = captionProps == null ? void 0 : captionProps.colors) == null ? void 0 : _a.text) ?? ((_b = captionProps == null ? void 0 : captionProps.color) == null ? void 0 : _b.text);
7943
+ var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2, _o2, _p, _q, _r2, _s2, _t2, _u, _v, _w, _x, _y;
7944
+ const useTrackDefaults = ((_a = element.props) == null ? void 0 : _a.useTrackDefaults) ?? true;
7945
+ const trackColors = captionProps == null ? void 0 : captionProps.colors;
7946
+ const elementColors = (_b = element.props) == null ? void 0 : _b.colors;
7947
+ const resolvedColors = useTrackDefaults ? trackColors : { ...trackColors ?? {}, ...elementColors ?? {} };
7948
+ const captionTextColor = (resolvedColors == null ? void 0 : resolvedColors.text) ?? ((_c = captionProps == null ? void 0 : captionProps.color) == null ? void 0 : _c.text);
7654
7949
  const { x: x2, y: y2 } = convertToCanvasPosition(
7655
- (applyToAll ? captionProps == null ? void 0 : captionProps.x : ((_c = element.props) == null ? void 0 : _c.x) ?? (captionProps == null ? void 0 : captionProps.x)) ?? 0,
7656
- (applyToAll ? captionProps == null ? void 0 : captionProps.y : ((_d = element.props) == null ? void 0 : _d.y) ?? (captionProps == null ? void 0 : captionProps.y)) ?? 0,
7950
+ (useTrackDefaults ? captionProps == null ? void 0 : captionProps.x : (_d = element.props) == null ? void 0 : _d.x) ?? (captionProps == null ? void 0 : captionProps.x) ?? 0,
7951
+ (useTrackDefaults ? captionProps == null ? void 0 : captionProps.y : (_e2 = element.props) == null ? void 0 : _e2.y) ?? (captionProps == null ? void 0 : captionProps.y) ?? 0,
7657
7952
  canvasMetadata
7658
7953
  );
7659
- let width = ((_e2 = element.props) == null ? void 0 : _e2.width) ? element.props.width * canvasMetadata.scaleX : canvasMetadata.width - 2 * MARGIN;
7660
- if ((_f = element.props) == null ? void 0 : _f.maxWidth) {
7954
+ let width = ((_f = element.props) == null ? void 0 : _f.width) ? element.props.width * canvasMetadata.scaleX : canvasMetadata.width - 2 * MARGIN;
7955
+ if ((_g = element.props) == null ? void 0 : _g.maxWidth) {
7661
7956
  width = Math.min(width, element.props.maxWidth * canvasMetadata.scaleX);
7662
7957
  }
7663
- const elementColors = (_g = element.props) == null ? void 0 : _g.colors;
7664
- const resolvedFill = (applyToAll ? captionTextColor : ((_h2 = element.props) == null ? void 0 : _h2.fill) ?? (elementColors == null ? void 0 : elementColors.text) ?? captionTextColor) ?? DEFAULT_CAPTION_PROPS.fill;
7665
- const trackColors = captionProps == null ? void 0 : captionProps.colors;
7958
+ const resolvedFill = (useTrackDefaults ? void 0 : (_h2 = element.props) == null ? void 0 : _h2.fill) ?? captionTextColor ?? DEFAULT_CAPTION_PROPS.fill;
7666
7959
  const trackStroke = trackColors == null ? void 0 : trackColors.outlineColor;
7667
7960
  const elementStroke = (elementColors == null ? void 0 : elementColors.outlineColor) ?? ((_i2 = element.props) == null ? void 0 : _i2.stroke);
7668
- const resolvedStroke = (applyToAll ? trackStroke ?? elementStroke : elementStroke ?? trackStroke) ?? void 0;
7669
- const caption = new Uo(((_j = element.props) == null ? void 0 : _j.text) || element.t || "", {
7961
+ const resolvedStroke = (useTrackDefaults ? trackStroke : elementStroke ?? trackStroke) ?? void 0;
7962
+ const trackFont = (captionProps == null ? void 0 : captionProps.font) ?? {};
7963
+ const elementFont = ((_j = element.props) == null ? void 0 : _j.font) ?? {};
7964
+ const resolvedFont = useTrackDefaults ? trackFont : { ...trackFont, ...elementFont };
7965
+ const caption = new Uo(((_k = element.props) == null ? void 0 : _k.text) || element.t || "", {
7670
7966
  left: x2,
7671
7967
  top: y2,
7672
7968
  originX: "center",
7673
7969
  originY: "center",
7674
- angle: ((_k = element.props) == null ? void 0 : _k.rotation) || 0,
7970
+ angle: ((_l = element.props) == null ? void 0 : _l.rotation) || 0,
7675
7971
  fontSize: Math.round(
7676
- ((applyToAll ? (_l = captionProps == null ? void 0 : captionProps.font) == null ? void 0 : _l.size : ((_n2 = (_m = element.props) == null ? void 0 : _m.font) == null ? void 0 : _n2.size) ?? ((_o2 = captionProps == null ? void 0 : captionProps.font) == null ? void 0 : _o2.size)) ?? DEFAULT_CAPTION_PROPS.size) * canvasMetadata.scaleX
7972
+ ((resolvedFont == null ? void 0 : resolvedFont.size) ?? DEFAULT_CAPTION_PROPS.size) * canvasMetadata.scaleX
7677
7973
  ),
7678
- fontFamily: (applyToAll ? (_p = captionProps == null ? void 0 : captionProps.font) == null ? void 0 : _p.family : ((_r2 = (_q = element.props) == null ? void 0 : _q.font) == null ? void 0 : _r2.family) ?? ((_s2 = captionProps == null ? void 0 : captionProps.font) == null ? void 0 : _s2.family)) ?? DEFAULT_CAPTION_PROPS.family,
7974
+ fontFamily: (resolvedFont == null ? void 0 : resolvedFont.family) ?? DEFAULT_CAPTION_PROPS.family,
7679
7975
  fill: resolvedFill,
7680
- fontWeight: (applyToAll ? (_t2 = captionProps == null ? void 0 : captionProps.font) == null ? void 0 : _t2.weight : ((_v = (_u = element.props) == null ? void 0 : _u.font) == null ? void 0 : _v.weight) ?? ((_w = captionProps == null ? void 0 : captionProps.font) == null ? void 0 : _w.weight)) ?? DEFAULT_CAPTION_PROPS.fontWeight,
7976
+ fontWeight: (resolvedFont == null ? void 0 : resolvedFont.weight) ?? DEFAULT_CAPTION_PROPS.fontWeight,
7681
7977
  ...resolvedStroke ? { stroke: resolvedStroke } : {},
7682
- opacity: (applyToAll ? captionProps == null ? void 0 : captionProps.opacity : ((_x = element.props) == null ? void 0 : _x.opacity) ?? (captionProps == null ? void 0 : captionProps.opacity)) ?? 1,
7978
+ opacity: (useTrackDefaults ? void 0 : (_m = element.props) == null ? void 0 : _m.opacity) ?? (captionProps == null ? void 0 : captionProps.opacity) ?? 1,
7683
7979
  width,
7684
7980
  splitByGrapheme: false,
7685
- textAlign: ((_y = element.props) == null ? void 0 : _y.textAlign) ?? "center",
7981
+ textAlign: ((_n2 = element.props) == null ? void 0 : _n2.textAlign) ?? "center",
7686
7982
  shadow: new Ds({
7687
- offsetX: (applyToAll ? (_z = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _z[0] : ((_B = (_A = element.props) == null ? void 0 : _A.shadowOffset) == null ? void 0 : _B[0]) ?? ((_C = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _C[0])) ?? ((_D = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _D[0]),
7688
- offsetY: (applyToAll ? (_E = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _E[1] : ((_G = (_F = element.props) == null ? void 0 : _F.shadowOffset) == null ? void 0 : _G[1]) ?? ((_H = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _H[1])) ?? ((_I = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _I[1]),
7689
- blur: (applyToAll ? captionProps == null ? void 0 : captionProps.shadowBlur : ((_J = element.props) == null ? void 0 : _J.shadowBlur) ?? (captionProps == null ? void 0 : captionProps.shadowBlur)) ?? DEFAULT_CAPTION_PROPS.shadowBlur,
7690
- color: (applyToAll ? captionProps == null ? void 0 : captionProps.shadowColor : ((_K = element.props) == null ? void 0 : _K.shadowColor) ?? (captionProps == null ? void 0 : captionProps.shadowColor)) ?? DEFAULT_CAPTION_PROPS.shadowColor
7983
+ offsetX: (useTrackDefaults ? void 0 : (_p = (_o2 = element.props) == null ? void 0 : _o2.shadowOffset) == null ? void 0 : _p[0]) ?? ((_q = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _q[0]) ?? ((_r2 = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _r2[0]),
7984
+ offsetY: (useTrackDefaults ? void 0 : (_t2 = (_s2 = element.props) == null ? void 0 : _s2.shadowOffset) == null ? void 0 : _t2[1]) ?? ((_u = captionProps == null ? void 0 : captionProps.shadowOffset) == null ? void 0 : _u[1]) ?? ((_v = DEFAULT_CAPTION_PROPS.shadowOffset) == null ? void 0 : _v[1]),
7985
+ blur: (useTrackDefaults ? void 0 : (_w = element.props) == null ? void 0 : _w.shadowBlur) ?? (captionProps == null ? void 0 : captionProps.shadowBlur) ?? DEFAULT_CAPTION_PROPS.shadowBlur,
7986
+ color: (useTrackDefaults ? void 0 : (_x = element.props) == null ? void 0 : _x.shadowColor) ?? (captionProps == null ? void 0 : captionProps.shadowColor) ?? DEFAULT_CAPTION_PROPS.shadowColor
7691
7987
  }),
7692
- strokeWidth: ((applyToAll ? captionProps == null ? void 0 : captionProps.lineWidth : ((_L = element.props) == null ? void 0 : _L.lineWidth) ?? (captionProps == null ? void 0 : captionProps.lineWidth)) ?? DEFAULT_CAPTION_PROPS.lineWidth) * 0.025
7988
+ strokeWidth: ((useTrackDefaults ? void 0 : (_y = element.props) == null ? void 0 : _y.lineWidth) ?? (captionProps == null ? void 0 : captionProps.lineWidth) ?? DEFAULT_CAPTION_PROPS.lineWidth) * 0.025
7693
7989
  });
7694
7990
  caption.set("id", element.id);
7695
7991
  caption.set("zIndex", index);
@@ -7744,8 +8040,13 @@ const addImageElement = async ({
7744
8040
  currentFrameEffect,
7745
8041
  lockAspectRatio = true
7746
8042
  }) => {
8043
+ var _a, _b;
7747
8044
  try {
7748
- const img = await oa.fromURL(imageUrl || element.props.src || "");
8045
+ const rawSrc = imageUrl || element.props.src || "";
8046
+ const mediaFilter = (_b = (_a = element.props) == null ? void 0 : _a.mediaFilter) == null ? void 0 : _b.trim();
8047
+ const useFilter = !!mediaFilter && mediaFilter !== "none";
8048
+ const fromUrlOpts = useFilter && /^https?:\/\//i.test(rawSrc) ? { crossOrigin: "anonymous" } : {};
8049
+ const img = await oa.fromURL(rawSrc, fromUrlOpts);
7749
8050
  img.set({
7750
8051
  originX: "center",
7751
8052
  originY: "center",
@@ -7782,7 +8083,7 @@ const addMediaGroup = ({
7782
8083
  currentFrameEffect,
7783
8084
  lockAspectRatio = true
7784
8085
  }) => {
7785
- var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2;
8086
+ var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2, _o2;
7786
8087
  let frameSize;
7787
8088
  let angle;
7788
8089
  let framePosition;
@@ -7837,9 +8138,13 @@ const addMediaGroup = ({
7837
8138
  originX: "center",
7838
8139
  originY: "center",
7839
8140
  scaleX: newSize.width / img.width,
7840
- scaleY: newSize.height / img.height,
7841
- opacity: ((_n2 = element.props) == null ? void 0 : _n2.opacity) ?? 1
8141
+ scaleY: newSize.height / img.height
7842
8142
  });
8143
+ applyFabricMediaColorFilters(
8144
+ img,
8145
+ (_n2 = element.props) == null ? void 0 : _n2.mediaFilter,
8146
+ ((_o2 = element.props) == null ? void 0 : _o2.opacity) ?? 1
8147
+ );
7843
8148
  const { x: x2, y: y2 } = convertToCanvasPosition(
7844
8149
  (framePosition == null ? void 0 : framePosition.x) || 0,
7845
8150
  (framePosition == null ? void 0 : framePosition.y) || 0,
@@ -8325,7 +8630,8 @@ const CaptionElement = {
8325
8630
  context.canvasMetadata,
8326
8631
  context.videoSize
8327
8632
  );
8328
- if ((_a = context.captionPropsRef.current) == null ? void 0 : _a.applyToAll) {
8633
+ const useTrackDefaults = ((_a = element.props) == null ? void 0 : _a.useTrackDefaults) ?? true;
8634
+ if (useTrackDefaults) {
8329
8635
  return {
8330
8636
  element,
8331
8637
  operation: CANVAS_OPERATIONS.CAPTION_PROPS_UPDATED,
@@ -8542,6 +8848,72 @@ const EffectElement = {
8542
8848
  return;
8543
8849
  }
8544
8850
  };
8851
+ const EmojiElement = {
8852
+ name: ELEMENT_TYPES.EMOJI,
8853
+ async add(params) {
8854
+ var _a;
8855
+ const { element, index, canvas, canvasMetadata, lockAspectRatio } = params;
8856
+ await addImageElement({
8857
+ element,
8858
+ index,
8859
+ canvas,
8860
+ canvasMetadata,
8861
+ lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio) ?? true
8862
+ });
8863
+ },
8864
+ updateFromFabricObject(object, element, context) {
8865
+ const canvasCenter = getObjectCanvasCenter(object);
8866
+ const { x: x2, y: y2 } = convertToVideoPosition(
8867
+ canvasCenter.x,
8868
+ canvasCenter.y,
8869
+ context.canvasMetadata,
8870
+ context.videoSize
8871
+ );
8872
+ if (object.type === "group") {
8873
+ const scaledW2 = (object.width ?? 0) * (object.scaleX ?? 1);
8874
+ const scaledH2 = (object.height ?? 0) * (object.scaleY ?? 1);
8875
+ const { width: fw, height: fh2 } = convertToVideoDimensions(
8876
+ scaledW2,
8877
+ scaledH2,
8878
+ context.canvasMetadata
8879
+ );
8880
+ const updatedFrameSize = [fw, fh2];
8881
+ const frame2 = element.frame;
8882
+ return {
8883
+ element: {
8884
+ ...element,
8885
+ frame: {
8886
+ ...frame2,
8887
+ rotation: getObjectCanvasAngle(object),
8888
+ size: updatedFrameSize,
8889
+ x: x2,
8890
+ y: y2
8891
+ }
8892
+ }
8893
+ };
8894
+ }
8895
+ const scaledW = (object.width ?? 0) * (object.scaleX ?? 1);
8896
+ const scaledH = (object.height ?? 0) * (object.scaleY ?? 1);
8897
+ const { width, height } = convertToVideoDimensions(
8898
+ scaledW,
8899
+ scaledH,
8900
+ context.canvasMetadata
8901
+ );
8902
+ return {
8903
+ element: {
8904
+ ...element,
8905
+ props: {
8906
+ ...element.props,
8907
+ rotation: getObjectCanvasAngle(object),
8908
+ width,
8909
+ height,
8910
+ x: x2,
8911
+ y: y2
8912
+ }
8913
+ }
8914
+ };
8915
+ }
8916
+ };
8545
8917
  class ElementController {
8546
8918
  constructor() {
8547
8919
  __publicField2(this, "elements", /* @__PURE__ */ new Map());
@@ -8563,6 +8935,7 @@ function registerElements() {
8563
8935
  elementController.register(RectElement);
8564
8936
  elementController.register(CircleElement);
8565
8937
  elementController.register(TextElement);
8938
+ elementController.register(EmojiElement);
8566
8939
  elementController.register(CaptionElement);
8567
8940
  elementController.register(WatermarkElement);
8568
8941
  elementController.register(ArrowElement);
@@ -9147,6 +9520,8 @@ const DEFAULT_ELEMENT_COLORS = {
9147
9520
  animation: "#4B9B78",
9148
9521
  /** Icon element color - bright orchid */
9149
9522
  icon: "#A76CD4",
9523
+ /** Emoji element color - warm amber */
9524
+ emoji: "#F59E0B",
9150
9525
  /** Circle element color - deep byzantium */
9151
9526
  circle: "#703D8B",
9152
9527
  /** Effect element color - cyan accent for global effects */
@@ -19035,6 +19410,102 @@ const setElementColors = (colors) => {
19035
19410
  ...colors
19036
19411
  };
19037
19412
  };
19413
+ const VIEWPORT_MARGIN = 8;
19414
+ function clampMenuPosition(clientX, clientY, width, height) {
19415
+ const vw2 = window.innerWidth;
19416
+ const vh3 = window.innerHeight;
19417
+ const maxLeft = Math.max(VIEWPORT_MARGIN, vw2 - width - VIEWPORT_MARGIN);
19418
+ const maxTop = Math.max(VIEWPORT_MARGIN, vh3 - height - VIEWPORT_MARGIN);
19419
+ return {
19420
+ left: Math.min(Math.max(VIEWPORT_MARGIN, clientX), maxLeft),
19421
+ top: Math.min(Math.max(VIEWPORT_MARGIN, clientY), maxTop)
19422
+ };
19423
+ }
19424
+ const TrackElementContextMenu = ({
19425
+ x: x2,
19426
+ y: y2,
19427
+ canSplit,
19428
+ onSplit,
19429
+ onDelete,
19430
+ onClose
19431
+ }) => {
19432
+ const menuRef = useRef(null);
19433
+ const [position, setPosition] = useState(() => ({
19434
+ left: x2,
19435
+ top: y2
19436
+ }));
19437
+ const reposition = useCallback(() => {
19438
+ const el = menuRef.current;
19439
+ if (!el) return;
19440
+ const { width, height } = el.getBoundingClientRect();
19441
+ setPosition(clampMenuPosition(x2, y2, width, height));
19442
+ }, [x2, y2]);
19443
+ useLayoutEffect(() => {
19444
+ reposition();
19445
+ }, [reposition]);
19446
+ useEffect(() => {
19447
+ window.addEventListener("resize", reposition);
19448
+ return () => window.removeEventListener("resize", reposition);
19449
+ }, [reposition]);
19450
+ useEffect(() => {
19451
+ const handleClickOutside = (e3) => {
19452
+ if (menuRef.current && !menuRef.current.contains(e3.target)) {
19453
+ onClose();
19454
+ }
19455
+ };
19456
+ const handleEscape = (e3) => {
19457
+ if (e3.key === "Escape") onClose();
19458
+ };
19459
+ document.addEventListener("mousedown", handleClickOutside);
19460
+ document.addEventListener("keydown", handleEscape);
19461
+ return () => {
19462
+ document.removeEventListener("mousedown", handleClickOutside);
19463
+ document.removeEventListener("keydown", handleEscape);
19464
+ };
19465
+ }, [onClose]);
19466
+ const wrap = (fn2) => {
19467
+ fn2();
19468
+ onClose();
19469
+ };
19470
+ const menu = /* @__PURE__ */ jsxs(
19471
+ "div",
19472
+ {
19473
+ ref: menuRef,
19474
+ className: "twick-canvas-context-menu",
19475
+ style: { left: position.left, top: position.top },
19476
+ role: "menu",
19477
+ children: [
19478
+ /* @__PURE__ */ jsx(
19479
+ "button",
19480
+ {
19481
+ type: "button",
19482
+ className: "twick-canvas-context-menu-item",
19483
+ onClick: () => wrap(onSplit),
19484
+ disabled: !canSplit,
19485
+ role: "menuitem",
19486
+ style: !canSplit ? { opacity: 0.45, cursor: "not-allowed" } : void 0,
19487
+ children: "Split at playhead"
19488
+ }
19489
+ ),
19490
+ /* @__PURE__ */ jsx("div", { className: "twick-canvas-context-menu-separator", role: "separator" }),
19491
+ /* @__PURE__ */ jsx(
19492
+ "button",
19493
+ {
19494
+ type: "button",
19495
+ className: "twick-canvas-context-menu-item twick-canvas-context-menu-item-danger",
19496
+ onClick: () => wrap(onDelete),
19497
+ role: "menuitem",
19498
+ children: "Delete"
19499
+ }
19500
+ )
19501
+ ]
19502
+ }
19503
+ );
19504
+ if (typeof document === "undefined") {
19505
+ return null;
19506
+ }
19507
+ return createPortal(menu, document.body);
19508
+ };
19038
19509
  const TrackElementView = ({
19039
19510
  element,
19040
19511
  parentWidth,
@@ -19047,13 +19518,20 @@ const TrackElementView = ({
19047
19518
  onDrag,
19048
19519
  allowOverlap = false,
19049
19520
  onDragStateChange,
19050
- elementColors
19521
+ elementColors,
19522
+ currentTime = 0,
19523
+ onContextMenuTarget,
19524
+ onDeleteElement,
19525
+ onSplitElement
19051
19526
  }) => {
19052
19527
  var _a, _b;
19053
19528
  const ref = useRef(null);
19054
19529
  const dragType = useRef(null);
19055
19530
  const lastPosRef = useRef(null);
19056
19531
  const [isDragging2, setIsDragging] = useState(false);
19532
+ const [clipMenu, setClipMenu] = useState(
19533
+ null
19534
+ );
19057
19535
  const [position, setPosition] = useState({
19058
19536
  start: 0,
19059
19537
  end: 0
@@ -19083,6 +19561,7 @@ const TrackElementView = ({
19083
19561
  newStart = nextStart - span;
19084
19562
  }
19085
19563
  }
19564
+ newStart = Math.max(0, Math.min(newStart, duration - span));
19086
19565
  return {
19087
19566
  start: newStart,
19088
19567
  end: newStart + span
@@ -19105,6 +19584,7 @@ const TrackElementView = ({
19105
19584
  if (prevEnd !== null && !allowOverlap && newStart < prevEnd) {
19106
19585
  newStart = prevEnd;
19107
19586
  }
19587
+ newStart = Math.max(0, Math.min(newStart, prev.end - MIN_DURATION));
19108
19588
  return {
19109
19589
  start: newStart,
19110
19590
  end: prev.end
@@ -19129,6 +19609,7 @@ const TrackElementView = ({
19129
19609
  newEnd = nextStart;
19130
19610
  }
19131
19611
  }
19612
+ newEnd = Math.max(prev.start + MIN_DURATION, Math.min(newEnd, duration));
19132
19613
  return {
19133
19614
  start: prev.start,
19134
19615
  end: newEnd
@@ -19166,7 +19647,7 @@ const TrackElementView = ({
19166
19647
  };
19167
19648
  const getElementColor = (elementType) => {
19168
19649
  const colors = elementColors || ELEMENT_COLORS;
19169
- const key = elementType === TIMELINE_ELEMENT_TYPE.VIDEO ? "video" : elementType === TIMELINE_ELEMENT_TYPE.AUDIO ? "audio" : elementType === TIMELINE_ELEMENT_TYPE.IMAGE ? "image" : elementType === TIMELINE_ELEMENT_TYPE.TEXT ? "text" : elementType === TIMELINE_ELEMENT_TYPE.CAPTION ? "caption" : elementType === TIMELINE_ELEMENT_TYPE.RECT ? "rect" : elementType === TIMELINE_ELEMENT_TYPE.CIRCLE ? "circle" : elementType === TIMELINE_ELEMENT_TYPE.ICON ? "icon" : elementType === TIMELINE_ELEMENT_TYPE.EFFECT ? "effect" : "element";
19650
+ const key = elementType === TIMELINE_ELEMENT_TYPE.VIDEO ? "video" : elementType === TIMELINE_ELEMENT_TYPE.AUDIO ? "audio" : elementType === TIMELINE_ELEMENT_TYPE.IMAGE ? "image" : elementType === TIMELINE_ELEMENT_TYPE.TEXT ? "text" : elementType === TIMELINE_ELEMENT_TYPE.CAPTION ? "caption" : elementType === TIMELINE_ELEMENT_TYPE.RECT ? "rect" : elementType === TIMELINE_ELEMENT_TYPE.CIRCLE ? "circle" : elementType === TIMELINE_ELEMENT_TYPE.ICON ? "icon" : elementType === TIMELINE_ELEMENT_TYPE.EMOJI ? "emoji" : elementType === TIMELINE_ELEMENT_TYPE.EFFECT ? "effect" : "element";
19170
19651
  if (key in colors) {
19171
19652
  return colors[key];
19172
19653
  }
@@ -19176,6 +19657,16 @@ const TrackElementView = ({
19176
19657
  return selectedIds.has(element.getId());
19177
19658
  }, [selectedIds, element]);
19178
19659
  const hasHandles = (selectedItem == null ? void 0 : selectedItem.getId()) === element.getId();
19660
+ const contextActionsEnabled = Boolean(
19661
+ onDeleteElement && onSplitElement && onContextMenuTarget
19662
+ );
19663
+ const handleContextMenu = (e3) => {
19664
+ if (!contextActionsEnabled) return;
19665
+ e3.preventDefault();
19666
+ e3.stopPropagation();
19667
+ onContextMenuTarget == null ? void 0 : onContextMenuTarget(element);
19668
+ setClipMenu({ x: e3.clientX, y: e3.clientY });
19669
+ };
19179
19670
  const motionProps = {
19180
19671
  ref,
19181
19672
  className: `twick-track-element ${isSelected ? "twick-track-element-selected" : "twick-track-element-default"} ${isDragging2 ? "twick-track-element-dragging" : ""}`,
@@ -19196,6 +19687,7 @@ const TrackElementView = ({
19196
19687
  onSelection(element, e3);
19197
19688
  }
19198
19689
  },
19690
+ onContextMenu: handleContextMenu,
19199
19691
  style: {
19200
19692
  backgroundColor: getElementColor(element.getType()),
19201
19693
  width: `${(position.end - position.start) / duration * 100}%`,
@@ -19203,39 +19695,52 @@ const TrackElementView = ({
19203
19695
  touchAction: "none"
19204
19696
  }
19205
19697
  };
19206
- return /* @__PURE__ */ jsx(motion.div, { ...motionProps, children: /* @__PURE__ */ jsxs("div", { style: { touchAction: "none", height: "100%" }, ...bind(), children: [
19207
- hasHandles ? /* @__PURE__ */ jsx(
19208
- "div",
19698
+ return /* @__PURE__ */ jsxs(motion.div, { ...motionProps, children: [
19699
+ clipMenu && contextActionsEnabled ? /* @__PURE__ */ jsx(
19700
+ TrackElementContextMenu,
19209
19701
  {
19210
- style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19211
- ...bindStartHandle(),
19212
- className: "twick-track-element-handle twick-track-element-handle-start"
19702
+ x: clipMenu.x,
19703
+ y: clipMenu.y,
19704
+ canSplit: canSplitElement(element, currentTime),
19705
+ onSplit: () => onSplitElement == null ? void 0 : onSplitElement(element, currentTime),
19706
+ onDelete: () => onDeleteElement == null ? void 0 : onDeleteElement(element),
19707
+ onClose: () => setClipMenu(null)
19213
19708
  }
19214
19709
  ) : null,
19215
- /* @__PURE__ */ jsx("div", { className: "twick-track-element-content", children: element.getType() === TIMELINE_ELEMENT_TYPE.EFFECT ? ((_b = (_a = element.getProps) == null ? void 0 : _a.call(element)) == null ? void 0 : _b.effectKey) ?? "Effect" : element.getText ? element.getText() : element.getName() || element.getType() }),
19216
- hasHandles ? /* @__PURE__ */ jsx(
19217
- "div",
19218
- {
19219
- style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19220
- ...bindEndHandle(),
19221
- className: "twick-track-element-handle twick-track-element-handle-end"
19222
- }
19223
- ) : null,
19224
- element.getFrameEffects ? element.getFrameEffects().map((frameEffect) => {
19225
- return /* @__PURE__ */ jsx(
19710
+ /* @__PURE__ */ jsxs("div", { style: { touchAction: "none", height: "100%" }, ...bind(), children: [
19711
+ hasHandles ? /* @__PURE__ */ jsx(
19226
19712
  "div",
19227
19713
  {
19228
- className: "twick-track-element-frame-effect",
19229
- style: {
19230
- backgroundColor: getElementColor("frameEffect"),
19231
- width: `${(frameEffect.e - frameEffect.s) / element.getDuration() * 100}%`,
19232
- left: `${frameEffect.s / element.getDuration() * 100}%`
19233
- }
19234
- },
19235
- frameEffect.s + frameEffect.e
19236
- );
19237
- }) : null
19238
- ] }) });
19714
+ style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19715
+ ...bindStartHandle(),
19716
+ className: "twick-track-element-handle twick-track-element-handle-start"
19717
+ }
19718
+ ) : null,
19719
+ /* @__PURE__ */ jsx("div", { className: "twick-track-element-content", children: element.getType() === TIMELINE_ELEMENT_TYPE.EFFECT ? ((_b = (_a = element.getProps) == null ? void 0 : _a.call(element)) == null ? void 0 : _b.effectKey) ?? "Effect" : element.getText ? element.getText() : element.getName() || element.getType() }),
19720
+ hasHandles ? /* @__PURE__ */ jsx(
19721
+ "div",
19722
+ {
19723
+ style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19724
+ ...bindEndHandle(),
19725
+ className: "twick-track-element-handle twick-track-element-handle-end"
19726
+ }
19727
+ ) : null,
19728
+ element.getFrameEffects ? element.getFrameEffects().map((frameEffect) => {
19729
+ return /* @__PURE__ */ jsx(
19730
+ "div",
19731
+ {
19732
+ className: "twick-track-element-frame-effect",
19733
+ style: {
19734
+ backgroundColor: getElementColor("frameEffect"),
19735
+ width: `${(frameEffect.e - frameEffect.s) / element.getDuration() * 100}%`,
19736
+ left: `${frameEffect.s / element.getDuration() * 100}%`
19737
+ }
19738
+ },
19739
+ frameEffect.s + frameEffect.e
19740
+ );
19741
+ }) : null
19742
+ ] })
19743
+ ] });
19239
19744
  };
19240
19745
  const TrackBase = ({
19241
19746
  duration,
@@ -19248,11 +19753,21 @@ const TrackBase = ({
19248
19753
  onDrag,
19249
19754
  allowOverlap = false,
19250
19755
  onDragStateChange,
19251
- elementColors
19756
+ elementColors,
19757
+ currentTime,
19758
+ onContextMenuTarget,
19759
+ onDeleteElement,
19760
+ onSplitElement
19252
19761
  }) => {
19253
19762
  const trackRef = useRef(null);
19254
19763
  const trackWidthStyle = `${Math.max(100, duration * zoom * 100)}px`;
19255
- const elements = track.getElements();
19764
+ const elements = [...track.getElements()].sort((a2, b2) => {
19765
+ const byStart = a2.getStart() - b2.getStart();
19766
+ if (byStart !== 0) return byStart;
19767
+ const byEnd = a2.getEnd() - b2.getEnd();
19768
+ if (byEnd !== 0) return byEnd;
19769
+ return a2.getId().localeCompare(b2.getId());
19770
+ });
19256
19771
  return /* @__PURE__ */ jsx(
19257
19772
  "div",
19258
19773
  {
@@ -19274,6 +19789,10 @@ const TrackBase = ({
19274
19789
  onDrag,
19275
19790
  onDragStateChange,
19276
19791
  elementColors,
19792
+ currentTime,
19793
+ onContextMenuTarget,
19794
+ onDeleteElement,
19795
+ onSplitElement,
19277
19796
  nextStart: index < elements.length - 1 ? elements[index + 1].getStart() : null,
19278
19797
  prevEnd: index > 0 ? elements[index - 1].getEnd() : 0
19279
19798
  },
@@ -19578,7 +20097,11 @@ function TimelineView({
19578
20097
  onDropOnTimeline,
19579
20098
  videoResolution,
19580
20099
  enableDropOnTimeline = true,
19581
- chapters = []
20100
+ chapters = [],
20101
+ currentTime = 0,
20102
+ onContextMenuTarget,
20103
+ onDeleteElement,
20104
+ onSplitElement
19582
20105
  }) {
19583
20106
  const containerRef = useRef(null);
19584
20107
  const seekContainerRef = useRef(null);
@@ -19597,15 +20120,28 @@ function TimelineView({
19597
20120
  const handleDragWithDrop = useCallback(
19598
20121
  (payload, dropPointer) => {
19599
20122
  var _a;
19600
- if (dropPointer && onElementDrop) {
19601
- const rect = (_a = timelineContentRef.current) == null ? void 0 : _a.getBoundingClientRect();
19602
- const dropTarget = rect ? getTrackOrSeparatorAt(dropPointer.clientY, rect.top, TRACK_HEIGHT) : null;
19603
- onElementDrop({ ...payload, dropTarget });
19604
- } else {
20123
+ if (!dropPointer || !onElementDrop) {
20124
+ onElementDrag(payload);
20125
+ return;
20126
+ }
20127
+ const rect = (_a = timelineContentRef.current) == null ? void 0 : _a.getBoundingClientRect();
20128
+ const dropTarget = rect ? getTrackOrSeparatorAt(dropPointer.clientY, rect.top, TRACK_HEIGHT) : null;
20129
+ if ((dropTarget == null ? void 0 : dropTarget.type) === "track") {
20130
+ const elementTrackId = payload.element.getTrackId();
20131
+ const elementTrackIndex = (tracks || []).findIndex(
20132
+ (t2) => t2.getId() === elementTrackId
20133
+ );
20134
+ if (elementTrackIndex === dropTarget.trackIndex) {
20135
+ onElementDrag(payload);
20136
+ return;
20137
+ }
20138
+ } else if (!dropTarget) {
19605
20139
  onElementDrag(payload);
20140
+ return;
19606
20141
  }
20142
+ onElementDrop({ ...payload, dropTarget });
19607
20143
  },
19608
- [onElementDrag, onElementDrop]
20144
+ [onElementDrag, onElementDrop, tracks]
19609
20145
  );
19610
20146
  useEdgeAutoScroll({
19611
20147
  isActive: !!draggingElementId,
@@ -19834,7 +20370,11 @@ function TimelineView({
19834
20370
  onDragStateChange: (isDragging2, el) => {
19835
20371
  setDraggingElementId(isDragging2 && el ? el.getId() : null);
19836
20372
  },
19837
- elementColors
20373
+ elementColors,
20374
+ currentTime,
20375
+ onContextMenuTarget,
20376
+ onDeleteElement,
20377
+ onSplitElement
19838
20378
  }
19839
20379
  )
19840
20380
  ] }),
@@ -19877,13 +20417,14 @@ const useTimelineManager = () => {
19877
20417
  const deltaMin = -minStart;
19878
20418
  const deltaMax = duration - maxEnd;
19879
20419
  const clampedDelta = Math.max(deltaMin, Math.min(deltaMax, delta));
19880
- for (const el of elements) {
19881
- const newStart = el.getStart() + clampedDelta;
19882
- const newEnd = el.getEnd() + clampedDelta;
19883
- el.setStart(newStart);
19884
- el.setEnd(newEnd);
19885
- editor.updateElement(el);
19886
- }
20420
+ const batchUpdates = elements.map((el) => ({
20421
+ elementId: el.getId(),
20422
+ updates: {
20423
+ s: el.getStart() + clampedDelta,
20424
+ e: el.getEnd() + clampedDelta
20425
+ }
20426
+ }));
20427
+ editor.updateElements(batchUpdates);
19887
20428
  setSelectedItem(element);
19888
20429
  editor.refresh();
19889
20430
  return;
@@ -19892,7 +20433,8 @@ const useTimelineManager = () => {
19892
20433
  if (dragType === DRAG_TYPE.START) {
19893
20434
  if (element instanceof VideoElement$1 || element instanceof AudioElement) {
19894
20435
  const elementProps = element.getProps();
19895
- const delta = updates.start - element.getStart() * ((elementProps == null ? void 0 : elementProps.playbackRate) || 1);
20436
+ const playbackRate = (elementProps == null ? void 0 : elementProps.playbackRate) || 1;
20437
+ const delta = (updates.start - element.getStart()) * playbackRate;
19896
20438
  if (element instanceof AudioElement) {
19897
20439
  element.setStartAt(element.getStartAt() + delta);
19898
20440
  } else {
@@ -19900,10 +20442,10 @@ const useTimelineManager = () => {
19900
20442
  }
19901
20443
  }
19902
20444
  }
19903
- element.setStart(updates.start);
19904
- element.setEnd(updates.end);
19905
- const updatedElement = editor.updateElement(element);
19906
- setSelectedItem(updatedElement);
20445
+ editor.updateElements([
20446
+ { elementId: element.getId(), updates: { s: updates.start, e: updates.end } }
20447
+ ]);
20448
+ setSelectedItem(element);
19907
20449
  editor.refresh();
19908
20450
  };
19909
20451
  const isElementTrackType = (track) => track.getType() === TRACK_TYPES.ELEMENT;
@@ -20005,6 +20547,38 @@ const useTimelineManager = () => {
20005
20547
  totalDuration
20006
20548
  };
20007
20549
  };
20550
+ const useTimelineControl = () => {
20551
+ const { editor, setSelectedItem, selectedIds } = useTimelineContext();
20552
+ const deleteItem = (item) => {
20553
+ var _a;
20554
+ const tracks = ((_a = editor.getTimelineData()) == null ? void 0 : _a.tracks) ?? [];
20555
+ const toDelete = item !== void 0 ? [item] : resolveIds(selectedIds, tracks);
20556
+ for (const el of toDelete) {
20557
+ if (el instanceof Track) {
20558
+ editor.removeTrack(el);
20559
+ } else if (el instanceof TrackElement) {
20560
+ editor.removeElement(el);
20561
+ }
20562
+ }
20563
+ setSelectedItem(null);
20564
+ };
20565
+ const splitElement = (element, currentTime) => {
20566
+ if (!canSplitElement(element, currentTime)) return;
20567
+ void editor.splitElement(element, currentTime);
20568
+ };
20569
+ const handleUndo = () => {
20570
+ editor.undo();
20571
+ };
20572
+ const handleRedo = () => {
20573
+ editor.redo();
20574
+ };
20575
+ return {
20576
+ splitElement,
20577
+ deleteItem,
20578
+ handleUndo,
20579
+ handleRedo
20580
+ };
20581
+ };
20008
20582
  function useTimelineSelection() {
20009
20583
  const { editor, selectedIds, setSelection, setSelectedItem } = useTimelineContext();
20010
20584
  const handleItemSelect = useCallback(
@@ -20085,8 +20659,9 @@ const TimelineManager = ({
20085
20659
  elementColors
20086
20660
  }) => {
20087
20661
  var _a, _b;
20088
- const { playerState } = useLivePlayerContext();
20662
+ const { playerState, currentTime } = useLivePlayerContext();
20089
20663
  const { followPlayheadEnabled, editor, videoResolution, setSelectedItem } = useTimelineContext();
20664
+ const { deleteItem, splitElement } = useTimelineControl();
20090
20665
  const {
20091
20666
  timelineData,
20092
20667
  totalDuration,
@@ -20107,6 +20682,12 @@ const TimelineManager = ({
20107
20682
  setPlayheadState(state);
20108
20683
  }, []);
20109
20684
  const isPlayheadActive = followPlayheadEnabled && playerState === PLAYER_STATE.PLAYING || playheadState.isDragging;
20685
+ const handleContextMenuTarget = useCallback(
20686
+ (element) => {
20687
+ setSelectedItem(element);
20688
+ },
20689
+ [setSelectedItem]
20690
+ );
20110
20691
  const handleDropOnTimeline = useCallback(
20111
20692
  async (params) => {
20112
20693
  const { track, timeSec, type, url } = params;
@@ -20154,6 +20735,10 @@ const TimelineManager = ({
20154
20735
  onEmptyClick: handleEmptyClick,
20155
20736
  onMarqueeSelect: handleMarqueeSelect,
20156
20737
  elementColors,
20738
+ currentTime,
20739
+ onContextMenuTarget: handleContextMenuTarget,
20740
+ onDeleteElement: (el) => deleteItem(el),
20741
+ onSplitElement: (el, t2) => splitElement(el, t2),
20157
20742
  playheadPositionPx: playheadState.positionPx,
20158
20743
  isPlayheadActive,
20159
20744
  chapters: ((_a = timelineData == null ? void 0 : timelineData.metadata) == null ? void 0 : _a.chapters) ?? [],
@@ -20237,11 +20822,12 @@ const PlayerControls = ({
20237
20822
  }
20238
20823
  }, [selectedIds.size, onDelete]);
20239
20824
  const hasSelection = selectedIds.size > 0;
20825
+ const canSplitSelected = selectedItem instanceof TrackElement ? canSplitElement(selectedItem, currentTime) : false;
20240
20826
  const handleSplit = useCallback(() => {
20241
- if (selectedItem instanceof TrackElement && onSplit) {
20827
+ if (selectedItem instanceof TrackElement && onSplit && canSplitSelected) {
20242
20828
  onSplit(selectedItem, currentTime);
20243
20829
  }
20244
- }, [selectedItem, onSplit, currentTime]);
20830
+ }, [selectedItem, onSplit, currentTime, canSplitSelected]);
20245
20831
  const handleZoomIn = useCallback(() => {
20246
20832
  if (setZoomLevel && zoomLevel < MAX_ZOOM) {
20247
20833
  setZoomLevel(zoomLevel + ZOOM_STEP);
@@ -20268,9 +20854,9 @@ const PlayerControls = ({
20268
20854
  "button",
20269
20855
  {
20270
20856
  onClick: handleSplit,
20271
- disabled: !(selectedItem instanceof TrackElement),
20857
+ disabled: !canSplitSelected,
20272
20858
  title: "Split",
20273
- className: `control-btn split-btn ${!(selectedItem instanceof TrackElement) ? "btn-disabled" : ""}`,
20859
+ className: `control-btn split-btn ${!canSplitSelected ? "btn-disabled" : ""}`,
20274
20860
  children: /* @__PURE__ */ jsx(Scissors, { className: "icon-md" })
20275
20861
  }
20276
20862
  ),
@@ -20384,37 +20970,6 @@ const usePlayerControl = () => {
20384
20970
  togglePlayback
20385
20971
  };
20386
20972
  };
20387
- const useTimelineControl = () => {
20388
- const { editor, setSelectedItem, selectedIds } = useTimelineContext();
20389
- const deleteItem = (item) => {
20390
- var _a;
20391
- const tracks = ((_a = editor.getTimelineData()) == null ? void 0 : _a.tracks) ?? [];
20392
- const toDelete = item !== void 0 ? [item] : resolveIds(selectedIds, tracks);
20393
- for (const el of toDelete) {
20394
- if (el instanceof Track) {
20395
- editor.removeTrack(el);
20396
- } else if (el instanceof TrackElement) {
20397
- editor.removeElement(el);
20398
- }
20399
- }
20400
- setSelectedItem(null);
20401
- };
20402
- const splitElement = (element, currentTime) => {
20403
- editor.splitElement(element, currentTime);
20404
- };
20405
- const handleUndo = () => {
20406
- editor.undo();
20407
- };
20408
- const handleRedo = () => {
20409
- editor.redo();
20410
- };
20411
- return {
20412
- splitElement,
20413
- deleteItem,
20414
- handleUndo,
20415
- handleRedo
20416
- };
20417
- };
20418
20973
  function shouldIgnoreKeydown() {
20419
20974
  const active = document.activeElement;
20420
20975
  if (!active) return false;