@hyperframes/studio 0.8.55 → 0.8.56

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 (82) hide show
  1. package/dist/assets/{hyperframes-player-BjAGc2ID.js → hyperframes-player-k8LzMtXX.js} +1 -1
  2. package/dist/assets/{index-TcBZyzNA.js → index-BlkJ_Axe.js} +191 -191
  3. package/dist/assets/{index-DaNpNFJC.js → index-CxR7F2YQ.js} +1 -1
  4. package/dist/assets/{index-BEIRytvV.js → index-DFlILHJe.js} +1 -1
  5. package/dist/assets/{index-DrHQAuGO.css → index-ZmBtVrEm.css} +1 -1
  6. package/dist/index.d.ts +16 -8
  7. package/dist/index.html +2 -2
  8. package/dist/index.js +3111 -2998
  9. package/dist/index.js.map +1 -1
  10. package/dist/styles.css +1 -1
  11. package/package.json +7 -7
  12. package/src/App.tsx +14 -1
  13. package/src/components/EditorShell.readOnlyHostMount.test.tsx +116 -0
  14. package/src/components/EditorShell.selectionSync.test.tsx +57 -67
  15. package/src/components/EditorShell.tsx +59 -50
  16. package/src/components/EditorShellTestMocks.ts +46 -0
  17. package/src/components/editor/DomEditOverlay.tsx +19 -40
  18. package/src/components/editor/DomEditOverlayHover.test.tsx +1 -15
  19. package/src/components/editor/DomEditSelectionChrome.tsx +11 -9
  20. package/src/components/editor/PropertyPanel.deselect.test.tsx +71 -0
  21. package/src/components/editor/PropertyPanel.tsx +4 -3
  22. package/src/components/editor/SnapToolbar.test.tsx +1 -0
  23. package/src/components/editor/domEditOverlayTestMocks.ts +16 -0
  24. package/src/components/editor/previewReadOnly.overlay.test.tsx +260 -0
  25. package/src/components/editor/previewReadOnlyContext.tsx +59 -0
  26. package/src/components/editor/propertyPanelFlatLayoutSection.tsx +9 -0
  27. package/src/components/editor/propertyPanelFlatPrimitives.tsx +4 -1
  28. package/src/components/editor/propertyPanelInputCoverage.test.tsx +3 -13
  29. package/src/components/editor/useDomEditNudge.ts +9 -0
  30. package/src/components/editor/useDomEditOverlayGestures.ts +4 -0
  31. package/src/components/editor/useOffCanvasIndicators.ts +50 -0
  32. package/src/components/nle/PreviewOverlays.topologyLens.test.tsx +4 -35
  33. package/src/components/nle/PreviewOverlays.tsx +15 -8
  34. package/src/components/nle/PreviewOverlaysTestMocks.ts +40 -0
  35. package/src/components/nle/TimelinePane.tsx +5 -1
  36. package/src/components/nle/previewReadOnly.test.tsx +128 -0
  37. package/src/components/nle/useCanvasZOrderTimelineMirror.test.tsx +1 -1
  38. package/src/components/nle/useCanvasZOrderTimelineMirror.ts +3 -6
  39. package/src/components/nle/usePreviewBlockDrop.ts +3 -1
  40. package/src/hooks/appHotkeysDispatch.ts +16 -1
  41. package/src/hooks/newTweenTargetHooks.test.tsx +1 -0
  42. package/src/hooks/timelineAudioGroupCreate.ts +3 -4
  43. package/src/hooks/timelineTrackVisibility.ts +7 -8
  44. package/src/hooks/useAppHotkeys.test.ts +71 -3
  45. package/src/hooks/useAppHotkeys.textEditing.test.tsx +1 -0
  46. package/src/hooks/useAppHotkeys.ts +4 -0
  47. package/src/hooks/useDomEditCommits.test.tsx +1 -0
  48. package/src/hooks/useDomEditCommits.ts +3 -0
  49. package/src/hooks/useDomEditSession.test.tsx +1 -0
  50. package/src/hooks/useDomEditSession.ts +7 -4
  51. package/src/hooks/useDomEditWiring.ts +40 -40
  52. package/src/hooks/useDomGeometryCommits.test.tsx +85 -2
  53. package/src/hooks/useDomGeometryCommits.ts +26 -20
  54. package/src/hooks/useGestureCommit.test.tsx +29 -0
  55. package/src/hooks/useGestureCommit.ts +23 -0
  56. package/src/hooks/useInlineTextEdit.test.tsx +23 -2
  57. package/src/hooks/useInlineTextEdit.ts +11 -2
  58. package/src/hooks/useTimelineAssetDropOps.test.tsx +3 -4
  59. package/src/hooks/useTimelineAssetDropOps.ts +1 -6
  60. package/src/hooks/useTimelineEditing.test.tsx +10 -7
  61. package/src/hooks/useTimelineEditing.ts +2 -7
  62. package/src/player/components/Timeline.tsx +22 -23
  63. package/src/player/components/TimelineClip.test.tsx +52 -0
  64. package/src/player/components/TimelineClip.tsx +13 -2
  65. package/src/player/components/TimelineTrackHeader.test.tsx +26 -5
  66. package/src/player/components/TimelineTrackHeader.tsx +1 -1
  67. package/src/player/components/timelineMotionStyles.test.ts +3 -3
  68. package/src/player/components/timelineTheme.test.ts +4 -4
  69. package/src/player/components/timelineTheme.ts +34 -30
  70. package/src/player/components/timelineTrackDisplay.ts +1 -4
  71. package/src/player/components/useTimelineTrackDerivations.ts +2 -2
  72. package/src/player/hooks/timelineSyncHydration.test.ts +0 -18
  73. package/src/player/hooks/timelineSyncHydration.ts +0 -23
  74. package/src/player/hooks/useTimelineSyncCallbacks.ts +0 -3
  75. package/src/player/store/playerStore.ts +0 -4
  76. package/src/player/store/timelineElement.ts +2 -4
  77. package/src/player/store/timelineResetState.ts +0 -1
  78. package/src/styles/hex-baseline.json +3 -4
  79. package/src/styles/studio.css +7 -7
  80. package/src/styles/theme.css +29 -0
  81. package/src/player/hooks/useTimelineRowElements.test.ts +0 -28
  82. package/src/player/hooks/useTimelineRowElements.ts +0 -17
