@twick/video-editor 0.15.27 → 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.js CHANGED
@@ -7,6 +7,7 @@ const jsxRuntime = require("react/jsx-runtime");
7
7
  const livePlayer = require("@twick/live-player");
8
8
  const timeline = require("@twick/timeline");
9
9
  const React = require("react");
10
+ const reactDom = require("react-dom");
10
11
  function t(t2, e3, s2) {
11
12
  return (e3 = function(t3) {
12
13
  var e4 = function(t4, e5) {
@@ -5945,6 +5946,9 @@ function ea() {
5945
5946
  function sa() {
5946
5947
  return !ta && (!(arguments.length > 0 && void 0 !== arguments[0]) || arguments[0]) && (ta = ea()), ta;
5947
5948
  }
5949
+ function ia(t2) {
5950
+ ta = t2;
5951
+ }
5948
5952
  const ra = ["filters", "resizeFilter", "src", "crossOrigin", "type"], na = ["cropX", "cropY"];
5949
5953
  class oa extends Li {
5950
5954
  static getDefaults() {
@@ -6472,13 +6476,7 @@ function Za(e3, s2) {
6472
6476
  return tt.setClass(r2, e3), r2;
6473
6477
  }
6474
6478
  t(Qa, "type", "ColorMatrix"), t(Qa, "defaults", Ja), t(Qa, "uniformLocations", ["uColorMatrix", "uConstants"]), tt.setClass(Qa);
6475
- 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]);
6476
- 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]);
6477
- 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]);
6478
- 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]);
6479
- 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]);
6480
- 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]);
6481
- 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]);
6479
+ 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]);
6482
6480
  class oh extends Va {
6483
6481
  constructor() {
6484
6482
  let t2 = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {};
@@ -6841,6 +6839,7 @@ class bh extends Va {
6841
6839
  }
6842
6840
  }
6843
6841
  t(bh, "type", "Vibrance"), t(bh, "defaults", { vibrance: 0 }), t(bh, "uniformLocations", ["uVibrance"]), tt.setClass(bh);
6842
+ 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 });
6844
6843
  var __defProp2 = Object.defineProperty;
6845
6844
  var __defNormalProp2 = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6846
6845
  var __publicField2 = (obj, key, value) => __defNormalProp2(obj, key + "", value);
