Commit cd0e6b0
authored
feat(studio): Timing inspector + fix mixed-content text editing (#896)
* feat(studio): add clipboard payload types and ID deduplication
* feat(studio): add Ctrl+C/V/X copy/paste for timeline clips and DOM elements
* fix(studio): use duck-typing for cross-frame element access in clipboard
Elements from the preview iframe are from a different window context,
so `el instanceof HTMLElement` always returns false. Use `"outerHTML"
in el` instead to correctly detect elements across frame boundaries.
* fix(studio): preserve playhead position after paste
reloadPreview() used location.reload() which bypassed the
NLELayout saveSeekPosition effect, causing the playhead to reset
to 0:00 after paste. Switch to setRefreshKey which triggers the
effect and restores the seek position after the iframe reloads.
* fix(studio): paste DOM elements as siblings, not at composition root
DOM element paste was inserting at the composition root, losing the
parent context that provides CSS styles and positioning. Now stores
the origin selector on copy and inserts the paste as a sibling
immediately after the original element, preserving style inheritance.
Falls back to root insertion if the selector can't be matched.
* fix(studio): address review — deduplicateIds, native copy, altKey guard
- deduplicateIds regex used \b which matched data-composition-id,
data-clip-id, etc. Switch to lookbehind (?<=\s) so only standalone
id="..." attributes are rewritten. Add test pinning this.
- Ctrl+C no longer calls preventDefault() before confirming there's
a selected element. Native browser copy (text selections outside
inputs) is preserved when nothing is selected in the Studio.
- Add !event.altKey guard on C/V/X to avoid intercepting Cmd+Alt+V
(paste-as-plain-text) and similar OS gestures.
- Remove no-op .replace(/"/g, '"') flagged by CodeQL.
* fix(studio): address review round 2 — Cmd+X guard, data-start scope, revert drive-by
- Cmd+X now pre-checks selection state before preventDefault, mirroring
the Cmd+C fix. Native cut preserved when nothing is selected.
- handleCut returns Promise<boolean> so the caller can gate on it.
- data-start rewrite scoped to the outermost opening tag only, so nested
clip timing is preserved on paste.
- Removed system clipboard write (cross-tab paste unsupported, in-memory
ref is the only read path).
- Reverted the reloadPreview drive-by (setRefreshKey→location.reload);
the perf branch (#895) handles this properly via refreshPlayer().
* perf(studio): use lightweight iframe.src reload instead of Player teardown
Content refreshes (paste, move, resize, delete, asset drop) previously
triggered setRefreshKey which changed the Player's React key, causing
full web-component destruction + iframe teardown + crossfade animation
+ re-initialization of all event listeners and asset polling.
Now NLELayout intercepts refreshKey changes and calls refreshPlayer()
which just appends a cache-busting _t param to the iframe src. The
Player web component stays alive, event listeners persist, and the
reload is ~10x faster with no "waiting for media" flash.
Key-based teardown is preserved for actual structural changes (project
switch, composition drill-down via directUrl change).
* perf(studio): skip asset-loading overlay on content refreshes
The asset-loading overlay ("Preparing preview assets") polled for
video/audio readyState on every iframe load, including content
refreshes from paste/move/resize. On reloads the browser serves
assets from cache so they resolve near-instantly — the overlay
just created a disruptive flash. Now skips the polling on
subsequent loads (loadCountRef > 1), only showing it on the
initial cold load.
* feat(studio): add Timing section to inspector Design panel
Adds Start, End, and Duration fields to the Design panel when the
selected element has data-start/data-duration attributes. Editing
any field commits via the attribute patch pipeline (same as timeline
edits) and refreshes the preview. End is computed from start+duration
and writing End adjusts duration accordingly.
* fix(studio): preserve bare text nodes in mixed-content elements
collectDomEditTextFields only captured child HTML elements, ignoring
bare text nodes. For elements like:
<div class="headline">If you're <span>turning 65</span> soon...</div>
only the <span> was collected as a text field. When commitDomTextFields
serialized back, "If you're " and " soon..." were lost.
Now walks childNodes and creates text-node fields for bare text nodes
alongside child element fields. serializeDomEditTextFields emits bare
text for text-node fields, preserving the complete mixed content.
* fix(studio): address #896 review — remove scrub from timing, add mixed-content test
- Remove scrub from Timing fields: 1px = 1 second is too coarse.
Scroll-wheel and direct typing still work with sub-second precision.
- Add mixed-content text-node serialization test in a separate file
(domEditingTextFields.test.ts) to avoid bloating the existing
domEditing.test.ts past the filesize limit.1 parent 83b3eba commit cd0e6b0
9 files changed
Lines changed: 210 additions & 7 deletions
File tree
- packages/studio/src
- components
- editor
- contexts
- hooks
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
58 | 59 | | |
59 | 60 | | |
60 | 61 | | |
| |||
168 | 169 | | |
169 | 170 | | |
170 | 171 | | |
| 172 | + | |
171 | 173 | | |
172 | 174 | | |
173 | 175 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | | - | |
| 2 | + | |
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
| 42 | + | |
42 | 43 | | |
43 | 44 | | |
44 | 45 | | |
| |||
114 | 115 | | |
115 | 116 | | |
116 | 117 | | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
117 | 179 | | |
118 | 180 | | |
119 | 181 | | |
| |||
126 | 188 | | |
127 | 189 | | |
128 | 190 | | |
| 191 | + | |
129 | 192 | | |
130 | 193 | | |
131 | 194 | | |
| |||
322 | 385 | | |
323 | 386 | | |
324 | 387 | | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
325 | 392 | | |
326 | 393 | | |
327 | 394 | | |
| |||
Lines changed: 39 additions & 5 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
73 | 73 | | |
74 | 74 | | |
75 | 75 | | |
76 | | - | |
77 | | - | |
78 | | - | |
79 | | - | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
80 | 111 | | |
81 | 112 | | |
82 | 113 | | |
| |||
99 | 130 | | |
100 | 131 | | |
101 | 132 | | |
102 | | - | |
| 133 | + | |
103 | 134 | | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
104 | 138 | | |
105 | 139 | | |
106 | 140 | | |
| |||
Lines changed: 60 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
65 | 65 | | |
66 | 66 | | |
67 | 67 | | |
68 | | - | |
| 68 | + | |
69 | 69 | | |
70 | 70 | | |
71 | 71 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
| 31 | + | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| |||
74 | 75 | | |
75 | 76 | | |
76 | 77 | | |
| 78 | + | |
77 | 79 | | |
78 | 80 | | |
79 | 81 | | |
| |||
114 | 116 | | |
115 | 117 | | |
116 | 118 | | |
| 119 | + | |
117 | 120 | | |
118 | 121 | | |
119 | 122 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
189 | 189 | | |
190 | 190 | | |
191 | 191 | | |
| 192 | + | |
192 | 193 | | |
193 | 194 | | |
194 | 195 | | |
| |||
437 | 438 | | |
438 | 439 | | |
439 | 440 | | |
| 441 | + | |
440 | 442 | | |
441 | 443 | | |
442 | 444 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
193 | 193 | | |
194 | 194 | | |
195 | 195 | | |
| 196 | + | |
196 | 197 | | |
197 | 198 | | |
198 | 199 | | |
| |||
305 | 306 | | |
306 | 307 | | |
307 | 308 | | |
| 309 | + | |
308 | 310 | | |
309 | 311 | | |
310 | 312 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
113 | 113 | | |
114 | 114 | | |
115 | 115 | | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
116 | 148 | | |
117 | 149 | | |
118 | 150 | | |
| |||
321 | 353 | | |
322 | 354 | | |
323 | 355 | | |
| 356 | + | |
324 | 357 | | |
325 | 358 | | |
326 | 359 | | |
| |||
0 commit comments