@@ -22,6 +22,8 @@ export interface TimelineTheme {
22
22
  tickMinor: string;
23
23
  clipBackground: string;
24
24
  clipBackgroundActive: string;
25
+ clipBackgroundHover: string;
26
+ clipBackgroundDragging: string;
25
27
  clipBorder: string;
26
28
  clipBorderHover: string;
27
29
  clipBorderActive: string;
@@ -36,43 +38,45 @@ export interface TimelineTheme {
36
38
  }
37
39
 
38
40
  const TRACK_STYLE: TimelineTrackStyle = {
39
- clip: "rgba(255,255,255,0.055)",
40
- clipActive: "rgba(60,230,172,0.16)",
41
- accent: "#3CE6AC",
42
- label: "rgba(255,255,255,0.5)",
41
+ clip: "var(--timeline-track-clip-fill)",
42
+ clipActive: "var(--timeline-track-clip-active)",
43
+ accent: "var(--color-accent)",
44
+ label: "var(--timeline-track-label)",
43
45
  };
44
46
 
47
+ // Every field reads a CSS custom property (declared in styles/theme.css)
48
+ // rather than a literal, so a host themes the timeline the same way it
49
+ // themes the rest of Studio: by overriding the token, not this object.
45
50
  export const defaultTimelineTheme: TimelineTheme = {
46
- // Near-black card surfaces: the panels sit dark while the shell canvas
47
- // between them is a step LIGHTER (#18181B), so the gaps read as visible
48
- // seams around dark cards (CapCut-style).
49
- shellBackground: "#0A0A0B",
50
- shellBorder: "rgba(255,255,255,0.05)",
51
- rulerBorder: "rgba(255,255,255,0.16)",
52
- // All track lanes use a single uniform color — one step lighter than the panel
53
- // surface (#0A0A0B) so lanes are visibly distinct from the ruler/shell.
54
- rowBackground: "#101014",
55
- rowBorder: "rgba(255,255,255,0.06)",
56
- gutterBackground: "#0E0F12",
57
- gutterBorder: "rgba(255,255,255,0.10)",
58
- textPrimary: "rgba(255,255,255,0.92)",
59
- textSecondary: "rgba(255,255,255,0.62)",
60
- tickText: "rgba(255,255,255,0.34)",
61
- tickMajor: "rgba(255,255,255,0.10)",
62
- tickMinor: "rgba(255,255,255,0.06)",
63
- clipBackground: "#141922",
64
- clipBackgroundActive: "#181e28",
65
- clipBorder: "rgba(255,255,255,0.10)",
66
- clipBorderHover: "rgba(255,255,255,0.18)",
67
- clipBorderActive: "rgba(255,255,255,0.24)",
51
+ shellBackground: "var(--timeline-shell-bg)",
52
+ shellBorder: "var(--timeline-shell-border)",
53
+ rulerBorder: "var(--timeline-ruler-border)",
54
+ rowBackground: "var(--timeline-row-bg)",
55
+ rowBorder: "var(--timeline-row-border)",
56
+ gutterBackground: "var(--timeline-gutter-bg)",
57
+ gutterBorder: "var(--timeline-gutter-border)",
58
+ textPrimary: "var(--timeline-text-primary)",
59
+ textSecondary: "var(--timeline-text-secondary)",
60
+ tickText: "var(--timeline-tick-text)",
61
+ tickMajor: "var(--timeline-tick-major)",
62
+ tickMinor: "var(--timeline-tick-minor)",
63
+ clipBackground: "var(--timeline-clip-bg)",
64
+ clipBackgroundActive: "var(--timeline-clip-bg-active)",
65
+ clipBackgroundHover: "var(--timeline-clip-bg-hover)",
66
+ clipBackgroundDragging: "var(--timeline-clip-bg-dragging)",
67
+ clipBorder: "var(--timeline-clip-border)",
68
+ clipBorderHover: "var(--timeline-clip-border-hover)",
69
+ clipBorderActive: "var(--timeline-clip-border-active)",
70
+ // Shadows stay literal: a light host still wants a dark contact shadow, so
71
+ // these are not part of the surface/hairline/text/clip-colour theming ask.
68
72
  clipShadow: "none",
69
73
  clipShadowHover: "0 2px 8px rgba(0,0,0,0.2)",
70
74
  clipShadowActive: "0 2px 8px rgba(0,0,0,0.2), 0 0 0 1px rgba(255,255,255,0.04)",
71
75
  clipShadowDragging: "0 8px 24px rgba(0,0,0,0.4), 0 0 0 1px rgba(255,255,255,0.06)",
72
- handleColor: "rgba(255,255,255,0.2)",
73
- panelResizeSeam: "rgba(255,255,255,0.12)",
74
- panelResizeActive: "rgba(255,255,255,0.24)",
75
- clipRadius: "8px",
76
+ handleColor: "var(--timeline-handle)",
77
+ panelResizeSeam: "var(--timeline-resize-seam)",
78
+ panelResizeActive: "var(--timeline-resize-active)",
79
+ clipRadius: "var(--radius-lg)",
76
80
  };
77
81
 
78
82
  export function getTimelineTrackStyle(_tag: string): TimelineTrackStyle {
@@ -1,10 +1,7 @@
1
1
  /**
2
2
  * The one owner of "what track number does the user see".
3
3
  *
4
- * `TimelineElement.track` is a z-order SORT key, not a row number: an expanded
5
- * sub-composition child gets a synthesized fractional key (`host.track + n /
6
- * (siblings + 2)`, see `useTimelineRowElements`), so putting it in a string
7
- * announces "Hide track 0.16666666666666666". Every user-visible track number,
4
+ * `TimelineElement.track` is a z-order SORT key, not a row number. Every
8
5
  * whether it is rendered by a component or baked into an undo-history label,
9
6
  * routes through here; the raw key stays in callbacks and lookups only.
10
7
  */
@@ -98,8 +98,8 @@ function buildGroupInfo(
98
98
  id: groupId,
99
99
  label: membership.labelByGroup.get(groupId) ?? groupId,
100
100
  // Exactly x.5, which sub-composition child rows are now kept strictly below
101
- // (`useTimelineRowElements`) — they used to be able to land here and
102
- // collide, duplicating the group header.
101
+ // the timeline row source — they used to be able to land here and collide,
102
+ // duplicating the group header.
103
103
  anchorKey: (memberTracks[0] ?? fallbackTrackNum) - 0.5,
104
104
  memberTracks,
105
105
  memberElements: memberTracks.flatMap((track) => rawByTrack.get(track) ?? []),
@@ -3,7 +3,6 @@
3
3
  import { describe, expect, it } from "vitest";
4
4
  import {
5
5
  collectSubCompositionDomChildren,
6
- collectTopLevelElementIds,
7
6
  collectSubCompositionHostState,
8
7
  } from "./timelineSyncHydration";
9
8
  import type { ClipManifestClip } from "../lib/playbackTypes";
@@ -95,20 +94,3 @@ describe("collectSubCompositionHostState", () => {
95
94
  expect(state.size).toBe(0);
96
95
  });
97
96
  });
98
-
99
- describe("collectTopLevelElementIds", () => {
100
- const docOf = (html: string) => new DOMParser().parseFromString(html, "text/html");
101
-
102
- it("names the sub-composition host and a timed element, not what is nested inside them", () => {
103
- const doc = docOf(`<div data-composition-id="root">
104
- <div id="wrap"><div id="a" data-start="0" data-duration="2"><div id="inner" data-start="0"></div></div></div>
105
- <div id="scene" data-composition-id="scene" data-composition-src="scene.html"><div id="box" data-start="0"></div></div>
106
- </div>`);
107
- expect([...collectTopLevelElementIds(doc)!].sort()).toEqual(["a", "scene"]);
108
- });
109
-
110
- it("is null when the document has no composition root", () => {
111
- expect(collectTopLevelElementIds(docOf("<p>hi</p>"))).toBeNull();
112
- expect(collectTopLevelElementIds(null)).toBeNull();
113
- });
114
- });
@@ -14,7 +14,6 @@ import { usePlayerStore } from "../store/playerStore";
14
14
  import type { TimelineElement, DomClipChild, SubCompositionHostState } from "../store/playerStore";
15
15
  import { resolveCssStackingContextId } from "@hyperframes/core/runtime/stacking-context";
16
16
  import type { ClipTree } from "@hyperframes/core/runtime/clipTree";
17
- import { topLevelElements, type StructureNode } from "@hyperframes/parsers/top-level-elements";
18
17
  import { HF_AUDIO_GROUP_ATTR } from "@hyperframes/core/audio-groups";
19
18
  import { groupInfoFor } from "../lib/timelineGroupInfo";
20
19
  import type { PlaybackAdapter, ClipManifestClip, IframeWindow } from "../lib/playbackTypes";
@@ -137,28 +136,6 @@ export function collectSubCompositionDomChildren(
137
136
  return out;
138
137
  }
139
138
 
140
- interface DomStructureNode extends StructureNode<DomStructureNode> {
141
- id: string;
142
- }
143
-
144
- function toStructureNode(el: Element): DomStructureNode {
145
- const attrs: Record<string, string> = {};
146
- for (const attr of Array.from(el.attributes)) attrs[attr.name] = attr.value;
147
- return {
148
- tag: el.tagName,
149
- attrs,
150
- id: el.id,
151
- children: Array.from(el.children).map(toStructureNode),
152
- };
153
- }
154
-
155
- /** DOM ids of the timeline's rows, by the definition the structure lint shares; null when there is no readable root. */
156
- export function collectTopLevelElementIds(doc: Document | null): Set<string> | null {
157
- const root = doc?.querySelector("[data-composition-id]");
158
- if (!root) return null;
159
- return new Set(topLevelElements(toStructureNode(root)).flatMap((n) => (n.id ? [n.id] : [])));
160
- }
161
-
162
139
  /** The host-element `data-*` state one element carries, or null when it has none. */
163
140
  function readSubCompositionHostState(el: Element): SubCompositionHostState | null {
164
141
  const state: SubCompositionHostState = {};
@@ -19,7 +19,6 @@ import {
19
19
  buildTimelineElementsFromClips,
20
20
  syncManifestTimeline,
21
21
  clipTreeParentMap,
22
- collectTopLevelElementIds,
23
22
  collectSubCompositionDomChildren,
24
23
  collectSubCompositionHostState,
25
24
  hydrateTimelineFromPreview,
@@ -130,13 +129,11 @@ export function useTimelineSyncCallbacks({
130
129
  const parentMap = clipTreeParentMap(iframeRef.current?.contentWindow ?? null);
131
130
  const domClipChildren = collectSubCompositionDomChildren(iframeDoc, data.clips, parentMap);
132
131
  usePlayerStore.getState().setClipParentMap(parentMap);
133
- usePlayerStore.getState().setTopLevelIds(collectTopLevelElementIds(iframeDoc));
134
132
  usePlayerStore.getState().setDomClipChildren(domClipChildren);
135
133
  usePlayerStore
136
134
  .getState()
137
135
  .setSubCompositionHostState(collectSubCompositionHostState(iframeDoc, data.clips));
138
136
  } catch {
139
- usePlayerStore.getState().setTopLevelIds(null);
140
137
  // cross-origin or __clipTree not available — maps stay empty
141
138
  }
142
139
 
@@ -202,8 +202,6 @@ interface PlayerState extends PlayerStoreSlices {
202
202
  clipManifest: ClipManifestClip[] | null;
203
203
  setClipManifest: (clips: ClipManifestClip[] | null) => void;
204
204
  clipParentMap: Map<string, string>;
205
- topLevelIds: ReadonlySet<string> | null;
206
- setTopLevelIds: (ids: ReadonlySet<string> | null) => void;
207
205
  setClipParentMap: (map: Map<string, string>) => void;
208
206
  /**
209
207
  * Sub-composition DOM descendants (groups + their children) that have no
@@ -394,8 +392,6 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
394
392
  setClipManifest: (clips) => set({ clipManifest: clips }),
395
393
  clipParentMap: new Map(),
396
394
  setClipParentMap: (map) => set({ clipParentMap: map }),
397
- topLevelIds: null,
398
- setTopLevelIds: (ids) => set({ topLevelIds: ids }),
399
395
  domClipChildren: [],
400
396
  setDomClipChildren: (children) => set({ domClipChildren: children }),
401
397
  subCompositionHostState: new Map(),
@@ -77,10 +77,8 @@ export interface TimelineElement {
77
77
  audioGroupFxChain?: string;
78
78
  audioGroupAutomation?: string;
79
79
  /**
80
- * Set by useTimelineRowElements on an inline-expanded sub-composition
81
- * child: the absolute master-timeline start of the sub-comp host the child
82
- * lives in. Presence marks the element as expanded; edits subtract it to get
83
- * the child's local (sourceFile-relative) time. Works at any nesting depth.
80
+ * Legacy source-coordinate marker for an inline sub-composition child. The
81
+ * current timeline keeps sub-compositions as single rows.
84
82
  */
85
83
  expandedParentStart?: number;
86
84
  expandedHostKey?: string;
@@ -44,7 +44,6 @@ export function createTimelineResetState() {
44
44
  beatPersist: null,
45
45
  clipManifest: null,
46
46
  clipParentMap: new Map<string, string>(),
47
- topLevelIds: null,
48
47
  domClipChildren: [],
49
48
  subCompositionHostState: new Map<string, SubCompositionHostState>(),
50
49
  };
@@ -1,5 +1,5 @@
1
1
  {
2
- "total": 418,
2
+ "total": 385,
3
3
  "files": {
4
4
  "packages/studio/src/captions/components/CaptionOverlay.tsx": 5,
5
5
  "packages/studio/src/captions/components/CaptionTimeline.tsx": 11,
@@ -61,7 +61,6 @@
61
61
  "packages/studio/src/player/components/SpeedMenu.tsx": 2,
62
62
  "packages/studio/src/player/components/TimelineAutomationLane.tsx": 4,
63
63
  "packages/studio/src/player/components/TimelineCanvas.tsx": 13,
64
- "packages/studio/src/player/components/TimelineClip.tsx": 2,
65
64
  "packages/studio/src/player/components/TimelineClipDiamonds.tsx": 2,
66
65
  "packages/studio/src/player/components/TimelineDiamondConnectors.tsx": 2,
67
66
  "packages/studio/src/player/components/TimelineEmptyState.tsx": 1,
@@ -75,9 +74,9 @@
75
74
  "packages/studio/src/player/components/TimelineTrackHeader.tsx": 2,
76
75
  "packages/studio/src/player/components/TimelineTrackPlainHeader.tsx": 2,
77
76
  "packages/studio/src/player/components/VideoThumbnail.tsx": 6,
78
- "packages/studio/src/player/components/timelineTheme.ts": 29,
77
+ "packages/studio/src/player/components/timelineTheme.ts": 5,
79
78
  "packages/studio/src/player/components/trackHeaderLabelRows.tsx": 6,
80
- "packages/studio/src/styles/studio.css": 70,
79
+ "packages/studio/src/styles/studio.css": 63,
81
80
  "packages/studio/src/telemetry/client.ts": 1,
82
81
  "packages/studio/src/webmcp/webmcpTestUtils.ts": 1
83
82
  }
@@ -200,8 +200,8 @@ body {
200
200
  }
201
201
 
202
202
  .timeline-clip {
203
- background-color: rgba(255, 255, 255, 0.055);
204
- border: 1px solid rgba(255, 255, 255, 0.09);
203
+ background-color: var(--clip-bg);
204
+ border: 1px solid var(--clip-border);
205
205
  border-radius: 8px;
206
206
  }
207
207
 
@@ -210,13 +210,13 @@ body {
210
210
  position: absolute;
211
211
  inset: 0;
212
212
  border-radius: inherit;
213
- background: rgba(60, 230, 172, 0.2);
213
+ background: var(--clip-bg-active);
214
214
  opacity: 0;
215
215
  pointer-events: none;
216
216
  }
217
217
 
218
218
  .timeline-clip[data-active] {
219
- border-color: rgba(60, 230, 172, 0.55);
219
+ border-color: var(--clip-border-active);
220
220
  }
221
221
 
222
222
  .timeline-clip[data-active]::before {
@@ -240,11 +240,11 @@ body {
240
240
  }
241
241
 
242
242
  .timeline-clip.is-hovered {
243
- background-color: rgba(255, 255, 255, 0.09);
243
+ background-color: var(--clip-bg-hover);
244
244
  }
245
245
 
246
246
  .timeline-clip[data-active].is-hovered {
247
- border-color: rgba(60, 230, 172, 0.75);
247
+ border-color: var(--clip-border-hover);
248
248
  }
249
249
 
250
250
  .timeline-clip.is-selected {
@@ -258,7 +258,7 @@ body {
258
258
  .timeline-clip.is-dragging {
259
259
  /* Solid background so the picked-up clip reads clearly while dragging — audio
260
260
  clips (waveform drawn on a separate layer) would otherwise look transparent. */
261
- background-color: rgba(38, 42, 52, 0.96);
261
+ background-color: var(--clip-bg-dragging);
262
262
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
263
263
  }
264
264
 
@@ -46,6 +46,35 @@
46
46
  --color-brand-gradient-from: #06e3fa;
47
47
  --color-brand-gradient-to: #4fdb5e;
48
48
 
49
+ /* The timeline's own surfaces: darker and bluer than the shared scale, so they
50
+ * get their own names instead of snapping to the nearest --color-bg/-surface
51
+ * step (a host themes the timeline by overriding these). */
52
+ --timeline-shell-bg: #0a0a0b;
53
+ --timeline-shell-border: rgba(255, 255, 255, 0.05);
54
+ --timeline-ruler-border: rgba(255, 255, 255, 0.16);
55
+ --timeline-row-bg: #101014;
56
+ --timeline-row-border: rgba(255, 255, 255, 0.06);
57
+ --timeline-gutter-bg: #0e0f12;
58
+ --timeline-gutter-border: rgba(255, 255, 255, 0.1);
59
+ --timeline-text-primary: rgba(255, 255, 255, 0.92);
60
+ --timeline-text-secondary: rgba(255, 255, 255, 0.62);
61
+ --timeline-tick-text: rgba(255, 255, 255, 0.34);
62
+ --timeline-tick-major: rgba(255, 255, 255, 0.1);
63
+ --timeline-tick-minor: rgba(255, 255, 255, 0.06);
64
+ --timeline-clip-bg: rgba(255, 255, 255, 0.12);
65
+ --timeline-clip-bg-active: rgba(60, 230, 172, 0.2);
66
+ --timeline-clip-bg-hover: rgba(255, 255, 255, 0.16);
67
+ --timeline-clip-bg-dragging: rgba(38, 42, 52, 0.96);
68
+ --timeline-clip-border: rgba(255, 255, 255, 0.22);
69
+ --timeline-clip-border-hover: rgba(60, 230, 172, 0.75);
70
+ --timeline-clip-border-active: rgba(60, 230, 172, 0.55);
71
+ --timeline-handle: rgba(255, 255, 255, 0.55);
72
+ --timeline-resize-seam: rgba(255, 255, 255, 0.12);
73
+ --timeline-resize-active: rgba(255, 255, 255, 0.24);
74
+ --timeline-track-clip-fill: rgba(255, 255, 255, 0.055);
75
+ --timeline-track-clip-active: rgba(60, 230, 172, 0.16);
76
+ --timeline-track-label: rgba(255, 255, 255, 0.5);
77
+
49
78
  /* -------------------------------------------------------------- geometry */
50
79
 
51
80
  --radius-sm: 0.25rem;
@@ -1,28 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import type { TimelineElement } from "../store/playerStore";
3
- import { selectTimelineRowElements } from "./useTimelineRowElements";
4
-
5
- const el = (id: string, domId?: string): TimelineElement => ({
6
- id,
7
- domId,
8
- start: 0,
9
- duration: 1,
10
- track: 0,
11
- tag: "div",
12
- });
13
-
14
- describe("selectTimelineRowElements", () => {
15
- it("drops an element the shared definition does not call top-level", () => {
16
- const rows = selectTimelineRowElements([el("a", "a"), el("b", "b")], new Set(["a"]));
17
- expect(rows.map((r) => r.id)).toEqual(["a"]);
18
- });
19
-
20
- it("keeps everything until the document has been read", () => {
21
- const all = [el("a", "a"), el("b", "b")];
22
- expect(selectTimelineRowElements(all, null)).toBe(all);
23
- });
24
-
25
- it("keeps an element with no DOM id", () => {
26
- expect(selectTimelineRowElements([el("k")], new Set())).toHaveLength(1);
27
- });
28
- });
@@ -1,17 +0,0 @@
1
- import { useMemo } from "react";
2
- import { usePlayerStore, type TimelineElement } from "../store/playerStore";
3
-
4
- /** Rows are the ids `topLevelElements` names, as the lint does; id-less ones are kept. */
5
- export function selectTimelineRowElements(
6
- elements: TimelineElement[],
7
- topLevelIds: ReadonlySet<string> | null,
8
- ): TimelineElement[] {
9
- if (!topLevelIds) return elements;
10
- return elements.filter((el) => el.domId === undefined || topLevelIds.has(el.domId));
11
- }
12
-
13
- export function useTimelineRowElements(): TimelineElement[] {
14
- const elements = usePlayerStore((s) => s.elements);
15
- const topLevelIds = usePlayerStore((s) => s.topLevelIds);
16
- return useMemo(() => selectTimelineRowElements(elements, topLevelIds), [elements, topLevelIds]);
17
- }