@twick/video-editor 0.15.15 → 0.15.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,17 +4,6 @@ import { TimelineTickConfig } from '../components/video-editor';
4
4
  /**
5
5
  * Initial timeline data structure for new video editor projects.
6
6
  * Provides a default timeline with a sample text element to get started.
7
- *
8
- * @example
9
- * ```js
10
- * import { INITIAL_TIMELINE_DATA } from '@twick/video-editor';
11
- *
12
- * // Use as starting point for new projects
13
- * const newProject = {
14
- * ...INITIAL_TIMELINE_DATA,
15
- * tracks: [...INITIAL_TIMELINE_DATA.tracks, newTrack]
16
- * };
17
- * ```
18
7
  */
19
8
  export declare const INITIAL_TIMELINE_DATA: {
20
9
  tracks: {
@@ -39,43 +28,8 @@ export declare const INITIAL_TIMELINE_DATA: {
39
28
  /**
40
29
  * Minimum duration for timeline elements in seconds.
41
30
  * Used to prevent elements from having zero or negative duration.
42
- *
43
- * @example
44
- * ```js
45
- * import { MIN_DURATION } from '@twick/video-editor';
46
- *
47
- * const elementDuration = Math.max(duration, MIN_DURATION);
48
- * // Ensures element has at least 0.1 seconds duration
49
- * ```
50
31
  */
51
32
  export declare const MIN_DURATION = 0.1;
52
- /**
53
- * Drag operation types for timeline interactions.
54
- * Defines the different phases of drag operations on timeline elements.
55
- *
56
- * @example
57
- * ```js
58
- * import { DRAG_TYPE } from '@twick/video-editor';
59
- *
60
- * function handleDrag(type) {
61
- * switch (type) {
62
- * case DRAG_TYPE.START:
63
- * // Handle drag start
64
- * break;
65
- * case DRAG_TYPE.MOVE:
66
- * // Handle drag move
67
- * break;
68
- * case DRAG_TYPE.END:
69
- * // Handle drag end
70
- * break;
71
- * }
72
- * }
73
- * ```
74
- */
75
- /**
76
- * MIME type for media items dragged from the studio's media panels (video, audio, image)
77
- * to the timeline. The data format is JSON: { type: "video"|"audio"|"image", url: string }.
78
- */
79
33
  export declare const TIMELINE_DROP_MEDIA_TYPE = "application/x-twick-media";
80
34
  export declare const DRAG_TYPE: {
81
35
  /** Drag operation is starting */
@@ -85,35 +39,10 @@ export declare const DRAG_TYPE: {
85
39
  /** Drag operation has ended */
86
40
  readonly END: "end";
87
41
  };
88
- /**
89
- * Default zoom level for timeline view.
90
- * Controls the initial magnification of the timeline interface.
91
- *
92
- * @example
93
- * ```js
94
- * import { DEFAULT_TIMELINE_ZOOM } from '@twick/video-editor';
95
- *
96
- * const [zoom, setZoom] = useState(DEFAULT_TIMELINE_ZOOM);
97
- * // Timeline starts with 1.5x zoom
98
- * ```
99
- */
100
42
  export declare const DEFAULT_TIMELINE_ZOOM = 1.5;
101
43
  /**
102
44
  * Default timeline zoom configuration including min, max, step, and default values.
103
45
  * Controls the zoom behavior and constraints for the timeline view.
104
- *
105
- * @example
106
- * ```js
107
- * import { DEFAULT_TIMELINE_ZOOM_CONFIG } from '@twick/video-editor';
108
- *
109
- * // Use default zoom configuration
110
- * <VideoEditor
111
- * editorConfig={{
112
- * videoProps: { width: 1920, height: 1080 },
113
- * timelineZoomConfig: DEFAULT_TIMELINE_ZOOM_CONFIG
114
- * }}
115
- * />
116
- * ```
117
46
  */
118
47
  /**
119
48
  * Default frames per second for timeline time display.
@@ -139,54 +68,16 @@ export declare const DEFAULT_TIMELINE_ZOOM_CONFIG: {
139
68
  *
140
69
  * Each configuration applies when the duration is less than the specified threshold.
141
70
  * Configurations are ordered by duration threshold ascending.
142
- *
143
- * @example
144
- * ```js
145
- * import { DEFAULT_TIMELINE_TICK_CONFIGS } from '@twick/video-editor';
146
- *
147
- * // Use default configurations
148
- * <VideoEditor
149
- * editorConfig={{
150
- * videoProps: { width: 1920, height: 1080 },
151
- * timelineTickConfigs: DEFAULT_TIMELINE_TICK_CONFIGS
152
- * }}
153
- * />
154
- * ```
155
71
  */
156
72
  export declare const DEFAULT_TIMELINE_TICK_CONFIGS: TimelineTickConfig[];
157
73
  /**
158
74
  * Default color scheme for different element types in the timeline.
159
75
  * Provides consistent visual distinction between various timeline elements.
160
- *
161
- * @example
162
- * ```js
163
- * import { DEFAULT_ELEMENT_COLORS } from '@twick/video-editor';
164
- *
165
- * const videoColor = DEFAULT_ELEMENT_COLORS.video; // "#4B2E83"
166
- * const textColor = DEFAULT_ELEMENT_COLORS.text; // "#375A7F"
167
- *
168
- * // Apply colors to timeline elements
169
- * element.style.backgroundColor = DEFAULT_ELEMENT_COLORS[element.type];
170
- * ```
171
76
  */
172
77
  export declare const DEFAULT_ELEMENT_COLORS: ElementColors;
173
78
  /**
174
79
  * Available text fonts for video editor text elements.
175
80
  * Includes Google Fonts, display fonts, and custom CDN fonts.
176
- *
177
- * @example
178
- * ```js
179
- * import { AVAILABLE_TEXT_FONTS } from '@twick/video-editor';
180
- *
181
- * // Use Google Fonts
182
- * const googleFont = AVAILABLE_TEXT_FONTS.ROBOTO; // "Roboto"
183
- *
184
- * // Use decorative fonts
185
- * const decorativeFont = AVAILABLE_TEXT_FONTS.BANGERS; // "Bangers"
186
- *
187
- * // Apply font to text element
188
- * textElement.style.fontFamily = AVAILABLE_TEXT_FONTS.POPPINS;
189
- * ```
190
81
  */
191
82
  export declare const AVAILABLE_TEXT_FONTS: {
192
83
  /** Modern sans-serif font */
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Creates a debounced version of a function.
3
+ * The function will only be called after it has not been invoked
4
+ * for the specified delay.
5
+ *
6
+ * Useful for expensive operations that should not run on every
7
+ * keystroke / mouse move (e.g. resize handlers, search, etc.).
8
+ */
9
+ export declare function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void;
10
+ /**
11
+ * Creates a throttled version of a function.
12
+ * The function will be called at most once in every `interval`
13
+ * milliseconds, ignoring additional calls in between.
14
+ *
15
+ * Useful for high–frequency events like scroll / mousemove where
16
+ * you still want regular updates but not on every event.
17
+ */
18
+ export declare function throttle<T extends (...args: any[]) => any>(fn: T, interval: number): (...args: Parameters<T>) => void;
@@ -27,8 +27,9 @@ export declare const usePlayerManager: ({ videoProps, canvasConfig, }: {
27
27
  }) => {
28
28
  twickCanvas: any;
29
29
  projectData: any;
30
- updateCanvas: (seekTime: number) => void;
30
+ updateCanvas: (seekTime: number, forceRefresh?: boolean) => void;
31
31
  buildCanvas: any;
32
+ resizeCanvas: any;
32
33
  onPlayerUpdate: (event: CustomEvent) => void;
33
34
  playerUpdating: boolean;
34
35
  handleDropOnCanvas: (payload: CanvasDropPayload) => Promise<void>;
package/dist/index.d.ts CHANGED
@@ -15,6 +15,7 @@ import { setElementColors } from './helpers/editor.utils';
15
15
  export { setElementColors };
16
16
  export type { MediaItem, PaginationOptions, SearchOptions, Animation, TextEffect, ElementColors };
17
17
  export type { PlayerControlsProps, VideoEditorProps, VideoEditorConfig, TimelineTickConfig, TimelineZoomConfig, CanvasConfig };
18
+ export { throttle, debounce } from './helpers/function.utils';
18
19
  export { ANIMATIONS, TEXT_EFFECTS };
19
20
  export { usePlayerControl, useEditorManager, BrowserMediaManager, BaseMediaManager, animationGifs, getAnimationGif, PlayerControls, TimelineManager, useTimelineControl };
20
21
  export * from './helpers/constants';
package/dist/index.js CHANGED
@@ -6885,9 +6885,9 @@ const CANVAS_OPERATIONS = {
6885
6885
  CAPTION_PROPS_UPDATED: "CAPTION_PROPS_UPDATED",
6886
6886
  /** Watermark has been updated */
6887
6887
  WATERMARK_UPDATED: "WATERMARK_UPDATED",
6888
- /** A new element was added via drop on canvas; payload is { element } */
6888
+ /** A new element was added via drop on canvas; payload is &#123; element &#125; */
6889
6889
  ADDED_NEW_ELEMENT: "ADDED_NEW_ELEMENT",
6890
- /** Z-order changed (bring to front / send to back). Payload is { elementId, direction }. Timeline should reorder tracks. */
6890
+ /** Z-order changed (bring to front / send to back). Payload is &#123; elementId, direction &#125;. Timeline should reorder tracks. */
6891
6891
  Z_ORDER_CHANGED: "Z_ORDER_CHANGED"
6892
6892
  };
6893
6893
  const ELEMENT_TYPES = {
@@ -8443,6 +8443,30 @@ const useTwickCanvas = ({
8443
8443
  scaleX: 1,
8444
8444
  scaleY: 1
8445
8445
  });
8446
+ const resizeCanvas = ({
8447
+ canvasSize,
8448
+ videoSize = videoSizeRef.current
8449
+ }) => {
8450
+ const canvas = twickCanvasRef.current;
8451
+ if (!canvas || !getCanvasContext(canvas)) return;
8452
+ if (!(videoSize == null ? void 0 : videoSize.width) || !(videoSize == null ? void 0 : videoSize.height)) return;
8453
+ if (canvasResolutionRef.current.width === canvasSize.width && canvasResolutionRef.current.height === canvasSize.height) {
8454
+ return;
8455
+ }
8456
+ canvasMetadataRef.current = {
8457
+ width: canvasSize.width,
8458
+ height: canvasSize.height,
8459
+ aspectRatio: canvasSize.width / canvasSize.height,
8460
+ scaleX: Number((canvasSize.width / videoSize.width).toFixed(2)),
8461
+ scaleY: Number((canvasSize.height / videoSize.height).toFixed(2))
8462
+ };
8463
+ canvas.setDimensions({
8464
+ width: canvasSize.width,
8465
+ height: canvasSize.height
8466
+ });
8467
+ canvasResolutionRef.current = canvasSize;
8468
+ canvas.requestRenderAll();
8469
+ };
8446
8470
  const onVideoSizeChange = (videoSize) => {
8447
8471
  if (videoSize) {
8448
8472
  videoSizeRef.current = videoSize;
@@ -8716,9 +8740,7 @@ const useTwickCanvas = ({
8716
8740
  reorderElementsByZIndex(twickCanvas);
8717
8741
  }
8718
8742
  };
8719
- const addWatermarkToCanvas = ({
8720
- element
8721
- }) => {
8743
+ const addWatermarkToCanvas = ({ element }) => {
8722
8744
  if (!twickCanvas) return;
8723
8745
  const handler = elementController.get("watermark");
8724
8746
  if (handler) {
@@ -8748,6 +8770,7 @@ const useTwickCanvas = ({
8748
8770
  return {
8749
8771
  twickCanvas,
8750
8772
  buildCanvas,
8773
+ resizeCanvas,
8751
8774
  onVideoSizeChange,
8752
8775
  addWatermarkToCanvas,
8753
8776
  addElementToCanvas,
@@ -9261,6 +9284,7 @@ const usePlayerManager = ({
9261
9284
  const {
9262
9285
  twickCanvas,
9263
9286
  buildCanvas,
9287
+ resizeCanvas,
9264
9288
  setCanvasElements,
9265
9289
  bringToFront,
9266
9290
  sendToBack,
@@ -9271,9 +9295,9 @@ const usePlayerManager = ({
9271
9295
  onCanvasOperation: handleCanvasOperation,
9272
9296
  enableShiftAxisLock: (canvasConfig == null ? void 0 : canvasConfig.enableShiftAxisLock) ?? false
9273
9297
  });
9274
- const updateCanvas = (seekTime) => {
9298
+ const updateCanvas = (seekTime, forceRefresh = false) => {
9275
9299
  var _a;
9276
- if (changeLog === currentChangeLog.current && seekTime === prevSeekTime.current) {
9300
+ if (!forceRefresh && changeLog === currentChangeLog.current && seekTime === prevSeekTime.current) {
9277
9301
  return;
9278
9302
  }
9279
9303
  prevSeekTime.current = seekTime;
@@ -9367,6 +9391,7 @@ const usePlayerManager = ({
9367
9391
  projectData,
9368
9392
  updateCanvas,
9369
9393
  buildCanvas,
9394
+ resizeCanvas,
9370
9395
  onPlayerUpdate,
9371
9396
  playerUpdating,
9372
9397
  handleDropOnCanvas,
@@ -9460,6 +9485,47 @@ function getCanvasY(e3, container, videoSize) {
9460
9485
  const relY = (e3.clientY - rect.top) / rect.height;
9461
9486
  return Math.max(0, Math.min(videoSize.height, relY * videoSize.height));
9462
9487
  }
9488
+ function debounce(fn2, delay2) {
9489
+ let timeoutId = null;
9490
+ return (...args) => {
9491
+ if (timeoutId !== null) {
9492
+ clearTimeout(timeoutId);
9493
+ }
9494
+ timeoutId = setTimeout(() => {
9495
+ timeoutId = null;
9496
+ fn2(...args);
9497
+ }, delay2);
9498
+ };
9499
+ }
9500
+ function throttle(fn2, interval) {
9501
+ let lastCallTime = 0;
9502
+ let trailingTimeoutId = null;
9503
+ let lastArgs = null;
9504
+ return (...args) => {
9505
+ const now2 = Date.now();
9506
+ const remaining = interval - (now2 - lastCallTime);
9507
+ if (remaining <= 0) {
9508
+ if (trailingTimeoutId !== null) {
9509
+ clearTimeout(trailingTimeoutId);
9510
+ trailingTimeoutId = null;
9511
+ }
9512
+ lastCallTime = now2;
9513
+ fn2(...args);
9514
+ } else {
9515
+ lastArgs = args;
9516
+ if (trailingTimeoutId === null) {
9517
+ trailingTimeoutId = setTimeout(() => {
9518
+ trailingTimeoutId = null;
9519
+ lastCallTime = Date.now();
9520
+ if (lastArgs) {
9521
+ fn2(...lastArgs);
9522
+ lastArgs = null;
9523
+ }
9524
+ }, remaining);
9525
+ }
9526
+ }
9527
+ };
9528
+ }
9463
9529
  const CanvasContextMenu = ({
9464
9530
  x: x2,
9465
9531
  y: y2,
@@ -9555,6 +9621,7 @@ const CanvasContextMenu = ({
9555
9621
  }
9556
9622
  );
9557
9623
  };
9624
+ const RESIZE_THROTTLE_MS = 200;
9558
9625
  const PlayerManager = ({
9559
9626
  videoProps,
9560
9627
  playerProps,
@@ -9564,6 +9631,7 @@ const PlayerManager = ({
9564
9631
  const containerRef = React.useRef(null);
9565
9632
  const canvasRef = React.useRef(null);
9566
9633
  const durationRef = React.useRef(0);
9634
+ const seekTimeRef = React.useRef(0);
9567
9635
  const { changeLog } = timeline.useTimelineContext();
9568
9636
  const {
9569
9637
  playerState,
@@ -9576,6 +9644,7 @@ const PlayerManager = ({
9576
9644
  twickCanvas,
9577
9645
  projectData,
9578
9646
  updateCanvas,
9647
+ resizeCanvas,
9579
9648
  playerUpdating,
9580
9649
  onPlayerUpdate,
9581
9650
  buildCanvas,
@@ -9597,19 +9666,44 @@ const PlayerManager = ({
9597
9666
  React.useEffect(() => {
9598
9667
  const container = containerRef.current;
9599
9668
  const canvasSize = {
9600
- width: container == null ? void 0 : container.clientWidth,
9601
- height: container == null ? void 0 : container.clientHeight
9669
+ width: (container == null ? void 0 : container.clientWidth) ?? 0,
9670
+ height: (container == null ? void 0 : container.clientHeight) ?? 0
9602
9671
  };
9603
- buildCanvas({
9604
- backgroundColor: videoProps.backgroundColor,
9605
- videoSize: {
9606
- width: videoProps.width,
9607
- height: videoProps.height
9608
- },
9609
- canvasSize,
9610
- canvasRef: canvasRef.current
9611
- });
9672
+ if (canvasSize.width > 0 && canvasSize.height > 0) {
9673
+ buildCanvas({
9674
+ backgroundColor: videoProps.backgroundColor,
9675
+ videoSize: {
9676
+ width: videoProps.width,
9677
+ height: videoProps.height
9678
+ },
9679
+ canvasSize,
9680
+ canvasRef: canvasRef.current
9681
+ });
9682
+ }
9612
9683
  }, [videoProps]);
9684
+ const handleResize = React.useMemo(
9685
+ () => throttle(() => {
9686
+ const container = containerRef.current;
9687
+ if (!container || !canvasMode || !twickCanvas) return;
9688
+ const width = container.clientWidth;
9689
+ const height = container.clientHeight;
9690
+ if (width <= 0 || height <= 0) return;
9691
+ resizeCanvas({
9692
+ canvasSize: { width, height },
9693
+ videoSize: { width: videoProps.width, height: videoProps.height }
9694
+ });
9695
+ updateCanvas(seekTimeRef.current, true);
9696
+ }, RESIZE_THROTTLE_MS),
9697
+ [canvasMode, twickCanvas, resizeCanvas, updateCanvas, videoProps.width, videoProps.height]
9698
+ );
9699
+ React.useEffect(() => {
9700
+ const container = containerRef.current;
9701
+ if (!container || !canvasMode) return;
9702
+ const resizeObserver = new ResizeObserver(handleResize);
9703
+ resizeObserver.observe(container);
9704
+ return () => resizeObserver.disconnect();
9705
+ }, [canvasMode, handleResize]);
9706
+ seekTimeRef.current = seekTime;
9613
9707
  React.useEffect(() => {
9614
9708
  if (twickCanvas && playerState === livePlayer.PLAYER_STATE.PAUSED) {
9615
9709
  updateCanvas(seekTime);
@@ -11119,13 +11213,23 @@ function SeekTrack({
11119
11213
  minorIntervalSec: minors > 0 ? major / (minors + 1) : major
11120
11214
  };
11121
11215
  }, [duration, timelineTickConfigs]);
11122
- const handleSeek = (clientX) => {
11123
- if (!containerRef.current) return;
11124
- const rect = containerRef.current.getBoundingClientRect();
11125
- const x2 = clientX - rect.left + (containerRef.current.scrollLeft || 0);
11126
- const newTime = Math.max(0, Math.min(duration, x2 / pixelsPerSecond));
11127
- onSeek(newTime);
11128
- };
11216
+ const seekToTime = React.useCallback(
11217
+ (time2) => {
11218
+ const clamped = Math.max(0, Math.min(duration, time2));
11219
+ onSeek(clamped);
11220
+ },
11221
+ [duration, onSeek]
11222
+ );
11223
+ const seekFromClientX = React.useCallback(
11224
+ (clientX) => {
11225
+ if (!containerRef.current) return;
11226
+ const rect = containerRef.current.getBoundingClientRect();
11227
+ const x2 = clientX - rect.left + (containerRef.current.scrollLeft || 0);
11228
+ const newTime = Math.max(0, Math.min(duration, x2 / pixelsPerSecond));
11229
+ seekToTime(newTime);
11230
+ },
11231
+ [duration, pixelsPerSecond, seekToTime]
11232
+ );
11129
11233
  const bind = useDrag(({ event, xy: [x2], active }) => {
11130
11234
  if (event) {
11131
11235
  event.stopPropagation();
@@ -11135,10 +11239,11 @@ function SeekTrack({
11135
11239
  const rect = containerRef.current.getBoundingClientRect();
11136
11240
  const xPos = x2 - rect.left + (containerRef.current.scrollLeft || 0);
11137
11241
  const newTime = Math.max(0, Math.min(duration, xPos / pixelsPerSecond));
11138
- setDragPosition(xPos);
11139
- onSeek(newTime);
11140
- if (!active) {
11242
+ if (active) {
11243
+ setDragPosition(xPos);
11244
+ } else {
11141
11245
  setDragPosition(null);
11246
+ seekToTime(newTime);
11142
11247
  }
11143
11248
  });
11144
11249
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "twick-seek-track", children: /* @__PURE__ */ jsxRuntime.jsxs(
@@ -11146,7 +11251,7 @@ function SeekTrack({
11146
11251
  {
11147
11252
  ref: containerRef,
11148
11253
  className: "twick-seek-track-container-no-scrollbar",
11149
- onClick: (e3) => handleSeek(e3.clientX),
11254
+ onClick: (e3) => seekFromClientX(e3.clientX),
11150
11255
  style: {
11151
11256
  overflowX: "auto",
11152
11257
  overflowY: "hidden",
@@ -18960,16 +19065,20 @@ function usePlayheadScroll(scrollContainerRef, playheadPositionPx, isActive, con
18960
19065
  const container = scrollContainerRef.current;
18961
19066
  const contentX = labelWidth + playheadPositionPx;
18962
19067
  const scrollToKeepPlayheadVisible = () => {
18963
- const { scrollLeft, clientWidth } = container;
19068
+ const { scrollLeft, clientWidth, scrollWidth } = container;
19069
+ const maxScrollLeft = Math.max(0, scrollWidth - clientWidth);
18964
19070
  const minVisible = scrollLeft + margin;
18965
19071
  const maxVisible = scrollLeft + clientWidth - margin;
18966
19072
  let newScrollLeft = null;
18967
19073
  if (contentX < minVisible) {
18968
- newScrollLeft = Math.max(0, contentX - margin);
19074
+ newScrollLeft = Math.max(0, Math.min(maxScrollLeft, contentX - margin));
18969
19075
  } else if (contentX > maxVisible) {
18970
- newScrollLeft = contentX - clientWidth + margin;
19076
+ newScrollLeft = Math.max(
19077
+ 0,
19078
+ Math.min(maxScrollLeft, contentX - clientWidth + margin)
19079
+ );
18971
19080
  }
18972
- if (newScrollLeft !== null) {
19081
+ if (newScrollLeft !== null && Math.abs(newScrollLeft - scrollLeft) > 0.5) {
18973
19082
  container.scrollLeft = newScrollLeft;
18974
19083
  }
18975
19084
  };
@@ -20381,9 +20490,11 @@ exports.TEXT_EFFECTS = TEXT_EFFECTS;
20381
20490
  exports.TIMELINE_DROP_MEDIA_TYPE = TIMELINE_DROP_MEDIA_TYPE;
20382
20491
  exports.TimelineManager = TimelineManager;
20383
20492
  exports.animationGifs = animationGifs;
20493
+ exports.debounce = debounce;
20384
20494
  exports.default = VideoEditor;
20385
20495
  exports.getAnimationGif = getAnimationGif;
20386
20496
  exports.setElementColors = setElementColors;
20497
+ exports.throttle = throttle;
20387
20498
  exports.useEditorManager = useEditorManager;
20388
20499
  exports.usePlayerControl = usePlayerControl;
20389
20500
  exports.useTimelineControl = useTimelineControl;