Logoreact-timeline-editor
Advanced Guides

Cut Block (Blade Tool)

Slice and split timeline action blocks at precise time positions using the CutOverlay component.

The CutOverlay provides a visual "blade" that lets users split action blocks at any time position. It operates as a standalone overlay component mounted next to the timeline.

Live Preview

Turn on blade mode to cut blocks.

How it Works

  1. CutOverlay sits on top of <Timeline> inside a position: relative wrapper
  2. When the user hovers over an action block, a precision blade line appears
  3. When they click, onCut fires with the exact row ID, action ID, and cut time
  4. Use the included splitActionInRow utility to perform the immutable data update

Basic Usage

import React, { useState } from "react";
import {
  Timeline,
  TimelineRow,
  CutOverlay,
  splitActionInRow,
} from "@keplar-404/react-timeline-editor";

const mockData: TimelineRow[] = [
  {
    id: "0",
    actions: [{ id: "action00", start: 0, end: 10, effectId: "effect0" }],
  },
];

export const EditorCutBlock = () => {
  const [data, setData] = useState(mockData);
  const [isCutMode, setIsCutMode] = useState(false);

  // Geometry must match between Timeline and CutOverlay
  const scale = 5;
  const scaleSplitCount = 10;
  const scaleWidth = 160;
  const startLeft = 20;
  const rowHeight = 32;
  const editAreaTopOffset = 32; // Height of the time ruler

  const handleCut = (rowId: string, actionId: string, cutTime: number) => {
    // splitActionInRow returns a new immutable data array
    const newData = splitActionInRow(data, rowId, actionId, cutTime);
    setData(newData);
  };

  return (
    <div
      style={{
        position: "relative",
        width: "100%",
        height: "400px",
        display: "flex",
        flexDirection: "column",
      }}
    >
      <div style={{ flexShrink: 0, paddingBottom: "8px" }}>
        <button
          className="bg-blue-600 text-white px-4 py-2 rounded"
          onClick={() => setIsCutMode(!isCutMode)}
        >
          {isCutMode ? "Disable Cut Blade" : "Enable Cut Blade (Hold C)"}
        </button>
      </div>

      <div style={{ flex: 1, position: "relative" }}>
        <Timeline
          editorData={data}
          effects={{}}
          onChange={setData}
          scale={scale}
          scaleWidth={scaleWidth}
          startLeft={startLeft}
          rowHeight={rowHeight}
          disableDrag={isCutMode} // Disable regular dragging while in cut mode
          style={{ width: "100%", height: "100%" }}
        />

        <CutOverlay
          data={data}
          scale={scale}
          scaleSplitCount={scaleSplitCount}
          scaleWidth={scaleWidth}
          startLeft={startLeft}
          rowHeight={rowHeight}
          editAreaTopOffset={editAreaTopOffset}
          gridSnap={false}
          config={{ keyboardModifier: "c" }} // Activate with 'C' key
          onModifierChange={setIsCutMode}
          onCut={handleCut}
        />
      </div>
    </div>
  );
};

Key constraint: Every geometry prop (scale, scaleWidth, startLeft, rowHeight) must be identical in both <Timeline> and <CutOverlay>, otherwise the blade and block positions won't align.

Visual Configuration

Customize the blade's appearance via the config prop:

<CutOverlay
  {/* ...required geometry props... */}
  config={{
    bladeColor: '#ef4444',                // Vertical blade line color
    showPill: true,                       // Show floating time label
    formatPillLabel: (time) => `✂ ${time.toFixed(2)}s`, // Custom label format
    pillColor: '#ef4444',                 // Label background
    pillTextColor: '#ffffff',             // Label text color
    showBlockHighlight: true,             // Highlight hovered block
    blockHighlightColor: 'rgba(239,68,68,0.08)', // Block highlight fill
    blockHighlightBorderColor: 'rgba(239,68,68,0.3)', // Block highlight border
    cursor: 'col-resize',                 // Custom CSS cursor
    keyboardModifier: 'c',                // Hold 'c' key to activate
  }}
  onCut={handleCut}
/>

splitActionInRow Utility

import { splitActionInRow } from "@keplar-404/react-timeline-editor";

const newData = splitActionInRow(
  data, // Current TimelineRow[]
  "row-1", // Row ID
  "action-1", // Action ID to split
  5, // Cut time in seconds
);

// Result: the action [0, 10] becomes two: [0, 5] and [5, 10]

The original action's effectId and data are inherited by both resulting pieces.

On this page