@clipkit/editor 1.0.0

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.
Files changed (248) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +51 -0
  3. package/dist/Editor.d.ts +3 -0
  4. package/dist/Editor.d.ts.map +1 -0
  5. package/dist/Editor.js +73 -0
  6. package/dist/Editor.js.map +1 -0
  7. package/dist/ExportDialog.d.ts +12 -0
  8. package/dist/ExportDialog.d.ts.map +1 -0
  9. package/dist/ExportDialog.js +30 -0
  10. package/dist/ExportDialog.js.map +1 -0
  11. package/dist/MotionPathOverlay.d.ts +5 -0
  12. package/dist/MotionPathOverlay.d.ts.map +1 -0
  13. package/dist/MotionPathOverlay.js +156 -0
  14. package/dist/MotionPathOverlay.js.map +1 -0
  15. package/dist/PerfHud.d.ts +2 -0
  16. package/dist/PerfHud.d.ts.map +1 -0
  17. package/dist/PerfHud.js +85 -0
  18. package/dist/PerfHud.js.map +1 -0
  19. package/dist/Stage.d.ts +2 -0
  20. package/dist/Stage.d.ts.map +1 -0
  21. package/dist/Stage.js +406 -0
  22. package/dist/Stage.js.map +1 -0
  23. package/dist/StageOverlay.d.ts +7 -0
  24. package/dist/StageOverlay.d.ts.map +1 -0
  25. package/dist/StageOverlay.js +508 -0
  26. package/dist/StageOverlay.js.map +1 -0
  27. package/dist/commands.d.ts +18 -0
  28. package/dist/commands.d.ts.map +1 -0
  29. package/dist/commands.js +103 -0
  30. package/dist/commands.js.map +1 -0
  31. package/dist/configuration.d.ts +9 -0
  32. package/dist/configuration.d.ts.map +1 -0
  33. package/dist/configuration.js +21 -0
  34. package/dist/configuration.js.map +1 -0
  35. package/dist/controls/AnimationsStack.d.ts +8 -0
  36. package/dist/controls/AnimationsStack.d.ts.map +1 -0
  37. package/dist/controls/AnimationsStack.js +188 -0
  38. package/dist/controls/AnimationsStack.js.map +1 -0
  39. package/dist/controls/CameraControl.d.ts +19 -0
  40. package/dist/controls/CameraControl.d.ts.map +1 -0
  41. package/dist/controls/CameraControl.js +47 -0
  42. package/dist/controls/CameraControl.js.map +1 -0
  43. package/dist/controls/CaptionLengthControl.d.ts +5 -0
  44. package/dist/controls/CaptionLengthControl.d.ts.map +1 -0
  45. package/dist/controls/CaptionLengthControl.js +11 -0
  46. package/dist/controls/CaptionLengthControl.js.map +1 -0
  47. package/dist/controls/CaptionTranscribe.d.ts +2 -0
  48. package/dist/controls/CaptionTranscribe.d.ts.map +1 -0
  49. package/dist/controls/CaptionTranscribe.js +95 -0
  50. package/dist/controls/CaptionTranscribe.js.map +1 -0
  51. package/dist/controls/ColorPicker.d.ts +17 -0
  52. package/dist/controls/ColorPicker.d.ts.map +1 -0
  53. package/dist/controls/ColorPicker.js +354 -0
  54. package/dist/controls/ColorPicker.js.map +1 -0
  55. package/dist/controls/ControlRenderer.d.ts +20 -0
  56. package/dist/controls/ControlRenderer.d.ts.map +1 -0
  57. package/dist/controls/ControlRenderer.js +106 -0
  58. package/dist/controls/ControlRenderer.js.map +1 -0
  59. package/dist/controls/CropControl.d.ts +2 -0
  60. package/dist/controls/CropControl.d.ts.map +1 -0
  61. package/dist/controls/CropControl.js +177 -0
  62. package/dist/controls/CropControl.js.map +1 -0
  63. package/dist/controls/EffectsStack.d.ts +8 -0
  64. package/dist/controls/EffectsStack.d.ts.map +1 -0
  65. package/dist/controls/EffectsStack.js +89 -0
  66. package/dist/controls/EffectsStack.js.map +1 -0
  67. package/dist/controls/GradeControl.d.ts +2 -0
  68. package/dist/controls/GradeControl.d.ts.map +1 -0
  69. package/dist/controls/GradeControl.js +120 -0
  70. package/dist/controls/GradeControl.js.map +1 -0
  71. package/dist/controls/KeyframeDiamond.d.ts +11 -0
  72. package/dist/controls/KeyframeDiamond.d.ts.map +1 -0
  73. package/dist/controls/KeyframeDiamond.js +87 -0
  74. package/dist/controls/KeyframeDiamond.js.map +1 -0
  75. package/dist/controls/LightingControls.d.ts +24 -0
  76. package/dist/controls/LightingControls.d.ts.map +1 -0
  77. package/dist/controls/LightingControls.js +108 -0
  78. package/dist/controls/LightingControls.js.map +1 -0
  79. package/dist/controls/ShapePresetControl.d.ts +4 -0
  80. package/dist/controls/ShapePresetControl.d.ts.map +1 -0
  81. package/dist/controls/ShapePresetControl.js +30 -0
  82. package/dist/controls/ShapePresetControl.js.map +1 -0
  83. package/dist/controls/ValueField.d.ts +10 -0
  84. package/dist/controls/ValueField.d.ts.map +1 -0
  85. package/dist/controls/ValueField.js +158 -0
  86. package/dist/controls/ValueField.js.map +1 -0
  87. package/dist/controls/VolumeControl.d.ts +10 -0
  88. package/dist/controls/VolumeControl.d.ts.map +1 -0
  89. package/dist/controls/VolumeControl.js +75 -0
  90. package/dist/controls/VolumeControl.js.map +1 -0
  91. package/dist/controls/compound.d.ts +46 -0
  92. package/dist/controls/compound.d.ts.map +1 -0
  93. package/dist/controls/compound.js +160 -0
  94. package/dist/controls/compound.js.map +1 -0
  95. package/dist/controls/layout.d.ts +38 -0
  96. package/dist/controls/layout.d.ts.map +1 -0
  97. package/dist/controls/layout.js +162 -0
  98. package/dist/controls/layout.js.map +1 -0
  99. package/dist/controls/primitives.d.ts +83 -0
  100. package/dist/controls/primitives.d.ts.map +1 -0
  101. package/dist/controls/primitives.js +194 -0
  102. package/dist/controls/primitives.js.map +1 -0
  103. package/dist/controls/transcribe.worker.d.ts +2 -0
  104. package/dist/controls/transcribe.worker.d.ts.map +1 -0
  105. package/dist/controls/transcribe.worker.js +22 -0
  106. package/dist/controls/transcribe.worker.js.map +1 -0
  107. package/dist/frame/AddElementBar.d.ts +2 -0
  108. package/dist/frame/AddElementBar.d.ts.map +1 -0
  109. package/dist/frame/AddElementBar.js +103 -0
  110. package/dist/frame/AddElementBar.js.map +1 -0
  111. package/dist/frame/Breadcrumbs.d.ts +2 -0
  112. package/dist/frame/Breadcrumbs.d.ts.map +1 -0
  113. package/dist/frame/Breadcrumbs.js +32 -0
  114. package/dist/frame/Breadcrumbs.js.map +1 -0
  115. package/dist/frame/GroupFlash.d.ts +2 -0
  116. package/dist/frame/GroupFlash.d.ts.map +1 -0
  117. package/dist/frame/GroupFlash.js +65 -0
  118. package/dist/frame/GroupFlash.js.map +1 -0
  119. package/dist/frame/Resizable.d.ts +12 -0
  120. package/dist/frame/Resizable.d.ts.map +1 -0
  121. package/dist/frame/Resizable.js +37 -0
  122. package/dist/frame/Resizable.js.map +1 -0
  123. package/dist/frame/Section.d.ts +23 -0
  124. package/dist/frame/Section.d.ts.map +1 -0
  125. package/dist/frame/Section.js +23 -0
  126. package/dist/frame/Section.js.map +1 -0
  127. package/dist/frame/ZoomControl.d.ts +9 -0
  128. package/dist/frame/ZoomControl.d.ts.map +1 -0
  129. package/dist/frame/ZoomControl.js +15 -0
  130. package/dist/frame/ZoomControl.js.map +1 -0
  131. package/dist/index.d.ts +9 -0
  132. package/dist/index.d.ts.map +1 -0
  133. package/dist/index.js +13 -0
  134. package/dist/index.js.map +1 -0
  135. package/dist/lib/camera-gizmo.d.ts +15 -0
  136. package/dist/lib/camera-gizmo.d.ts.map +1 -0
  137. package/dist/lib/camera-gizmo.js +57 -0
  138. package/dist/lib/camera-gizmo.js.map +1 -0
  139. package/dist/lib/camera-tool.d.ts +43 -0
  140. package/dist/lib/camera-tool.d.ts.map +1 -0
  141. package/dist/lib/camera-tool.js +80 -0
  142. package/dist/lib/camera-tool.js.map +1 -0
  143. package/dist/lib/caption-segments.d.ts +17 -0
  144. package/dist/lib/caption-segments.d.ts.map +1 -0
  145. package/dist/lib/caption-segments.js +50 -0
  146. package/dist/lib/caption-segments.js.map +1 -0
  147. package/dist/lib/group.d.ts +12 -0
  148. package/dist/lib/group.d.ts.map +1 -0
  149. package/dist/lib/group.js +61 -0
  150. package/dist/lib/group.js.map +1 -0
  151. package/dist/lib/keyframes.d.ts +29 -0
  152. package/dist/lib/keyframes.d.ts.map +1 -0
  153. package/dist/lib/keyframes.js +92 -0
  154. package/dist/lib/keyframes.js.map +1 -0
  155. package/dist/lib/sfx-preview.d.ts +18 -0
  156. package/dist/lib/sfx-preview.d.ts.map +1 -0
  157. package/dist/lib/sfx-preview.js +74 -0
  158. package/dist/lib/sfx-preview.js.map +1 -0
  159. package/dist/lib/shape-presets.d.ts +35 -0
  160. package/dist/lib/shape-presets.d.ts.map +1 -0
  161. package/dist/lib/shape-presets.js +81 -0
  162. package/dist/lib/shape-presets.js.map +1 -0
  163. package/dist/lib/ungroup.d.ts +12 -0
  164. package/dist/lib/ungroup.d.ts.map +1 -0
  165. package/dist/lib/ungroup.js +40 -0
  166. package/dist/lib/ungroup.js.map +1 -0
  167. package/dist/lib/utils.d.ts +3 -0
  168. package/dist/lib/utils.d.ts.map +1 -0
  169. package/dist/lib/utils.js +9 -0
  170. package/dist/lib/utils.js.map +1 -0
  171. package/dist/panels/AssetsPanel.d.ts +2 -0
  172. package/dist/panels/AssetsPanel.d.ts.map +1 -0
  173. package/dist/panels/AssetsPanel.js +108 -0
  174. package/dist/panels/AssetsPanel.js.map +1 -0
  175. package/dist/panels/InspectorPanel.d.ts +2 -0
  176. package/dist/panels/InspectorPanel.d.ts.map +1 -0
  177. package/dist/panels/InspectorPanel.js +286 -0
  178. package/dist/panels/InspectorPanel.js.map +1 -0
  179. package/dist/panels/InterpolationPanel.d.ts +2 -0
  180. package/dist/panels/InterpolationPanel.d.ts.map +1 -0
  181. package/dist/panels/InterpolationPanel.js +226 -0
  182. package/dist/panels/InterpolationPanel.js.map +1 -0
  183. package/dist/panels/LayersTree.d.ts +4 -0
  184. package/dist/panels/LayersTree.d.ts.map +1 -0
  185. package/dist/panels/LayersTree.js +137 -0
  186. package/dist/panels/LayersTree.js.map +1 -0
  187. package/dist/panels/LeftRail.d.ts +6 -0
  188. package/dist/panels/LeftRail.d.ts.map +1 -0
  189. package/dist/panels/LeftRail.js +35 -0
  190. package/dist/panels/LeftRail.js.map +1 -0
  191. package/dist/panels/SourcePanel.d.ts +2 -0
  192. package/dist/panels/SourcePanel.d.ts.map +1 -0
  193. package/dist/panels/SourcePanel.js +470 -0
  194. package/dist/panels/SourcePanel.js.map +1 -0
  195. package/dist/panels/TimelinePanel.d.ts +11 -0
  196. package/dist/panels/TimelinePanel.d.ts.map +1 -0
  197. package/dist/panels/TimelinePanel.js +98 -0
  198. package/dist/panels/TimelinePanel.js.map +1 -0
  199. package/dist/panels/assets/SfxBrowser.d.ts +2 -0
  200. package/dist/panels/assets/SfxBrowser.d.ts.map +1 -0
  201. package/dist/panels/assets/SfxBrowser.js +49 -0
  202. package/dist/panels/assets/SfxBrowser.js.map +1 -0
  203. package/dist/panels/assets/use-assets.d.ts +11 -0
  204. package/dist/panels/assets/use-assets.d.ts.map +1 -0
  205. package/dist/panels/assets/use-assets.js +84 -0
  206. package/dist/panels/assets/use-assets.js.map +1 -0
  207. package/dist/panels/assets/use-sfx.d.ts +6 -0
  208. package/dist/panels/assets/use-sfx.d.ts.map +1 -0
  209. package/dist/panels/assets/use-sfx.js +47 -0
  210. package/dist/panels/assets/use-sfx.js.map +1 -0
  211. package/dist/panels/timeline/CanvasTimeline.d.ts +7 -0
  212. package/dist/panels/timeline/CanvasTimeline.d.ts.map +1 -0
  213. package/dist/panels/timeline/CanvasTimeline.js +1536 -0
  214. package/dist/panels/timeline/CanvasTimeline.js.map +1 -0
  215. package/dist/panels/timeline/Clip.d.ts +37 -0
  216. package/dist/panels/timeline/Clip.d.ts.map +1 -0
  217. package/dist/panels/timeline/Clip.js +176 -0
  218. package/dist/panels/timeline/Clip.js.map +1 -0
  219. package/dist/panels/timeline/CurveEditor.d.ts +2 -0
  220. package/dist/panels/timeline/CurveEditor.d.ts.map +1 -0
  221. package/dist/panels/timeline/CurveEditor.js +233 -0
  222. package/dist/panels/timeline/CurveEditor.js.map +1 -0
  223. package/dist/panels/timeline/MixerRail.d.ts +7 -0
  224. package/dist/panels/timeline/MixerRail.d.ts.map +1 -0
  225. package/dist/panels/timeline/MixerRail.js +295 -0
  226. package/dist/panels/timeline/MixerRail.js.map +1 -0
  227. package/dist/panels/timeline/Waveform.d.ts +11 -0
  228. package/dist/panels/timeline/Waveform.d.ts.map +1 -0
  229. package/dist/panels/timeline/Waveform.js +63 -0
  230. package/dist/panels/timeline/Waveform.js.map +1 -0
  231. package/dist/panels/timeline/clip-style.d.ts +10 -0
  232. package/dist/panels/timeline/clip-style.d.ts.map +1 -0
  233. package/dist/panels/timeline/clip-style.js +20 -0
  234. package/dist/panels/timeline/clip-style.js.map +1 -0
  235. package/dist/panels/timeline/filmstrip.d.ts +7 -0
  236. package/dist/panels/timeline/filmstrip.d.ts.map +1 -0
  237. package/dist/panels/timeline/filmstrip.js +135 -0
  238. package/dist/panels/timeline/filmstrip.js.map +1 -0
  239. package/dist/panels/timeline/timeline-layout.d.ts +65 -0
  240. package/dist/panels/timeline/timeline-layout.d.ts.map +1 -0
  241. package/dist/panels/timeline/timeline-layout.js +118 -0
  242. package/dist/panels/timeline/timeline-layout.js.map +1 -0
  243. package/dist/types.d.ts +68 -0
  244. package/dist/types.d.ts.map +1 -0
  245. package/dist/types.js +3 -0
  246. package/dist/types.js.map +1 -0
  247. package/package.json +56 -0
  248. package/src/styles.css +185 -0