@@ -6901,6 +6900,8 @@ const ELEMENT_TYPES = {
6901
6900
  RECT: "rect",
6902
6901
  /** Circle element type */
6903
6902
  CIRCLE: "circle",
6903
+ /** Emoji sticker element type */
6904
+ EMOJI: "emoji",
6904
6905
  /** Arrow annotation element type */
6905
6906
  ARROW: "arrow",
6906
6907
  /** Line annotation / shape element type */
@@ -7149,6 +7150,24 @@ const rotateControl = new ai({
7149
7150
  /** Whether to show connection line */
7150
7151
  withConnection: true
7151
7152
  });
7153
+ const COLOR_FILTERS = {
7154
+ SATURATED: "saturated",
7155
+ BRIGHT: "bright",
7156
+ VIBRANT: "vibrant",
7157
+ RETRO: "retro",
7158
+ BLACK_WHITE: "blackWhite",
7159
+ SEPIA: "sepia",
7160
+ COOL: "cool",
7161
+ WARM: "warm",
7162
+ CINEMATIC: "cinematic",
7163
+ SOFT_GLOW: "softGlow",
7164
+ MOODY: "moody",
7165
+ DREAMY: "dreamy",
7166
+ INVERTED: "inverted",
7167
+ VINTAGE: "vintage",
7168
+ DRAMATIC: "dramatic",
7169
+ FADED: "faded"
7170
+ };
7152
7171
  class LRUCache {
7153
7172
  constructor(maxSize = 100) {
7154
7173
  if (maxSize <= 0) {
@@ -7532,6 +7551,275 @@ const getObjectFitSize = (objectFit, elementSize, containerSize) => {
7532
7551
  };
7533
7552
  }
7534
7553
  };
7554
+ const {
7555
+ Blur,
7556
+ Brightness,
7557
+ ColorMatrix,
7558
+ Contrast,
7559
+ Grayscale,
7560
+ HueRotation,
7561
+ Invert,
7562
+ Saturation,
7563
+ Sepia
7564
+ } = Sh;
7565
+ let canvas2dFilterBackendInstalled = false;
7566
+ function ensureCanvas2dImageFilterBackend() {
7567
+ if (canvas2dFilterBackendInstalled) return;
7568
+ canvas2dFilterBackendInstalled = true;
7569
+ ia(new Zo());
7570
+ }
7571
+ function getSourceBitmapSize(img) {
7572
+ const el = img.getElement();
7573
+ if (!el) return null;
7574
+ const w2 = "naturalWidth" in el && el.naturalWidth > 0 ? el.naturalWidth : el.width;
7575
+ const h2 = "naturalHeight" in el && el.naturalHeight > 0 ? el.naturalHeight : el.height;
7576
+ if (!w2 || !h2) return null;
7577
+ return { w: w2, h: h2 };
7578
+ }
7579
+ function applyFiltersSafe(img) {
7580
+ ensureCanvas2dImageFilterBackend();
7581
+ img.applyFilters();
7582
+ }
7583
+ const bright = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.48));
7584
+ const contr = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.55));
7585
+ const sat = (m2) => Math.min(1, Math.max(-1, (m2 - 1) * 0.72));
7586
+ const IDENTITY = [
7587
+ 1,
7588
+ 0,
7589
+ 0,
7590
+ 0,
7591
+ 0,
7592
+ 0,
7593
+ 1,
7594
+ 0,
7595
+ 0,
7596
+ 0,
7597
+ 0,
7598
+ 0,
7599
+ 1,
7600
+ 0,
7601
+ 0,
7602
+ 0,
7603
+ 0,
7604
+ 0,
7605
+ 1,
7606
+ 0
7607
+ ];
7608
+ const SEPIA_MATRIX = [
7609
+ 0.393,
7610
+ 0.769,
7611
+ 0.189,
7612
+ 0,
7613
+ 0,
7614
+ 0.349,
7615
+ 0.686,
7616
+ 0.168,
7617
+ 0,
7618
+ 0,
7619
+ 0.272,
7620
+ 0.534,
7621
+ 0.131,
7622
+ 0,
7623
+ 0,
7624
+ 0,
7625
+ 0,
7626
+ 0,
7627
+ 1,
7628
+ 0
7629
+ ];
7630
+ function sepiaMix(strength) {
7631
+ const t2 = Math.min(1, Math.max(0, strength));
7632
+ const m2 = IDENTITY.map((v2, i2) => v2 + (SEPIA_MATRIX[i2] - v2) * t2);
7633
+ return new ColorMatrix({ matrix: m2, colorsOnly: true });
7634
+ }
7635
+ const twickBlurToFabric = (v2) => Math.min(0.22, Math.max(0, v2 * 0.045));
7636
+ function buildFilters(filterType) {
7637
+ switch (filterType) {
7638
+ case COLOR_FILTERS.SATURATED:
7639
+ return {
7640
+ filters: [
7641
+ new Saturation({ saturation: sat(1.4) }),
7642
+ new Contrast({ contrast: contr(1.1) })
7643
+ ],
7644
+ opacityFactor: 1
7645
+ };
7646
+ case COLOR_FILTERS.BRIGHT:
7647
+ return {
7648
+ filters: [
7649
+ new Brightness({ brightness: bright(1.3) }),
7650
+ new Contrast({ contrast: contr(1.05) })
7651
+ ],
7652
+ opacityFactor: 1
7653
+ };
7654
+ case COLOR_FILTERS.VIBRANT:
7655
+ return {
7656
+ filters: [
7657
+ new Saturation({ saturation: sat(1.6) }),
7658
+ new Brightness({ brightness: bright(1.15) }),
7659
+ new Contrast({ contrast: contr(1.1) })
7660
+ ],
7661
+ opacityFactor: 1
7662
+ };
7663
+ case COLOR_FILTERS.RETRO:
7664
+ return {
7665
+ filters: [
7666
+ sepiaMix(0.8),
7667
+ new Contrast({ contrast: contr(1.3) }),
7668
+ new Brightness({ brightness: bright(0.85) }),
7669
+ new Saturation({ saturation: sat(0.8) })
7670
+ ],
7671
+ opacityFactor: 1
7672
+ };
7673
+ case COLOR_FILTERS.BLACK_WHITE:
7674
+ return {
7675
+ filters: [
7676
+ new Grayscale(),
7677
+ new Contrast({ contrast: contr(1.25) }),
7678
+ new Brightness({ brightness: bright(1.05) })
7679
+ ],
7680
+ opacityFactor: 1
7681
+ };
7682
+ case COLOR_FILTERS.SEPIA:
7683
+ return {
7684
+ filters: [
7685
+ new Sepia(),
7686
+ new Contrast({ contrast: contr(1.08) })
7687
+ ],
7688
+ opacityFactor: 1
7689
+ };
7690
+ case COLOR_FILTERS.COOL:
7691
+ return {
7692
+ filters: [
7693
+ new HueRotation({ rotation: 15 / 180 }),
7694
+ new Brightness({ brightness: bright(1.1) }),
7695
+ new Saturation({ saturation: sat(1.3) }),
7696
+ new Contrast({ contrast: contr(1.05) })
7697
+ ],
7698
+ opacityFactor: 1
7699
+ };
7700
+ case COLOR_FILTERS.WARM:
7701
+ return {
7702
+ filters: [
7703
+ new HueRotation({ rotation: -15 / 180 }),
7704
+ new Brightness({ brightness: bright(1.15) }),
7705
+ new Saturation({ saturation: sat(1.3) }),
7706
+ new Contrast({ contrast: contr(1.05) })
7707
+ ],
7708
+ opacityFactor: 1
7709
+ };
7710
+ case COLOR_FILTERS.CINEMATIC:
7711
+ return {
7712
+ filters: [
7713
+ new Contrast({ contrast: contr(1.4) }),
7714
+ new Brightness({ brightness: bright(0.95) }),
7715
+ new Saturation({ saturation: sat(0.85) }),
7716
+ sepiaMix(0.2)
7717
+ ],
7718
+ opacityFactor: 1
7719
+ };
7720
+ case COLOR_FILTERS.SOFT_GLOW:
7721
+ return {
7722
+ filters: [
7723
+ new Brightness({ brightness: bright(1.2) }),
7724
+ new Contrast({ contrast: contr(0.95) }),
7725
+ new Blur({ blur: twickBlurToFabric(1.2) }),
7726
+ new Saturation({ saturation: sat(1.1) })
7727
+ ],
7728
+ opacityFactor: 1
7729
+ };
7730
+ case COLOR_FILTERS.MOODY:
7731
+ return {
7732
+ filters: [
7733
+ new Brightness({ brightness: bright(1.05) }),
7734
+ new Contrast({ contrast: contr(1.4) }),
7735
+ new Saturation({ saturation: sat(0.65) }),
7736
+ sepiaMix(0.2)
7737
+ ],
7738
+ opacityFactor: 1
7739
+ };
7740
+ case COLOR_FILTERS.DREAMY:
7741
+ return {
7742
+ filters: [
7743
+ new Brightness({ brightness: bright(1.3) }),
7744
+ new Blur({ blur: twickBlurToFabric(2) }),
7745
+ new Saturation({ saturation: sat(1.4) }),
7746
+ new Contrast({ contrast: contr(0.95) })
7747
+ ],
7748
+ opacityFactor: 1
7749
+ };
7750
+ case COLOR_FILTERS.INVERTED:
7751
+ return {
7752
+ filters: [
7753
+ new Invert({ invert: true, alpha: false }),
7754
+ new HueRotation({ rotation: 1 })
7755
+ ],
7756
+ opacityFactor: 1
7757
+ };
7758
+ case COLOR_FILTERS.VINTAGE:
7759
+ return {
7760
+ filters: [
7761
+ sepiaMix(0.4),
7762
+ new Saturation({ saturation: sat(1.4) }),
7763
+ new Contrast({ contrast: contr(1.2) }),
7764
+ new Brightness({ brightness: bright(1.1) })
7765
+ ],
7766
+ opacityFactor: 1
7767
+ };
7768
+ case COLOR_FILTERS.DRAMATIC:
7769
+ return {
7770
+ filters: [
7771
+ new Contrast({ contrast: contr(1.5) }),
7772
+ new Brightness({ brightness: bright(0.9) }),
7773
+ new Saturation({ saturation: sat(1.2) })
7774
+ ],
7775
+ opacityFactor: 1
7776
+ };
7777
+ case COLOR_FILTERS.FADED:
7778
+ return {
7779
+ filters: [
7780
+ new Brightness({ brightness: bright(1.2) }),
7781
+ new Saturation({ saturation: sat(0.8) }),
7782
+ new Contrast({ contrast: contr(0.9) })
7783
+ ],
7784
+ opacityFactor: 0.9
7785
+ };
7786
+ default:
7787
+ return { filters: [], opacityFactor: 1 };
7788
+ }
7789
+ }
7790
+ function applyFabricMediaColorFilters(img, mediaFilter, elementOpacity) {
7791
+ const key = (mediaFilter == null ? void 0 : mediaFilter.trim()) || "none";
7792
+ if (key === "none") {
7793
+ img.filters = [];
7794
+ img.set("opacity", elementOpacity);
7795
+ applyFiltersSafe(img);
7796
+ return;
7797
+ }
7798
+ const { filters: filterList, opacityFactor } = buildFilters(key);
7799
+ if (filterList.length === 0) {
7800
+ img.filters = [];
7801
+ img.set("opacity", elementOpacity);
7802
+ applyFiltersSafe(img);
7803
+ return;
7804
+ }
7805
+ if (!getSourceBitmapSize(img)) {
7806
+ img.filters = [];
7807
+ img.set("opacity", elementOpacity);
7808
+ return;
7809
+ }
7810
+ img.filters = filterList;
7811
+ img.set("opacity", elementOpacity * opacityFactor);
7812
+ try {
7813
+ applyFiltersSafe(img);
7814
+ } catch {
7815
+ img.filters = [];
7816
+ img.set("opacity", elementOpacity);
7817
+ try {
7818
+ applyFiltersSafe(img);
7819
+ } catch {
7820
+ }
7821
+ }
7822
+ }
7535
7823
  const MARGIN = 10;
