{"_id":"@asky-ai2/vue-timeline-editor","_rev":"3-80f3ca873c65fb8172fb23e408ae4b3f","name":"@asky-ai2/vue-timeline-editor","dist-tags":{"latest":"0.0.6"},"versions":{"0.0.4":{"name":"@asky-ai2/vue-timeline-editor","version":"0.0.4","keywords":["vue","timeline","scheduler","booking","calendar","drag","resize","timeline-editor"],"author":{"url":"Mohammudullah","name":"Asky"},"license":"MIT","_id":"@asky-ai2/vue-timeline-editor@0.0.4","maintainers":[{"name":"asky-ai","email":"andrea@asky.ai"},{"name":"mohammadullah","email":"mdmohammudullaha@gmail.com"}],"homepage":"https://github.com/asky/vue-timeline-editor#readme","bugs":{"url":"https://github.com/asky/vue-timeline-editor/issues"},"dist":{"shasum":"0b57fa3674d8a0807f55117a6f85a7453c6bfd16","tarball":"https://registry.npmjs.org/@asky-ai2/vue-timeline-editor/-/vue-timeline-editor-0.0.4.tgz","fileCount":87,"integrity":"sha512-cnoHIrPN3WXyTKDyewGeNYo/JGx/INERVWP4JgerQBOgwJtphergFfv8cWKSd7kbqZ94y1JzeRWAecICcdGArw==","signatures":[{"sig":"MEYCIQDnc+pRWqvvHUnJErl/oyu6qzbtnLuvWsF6PYdFqtTFlwIhANDFRywmsW2AKht0k17+1DDf+b67jVauER15NowthlUf","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":349875},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./timeline":{"types":"./dist/timeline.d.ts","import":"./dist/timeline.js","require":"./dist/timeline.cjs"},"./basic-theme.css":"./dist/basic-theme.css","./features/playhead":{"types":"./dist/features/playhead.d.ts","import":"./dist/features/playhead.js","require":"./dist/features/playhead.cjs"},"./features/sections":{"types":"./dist/features/sections.d.ts","import":"./dist/features/sections.js","require":"./dist/features/sections.cjs"},"./features/frame-dnd":{"types":"./dist/features/frame-dnd.d.ts","import":"./dist/features/frame-dnd.js","require":"./dist/features/frame-dnd.cjs"},"./features/use-snapping":{"types":"./dist/features/use-snapping.d.ts","import":"./dist/features/use-snapping.js","require":"./dist/features/use-snapping.cjs"}},"gitHead":"bd50d887149f6d075d5669aa4dc501b29bca521f","scripts":{"dev":"vite","build":"vite build && node -e \"require('fs').copyFileSync('src/styles/basic-theme.css','dist/basic-theme.css')\" && vue-tsc -p tsconfig.build.json && node -e \"require('fs').mkdirSync('dist/types',{recursive:true});require('fs').copyFileSync('src/types/timeline.d.ts','dist/types/timeline.d.ts')\"","preview":"vite preview"},"_npmUser":{"name":"mohammadullah","email":"mdmohammudullaha@gmail.com"},"repository":{"url":"git+https://github.com/asky/vue-timeline-editor.git","type":"git"},"_npmVersion":"10.9.2","description":"Interactive timeline editor for Vue 3 (drag, resize, zoom)","directories":{},"sideEffects":["*.css","dist/index.js","dist/index.cjs","dist/timeline.js","dist/timeline.cjs"],"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.3.0","vite":"^8.0.3","vue-tsc":"^2.2.12","typescript":"^5.4.0","@types/node":"^25.5.0","@vitejs/plugin-vue":"^6.0.0","vite-plugin-css-injected-by-js":"^4.0.1"},"peerDependencies":{"vue":"^3.3.0"},"_npmOperationalInternal":{"tmp":"tmp/vue-timeline-editor_0.0.4_1780901482575_0.022505680827159313","host":"s3://npm-registry-packages-npm-production"}},"0.0.5":{"name":"@asky-ai2/vue-timeline-editor","version":"0.0.5","keywords":["vue","timeline","scheduler","booking","calendar","drag","resize","timeline-editor"],"author":{"url":"Mohammudullah","name":"Asky"},"license":"MIT","_id":"@asky-ai2/vue-timeline-editor@0.0.5","maintainers":[{"name":"asky-ai","email":"andrea@asky.ai"},{"name":"mohammadullah","email":"mdmohammudullaha@gmail.com"}],"homepage":"https://github.com/asky/vue-timeline-editor#readme","bugs":{"url":"https://github.com/asky/vue-timeline-editor/issues"},"dist":{"shasum":"6980302710530a793f86b41aeef746e7d99c5f88","tarball":"https://registry.npmjs.org/@asky-ai2/vue-timeline-editor/-/vue-timeline-editor-0.0.5.tgz","fileCount":87,"integrity":"sha512-p8N6Y/EKjYvv/Er1kMX5Az1DYkQrrgIX516kE0x296+YEQiw8hJ022UK6k2nc0UhW43ZH0N7GOUqccYznxyQTw==","signatures":[{"sig":"MEUCIQDrJXKtNWK3SX9s9l06BY2+TVjsVVLkXHcd0XqgyUODmQIgFrNN08vI1S7KKfl5Hg47WK+g9FhF1v5C5T3AHcWzVAY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":352232},"main":"dist/index.cjs","type":"module","types":"dist/index.d.ts","module":"dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./timeline":{"types":"./dist/timeline.d.ts","import":"./dist/timeline.js","require":"./dist/timeline.cjs"},"./basic-theme.css":"./dist/basic-theme.css","./features/playhead":{"types":"./dist/features/playhead.d.ts","import":"./dist/features/playhead.js","require":"./dist/features/playhead.cjs"},"./features/sections":{"types":"./dist/features/sections.d.ts","import":"./dist/features/sections.js","require":"./dist/features/sections.cjs"},"./features/frame-dnd":{"types":"./dist/features/frame-dnd.d.ts","import":"./dist/features/frame-dnd.js","require":"./dist/features/frame-dnd.cjs"},"./features/use-snapping":{"types":"./dist/features/use-snapping.d.ts","import":"./dist/features/use-snapping.js","require":"./dist/features/use-snapping.cjs"}},"gitHead":"bd50d887149f6d075d5669aa4dc501b29bca521f","scripts":{"dev":"vite","build":"vite build && node -e \"require('fs').copyFileSync('src/styles/basic-theme.css','dist/basic-theme.css')\" && vue-tsc -p tsconfig.build.json && node -e \"require('fs').mkdirSync('dist/types',{recursive:true});require('fs').copyFileSync('src/types/timeline.d.ts','dist/types/timeline.d.ts')\"","preview":"vite preview"},"_npmUser":{"name":"mohammadullah","email":"mdmohammudullaha@gmail.com"},"repository":{"url":"git+https://github.com/asky/vue-timeline-editor.git","type":"git"},"_npmVersion":"10.9.2","description":"Interactive timeline editor for Vue 3 (drag, resize, zoom)","directories":{},"sideEffects":["*.css","dist/index.js","dist/index.cjs","dist/timeline.js","dist/timeline.cjs"],"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"devDependencies":{"vue":"^3.3.0","vite":"^8.0.3","vue-tsc":"^2.2.12","typescript":"^5.4.0","@types/node":"^25.5.0","@vitejs/plugin-vue":"^6.0.0","vite-plugin-css-injected-by-js":"^4.0.1"},"peerDependencies":{"vue":"^3.3.0"},"_npmOperationalInternal":{"tmp":"tmp/vue-timeline-editor_0.0.5_1780901778651_0.41983980843009405","host":"s3://npm-registry-packages-npm-production"}},"0.0.6":{"name":"@asky-ai2/vue-timeline-editor","version":"0.0.6","description":"Interactive timeline editor for Vue 3 (drag, resize, zoom)","author":{"name":"Mohammudullah Khan","email":"mdmohammudullaha@gmail.com"},"license":"MIT","type":"module","main":"dist/index.cjs","module":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"import":"./dist/index.js","require":"./dist/index.cjs","types":"./dist/index.d.ts"},"./timeline":{"import":"./dist/timeline.js","require":"./dist/timeline.cjs","types":"./dist/timeline.d.ts"},"./features/frame-dnd":{"import":"./dist/features/frame-dnd.js","require":"./dist/features/frame-dnd.cjs","types":"./dist/features/frame-dnd.d.ts"},"./features/use-snapping":{"import":"./dist/features/use-snapping.js","require":"./dist/features/use-snapping.cjs","types":"./dist/features/use-snapping.d.ts"},"./features/sections":{"import":"./dist/features/sections.js","require":"./dist/features/sections.cjs","types":"./dist/features/sections.d.ts"},"./features/playhead":{"import":"./dist/features/playhead.js","require":"./dist/features/playhead.cjs","types":"./dist/features/playhead.d.ts"},"./basic-theme.css":"./dist/basic-theme.css"},"sideEffects":["*.css","dist/index.js","dist/index.cjs","dist/timeline.js","dist/timeline.cjs"],"scripts":{"dev":"vite","build":"vite build && node -e \"require('fs').copyFileSync('src/styles/basic-theme.css','dist/basic-theme.css')\" && vue-tsc -p tsconfig.build.json && node -e \"require('fs').mkdirSync('dist/types',{recursive:true});require('fs').copyFileSync('src/types/timeline.d.ts','dist/types/timeline.d.ts')\"","preview":"vite preview"},"peerDependencies":{"vue":"^3.3.0"},"devDependencies":{"@types/node":"^25.5.0","@vitejs/plugin-vue":"^6.0.0","typescript":"^5.4.0","vite":"^8.0.3","vite-plugin-css-injected-by-js":"^4.0.1","vue":"^3.3.0","vue-tsc":"^2.2.12"},"keywords":["vue","timeline","scheduler","booking","calendar","drag","resize","timeline-editor"],"repository":{"type":"git","url":"git+https://github.com/asky/vue-timeline-editor.git"},"homepage":"https://github.com/asky/vue-timeline-editor#readme","bugs":{"url":"https://github.com/asky/vue-timeline-editor/issues"},"_id":"@asky-ai2/vue-timeline-editor@0.0.6","gitHead":"bd50d887149f6d075d5669aa4dc501b29bca521f","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-EdIuiMggCI1C0SXxxRTzHgSZ1XO/6EAfl+HTLyzs3IfJa130cHJDzLvT9sr0KknqgJSp2EdsWLRlotbzOVAUgA==","shasum":"e7863f9637d24ef942e688d2a36c9adc54d40a45","tarball":"https://registry.npmjs.org/@asky-ai2/vue-timeline-editor/-/vue-timeline-editor-0.0.6.tgz","fileCount":87,"unpackedSize":352283,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEA9n7wAh+CQF5/q+w74QOyoxtBcSUf20XOicq9FyYHVAiB77wzUZMK62WwLV2aB/T/aV2xfTSzozYHqQnn/THcTBw=="}]},"_npmUser":{"name":"mohammadullah","email":"mdmohammudullaha@gmail.com"},"directories":{},"maintainers":[{"name":"asky-ai","email":"andrea@asky.ai"},{"name":"mohammadullah","email":"mdmohammudullaha@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/vue-timeline-editor_0.0.6_1780902038739_0.9293315470625374"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-08T06:51:22.392Z","modified":"2026-06-08T07:00:39.036Z","0.0.4":"2026-06-08T06:51:22.709Z","0.0.5":"2026-06-08T06:56:18.790Z","0.0.6":"2026-06-08T07:00:38.872Z"},"bugs":{"url":"https://github.com/asky/vue-timeline-editor/issues"},"author":{"name":"Mohammudullah Khan","email":"mdmohammudullaha@gmail.com"},"license":"MIT","homepage":"https://github.com/asky/vue-timeline-editor#readme","keywords":["vue","timeline","scheduler","booking","calendar","drag","resize","timeline-editor"],"repository":{"type":"git","url":"git+https://github.com/asky/vue-timeline-editor.git"},"description":"Interactive timeline editor for Vue 3 (drag, resize, zoom)","maintainers":[{"name":"asky-ai","email":"andrea@asky.ai"},{"name":"mohammadullah","email":"mdmohammudullaha@gmail.com"}],"readme":"# Vue Timeline Editor\n\nA lightweight, flexible, and interactive timeline editor for Vue 3.\n\nDesigned for building booking systems, schedulers, and resource timelines with full control over UI and behavior. Every interactive capability is an **opt-in, tree-shakeable feature component** — you only ship what you mount.\n\n---\n\n## ✨ Features\n\n- 📅 Horizontal, time-based grid with configurable range and zoom\n- 🧱 Multi-section / multi-row layout (tables, resources, zones)\n- 🖱 Drag frames to move them between times and rows (`<Dnd/>`)\n- ↔️ Resize frames from either edge (`<Resize/>`)\n- 🎯 Snap to grid, frame edges, and rows (`<Snapping/>`)\n- 📏 Static + active snap guide lines (`<SnapGuideLines/>`)\n- ➕ Click (or tap) an empty area to create a frame (`<Sections rowClickable/>`)\n- 🔗 Linked frames that move/resize together (`<JoinRows/>`)\n- ▶️ Playhead with drag, click-seek, and a built-in play loop (`<Playhead/>`)\n- ✋ Pan by click-drag (`<PanScroll/>`)\n- 🚫 Blocked time ranges on rows and sections (`<BlockedAreas/>`)\n- 🔍 Section filtering — show only specific sections at runtime\n- 🪝 Optimistic add/remove with server-confirm and auto-revert\n- 🎨 Fully customizable via slots and a single overridable theme stylesheet\n- ⚡ Sticky section headers, transform-based axis rendering\n\n---\n\n## 🚀 Installation\n\n```bash\nnpm install @asky-ai2/vue-timeline-editor\n```\n\nPeer dependency: **Vue ^3.3**.\n\nImport the base theme once in your app entry:\n\n```ts\nimport '@asky-ai2/vue-timeline-editor/basic-theme.css';\n```\n\n---\n\n## ⚡ Quick start\n\n```vue\n<script setup lang=\"ts\">\nimport {\n  Timeline, Sections, Dnd, Resize, Snapping, SnapGuideLines,\n} from '@asky-ai2/vue-timeline-editor';\nimport '@asky-ai2/vue-timeline-editor/basic-theme.css';\n\nconst sections = [\n  {\n    title: 'Section 1',\n    uuid: 'section1',\n    rows: [\n      {\n        uuid: 'row1',\n        title: 'Row 1',\n        frames: [\n          { uuid: 'frame1', title: 'Booking', start_ms: 0, end_ms: 2 * 60 * 60 * 1000 },\n        ],\n      },\n    ],\n  },\n];\n</script>\n\n<template>\n  <Timeline :initial-range=\"{ start_seconds: 0, end_seconds: 24 * 60 * 60 }\">\n    <Sections :sections=\"sections\" />\n    <Dnd />\n    <Resize />\n    <Snapping />\n    <SnapGuideLines />\n  </Timeline>\n</template>\n```\n\n`<Timeline>` is the only required component. Everything inside it is optional —\nmount a feature component to enable that behavior, omit it to drop the code.\n\n---\n\n## 🧩 Components\n\n### `<Timeline>`\n\nRoot component. Provides the timeline context to every descendant feature.\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `initialRange` | `{ start_seconds, end_seconds }` | `0 → 24h` | Visible time window. |\n| `timeAxisTimeFormat` | `string` | `'HH:mm:ss'` | Time-axis label format (e.g. `'hh:mm a'`). |\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `@init` | `TimelineInitInterface` | Fires once mounted — exposes the imperative API. |\n| `@scroll` | `Event` | Editor scrolled. |\n\n| Slot | Props | Description |\n|------|-------|-------------|\n| `#frame` | `{ uuid, title, startMs, endMs, meta, selected, ... }` | Replaces a frame's inner content. |\n| `#rowLabel` | `{ uuid, title, sectionUuid }` | Replaces a row's label in the row axis. |\n\n**Reactive slot content.** Slot templates render in your component's scope, so they can close over your own reactive state:\n\n```vue\n<script setup>\nconst rowAccents = ref({});\n</script>\n\n<template>\n  <Timeline>\n    <template #rowLabel=\"{ uuid, title }\">\n      <span :style=\"{ color: rowAccents[uuid] }\">{{ title }}</span>\n    </template>\n  </Timeline>\n</template>\n```\n\n---\n\n### `<Sections>`\n\nInitializes section/row/frame data and provides click-to-add on empty areas.\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `sections` | `TimelineSectionInterface[]` | — | The section/row/frame tree. |\n| `filteredSections` | `(string \\| number)[] \\| null` | `null` | Show only the listed section UUIDs. `null` shows all. Changes apply instantly without re-initializing frame data. |\n| `rowClickable` | `boolean` | `false` | Enable the empty-area new-frame suggestion globally. |\n| `newFrameLabel` | `string` | `'+ Add'` | Touch-mode label text. |\n| `availableSlots` | `{ uuid, available_slots: { start_ms, end_ms }[] }[]` | — | Allowlist: dims/locks the editor except these slots. Clears to exit mode. |\n| `selectedRowUuids` | `(string \\| number)[]` | — | Row UUIDs whose allowed slots render selected. |\n| `frameHoldDuration` | `number` | `600` | Press-and-hold time (ms) before `@frame-hold` fires. |\n| `frameHoldThreshold` | `number` | `8` | Movement tolerance (px) that cancels the hold. |\n| `silentExternalSelection` | `boolean` | `false` | When true, `@frame-selected` / `@frame-deselected` only fire for user-driven clicks. |\n\n| Event | Payload | Description |\n|-------|---------|-------------|\n| `@add-frame` | `{ rowUuid, sectionUuid, start_ms, end_ms }` | Empty area activated. |\n| `@select-slot` | `{ rowUuid, sectionUuid, start_ms, end_ms }` | An allowlist slot was clicked. |\n| `@frame-hold` | `(frame, event)` | A frame was pressed and held. |\n| `@frame-selected` | `(frame)` | A frame became the primary selection. |\n| `@frame-deselected` | `(frame)` | A frame was deselected (always before `@frame-selected`). |\n\n| Slot | Props | Description |\n|------|-------|-------------|\n| `#label` | `{ rowUuid, sectionUuid, start_ms, end_ms, length_ms }` | Overrides the touch-mode suggestion label. |\n\n**Section filtering** — `filteredSections` lets you show only a subset of sections without discarding frame data. The editor height, grid, snapping, and row suggestions all recalculate as if only the visible sections exist. Restoring `null` brings everything back instantly.\n\n```vue\n<Sections :sections=\"sections\" :filtered-sections=\"['floor-1', 'floor-2']\" />\n```\n\n---\n\n### `<Dnd>`\n\nDrag frames to move them. Drags the whole selection together.\n\n- **Prop:** `edgeSnap` (`boolean`, default `true`).\n- **Events:** `@dragStart`, `@dragEnd`, `@drop`, `@dragCancel`, `@dragBlocked`.\n- **Slot:** `#highlighter` — replaces the drop-target indicator.\n\n---\n\n### `<Resize>`\n\nResize a frame from either edge.\n\n- **Events:** `@resizeStart`, `@resizeEnd`, `@resized`, `@resizeCancel`, `@resizeBlocked`.\n\n---\n\n### `<Snapping>`\n\nSnaps drag/resize to the grid, frame edges, and rows, and protects against overlap. Each pipeline step is a toggle prop (all default `true`):\n\n`rows`, `frames`, `times`, `guides`, `overlapping`.\n\n```vue\n<Snapping :overlapping=\"false\" />  <!-- allow overlapping frames -->\n```\n\n---\n\n### `<SnapGuideLines>`\n\nRenders grid lines and active snap guides.\n\n- **Props:** `majorGrid` (`true`), `minorGrid` (`false`), `activeSnapGuides` (`true`).\n- **Slots:** `#majorGrid`, `#minorGrid`, `#activeSnapGuide`.\n\n---\n\n### `<JoinRows>`\n\nFrames sharing a `linkGroupUuid` move and resize as one linked group.\nNo props — mount alongside `<Dnd/>` / `<Resize/>`.\n\n---\n\n### `<PanScroll>`\n\nClick-and-drag panning (mouse only; touch scrolls natively). No props.\n\n---\n\n### `<Playhead>`\n\nA draggable position marker with a ruler handle, a full-height line, and an off-screen edge indicator.\n\n| Prop | Type | Default | Description |\n|------|------|---------|-------------|\n| `v-model` | `number` | — | Playhead position in ms (two-way). |\n| `v-model:playing` | `boolean` | — | Playback running state (two-way). |\n| `rate` | `number` | `1` | Timeline-ms advanced per real-ms while playing. |\n| `loop` | `boolean` | `false` | Wrap to range start instead of pausing at end. |\n| `updateInterval` | `number` | `0` | Throttle reactive position updates. `0` = every frame, `1000` = once per second. The internal position still advances every frame; only the reactive `v-model` write is throttled. |\n| `draggable` | `boolean` | `true` | Allow dragging / ruler scrubbing. |\n| `clickSeek` | `boolean` | `true` | Allow clicking the ruler to jump. |\n| `onTimelineMountedScrollToPlayHead` | `boolean` | `false` | Scroll the playhead into view once after mount. |\n\n| Event | Description |\n|-------|-------------|\n| `@seek` | Fired on seek (drag end or click). |\n| `@dragStart` / `@dragEnd` | Drag lifecycle. |\n\n**`#handle` slot** — the slot receives `secondsToTimeString` as a prop so you can display the current time in the handle without importing a utility:\n\n```vue\n<Playhead v-model=\"posMs\">\n  <template #handle=\"{ currentMs, secondsToTimeString }\">\n    <div class=\"my-handle\">\n      {{ secondsToTimeString(currentMs / 1000) }}\n    </div>\n  </template>\n</Playhead>\n```\n\nOut of range, the playhead hides and an edge arrow points toward it.\n\n---\n\n### `<BlockedAreas>`\n\nRenders visual overlays for blocked time windows on individual rows or entire sections. Blocked areas are purely visual — they do not prevent drag/resize by default.\n\n```vue\n<BlockedAreas\n  :row-blockages=\"rowBlockages\"\n  :section-blockages=\"sectionBlockages\"\n/>\n```\n\n| Prop | Type | Description |\n|------|------|-------------|\n| `rowBlockages` | `RowBlockage[] \\| null` | Row-level blockages. |\n| `sectionBlockages` | `SectionBlockage[] \\| null` | Section-level blockages (covers all rows). |\n\n```ts\ninterface RowBlockage {\n  rowUuids: (string | number)[];   // rows to block — any order\n  start_ms?: number | null;        // null / omit = full timeline width\n  end_ms?: number | null;\n  full_row?: boolean;              // explicit \"all day\" intent flag\n  block_ulid: string;              // stable id for grouping / click handlers\n  title?: string | null;\n}\n\ninterface SectionBlockage {\n  sectionUuid: string | number;\n  start_ms?: number | null;\n  end_ms?: number | null;\n  full_row?: boolean;\n  block_ulid: string;\n  title?: string | null;\n}\n```\n\n**Sequential row merging** — consecutive `rowUuids` in a single `RowBlockage` are merged into one visual block. Non-consecutive rows produce separate blocks. A single blockage with rows `[1, 2, 3, 5]` produces two blocks: one spanning rows 1–3, one for row 5.\n\n**Section vs row appearance** — section blockages have no top/bottom border in the default theme (they cover all rows). The `#blockage` slot receives `isSection` so you can differentiate styling.\n\n**`#blockage` slot** — a single slot applied to every rendered block:\n\n```vue\n<BlockedAreas :row-blockages=\"blockages\">\n  <template #blockage=\"{ title, isSection, rowUuids, block_ulid, blockage }\">\n    <div class=\"my-block\" @click=\"openSidebar(block_ulid)\">\n      {{ title }}\n    </div>\n  </template>\n</BlockedAreas>\n```\n\nSlot props: `key`, `top`, `left`, `width`, `height`, `isSection`, `rowUuids`, `title`, `block_ulid`, `full_row`, `blockage` (original object).\n\n---\n\n## 🗂 Data shapes\n\n```ts\ninterface TimelineSectionInterface {\n  uuid: string | number;\n  title: string;\n  rows: TimelineRowInterface[];\n}\n\ninterface TimelineRowInterface {\n  uuid: string | number;\n  title: string;\n  frames: TimelineFrameInterface[];\n  new_frame_ms?: number;          // optional click-to-add length hint\n}\n\ninterface TimelineFrameInterface {\n  uuid: string | number;\n  title: string | null;\n  start_ms: number;               // absolute ms\n  end_ms: number;\n  linkGroupUuid?: string | number; // links frames for <JoinRows/>\n  meta?: unknown;                  // arbitrary payload, passed to #frame slot\n}\n```\n\nAll times are absolute milliseconds. The visible window is set in seconds via `initialRange`.\n\n---\n\n## 🔌 Imperative API\n\n`@init` hands back the live API:\n\n```ts\nimport type { TimelineInitInterface } from '@asky-ai2/vue-timeline-editor';\n\nconst onInit = (api: TimelineInitInterface) => {\n  // api.timeline  — data + frame CRUD\n  // api.config    — reactive config (range, zoom, pixel-per-ms, …)\n  // api.features  — mounted feature instances\n\n  // Frame CRUD\n  const handle = api.timeline.addFrame(\n    { uuid: 'tmp', title: 'New', start_ms: 0, end_ms: 3600000, rowUuid: 'row1', sectionUuid: 'section1' },\n    async (frame) => ({ uuid: await saveToServer(frame) }), // optional server sync\n  );\n  handle.promise\n    .then(() => console.log('saved as', handle.uuid))\n    .catch(() => console.log('reverted'));\n\n  api.timeline.removeFrame('frame1', () => deleteOnServer('frame1'));\n  api.timeline.updateFrame('frame1', { ...patch });\n  api.timeline.syncFrame({ uuid: 'frame1', ...data });   // upsert\n  api.timeline.syncFrames([...frames]);                   // bulk upsert\n\n  // Section data (also callable async, after @init)\n  api.timeline.initSections(sections);\n  api.timeline.setSectionFilter(['floor-1']);  // reactive filter\n\n  // Navigation\n  api.timeline.scrollToViewPort('frame1', /* highlight */ true);\n\n  // Pending / loading state\n  api.timeline.setPending('frame1', true);\n  api.timeline.setLoadingMode('immediate');\n\n  // Selection (requires <Sections/>)\n  api.features.data.frames.getSelectedFrames();\n\n  // Playhead (requires <Playhead/>)\n  api.features.data.playhead?.seek(3600000);\n  api.features.data.playhead?.play();\n  api.features.data.playhead?.pause();\n};\n```\n\n`addFrame` / `removeFrame` are **optimistic** — the change applies immediately; if the sync/confirm callback rejects, it auto-reverts. No boilerplate rollback code needed.\n\n---\n\n## 🎨 Theming\n\nAll visuals come from `basic-theme.css`. Override any `.vtd__*` class in your own CSS to restyle. Structural rules are injected automatically from the package bundle; colors, borders, and typography live in `basic-theme.css`, which you import and may override freely.\n\n---\n\n## 🌳 Tree-shaking\n\nFeature components are also published as individual entry points:\n\n```ts\nimport { Timeline } from '@asky-ai2/vue-timeline-editor';\nimport Dnd          from '@asky-ai2/vue-timeline-editor/features/dnd';\nimport Snapping     from '@asky-ai2/vue-timeline-editor/features/snapping';\nimport Sections     from '@asky-ai2/vue-timeline-editor/features/sections';\nimport Resize       from '@asky-ai2/vue-timeline-editor/features/resize';\nimport Playhead     from '@asky-ai2/vue-timeline-editor/features/playhead';\n```\n\n---\n\n## 👤 Author\n\n**Mohammudullah Khan** — [mdmohammudullaha@gmail.com](mailto:mdmohammudullaha@gmail.com)\n\n---\n\n## 📄 License\n\nMIT\n","readmeFilename":"README.md"}