@@ -0,0 +1,23 @@
1
+ // Section — THE flat inspector primitive per design/refs/README.md:
2
+ // a hairline-separated block with an uppercase micro-label header,
3
+ // collapsible by clicking the header, optional `+`/action slot on the
4
+ // right. No cards, no backgrounds — tight vertical rhythm.
5
+ 'use client';
6
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
+ import { useState } from 'react';
8
+ import { cn } from './../lib/utils.js';
9
+ export function Section({ title, action, defaultOpen = true, children, className, }) {
10
+ const [open, setOpen] = useState(defaultOpen);
11
+ return (_jsxs("div", { className: cn('border-b border-border', className), children: [_jsxs("div", { className: "flex items-center justify-between px-3 h-8 select-none", children: [_jsxs("button", { type: "button", className: "flex items-center gap-1.5 text-[11px] font-medium text-foreground/90 hover:text-foreground", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: [_jsx("svg", { width: "7", height: "7", viewBox: "0 0 8 8", "aria-hidden": "true", className: cn('text-muted-foreground transition-transform', open ? 'rotate-90' : ''), children: _jsx("path", { d: "M2 1 L6 4 L2 7 Z", fill: "currentColor" }) }), title] }), action && _jsx("div", { className: "flex items-center gap-1", children: action })] }), open && children && _jsx("div", { className: "px-3 pb-2.5", children: children })] }));
12
+ }
13
+ /** A label-left / value-right row inside a Section.
14
+ *
15
+ * THE GRID RHYTHM (ruled by Ian 2026-06-11): every row is 32px — a
16
+ * 24px well + 8px breathing room. The label column is FIXED (64px,
17
+ * matching the reference HTML) and the value area is FLUID — controls
18
+ * stretch to fill it, so every well's left and right edges align
19
+ * panel-wide regardless of panel width. */
20
+ export function FieldRow({ label, children, }) {
21
+ return (_jsxs("div", { className: "flex items-center gap-2 h-8", children: [_jsx("span", { className: "w-16 shrink-0 text-[11px] text-muted-foreground truncate", children: label }), _jsx("div", { className: "flex-1 flex items-center gap-2 min-w-0", children: children })] }));
22
+ }
23
+ //# sourceMappingURL=Section.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Section.js","sourceRoot":"","sources":["../../src/frame/Section.tsx"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,mEAAmE;AACnE,sEAAsE;AACtE,2DAA2D;AAE3D,YAAY,CAAC;;AAEb,OAAO,EAAE,QAAQ,EAAkB,MAAM,OAAO,CAAC;AACjD,OAAO,EAAE,EAAE,EAAE,MAAM,mBAAmB,CAAC;AAWvC,MAAM,UAAU,OAAO,CAAC,EACtB,KAAK,EACL,MAAM,EACN,WAAW,GAAG,IAAI,EAClB,QAAQ,EACR,SAAS,GACH;IACN,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,GAAG,QAAQ,CAAC,WAAW,CAAC,CAAC;IAC9C,OAAO,CACL,eAAK,SAAS,EAAE,EAAE,CAAC,wBAAwB,EAAE,SAAS,CAAC,aACrD,eAAK,SAAS,EAAC,wDAAwD,aACrE,kBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,4FAA4F,EACtG,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,mBAClB,IAAI,aAEnB,cACE,KAAK,EAAC,GAAG,EACT,MAAM,EAAC,GAAG,EACV,OAAO,EAAC,SAAS,iBACL,MAAM,EAClB,SAAS,EAAE,EAAE,CACX,4CAA4C,EAC5C,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,CACxB,YAED,eAAM,CAAC,EAAC,kBAAkB,EAAC,IAAI,EAAC,cAAc,GAAG,GAC7C,EACL,KAAK,IACC,EACR,MAAM,IAAI,cAAK,SAAS,EAAC,yBAAyB,YAAE,MAAM,GAAO,IAC9D,EACL,IAAI,IAAI,QAAQ,IAAI,cAAK,SAAS,EAAC,aAAa,YAAE,QAAQ,GAAO,IAC9D,CACP,CAAC;AACJ,CAAC;AAED;;;;;;2CAM2C;AAC3C,MAAM,UAAU,QAAQ,CAAC,EACvB,KAAK,EACL,QAAQ,GAIT;IACC,OAAO,CACL,eAAK,SAAS,EAAC,6BAA6B,aAC1C,eAAM,SAAS,EAAC,0DAA0D,YACvE,KAAK,GACD,EAEP,cAAK,SAAS,EAAC,wCAAwC,YAAE,QAAQ,GAAO,IACpE,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare function ZoomControl({ readout, onZoomOut, onZoomIn, onFit, fitLabel, }: {
2
+ /** e.g. "54%" or "56px/s". Clicking it fits. */
3
+ readout: string;
4
+ onZoomOut: () => void;
5
+ onZoomIn: () => void;
6
+ onFit: () => void;
7
+ fitLabel?: string;
8
+ }): import("react").JSX.Element;
9
+ //# sourceMappingURL=ZoomControl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZoomControl.d.ts","sourceRoot":"","sources":["../../src/frame/ZoomControl.tsx"],"names":[],"mappings":"AASA,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,SAAS,EACT,QAAQ,EACR,KAAK,EACL,QAAyB,GAC1B,EAAE;IACD,gDAAgD;IAChD,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,+BA8CA"}
@@ -0,0 +1,15 @@
1
+ // ZoomControl — the editor's one zoom cluster (stage corner + timeline
2
+ // transport), styled to the Figma direction in design/refs: no card
3
+ // pill, just quiet flat buttons with thin-stroke glyphs and a tabular
4
+ // readout, hairline divider before fit. The readout doubles as a fit
5
+ // button (the legacy affordance). Hosts that float it over content
6
+ // (the stage) supply their own surface.
7
+ 'use client';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ export function ZoomControl({ readout, onZoomOut, onZoomIn, onFit, fitLabel = 'Fit to width', }) {
10
+ return (_jsxs("div", { className: "flex items-center text-muted-foreground select-none", children: [_jsx(ZoomButton, { label: "Zoom out", onClick: onZoomOut, children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", "aria-hidden": "true", children: _jsx("path", { d: "M3.5 7 H10.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }) }) }), _jsx("button", { type: "button", className: "px-1 h-6 flex items-center whitespace-nowrap rounded-sm text-[11px] tabular-nums text-foreground/90 hover:text-foreground hover:bg-secondary transition-colors", onClick: onFit, title: fitLabel, children: readout }), _jsx(ZoomButton, { label: "Zoom in", onClick: onZoomIn, children: _jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", "aria-hidden": "true", children: _jsx("path", { d: "M7 3.5 V10.5 M3.5 7 H10.5", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round" }) }) }), _jsx("span", { className: "w-px h-3 bg-border mx-1", "aria-hidden": true }), _jsx(ZoomButton, { label: fitLabel, onClick: onFit, children: _jsx("svg", { width: "13", height: "13", viewBox: "0 0 14 14", "aria-hidden": "true", children: _jsx("path", { d: "M2 5 L2 2 L5 2 M9 2 L12 2 L12 5 M12 9 L12 12 L9 12 M5 12 L2 12 L2 9", fill: "none", stroke: "currentColor", strokeWidth: "1.2", strokeLinecap: "round", strokeLinejoin: "round" }) }) })] }));
11
+ }
12
+ function ZoomButton({ label, onClick, children, }) {
13
+ return (_jsx("button", { type: "button", className: "w-6 h-6 grid place-items-center rounded-sm hover:text-foreground hover:bg-secondary transition-colors", onClick: onClick, title: label, "aria-label": label, children: children }));
14
+ }
15
+ //# sourceMappingURL=ZoomControl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ZoomControl.js","sourceRoot":"","sources":["../../src/frame/ZoomControl.tsx"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,oEAAoE;AACpE,sEAAsE;AACtE,qEAAqE;AACrE,mEAAmE;AACnE,wCAAwC;AAExC,YAAY,CAAC;;AAEb,MAAM,UAAU,WAAW,CAAC,EAC1B,OAAO,EACP,SAAS,EACT,QAAQ,EACR,KAAK,EACL,QAAQ,GAAG,cAAc,GAQ1B;IACC,OAAO,CACL,eAAK,SAAS,EAAC,qDAAqD,aAClE,KAAC,UAAU,IAAC,KAAK,EAAC,UAAU,EAAC,OAAO,EAAE,SAAS,YAC7C,cAAK,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAC,OAAO,EAAC,WAAW,iBAAa,MAAM,YAChE,eACE,CAAC,EAAC,cAAc,EAChB,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,EACjB,aAAa,EAAC,OAAO,GACrB,GACE,GACK,EACb,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,gKAAgK,EAC1K,OAAO,EAAE,KAAK,EACd,KAAK,EAAE,QAAQ,YAEd,OAAO,GACD,EACT,KAAC,UAAU,IAAC,KAAK,EAAC,SAAS,EAAC,OAAO,EAAE,QAAQ,YAC3C,cAAK,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAC,OAAO,EAAC,WAAW,iBAAa,MAAM,YAChE,eACE,CAAC,EAAC,2BAA2B,EAC7B,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,EACjB,aAAa,EAAC,OAAO,GACrB,GACE,GACK,EACb,eAAM,SAAS,EAAC,yBAAyB,wBAAe,EACxD,KAAC,UAAU,IAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,YACzC,cAAK,KAAK,EAAC,IAAI,EAAC,MAAM,EAAC,IAAI,EAAC,OAAO,EAAC,WAAW,iBAAa,MAAM,YAChE,eACE,CAAC,EAAC,qEAAqE,EACvE,IAAI,EAAC,MAAM,EACX,MAAM,EAAC,cAAc,EACrB,WAAW,EAAC,KAAK,EACjB,aAAa,EAAC,OAAO,EACrB,cAAc,EAAC,OAAO,GACtB,GACE,GACK,IACT,CACP,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,EAClB,KAAK,EACL,OAAO,EACP,QAAQ,GAKT;IACC,OAAO,CACL,iBACE,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,uGAAuG,EACjH,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,KAAK,gBACA,KAAK,YAEhB,QAAQ,GACF,CACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,9 @@
1
+ export { Editor } from './Editor.js';
2
+ export { ExportDialog } from './ExportDialog.js';
3
+ export type { EditorProps, ExportFormatOption, ExportRequest, ExportResolution, ExportQuality, } from './types.js';
4
+ export { EDITOR_COMMANDS } from './commands.js';
5
+ export type { EditorCommand } from './commands.js';
6
+ export { Section, FieldRow } from './frame/Section.js';
7
+ export { ADVANCED_CONFIGURATION, BASIC_CONFIGURATION, useEditor, useEditorStore, createLocalAssetStore, createMemoryAssetStore, } from '@clipkit/editor-core';
8
+ export type { EditorConfiguration, EditorViewsConfiguration, AssetStore, ClipkitAsset, AssetKind, } from '@clipkit/editor-core';
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,gBAAgB,EAChB,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,YAAY,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EACV,mBAAmB,EACnB,wBAAwB,EACxB,UAAU,EACV,YAAY,EACZ,SAAS,GACV,MAAM,sBAAsB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ // Public entry point for @clipkit/editor — the configurable editor.
2
+ //
3
+ // import { Editor, BASIC_CONFIGURATION } from '@clipkit/editor';
4
+ // <Editor initialSource={source} configuration={BASIC_CONFIGURATION} />
5
+ //
6
+ // The data layer (store, hooks, registry, configurations) comes from
7
+ // @clipkit/editor-core and is re-exported so embedders need one import.
8
+ export { Editor } from './Editor.js';
9
+ export { ExportDialog } from './ExportDialog.js';
10
+ export { EDITOR_COMMANDS } from './commands.js';
11
+ export { Section, FieldRow } from './frame/Section.js';
12
+ export { ADVANCED_CONFIGURATION, BASIC_CONFIGURATION, useEditor, useEditorStore, createLocalAssetStore, createMemoryAssetStore, } from '@clipkit/editor-core';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,EAAE;AACF,mEAAmE;AACnE,0EAA0E;AAC1E,EAAE;AACF,qEAAqE;AACrE,wEAAwE;AAExE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AASjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAEvD,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,SAAS,EACT,cAAc,EACd,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { Element, Source } from '@clipkit/protocol';
2
+ import { projectElementQuad, unprojectToPlane, elementDepthZ, type Pt } from '@clipkit/runtime';
3
+ export { projectElementQuad, unprojectToPlane, elementDepthZ, type Pt };
4
+ /** True when the editor should use camera-projected gizmos: a camera is
5
+ * present and the stage is in camera (not flat) view. */
6
+ export declare function cameraGizmosActive(source: Source, stageView: string): boolean;
7
+ /** Point-in-convex-quad test (quad corners in order). */
8
+ export declare function pointInQuad(p: Pt, quad: readonly Pt[]): boolean;
9
+ /**
10
+ * Camera-aware hit test: the front-most (nearest in camera depth) active,
11
+ * visual element whose PROJECTED quad contains the canvas-pixel point.
12
+ * Mirrors the runtime's depth ordering so clicking selects what's on top.
13
+ */
14
+ export declare function cameraHitTest(source: Source, point: Pt, playhead: number, sourceDuration: number): Element | null;
15
+ //# sourceMappingURL=camera-gizmo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera-gizmo.d.ts","sourceRoot":"","sources":["../../src/lib/camera-gizmo.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,EACb,KAAK,EAAE,EACR,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,CAAC;AAExE;0DAC0D;AAC1D,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAE7E;AAED,yDAAyD;AACzD,wBAAgB,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,OAAO,CAa/D;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,EAAE,EACT,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACrB,OAAO,GAAG,IAAI,CAehB"}
@@ -0,0 +1,57 @@
1
+ // Camera-view gizmo helpers (CAMERA-PLAN item 6). Thin wrappers over the
2
+ // runtime's render-consistent projection (`projectElementQuad` /
3
+ // `unprojectToPlane`) so the editor's selection box, hit-testing, and
4
+ // move drag line up exactly with what's drawn under the scene camera.
5
+ //
6
+ // All "canvas" points are in CANVAS pixels (the projected image space the
7
+ // runtime renders into). The Stage maps canvas ⇄ screen with pan/zoom.
8
+ import { projectElementQuad, unprojectToPlane, elementDepthZ, } from '@clipkit/runtime';
9
+ import { isVisualElement, isElementActive } from '@clipkit/editor-core';
10
+ export { projectElementQuad, unprojectToPlane, elementDepthZ };
11
+ /** True when the editor should use camera-projected gizmos: a camera is
12
+ * present and the stage is in camera (not flat) view. */
13
+ export function cameraGizmosActive(source, stageView) {
14
+ return stageView === 'camera' && !!source.camera;
15
+ }
16
+ /** Point-in-convex-quad test (quad corners in order). */
17
+ export function pointInQuad(p, quad) {
18
+ let sign = 0;
19
+ for (let i = 0; i < quad.length; i++) {
20
+ const a = quad[i];
21
+ const b = quad[(i + 1) % quad.length];
22
+ const cross = (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x);
23
+ if (cross !== 0) {
24
+ const s = cross > 0 ? 1 : -1;
25
+ if (sign === 0)
26
+ sign = s;
27
+ else if (s !== sign)
28
+ return false;
29
+ }
30
+ }
31
+ return true;
32
+ }
33
+ /**
34
+ * Camera-aware hit test: the front-most (nearest in camera depth) active,
35
+ * visual element whose PROJECTED quad contains the canvas-pixel point.
36
+ * Mirrors the runtime's depth ordering so clicking selects what's on top.
37
+ */
38
+ export function cameraHitTest(source, point, playhead, sourceDuration) {
39
+ let best = null;
40
+ let bestZ = -Infinity;
41
+ for (const el of source.elements) {
42
+ if (!isVisualElement(el))
43
+ continue;
44
+ if (!isElementActive(el, playhead, sourceDuration))
45
+ continue;
46
+ const quad = projectElementQuad(source, el, playhead);
47
+ if (!quad || !pointInQuad(point, quad))
48
+ continue;
49
+ const z = elementDepthZ(source, el, playhead);
50
+ if (z >= bestZ) {
51
+ bestZ = z;
52
+ best = el;
53
+ }
54
+ }
55
+ return best;
56
+ }
57
+ //# sourceMappingURL=camera-gizmo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera-gizmo.js","sourceRoot":"","sources":["../../src/lib/camera-gizmo.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,iEAAiE;AACjE,sEAAsE;AACtE,sEAAsE;AACtE,EAAE;AACF,0EAA0E;AAC1E,uEAAuE;AAGvE,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,aAAa,GAEd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAExE,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,aAAa,EAAW,CAAC;AAExE;0DAC0D;AAC1D,MAAM,UAAU,kBAAkB,CAAC,MAAc,EAAE,SAAiB;IAClE,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC;AACnD,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,WAAW,CAAC,CAAK,EAAE,IAAmB;IACpD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAE,CAAC;QACnB,MAAM,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,CAAE,CAAC;QACvC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACpE,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,MAAM,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YAC7B,IAAI,IAAI,KAAK,CAAC;gBAAE,IAAI,GAAG,CAAC,CAAC;iBACpB,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;QACpC,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAC3B,MAAc,EACd,KAAS,EACT,QAAgB,EAChB,cAAsB;IAEtB,IAAI,IAAI,GAAmB,IAAI,CAAC;IAChC,IAAI,KAAK,GAAG,CAAC,QAAQ,CAAC;IACtB,KAAK,MAAM,EAAE,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACjC,IAAI,CAAC,eAAe,CAAC,EAAE,CAAC;YAAE,SAAS;QACnC,IAAI,CAAC,eAAe,CAAC,EAAE,EAAE,QAAQ,EAAE,cAAc,CAAC;YAAE,SAAS;QAC7D,MAAM,IAAI,GAAG,kBAAkB,CAAC,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC;YAAE,SAAS;QACjD,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;YACf,KAAK,GAAG,CAAC,CAAC;YACV,IAAI,GAAG,EAAE,CAAC;QACZ,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { Camera } from '@clipkit/protocol';
2
+ import { resolveCameraPose, type CameraPose } from '@clipkit/runtime';
3
+ export { resolveCameraPose, type CameraPose };
4
+ type PoseField = 'perspective' | 'x' | 'y' | 'z' | 'x_rotation' | 'y_rotation' | 'z_rotation';
5
+ /**
6
+ * Set a camera pose field to `value` at `time`. Animated field → replace
7
+ * the keyframe at the playhead (or insert one, keeping the curve sorted);
8
+ * static field → set the scalar. Returns a new Camera.
9
+ */
10
+ export declare function setCameraFieldAt(camera: Camera, field: PoseField, value: number, time: number): Camera;
11
+ export interface Vec3 {
12
+ x: number;
13
+ y: number;
14
+ z: number;
15
+ }
16
+ /**
17
+ * Orientation (Euler degrees) that aims the camera at `target` from
18
+ * `eye`, about `origin`. Derived so the camera's rotation maps +z onto
19
+ * the target direction `u = target − origin − eye` (verified: the target
20
+ * then projects to screen center). Roll (z_rotation) stays 0.
21
+ */
22
+ export declare function lookAtAngles(target: Vec3, origin: {
23
+ x: number;
24
+ y: number;
25
+ }, eye: Vec3): {
26
+ x_rotation: number;
27
+ y_rotation: number;
28
+ };
29
+ /** Set the camera's orientation to look at `target` from `eye` (keyframe-
30
+ * aware at `time`). Used by the one-shot "Look at" and the lock track. */
31
+ export declare function cameraLookAt(camera: Camera, eye: Vec3, target: Vec3, origin: {
32
+ x: number;
33
+ y: number;
34
+ }, time: number): Camera;
35
+ export type CameraDragMode = 'orbit' | 'dolly' | 'pan';
36
+ /**
37
+ * Apply one drag delta to the camera from a captured base pose. `dxRot`/
38
+ * `dyRot` are raw screen px (orbit, in degrees via sensitivity); `dxSrc`/
39
+ * `dySrc` are source px (dolly/pan). Only the fields the mode touches are
40
+ * written, so unrelated curves are never clobbered.
41
+ */
42
+ export declare function cameraAfterDrag(camera: Camera, base: CameraPose, mode: CameraDragMode, dxPx: number, dyPx: number, dxSrc: number, dySrc: number, time: number): Camera;
43
+ //# sourceMappingURL=camera-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera-tool.d.ts","sourceRoot":"","sources":["../../src/lib/camera-tool.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAY,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEtE,OAAO,EAAE,iBAAiB,EAAE,KAAK,UAAU,EAAE,CAAC;AAI9C,KAAK,SAAS,GAAG,aAAa,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,YAAY,GAAG,YAAY,GAAG,YAAY,CAAC;AAE9F;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,MAAM,CAaR;AAED,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,MAAM,EAAE,IAAI,EACZ,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAChC,GAAG,EAAE,IAAI,GACR;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAS5C;AAED;2EAC2E;AAC3E,wBAAgB,YAAY,CAC1B,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,IAAI,EACT,MAAM,EAAE,IAAI,EACZ,MAAM,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EAChC,IAAI,EAAE,MAAM,GACX,MAAM,CAKR;AAED,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,OAAO,GAAG,KAAK,CAAC;AAMvD;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,GACX,MAAM,CAcR"}
@@ -0,0 +1,80 @@
1
+ // Stage camera tools (CAMERA-PLAN item 7). Keyframe-aware writes to the
2
+ // scene camera's pose: when a field is animated (inline Keyframe[]) the
3
+ // edit sets/inserts the keyframe at the playhead (auto-keyframe — build a
4
+ // fly-through by posing at different times); when it's static it edits
5
+ // the scalar. Pure data; the Stage drives the drag and persists via
6
+ // patchSource.
7
+ import { resolveCameraPose } from '@clipkit/runtime';
8
+ export { resolveCameraPose };
9
+ const KF_EPS = 1e-3;
10
+ /**
11
+ * Set a camera pose field to `value` at `time`. Animated field → replace
12
+ * the keyframe at the playhead (or insert one, keeping the curve sorted);
13
+ * static field → set the scalar. Returns a new Camera.
14
+ */
15
+ export function setCameraFieldAt(camera, field, value, time) {
16
+ const cur = camera[field];
17
+ if (Array.isArray(cur)) {
18
+ const arr = cur.map((k) => ({ ...k }));
19
+ const idx = arr.findIndex((k) => Math.abs(Number(k.time ?? 0) - time) < KF_EPS);
20
+ if (idx >= 0)
21
+ arr[idx] = { ...arr[idx], value };
22
+ else {
23
+ arr.push({ time, value });
24
+ arr.sort((a, b) => Number(a.time ?? 0) - Number(b.time ?? 0));
25
+ }
26
+ return { ...camera, [field]: arr };
27
+ }
28
+ return { ...camera, [field]: value };
29
+ }
30
+ /**
31
+ * Orientation (Euler degrees) that aims the camera at `target` from
32
+ * `eye`, about `origin`. Derived so the camera's rotation maps +z onto
33
+ * the target direction `u = target − origin − eye` (verified: the target
34
+ * then projects to screen center). Roll (z_rotation) stays 0.
35
+ */
36
+ export function lookAtAngles(target, origin, eye) {
37
+ const ux = target.x - origin.x - eye.x;
38
+ const uy = target.y - origin.y - eye.y;
39
+ const uz = target.z - eye.z;
40
+ const DEG = 180 / Math.PI;
41
+ return {
42
+ y_rotation: Math.atan2(ux, uz) * DEG,
43
+ x_rotation: Math.atan2(-uy, Math.hypot(ux, uz)) * DEG,
44
+ };
45
+ }
46
+ /** Set the camera's orientation to look at `target` from `eye` (keyframe-
47
+ * aware at `time`). Used by the one-shot "Look at" and the lock track. */
48
+ export function cameraLookAt(camera, eye, target, origin, time) {
49
+ const a = lookAtAngles(target, origin, eye);
50
+ let c = setCameraFieldAt(camera, 'y_rotation', a.y_rotation, time);
51
+ c = setCameraFieldAt(c, 'x_rotation', a.x_rotation, time);
52
+ return c;
53
+ }
54
+ /** Drag sensitivities. Orbit is degrees/px; dolly/pan are in source px
55
+ * (the caller pre-divides screen delta by zoom). */
56
+ const ORBIT_DEG_PER_PX = 0.3;
57
+ /**
58
+ * Apply one drag delta to the camera from a captured base pose. `dxRot`/
59
+ * `dyRot` are raw screen px (orbit, in degrees via sensitivity); `dxSrc`/
60
+ * `dySrc` are source px (dolly/pan). Only the fields the mode touches are
61
+ * written, so unrelated curves are never clobbered.
62
+ */
63
+ export function cameraAfterDrag(camera, base, mode, dxPx, dyPx, dxSrc, dySrc, time) {
64
+ let c = camera;
65
+ if (mode === 'orbit') {
66
+ c = setCameraFieldAt(c, 'y_rotation', base.y_rotation + dxPx * ORBIT_DEG_PER_PX, time);
67
+ c = setCameraFieldAt(c, 'x_rotation', base.x_rotation - dyPx * ORBIT_DEG_PER_PX, time);
68
+ }
69
+ else if (mode === 'dolly') {
70
+ // Drag up = push in (z toward viewer increases).
71
+ c = setCameraFieldAt(c, 'z', base.z - dySrc, time);
72
+ }
73
+ else {
74
+ // Pan/truck: move the eye opposite the drag so content follows the cursor.
75
+ c = setCameraFieldAt(c, 'x', base.x - dxSrc, time);
76
+ c = setCameraFieldAt(c, 'y', base.y - dySrc, time);
77
+ }
78
+ return c;
79
+ }
80
+ //# sourceMappingURL=camera-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera-tool.js","sourceRoot":"","sources":["../../src/lib/camera-tool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { CaptionWord, Element } from '@clipkit/protocol';
2
+ import { type ToCaptionSegmentsOptions } from '@clipkit/speech-to-text/caption';
3
+ /** The shared style fields of a caption, to clone onto each segment. */
4
+ export declare function captionStyleTemplate(caption: Element): Record<string, unknown>;
5
+ /** Build caption ELEMENTS from phrase segments. `baseTime` is added to each
6
+ * segment's start (the media/caption start on the timeline). */
7
+ export declare function buildSegmentCaptions(template: Record<string, unknown>, baseTime: number, segments: {
8
+ start: number;
9
+ words: CaptionWord[];
10
+ }[]): Element[];
11
+ /**
12
+ * Split ONE caption element into N phrase-sized caption segments (by words-per-
13
+ * segment / pauses / duration), each keeping the original's style. Returns the
14
+ * new caption elements (the caller replaces the original with these).
15
+ */
16
+ export declare function splitCaptionIntoSegments(caption: Element, options?: ToCaptionSegmentsOptions): Element[];
17
+ //# sourceMappingURL=caption-segments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caption-segments.d.ts","sourceRoot":"","sources":["../../src/lib/caption-segments.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EAAgB,KAAK,wBAAwB,EAAE,MAAM,iCAAiC,CAAC;AAW9F,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAK9E;AAED;iEACiE;AACjE,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,WAAW,EAAE,CAAA;CAAE,EAAE,GAClD,OAAO,EAAE,CAUX;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,GAAE,wBAA6B,GAAG,OAAO,EAAE,CAS5G"}
@@ -0,0 +1,50 @@
1
+ // Caption segmentation helpers (editor side) — turn one caption's words, or a
2
+ // transcript's segments, into N phrase-sized caption ELEMENTS sharing a style.
3
+ // One caption element = one segment; this is how a long caption becomes many.
4
+ import { segmentWords } from '@clipkit/speech-to-text/caption';
5
+ // Style fields copied from the template caption onto every segment (everything
6
+ // except the per-segment text/timing).
7
+ const STYLE_KEYS = ['x', 'y', 'x_anchor', 'y_anchor', 'width', 'height', 'rotation', 'scale', 'opacity', 'font_family', 'font_size', 'font_weight', 'fill_color', 'text_align', 'style', 'highlight_color', 'line_height', 'letter_spacing', 'track'];
8
+ const num = (v, d) => (typeof v === 'number' && Number.isFinite(v) ? v : d);
9
+ const round3 = (n) => Math.round(n * 1000) / 1000;
10
+ let segCounter = 0;
11
+ const uid = () => `cap-${(segCounter++).toString(36)}-${Date.now().toString(36).slice(-4)}`;
12
+ /** The shared style fields of a caption, to clone onto each segment. */
13
+ export function captionStyleTemplate(caption) {
14
+ const t = {};
15
+ const rec = caption;
16
+ for (const k of STYLE_KEYS)
17
+ if (rec[k] !== undefined)
18
+ t[k] = rec[k];
19
+ return t;
20
+ }
21
+ /** Build caption ELEMENTS from phrase segments. `baseTime` is added to each
22
+ * segment's start (the media/caption start on the timeline). */
23
+ export function buildSegmentCaptions(template, baseTime, segments) {
24
+ return segments.map((seg) => ({
25
+ ...template,
26
+ id: uid(),
27
+ type: 'caption',
28
+ name: 'Captions',
29
+ time: round3(baseTime + seg.start),
30
+ duration: 'auto',
31
+ words: seg.words,
32
+ }));
33
+ }
34
+ /**
35
+ * Split ONE caption element into N phrase-sized caption segments (by words-per-
36
+ * segment / pauses / duration), each keeping the original's style. Returns the
37
+ * new caption elements (the caller replaces the original with these).
38
+ */
39
+ export function splitCaptionIntoSegments(caption, options = {}) {
40
+ const words = (caption.words ?? []).filter((w) => w.text.trim().length > 0);
41
+ if (words.length === 0)
42
+ return [caption];
43
+ const groups = segmentWords(words, options);
44
+ const segments = groups.map((g) => {
45
+ const base = g[0].start;
46
+ return { start: base, words: g.map((w) => ({ text: w.text.trim(), start: Math.max(0, w.start - base), end: Math.max(0, w.end - base) })) };
47
+ });
48
+ return buildSegmentCaptions(captionStyleTemplate(caption), num(caption.time, 0), segments);
49
+ }
50
+ //# sourceMappingURL=caption-segments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"caption-segments.js","sourceRoot":"","sources":["../../src/lib/caption-segments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Element } from '@clipkit/protocol';
2
+ export interface GroupResult {
3
+ elements: Element[];
4
+ groupId: string;
5
+ }
6
+ /**
7
+ * Wrap the top-level elements named by `ids` into a new group with id `newId`.
8
+ * Returns the new top-level elements array + the group's id, or null if fewer
9
+ * than two of the ids are top-level elements.
10
+ */
11
+ export declare function groupElements(elements: readonly Element[], ids: readonly string[], newId: string): GroupResult | null;
12
+ //# sourceMappingURL=group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group.d.ts","sourceRoot":"","sources":["../../src/lib/group.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAgB,MAAM,mBAAmB,CAAC;AAK/D,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,EAAE,GAAG,EAAE,SAAS,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CA0CrH"}
@@ -0,0 +1,61 @@
1
+ // Group — wrap the selected TOP-LEVEL elements into a new group element, the
2
+ // inverse of ungroup (lib/ungroup.ts).
3
+ //
4
+ // The group takes identity spatial transform (children keep their absolute
5
+ // coords, so the frame renders identically) and a time offset = the earliest
6
+ // child's start, with each child's `time` made relative to it. The group's
7
+ // `duration` spans the children when they all have numeric durations. Insertion
8
+ // happens at the position of the first selected element.
9
+ const numberOr = (v, d) => (typeof v === 'number' && Number.isFinite(v) ? v : d);
10
+ const round3 = (n) => Math.round(n * 1000) / 1000;
11
+ /**
12
+ * Wrap the top-level elements named by `ids` into a new group with id `newId`.
13
+ * Returns the new top-level elements array + the group's id, or null if fewer
14
+ * than two of the ids are top-level elements.
15
+ */
16
+ export function groupElements(elements, ids, newId) {
17
+ const idSet = new Set(ids);
18
+ const indices = [];
19
+ elements.forEach((e, i) => {
20
+ if (typeof e.id === 'string' && idSet.has(e.id))
21
+ indices.push(i);
22
+ });
23
+ if (indices.length < 2)
24
+ return null;
25
+ const selected = indices.map((i) => elements[i]);
26
+ const groupTime = Math.min(...selected.map((e) => numberOr(e.time, 0)));
27
+ let maxEnd = 0;
28
+ let allNumericDuration = true;
29
+ const children = selected.map((e) => {
30
+ const rel = numberOr(e.time, 0) - groupTime;
31
+ const dur = e.duration;
32
+ if (typeof dur === 'number')
33
+ maxEnd = Math.max(maxEnd, rel + dur);
34
+ else
35
+ allNumericDuration = false;
36
+ return { ...e, time: round3(rel) };
37
+ });
38
+ const group = {
39
+ id: newId,
40
+ name: 'Group',
41
+ type: 'group',
42
+ time: round3(groupTime),
43
+ layer: numberOr(selected[0].layer, 1),
44
+ elements: children,
45
+ };
46
+ if (allNumericDuration && maxEnd > 0)
47
+ group.duration = round3(maxEnd);
48
+ const firstIdx = indices[0];
49
+ const out = [];
50
+ elements.forEach((e, i) => {
51
+ if (typeof e.id === 'string' && idSet.has(e.id)) {
52
+ if (i === firstIdx)
53
+ out.push(group);
54
+ }
55
+ else {
56
+ out.push(e);
57
+ }
58
+ });
59
+ return { elements: out, groupId: newId };
60
+ }
61
+ //# sourceMappingURL=group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group.js","sourceRoot":"","sources":["../../src/lib/group.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { Element, Keyframe, KeyframeAnimation } from '@clipkit/protocol';
2
+ /** Find an element by id, descending into groups. */
3
+ export declare function findElementById(elements: readonly Element[], id: string): Element | null;
4
+ /** "On a keyframe" tolerance, seconds. */
5
+ export declare const KF_EPS = 0.05;
6
+ export declare const kfTime: (k: Keyframe) => number;
7
+ /** Is there a keyframe within KF_EPS of this element-local time? */
8
+ export declare function isOnKeyframe(anim: KeyframeAnimation, local: number): boolean;
9
+ /**
10
+ * Sample the animation's value at an element-local time via the
11
+ * normative evaluator (scalars exact; arrays componentwise — position
12
+ * tangent curves are refined in the curve editor).
13
+ */
14
+ export declare function sampleAnimation(anim: KeyframeAnimation, local: number): Keyframe['value'];
15
+ /**
16
+ * Toggle a keyframe at an element-local time on animation `animIndex`.
17
+ * On a keyframe → splice it (removing the last keyframe removes the
18
+ * whole entry); otherwise → insert one sampled from the curve.
19
+ * Returns the next keyframe_animations array, or undefined when the
20
+ * last entry was removed (byte-clean documents).
21
+ */
22
+ /**
23
+ * Write a VALUE into the keyframe at an element-local time: replaces
24
+ * the value when the playhead sits on a keyframe, otherwise inserts a
25
+ * new keyframe there (AE-style auto-key for animated properties).
26
+ */
27
+ export declare function setKeyframeValueAt(anims: readonly KeyframeAnimation[], animIndex: number, local: number, value: Keyframe['value']): KeyframeAnimation[];
28
+ export declare function toggleKeyframeAt(anims: readonly KeyframeAnimation[], animIndex: number, local: number): KeyframeAnimation[] | undefined;
29
+ //# sourceMappingURL=keyframes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyframes.d.ts","sourceRoot":"","sources":["../../src/lib/keyframes.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,qDAAqD;AACrD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,SAAS,OAAO,EAAE,EAC5B,EAAE,EAAE,MAAM,GACT,OAAO,GAAG,IAAI,CAShB;AAED,0CAA0C;AAC1C,eAAO,MAAM,MAAM,OAAO,CAAC;AAE3B,eAAO,MAAM,MAAM,GAAI,GAAG,QAAQ,KAAG,MACkC,CAAC;AAIxE,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,iBAAiB,EACvB,KAAK,EAAE,MAAM,GACZ,QAAQ,CAAC,OAAO,CAAC,CAoBnB;AAED;;;;;;GAMG;AACH;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,GACvB,iBAAiB,EAAE,CAWrB;AAED,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,SAAS,iBAAiB,EAAE,EACnC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,GACZ,iBAAiB,EAAE,GAAG,SAAS,CAgBjC"}
@@ -0,0 +1,92 @@
1
+ // Shared keyframe-at-playhead logic (inspector diamond + timeline
2
+ // lane rows). All edits are literal keyframe_animations writes;
3
+ // insertion samples through the runtime's NORMATIVE applyEasing so a
4
+ // new keyframe sits exactly on the existing curve.
5
+ import { applyEasing } from '@clipkit/runtime';
6
+ /** Find an element by id, descending into groups. */
7
+ export function findElementById(elements, id) {
8
+ for (const el of elements) {
9
+ if (el.id === id)
10
+ return el;
11
+ if (el.type === 'group') {
12
+ const nested = findElementById(el.elements, id);
13
+ if (nested)
14
+ return nested;
15
+ }
16
+ }
17
+ return null;
18
+ }
19
+ /** "On a keyframe" tolerance, seconds. */
20
+ export const KF_EPS = 0.05;
21
+ export const kfTime = (k) => typeof k.time === 'number' ? k.time : parseFloat(String(k.time)) || 0;
22
+ const round3 = (v) => Math.round(v * 1000) / 1000;
23
+ /** Is there a keyframe within KF_EPS of this element-local time? */
24
+ export function isOnKeyframe(anim, local) {
25
+ return anim.keyframes.some((k) => Math.abs(kfTime(k) - local) <= KF_EPS);
26
+ }
27
+ /**
28
+ * Sample the animation's value at an element-local time via the
29
+ * normative evaluator (scalars exact; arrays componentwise — position
30
+ * tangent curves are refined in the curve editor).
31
+ */
32
+ export function sampleAnimation(anim, local) {
33
+ const sorted = [...anim.keyframes].sort((a, b) => kfTime(a) - kfTime(b));
34
+ if (sorted.length === 0)
35
+ return 0;
36
+ if (local <= kfTime(sorted[0]))
37
+ return sorted[0].value;
38
+ const last = sorted[sorted.length - 1];
39
+ if (local >= kfTime(last))
40
+ return last.value;
41
+ const bi = sorted.findIndex((k) => kfTime(k) > local);
42
+ const a = sorted[bi - 1];
43
+ const b = sorted[bi];
44
+ const p = (local - kfTime(a)) / (kfTime(b) - kfTime(a));
45
+ if (typeof a.value === 'number' && typeof b.value === 'number') {
46
+ return round3(a.value + (b.value - a.value) * applyEasing(b.easing, p));
47
+ }
48
+ if (Array.isArray(a.value) && Array.isArray(b.value)) {
49
+ const eased = applyEasing(b.easing, p);
50
+ return a.value.map((av, i) => round3(av + ((b.value[i] ?? av) - av) * eased));
51
+ }
52
+ return a.value;
53
+ }
54
+ /**
55
+ * Toggle a keyframe at an element-local time on animation `animIndex`.
56
+ * On a keyframe → splice it (removing the last keyframe removes the
57
+ * whole entry); otherwise → insert one sampled from the curve.
58
+ * Returns the next keyframe_animations array, or undefined when the
59
+ * last entry was removed (byte-clean documents).
60
+ */
61
+ /**
62
+ * Write a VALUE into the keyframe at an element-local time: replaces
63
+ * the value when the playhead sits on a keyframe, otherwise inserts a
64
+ * new keyframe there (AE-style auto-key for animated properties).
65
+ */
66
+ export function setKeyframeValueAt(anims, animIndex, local, value) {
67
+ const anim = anims[animIndex];
68
+ if (!anim)
69
+ return [...anims];
70
+ const hit = anim.keyframes.findIndex((k) => Math.abs(kfTime(k) - local) <= KF_EPS);
71
+ const keyframes = hit >= 0
72
+ ? anim.keyframes.map((k, i) => (i === hit ? { ...k, value } : k))
73
+ : [...anim.keyframes, { time: round3(local), value }].sort((a, b) => kfTime(a) - kfTime(b));
74
+ return anims.map((a, i) => (i === animIndex ? { ...a, keyframes } : a));
75
+ }
76
+ export function toggleKeyframeAt(anims, animIndex, local) {
77
+ const anim = anims[animIndex];
78
+ if (!anim)
79
+ return [...anims];
80
+ const hit = anim.keyframes.findIndex((k) => Math.abs(kfTime(k) - local) <= KF_EPS);
81
+ if (hit >= 0) {
82
+ const remaining = anim.keyframes.filter((_, i) => i !== hit);
83
+ if (remaining.length === 0) {
84
+ const next = anims.filter((_, i) => i !== animIndex);
85
+ return next.length > 0 ? next : undefined;
86
+ }
87
+ return anims.map((a, i) => (i === animIndex ? { ...a, keyframes: remaining } : a));
88
+ }
89
+ const inserted = [...anim.keyframes, { time: round3(local), value: sampleAnimation(anim, local) }].sort((a, b) => kfTime(a) - kfTime(b));
90
+ return anims.map((a, i) => (i === animIndex ? { ...a, keyframes: inserted } : a));
91
+ }
92
+ //# sourceMappingURL=keyframes.js.map