7536
7824
  const addTextElement = ({
7537
7825
  element,
@@ -7622,7 +7910,7 @@ const setImageProps = ({
7622
7910
  canvasMetadata,
7623
7911
  lockAspectRatio = true
7624
7912
  }) => {
7625
- var _a, _b, _c, _d, _e2;
7913
+ var _a, _b, _c, _d, _e2, _f;
7626
7914
  const width = (((_a = element.props) == null ? void 0 : _a.width) || 0) * canvasMetadata.scaleX || canvasMetadata.width;
7627
7915
  const height = (((_b = element.props) == null ? void 0 : _b.height) || 0) * canvasMetadata.scaleY || canvasMetadata.height;
7628
7916
  const { x: x2, y: y2 } = convertToCanvasPosition(
@@ -7636,11 +7924,15 @@ const setImageProps = ({
7636
7924
  img.set("height", height);
7637
7925
  img.set("left", x2);
7638
7926
  img.set("top", y2);
7639
- img.set("opacity", ((_e2 = element.props) == null ? void 0 : _e2.opacity) ?? 1);
7640
7927
  img.set("selectable", true);
7641
7928
  img.set("hasControls", true);
7642
7929
  img.set("touchAction", "all");
7643
7930
  img.set("lockUniScaling", lockAspectRatio);
7931
+ applyFabricMediaColorFilters(
7932
+ img,
7933
+ (_e2 = element.props) == null ? void 0 : _e2.mediaFilter,
7934
+ ((_f = element.props) == null ? void 0 : _f.opacity) ?? 1
7935
+ );
7644
7936
  };
7645
7937
  const addCaptionElement = ({
7646
7938
  element,
@@ -7650,48 +7942,52 @@ const addCaptionElement = ({
7650
7942
  canvasMetadata,
7651
7943
  lockAspectRatio = false
7652
7944
  }) => {
7653
- 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;
7654
- const applyToAll = (captionProps == null ? void 0 : captionProps.applyToAll) ?? false;
7655
- 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);
7945
+ 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;
7946
+ const useTrackDefaults = ((_a = element.props) == null ? void 0 : _a.useTrackDefaults) ?? true;
7947
+ const trackColors = captionProps == null ? void 0 : captionProps.colors;
7948
+ const elementColors = (_b = element.props) == null ? void 0 : _b.colors;
7949
+ const resolvedColors = useTrackDefaults ? trackColors : { ...trackColors ?? {}, ...elementColors ?? {} };
7950
+ const captionTextColor = (resolvedColors == null ? void 0 : resolvedColors.text) ?? ((_c = captionProps == null ? void 0 : captionProps.color) == null ? void 0 : _c.text);
7656
7951
  const { x: x2, y: y2 } = convertToCanvasPosition(
7657
- (applyToAll ? captionProps == null ? void 0 : captionProps.x : ((_c = element.props) == null ? void 0 : _c.x) ?? (captionProps == null ? void 0 : captionProps.x)) ?? 0,
7658
- (applyToAll ? captionProps == null ? void 0 : captionProps.y : ((_d = element.props) == null ? void 0 : _d.y) ?? (captionProps == null ? void 0 : captionProps.y)) ?? 0,
7952
+ (useTrackDefaults ? captionProps == null ? void 0 : captionProps.x : (_d = element.props) == null ? void 0 : _d.x) ?? (captionProps == null ? void 0 : captionProps.x) ?? 0,
7953
+ (useTrackDefaults ? captionProps == null ? void 0 : captionProps.y : (_e2 = element.props) == null ? void 0 : _e2.y) ?? (captionProps == null ? void 0 : captionProps.y) ?? 0,
7659
7954
  canvasMetadata
7660
7955
  );
7661
- let width = ((_e2 = element.props) == null ? void 0 : _e2.width) ? element.props.width * canvasMetadata.scaleX : canvasMetadata.width - 2 * MARGIN;
7662
- if ((_f = element.props) == null ? void 0 : _f.maxWidth) {
7956
+ let width = ((_f = element.props) == null ? void 0 : _f.width) ? element.props.width * canvasMetadata.scaleX : canvasMetadata.width - 2 * MARGIN;
7957
+ if ((_g = element.props) == null ? void 0 : _g.maxWidth) {
7663
7958
  width = Math.min(width, element.props.maxWidth * canvasMetadata.scaleX);
7664
7959
  }
7665
- const elementColors = (_g = element.props) == null ? void 0 : _g.colors;
7666
- const resolvedFill = (applyToAll ? captionTextColor : ((_h2 = element.props) == null ? void 0 : _h2.fill) ?? (elementColors == null ? void 0 : elementColors.text) ?? captionTextColor) ?? DEFAULT_CAPTION_PROPS.fill;
7667
- const trackColors = captionProps == null ? void 0 : captionProps.colors;
7960
+ const resolvedFill = (useTrackDefaults ? void 0 : (_h2 = element.props) == null ? void 0 : _h2.fill) ?? captionTextColor ?? DEFAULT_CAPTION_PROPS.fill;
7668
7961
  const trackStroke = trackColors == null ? void 0 : trackColors.outlineColor;
7669
7962
  const elementStroke = (elementColors == null ? void 0 : elementColors.outlineColor) ?? ((_i2 = element.props) == null ? void 0 : _i2.stroke);
7670
- const resolvedStroke = (applyToAll ? trackStroke ?? elementStroke : elementStroke ?? trackStroke) ?? void 0;
7671
- const caption = new Uo(((_j = element.props) == null ? void 0 : _j.text) || element.t || "", {
7963
+ const resolvedStroke = (useTrackDefaults ? trackStroke : elementStroke ?? trackStroke) ?? void 0;
7964
+ const trackFont = (captionProps == null ? void 0 : captionProps.font) ?? {};
7965
+ const elementFont = ((_j = element.props) == null ? void 0 : _j.font) ?? {};
7966
+ const resolvedFont = useTrackDefaults ? trackFont : { ...trackFont, ...elementFont };
7967
+ const caption = new Uo(((_k = element.props) == null ? void 0 : _k.text) || element.t || "", {
7672
7968
  left: x2,
7673
7969
  top: y2,
7674
7970
  originX: "center",
7675
7971
  originY: "center",
7676
- angle: ((_k = element.props) == null ? void 0 : _k.rotation) || 0,
7972
+ angle: ((_l = element.props) == null ? void 0 : _l.rotation) || 0,
7677
7973
  fontSize: Math.round(
7678
- ((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
7974
+ ((resolvedFont == null ? void 0 : resolvedFont.size) ?? DEFAULT_CAPTION_PROPS.size) * canvasMetadata.scaleX
7679
7975
  ),
7680
- 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,
7976
+ fontFamily: (resolvedFont == null ? void 0 : resolvedFont.family) ?? DEFAULT_CAPTION_PROPS.family,
7681
7977
  fill: resolvedFill,
7682
- 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,
7978
+ fontWeight: (resolvedFont == null ? void 0 : resolvedFont.weight) ?? DEFAULT_CAPTION_PROPS.fontWeight,
7683
7979
  ...resolvedStroke ? { stroke: resolvedStroke } : {},
7684
- opacity: (applyToAll ? captionProps == null ? void 0 : captionProps.opacity : ((_x = element.props) == null ? void 0 : _x.opacity) ?? (captionProps == null ? void 0 : captionProps.opacity)) ?? 1,
7980
+ opacity: (useTrackDefaults ? void 0 : (_m = element.props) == null ? void 0 : _m.opacity) ?? (captionProps == null ? void 0 : captionProps.opacity) ?? 1,
7685
7981
  width,
7686
7982
  splitByGrapheme: false,
7687
- textAlign: ((_y = element.props) == null ? void 0 : _y.textAlign) ?? "center",
7983
+ textAlign: ((_n2 = element.props) == null ? void 0 : _n2.textAlign) ?? "center",
7688
7984
  shadow: new Ds({
7689
- 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]),
7690
- 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]),
7691
- 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,
7692
- 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
7985
+ 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]),
7986
+ 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]),
7987
+ blur: (useTrackDefaults ? void 0 : (_w = element.props) == null ? void 0 : _w.shadowBlur) ?? (captionProps == null ? void 0 : captionProps.shadowBlur) ?? DEFAULT_CAPTION_PROPS.shadowBlur,
7988
+ color: (useTrackDefaults ? void 0 : (_x = element.props) == null ? void 0 : _x.shadowColor) ?? (captionProps == null ? void 0 : captionProps.shadowColor) ?? DEFAULT_CAPTION_PROPS.shadowColor
7693
7989
  }),
7694
- 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
7990
+ strokeWidth: ((useTrackDefaults ? void 0 : (_y = element.props) == null ? void 0 : _y.lineWidth) ?? (captionProps == null ? void 0 : captionProps.lineWidth) ?? DEFAULT_CAPTION_PROPS.lineWidth) * 0.025
7695
7991
  });
7696
7992
  caption.set("id", element.id);
7697
7993
  caption.set("zIndex", index);
@@ -7746,8 +8042,13 @@ const addImageElement = async ({
7746
8042
  currentFrameEffect,
7747
8043
  lockAspectRatio = true
7748
8044
  }) => {
8045
+ var _a, _b;
7749
8046
  try {
7750
- const img = await oa.fromURL(imageUrl || element.props.src || "");
8047
+ const rawSrc = imageUrl || element.props.src || "";
8048
+ const mediaFilter = (_b = (_a = element.props) == null ? void 0 : _a.mediaFilter) == null ? void 0 : _b.trim();
8049
+ const useFilter = !!mediaFilter && mediaFilter !== "none";
8050
+ const fromUrlOpts = useFilter && /^https?:\/\//i.test(rawSrc) ? { crossOrigin: "anonymous" } : {};
8051
+ const img = await oa.fromURL(rawSrc, fromUrlOpts);
7751
8052
  img.set({
7752
8053
  originX: "center",
7753
8054
  originY: "center",
@@ -7784,7 +8085,7 @@ const addMediaGroup = ({
7784
8085
  currentFrameEffect,
7785
8086
  lockAspectRatio = true
7786
8087
  }) => {
7787
- var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2;
8088
+ var _a, _b, _c, _d, _e2, _f, _g, _h2, _i2, _j, _k, _l, _m, _n2, _o2;
7788
8089
  let frameSize;
7789
8090
  let angle;
7790
8091
  let framePosition;
@@ -7839,9 +8140,13 @@ const addMediaGroup = ({
7839
8140
  originX: "center",
7840
8141
  originY: "center",
7841
8142
  scaleX: newSize.width / img.width,
7842
- scaleY: newSize.height / img.height,
7843
- opacity: ((_n2 = element.props) == null ? void 0 : _n2.opacity) ?? 1
8143
+ scaleY: newSize.height / img.height
7844
8144
  });
8145
+ applyFabricMediaColorFilters(
8146
+ img,
8147
+ (_n2 = element.props) == null ? void 0 : _n2.mediaFilter,
8148
+ ((_o2 = element.props) == null ? void 0 : _o2.opacity) ?? 1
8149
+ );
7845
8150
  const { x: x2, y: y2 } = convertToCanvasPosition(
7846
8151
  (framePosition == null ? void 0 : framePosition.x) || 0,
7847
8152
  (framePosition == null ? void 0 : framePosition.y) || 0,
@@ -8327,7 +8632,8 @@ const CaptionElement = {
8327
8632
  context.canvasMetadata,
8328
8633
  context.videoSize
8329
8634
  );
8330
- if ((_a = context.captionPropsRef.current) == null ? void 0 : _a.applyToAll) {
8635
+ const useTrackDefaults = ((_a = element.props) == null ? void 0 : _a.useTrackDefaults) ?? true;
8636
+ if (useTrackDefaults) {
8331
8637
  return {
8332
8638
  element,
8333
8639
  operation: CANVAS_OPERATIONS.CAPTION_PROPS_UPDATED,
@@ -8544,6 +8850,72 @@ const EffectElement = {
8544
8850
  return;
8545
8851
  }
8546
8852
  };
8853
+ const EmojiElement = {
8854
+ name: ELEMENT_TYPES.EMOJI,
8855
+ async add(params) {
8856
+ var _a;
8857
+ const { element, index, canvas, canvasMetadata, lockAspectRatio } = params;
8858
+ await addImageElement({
8859
+ element,
8860
+ index,
8861
+ canvas,
8862
+ canvasMetadata,
8863
+ lockAspectRatio: lockAspectRatio ?? ((_a = element.props) == null ? void 0 : _a.lockAspectRatio) ?? true
8864
+ });
8865
+ },
8866
+ updateFromFabricObject(object, element, context) {
8867
+ const canvasCenter = getObjectCanvasCenter(object);
8868
+ const { x: x2, y: y2 } = convertToVideoPosition(
8869
+ canvasCenter.x,
8870
+ canvasCenter.y,
8871
+ context.canvasMetadata,
8872
+ context.videoSize
8873
+ );
8874
+ if (object.type === "group") {
8875
+ const scaledW2 = (object.width ?? 0) * (object.scaleX ?? 1);
8876
+ const scaledH2 = (object.height ?? 0) * (object.scaleY ?? 1);
8877
+ const { width: fw, height: fh2 } = convertToVideoDimensions(
8878
+ scaledW2,
8879
+ scaledH2,
8880
+ context.canvasMetadata
8881
+ );
8882
+ const updatedFrameSize = [fw, fh2];
8883
+ const frame2 = element.frame;
8884
+ return {
8885
+ element: {
8886
+ ...element,
8887
+ frame: {
8888
+ ...frame2,
8889
+ rotation: getObjectCanvasAngle(object),
8890
+ size: updatedFrameSize,
8891
+ x: x2,
8892
+ y: y2
8893
+ }
8894
+ }
8895
+ };
8896
+ }
8897
+ const scaledW = (object.width ?? 0) * (object.scaleX ?? 1);
8898
+ const scaledH = (object.height ?? 0) * (object.scaleY ?? 1);
8899
+ const { width, height } = convertToVideoDimensions(
8900
+ scaledW,
8901
+ scaledH,
8902
+ context.canvasMetadata
8903
+ );
8904
+ return {
8905
+ element: {
8906
+ ...element,
8907
+ props: {
8908
+ ...element.props,
8909
+ rotation: getObjectCanvasAngle(object),
8910
+ width,
8911
+ height,
8912
+ x: x2,
8913
+ y: y2
8914
+ }
8915
+ }
8916
+ };
8917
+ }
8918
+ };
8547
8919
  class ElementController {
8548
8920
  constructor() {
8549
8921
  __publicField2(this, "elements", /* @__PURE__ */ new Map());
@@ -8565,6 +8937,7 @@ function registerElements() {
8565
8937
  elementController.register(RectElement);
8566
8938
  elementController.register(CircleElement);
8567
8939
  elementController.register(TextElement);
8940
+ elementController.register(EmojiElement);
8568
8941
  elementController.register(CaptionElement);
8569
8942
  elementController.register(WatermarkElement);
8570
8943
  elementController.register(ArrowElement);
@@ -9149,6 +9522,8 @@ const DEFAULT_ELEMENT_COLORS = {
9149
9522
  animation: "#4B9B78",
9150
9523
  /** Icon element color - bright orchid */
9151
9524
  icon: "#A76CD4",
9525
+ /** Emoji element color - warm amber */
9526
+ emoji: "#F59E0B",
9152
9527
  /** Circle element color - deep byzantium */
9153
9528
  circle: "#703D8B",
9154
9529
  /** Effect element color - cyan accent for global effects */
@@ -19037,6 +19412,102 @@ const setElementColors = (colors) => {
19037
19412
  ...colors
19038
19413
  };
19039
19414
  };
19415
+ const VIEWPORT_MARGIN = 8;
19416
+ function clampMenuPosition(clientX, clientY, width, height) {
19417
+ const vw2 = window.innerWidth;
19418
+ const vh3 = window.innerHeight;
19419
+ const maxLeft = Math.max(VIEWPORT_MARGIN, vw2 - width - VIEWPORT_MARGIN);
19420
+ const maxTop = Math.max(VIEWPORT_MARGIN, vh3 - height - VIEWPORT_MARGIN);
19421
+ return {
19422
+ left: Math.min(Math.max(VIEWPORT_MARGIN, clientX), maxLeft),
19423
+ top: Math.min(Math.max(VIEWPORT_MARGIN, clientY), maxTop)
19424
+ };
19425
+ }
19426
+ const TrackElementContextMenu = ({
19427
+ x: x2,
19428
+ y: y2,
19429
+ canSplit,
19430
+ onSplit,
19431
+ onDelete,
19432
+ onClose
19433
+ }) => {
19434
+ const menuRef = React.useRef(null);
19435
+ const [position, setPosition] = React.useState(() => ({
19436
+ left: x2,
19437
+ top: y2
19438
+ }));
19439
+ const reposition = React.useCallback(() => {
19440
+ const el = menuRef.current;
19441
+ if (!el) return;
19442
+ const { width, height } = el.getBoundingClientRect();
19443
+ setPosition(clampMenuPosition(x2, y2, width, height));
19444
+ }, [x2, y2]);
19445
+ React.useLayoutEffect(() => {
19446
+ reposition();
19447
+ }, [reposition]);
19448
+ React.useEffect(() => {
19449
+ window.addEventListener("resize", reposition);
19450
+ return () => window.removeEventListener("resize", reposition);
19451
+ }, [reposition]);
19452
+ React.useEffect(() => {
19453
+ const handleClickOutside = (e3) => {
19454
+ if (menuRef.current && !menuRef.current.contains(e3.target)) {
19455
+ onClose();
19456
+ }
19457
+ };
19458
+ const handleEscape = (e3) => {
19459
+ if (e3.key === "Escape") onClose();
19460
+ };
19461
+ document.addEventListener("mousedown", handleClickOutside);
19462
+ document.addEventListener("keydown", handleEscape);
19463
+ return () => {
19464
+ document.removeEventListener("mousedown", handleClickOutside);
19465
+ document.removeEventListener("keydown", handleEscape);
19466
+ };
19467
+ }, [onClose]);
19468
+ const wrap = (fn2) => {
19469
+ fn2();
19470
+ onClose();
19471
+ };
19472
+ const menu = /* @__PURE__ */ jsxRuntime.jsxs(
19473
+ "div",
19474
+ {
19475
+ ref: menuRef,
19476
+ className: "twick-canvas-context-menu",
19477
+ style: { left: position.left, top: position.top },
19478
+ role: "menu",
19479
+ children: [
19480
+ /* @__PURE__ */ jsxRuntime.jsx(
19481
+ "button",
19482
+ {
19483
+ type: "button",
19484
+ className: "twick-canvas-context-menu-item",
19485
+ onClick: () => wrap(onSplit),
19486
+ disabled: !canSplit,
19487
+ role: "menuitem",
19488
+ style: !canSplit ? { opacity: 0.45, cursor: "not-allowed" } : void 0,
19489
+ children: "Split at playhead"
19490
+ }
19491
+ ),
19492
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twick-canvas-context-menu-separator", role: "separator" }),
19493
+ /* @__PURE__ */ jsxRuntime.jsx(
19494
+ "button",
19495
+ {
19496
+ type: "button",
19497
+ className: "twick-canvas-context-menu-item twick-canvas-context-menu-item-danger",
19498
+ onClick: () => wrap(onDelete),
19499
+ role: "menuitem",
19500
+ children: "Delete"
19501
+ }
19502
+ )
19503
+ ]
19504
+ }
19505
+ );
19506
+ if (typeof document === "undefined") {
19507
+ return null;
19508
+ }
19509
+ return reactDom.createPortal(menu, document.body);
19510
+ };
19040
19511
  const TrackElementView = ({
19041
19512
  element,
19042
19513
  parentWidth,
@@ -19049,13 +19520,20 @@ const TrackElementView = ({
19049
19520
  onDrag,
19050
19521
  allowOverlap = false,
19051
19522
  onDragStateChange,
19052
- elementColors
19523
+ elementColors,
19524
+ currentTime = 0,
19525
+ onContextMenuTarget,
19526
+ onDeleteElement,
19527
+ onSplitElement
19053
19528
  }) => {
19054
19529
  var _a, _b;
19055
19530
  const ref = React.useRef(null);
19056
19531
  const dragType = React.useRef(null);
19057
19532
  const lastPosRef = React.useRef(null);
19058
19533
  const [isDragging2, setIsDragging] = React.useState(false);
19534
+ const [clipMenu, setClipMenu] = React.useState(
19535
+ null
19536
+ );
19059
19537
  const [position, setPosition] = React.useState({
19060
19538
  start: 0,
19061
19539
  end: 0
@@ -19085,6 +19563,7 @@ const TrackElementView = ({
19085
19563
  newStart = nextStart - span;
19086
19564
  }
19087
19565
  }
19566
+ newStart = Math.max(0, Math.min(newStart, duration - span));
19088
19567
  return {
19089
19568
  start: newStart,
19090
19569
  end: newStart + span
@@ -19107,6 +19586,7 @@ const TrackElementView = ({
19107
19586
  if (prevEnd !== null && !allowOverlap && newStart < prevEnd) {
19108
19587
  newStart = prevEnd;
19109
19588
  }
19589
+ newStart = Math.max(0, Math.min(newStart, prev.end - MIN_DURATION));
19110
19590
  return {
19111
19591
  start: newStart,
19112
19592
  end: prev.end
@@ -19131,6 +19611,7 @@ const TrackElementView = ({
19131
19611
  newEnd = nextStart;
19132
19612
  }
19133
19613
  }
19614
+ newEnd = Math.max(prev.start + MIN_DURATION, Math.min(newEnd, duration));
19134
19615
  return {
19135
19616
  start: prev.start,
19136
19617
  end: newEnd
@@ -19168,7 +19649,7 @@ const TrackElementView = ({
19168
19649
  };
19169
19650
  const getElementColor = (elementType) => {
19170
19651
  const colors = elementColors || ELEMENT_COLORS;
19171
- const key = elementType === timeline.TIMELINE_ELEMENT_TYPE.VIDEO ? "video" : elementType === timeline.TIMELINE_ELEMENT_TYPE.AUDIO ? "audio" : elementType === timeline.TIMELINE_ELEMENT_TYPE.IMAGE ? "image" : elementType === timeline.TIMELINE_ELEMENT_TYPE.TEXT ? "text" : elementType === timeline.TIMELINE_ELEMENT_TYPE.CAPTION ? "caption" : elementType === timeline.TIMELINE_ELEMENT_TYPE.RECT ? "rect" : elementType === timeline.TIMELINE_ELEMENT_TYPE.CIRCLE ? "circle" : elementType === timeline.TIMELINE_ELEMENT_TYPE.ICON ? "icon" : elementType === timeline.TIMELINE_ELEMENT_TYPE.EFFECT ? "effect" : "element";
19652
+ const key = elementType === timeline.TIMELINE_ELEMENT_TYPE.VIDEO ? "video" : elementType === timeline.TIMELINE_ELEMENT_TYPE.AUDIO ? "audio" : elementType === timeline.TIMELINE_ELEMENT_TYPE.IMAGE ? "image" : elementType === timeline.TIMELINE_ELEMENT_TYPE.TEXT ? "text" : elementType === timeline.TIMELINE_ELEMENT_TYPE.CAPTION ? "caption" : elementType === timeline.TIMELINE_ELEMENT_TYPE.RECT ? "rect" : elementType === timeline.TIMELINE_ELEMENT_TYPE.CIRCLE ? "circle" : elementType === timeline.TIMELINE_ELEMENT_TYPE.ICON ? "icon" : elementType === timeline.TIMELINE_ELEMENT_TYPE.EMOJI ? "emoji" : elementType === timeline.TIMELINE_ELEMENT_TYPE.EFFECT ? "effect" : "element";
19172
19653
  if (key in colors) {
19173
19654
  return colors[key];
19174
19655
  }
@@ -19178,6 +19659,16 @@ const TrackElementView = ({
19178
19659
  return selectedIds.has(element.getId());
19179
19660
  }, [selectedIds, element]);
19180
19661
  const hasHandles = (selectedItem == null ? void 0 : selectedItem.getId()) === element.getId();
19662
+ const contextActionsEnabled = Boolean(
19663
+ onDeleteElement && onSplitElement && onContextMenuTarget
19664
+ );
19665
+ const handleContextMenu = (e3) => {
19666
+ if (!contextActionsEnabled) return;
19667
+ e3.preventDefault();
19668
+ e3.stopPropagation();
19669
+ onContextMenuTarget == null ? void 0 : onContextMenuTarget(element);
19670
+ setClipMenu({ x: e3.clientX, y: e3.clientY });
19671
+ };
19181
19672
  const motionProps = {
19182
19673
  ref,
19183
19674
  className: `twick-track-element ${isSelected ? "twick-track-element-selected" : "twick-track-element-default"} ${isDragging2 ? "twick-track-element-dragging" : ""}`,
@@ -19198,6 +19689,7 @@ const TrackElementView = ({
19198
19689
  onSelection(element, e3);
19199
19690
  }
19200
19691
  },
19692
+ onContextMenu: handleContextMenu,
19201
19693
  style: {
19202
19694
  backgroundColor: getElementColor(element.getType()),
19203
19695
  width: `${(position.end - position.start) / duration * 100}%`,
@@ -19205,39 +19697,52 @@ const TrackElementView = ({
19205
19697
  touchAction: "none"
19206
19698
  }
19207
19699
  };
19208
- return /* @__PURE__ */ jsxRuntime.jsx(motion.div, { ...motionProps, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { touchAction: "none", height: "100%" }, ...bind(), children: [
19209
- hasHandles ? /* @__PURE__ */ jsxRuntime.jsx(
19210
- "div",
19211
- {
19212
- style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19213
- ...bindStartHandle(),
19214
- className: "twick-track-element-handle twick-track-element-handle-start"
19215
- }
19216
- ) : null,
19217
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twick-track-element-content", children: element.getType() === timeline.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() }),
19218
- hasHandles ? /* @__PURE__ */ jsxRuntime.jsx(
19219
- "div",
19700
+ return /* @__PURE__ */ jsxRuntime.jsxs(motion.div, { ...motionProps, children: [
19701
+ clipMenu && contextActionsEnabled ? /* @__PURE__ */ jsxRuntime.jsx(
19702
+ TrackElementContextMenu,
19220
19703
  {
19221
- style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19222
- ...bindEndHandle(),
19223
- className: "twick-track-element-handle twick-track-element-handle-end"
19704
+ x: clipMenu.x,
19705
+ y: clipMenu.y,
19706
+ canSplit: timeline.canSplitElement(element, currentTime),
19707
+ onSplit: () => onSplitElement == null ? void 0 : onSplitElement(element, currentTime),
19708
+ onDelete: () => onDeleteElement == null ? void 0 : onDeleteElement(element),
19709
+ onClose: () => setClipMenu(null)
19224
19710
  }
19225
19711
  ) : null,
19226
- element.getFrameEffects ? element.getFrameEffects().map((frameEffect) => {
19227
- return /* @__PURE__ */ jsxRuntime.jsx(
19712
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { touchAction: "none", height: "100%" }, ...bind(), children: [
19713
+ hasHandles ? /* @__PURE__ */ jsxRuntime.jsx(
19228
19714
  "div",
19229
19715
  {
19230
- className: "twick-track-element-frame-effect",
19231
- style: {
19232
- backgroundColor: getElementColor("frameEffect"),
19233
- width: `${(frameEffect.e - frameEffect.s) / element.getDuration() * 100}%`,
19234
- left: `${frameEffect.s / element.getDuration() * 100}%`
19235
- }
19236
- },
19237
- frameEffect.s + frameEffect.e
19238
- );
19239
- }) : null
19240
- ] }) });
19716
+ style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19717
+ ...bindStartHandle(),
19718
+ className: "twick-track-element-handle twick-track-element-handle-start"
19719
+ }
19720
+ ) : null,
19721
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twick-track-element-content", children: element.getType() === timeline.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() }),
19722
+ hasHandles ? /* @__PURE__ */ jsxRuntime.jsx(
19723
+ "div",
19724
+ {
19725
+ style: { touchAction: "none", zIndex: isSelected ? 100 : 1 },
19726
+ ...bindEndHandle(),
19727
+ className: "twick-track-element-handle twick-track-element-handle-end"
19728
+ }
19729
+ ) : null,
19730
+ element.getFrameEffects ? element.getFrameEffects().map((frameEffect) => {
19731
+ return /* @__PURE__ */ jsxRuntime.jsx(
19732
+ "div",
19733
+ {
19734
+ className: "twick-track-element-frame-effect",
19735
+ style: {
19736
+ backgroundColor: getElementColor("frameEffect"),
19737
+ width: `${(frameEffect.e - frameEffect.s) / element.getDuration() * 100}%`,
19738
+ left: `${frameEffect.s / element.getDuration() * 100}%`
19739
+ }
19740
+ },
19741
+ frameEffect.s + frameEffect.e
19742
+ );
19743
+ }) : null
19744
+ ] })
19745
+ ] });
19241
19746
  };
19242
19747
  const TrackBase = ({
19243
19748
  duration,
@@ -19250,11 +19755,21 @@ const TrackBase = ({
19250
19755
  onDrag,
19251
19756
  allowOverlap = false,
19252
19757
  onDragStateChange,
19253
- elementColors
19758
+ elementColors,
19759
+ currentTime,
19760
+ onContextMenuTarget,
19761
+ onDeleteElement,
19762
+ onSplitElement
19254
19763
  }) => {
19255
19764
  const trackRef = React.useRef(null);
19256
19765
  const trackWidthStyle = `${Math.max(100, duration * zoom * 100)}px`;
19257
- const elements = track.getElements();
19766
+ const elements = [...track.getElements()].sort((a2, b2) => {
19767
+ const byStart = a2.getStart() - b2.getStart();
19768
+ if (byStart !== 0) return byStart;
19769
+ const byEnd = a2.getEnd() - b2.getEnd();
19770
+ if (byEnd !== 0) return byEnd;
19771
+ return a2.getId().localeCompare(b2.getId());
19772
+ });
19258
19773
  return /* @__PURE__ */ jsxRuntime.jsx(
19259
19774
  "div",
19260
19775
  {
@@ -19276,6 +19791,10 @@ const TrackBase = ({
19276
19791
  onDrag,
19277
19792
  onDragStateChange,
19278
19793
  elementColors,
19794
+ currentTime,
19795
+ onContextMenuTarget,
19796
+ onDeleteElement,
19797
+ onSplitElement,
19279
19798
  nextStart: index < elements.length - 1 ? elements[index + 1].getStart() : null,
19280
19799
  prevEnd: index > 0 ? elements[index - 1].getEnd() : 0
19281
19800
  },
@@ -19580,7 +20099,11 @@ function TimelineView({
19580
20099
  onDropOnTimeline,
19581
20100
  videoResolution,
19582
20101
  enableDropOnTimeline = true,
19583
- chapters = []
20102
+ chapters = [],
20103
+ currentTime = 0,
20104
+ onContextMenuTarget,
20105
+ onDeleteElement,
20106
+ onSplitElement
19584
20107
  }) {
19585
20108
  const containerRef = React.useRef(null);
19586
20109
  const seekContainerRef = React.useRef(null);
@@ -19849,7 +20372,11 @@ function TimelineView({
19849
20372
  onDragStateChange: (isDragging2, el) => {
19850
20373
  setDraggingElementId(isDragging2 && el ? el.getId() : null);
19851
20374
  },
19852
- elementColors
20375
+ elementColors,
20376
+ currentTime,
20377
+ onContextMenuTarget,
20378
+ onDeleteElement,
20379
+ onSplitElement
19853
20380
  }
19854
20381
  )
19855
20382
  ] }),
@@ -19908,7 +20435,8 @@ const useTimelineManager = () => {
19908
20435
  if (dragType === DRAG_TYPE.START) {
19909
20436
  if (element instanceof timeline.VideoElement || element instanceof timeline.AudioElement) {
19910
20437
  const elementProps = element.getProps();
19911
- const delta = updates.start - element.getStart() * ((elementProps == null ? void 0 : elementProps.playbackRate) || 1);
20438
+ const playbackRate = (elementProps == null ? void 0 : elementProps.playbackRate) || 1;
20439
+ const delta = (updates.start - element.getStart()) * playbackRate;
19912
20440
  if (element instanceof timeline.AudioElement) {
19913
20441
  element.setStartAt(element.getStartAt() + delta);
19914
20442
  } else {
@@ -20021,6 +20549,38 @@ const useTimelineManager = () => {
20021
20549
  totalDuration
20022
20550
  };
20023
20551
  };
20552
+ const useTimelineControl = () => {
20553
+ const { editor, setSelectedItem, selectedIds } = timeline.useTimelineContext();
20554
+ const deleteItem = (item) => {
20555
+ var _a;
20556
+ const tracks = ((_a = editor.getTimelineData()) == null ? void 0 : _a.tracks) ?? [];
20557
+ const toDelete = item !== void 0 ? [item] : timeline.resolveIds(selectedIds, tracks);
20558
+ for (const el of toDelete) {
20559
+ if (el instanceof timeline.Track) {
20560
+ editor.removeTrack(el);
20561
+ } else if (el instanceof timeline.TrackElement) {
20562
+ editor.removeElement(el);
20563
+ }
20564
+ }
20565
+ setSelectedItem(null);
20566
+ };
20567
+ const splitElement = (element, currentTime) => {
20568
+ if (!timeline.canSplitElement(element, currentTime)) return;
20569
+ void editor.splitElement(element, currentTime);
20570
+ };
20571
+ const handleUndo = () => {
20572
+ editor.undo();
20573
+ };
20574
+ const handleRedo = () => {
20575
+ editor.redo();
20576
+ };
20577
+ return {
20578
+ splitElement,
20579
+ deleteItem,
20580
+ handleUndo,
20581
+ handleRedo
20582
+ };
20583
+ };
20024
20584
  function useTimelineSelection() {
20025
20585
  const { editor, selectedIds, setSelection, setSelectedItem } = timeline.useTimelineContext();
20026
20586
  const handleItemSelect = React.useCallback(
@@ -20101,8 +20661,9 @@ const TimelineManager = ({
20101
20661
  elementColors
20102
20662
  }) => {
20103
20663
  var _a, _b;
20104
- const { playerState } = livePlayer.useLivePlayerContext();
20664
+ const { playerState, currentTime } = livePlayer.useLivePlayerContext();
20105
20665
  const { followPlayheadEnabled, editor, videoResolution, setSelectedItem } = timeline.useTimelineContext();
20666
+ const { deleteItem, splitElement } = useTimelineControl();
20106
20667
  const {
20107
20668
  timelineData,
20108
20669
  totalDuration,
@@ -20123,6 +20684,12 @@ const TimelineManager = ({
20123
20684
  setPlayheadState(state);
20124
20685
  }, []);
20125
20686
  const isPlayheadActive = followPlayheadEnabled && playerState === livePlayer.PLAYER_STATE.PLAYING || playheadState.isDragging;
20687
+ const handleContextMenuTarget = React.useCallback(
20688
+ (element) => {
20689
+ setSelectedItem(element);
20690
+ },
20691
+ [setSelectedItem]
20692
+ );
20126
20693
  const handleDropOnTimeline = React.useCallback(
20127
20694
  async (params) => {
20128
20695
  const { track, timeSec, type, url } = params;
@@ -20170,6 +20737,10 @@ const TimelineManager = ({
20170
20737
  onEmptyClick: handleEmptyClick,
20171
20738
  onMarqueeSelect: handleMarqueeSelect,
20172
20739
  elementColors,
20740
+ currentTime,
20741
+ onContextMenuTarget: handleContextMenuTarget,
20742
+ onDeleteElement: (el) => deleteItem(el),
20743
+ onSplitElement: (el, t2) => splitElement(el, t2),
20173
20744
  playheadPositionPx: playheadState.positionPx,
20174
20745
  isPlayheadActive,
20175
20746
  chapters: ((_a = timelineData == null ? void 0 : timelineData.metadata) == null ? void 0 : _a.chapters) ?? [],
@@ -20253,11 +20824,12 @@ const PlayerControls = ({
20253
20824
  }
20254
20825
  }, [selectedIds.size, onDelete]);
20255
20826
  const hasSelection = selectedIds.size > 0;
20827
+ const canSplitSelected = selectedItem instanceof timeline.TrackElement ? timeline.canSplitElement(selectedItem, currentTime) : false;
20256
20828
  const handleSplit = React.useCallback(() => {
20257
- if (selectedItem instanceof timeline.TrackElement && onSplit) {
20829
+ if (selectedItem instanceof timeline.TrackElement && onSplit && canSplitSelected) {
20258
20830
  onSplit(selectedItem, currentTime);
20259
20831
  }
20260
- }, [selectedItem, onSplit, currentTime]);
20832
+ }, [selectedItem, onSplit, currentTime, canSplitSelected]);
20261
20833
  const handleZoomIn = React.useCallback(() => {
20262
20834
  if (setZoomLevel && zoomLevel < MAX_ZOOM) {
20263
20835
  setZoomLevel(zoomLevel + ZOOM_STEP);
@@ -20284,9 +20856,9 @@ const PlayerControls = ({
20284
20856
  "button",
20285
20857
  {
20286
20858
  onClick: handleSplit,
20287
- disabled: !(selectedItem instanceof timeline.TrackElement),
20859
+ disabled: !canSplitSelected,
20288
20860
  title: "Split",
20289
- className: `control-btn split-btn ${!(selectedItem instanceof timeline.TrackElement) ? "btn-disabled" : ""}`,
20861
+ className: `control-btn split-btn ${!canSplitSelected ? "btn-disabled" : ""}`,
20290
20862
  children: /* @__PURE__ */ jsxRuntime.jsx(Scissors, { className: "icon-md" })
20291
20863
  }
20292
20864
  ),
@@ -20400,37 +20972,6 @@ const usePlayerControl = () => {
20400
20972
  togglePlayback
20401
20973
  };
20402
20974
  };
20403
- const useTimelineControl = () => {
20404
- const { editor, setSelectedItem, selectedIds } = timeline.useTimelineContext();
20405
- const deleteItem = (item) => {
20406
- var _a;
20407
- const tracks = ((_a = editor.getTimelineData()) == null ? void 0 : _a.tracks) ?? [];
20408
- const toDelete = item !== void 0 ? [item] : timeline.resolveIds(selectedIds, tracks);
20409
- for (const el of toDelete) {
20410
- if (el instanceof timeline.Track) {
20411
- editor.removeTrack(el);
20412
- } else if (el instanceof timeline.TrackElement) {
20413
- editor.removeElement(el);
20414
- }
20415
- }
20416
- setSelectedItem(null);
20417
- };
20418
- const splitElement = (element, currentTime) => {
20419
- editor.splitElement(element, currentTime);
20420
- };
20421
- const handleUndo = () => {
20422
- editor.undo();
20423
- };
20424
- const handleRedo = () => {
20425
- editor.redo();
20426
- };
20427
- return {
20428
- splitElement,
20429
- deleteItem,
20430
- handleUndo,
20431
- handleRedo
20432
- };
20433
- };
20434
20975
  function shouldIgnoreKeydown() {
20435
20976
  const active = document.activeElement;
20436
20977
  if (!active) return false;