@bycrux/editor 1.2.2 → 1.4.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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/ControlsInfoModal.tsx +5 -5
  3. package/src/__tests__/no-opacity-modifier-on-editor-vars.test.ts +84 -0
  4. package/src/__tests__/node-shims.d.ts +19 -0
  5. package/src/__tests__/portal-roots-set-editor-text.test.ts +55 -0
  6. package/src/carousel/AddElementMenu.tsx +1 -1
  7. package/src/carousel/CarouselEditor.tsx +11 -11
  8. package/src/carousel/CarouselRenderModal.tsx +14 -14
  9. package/src/carousel/OverlayPicker.tsx +4 -4
  10. package/src/carousel/SlidePropertyPanel.tsx +6 -6
  11. package/src/carousel/__tests__/CarouselEditor.test.tsx +12 -0
  12. package/src/components/FilmstripScrubber.tsx +1 -1
  13. package/src/crop/VideoSourceCropModal.tsx +5 -5
  14. package/src/state/__tests__/use-project-sync.test.tsx +76 -0
  15. package/src/state/use-project-sync.ts +21 -1
  16. package/src/text/FontPicker.tsx +3 -3
  17. package/src/text/TextFormattingToolbar.tsx +3 -3
  18. package/src/types.ts +18 -0
  19. package/src/ui/Loader.tsx +1 -1
  20. package/src/ui/NumberField.tsx +3 -3
  21. package/src/ui/SwatchInput.tsx +1 -1
  22. package/src/ui/Tooltip.tsx +1 -1
  23. package/src/ui/input.tsx +1 -1
  24. package/src/ui/label.tsx +1 -1
  25. package/src/ui/textarea.tsx +1 -1
  26. package/src/video/AudioPolishModal.tsx +22 -22
  27. package/src/video/CaptionListPanel.test.tsx +8 -7
  28. package/src/video/CaptionListPanel.tsx +17 -17
  29. package/src/video/CaptionRegenModal.tsx +8 -8
  30. package/src/video/CaptionStyleGallery.tsx +1 -1
  31. package/src/video/CommandPalette.tsx +8 -8
  32. package/src/video/ImageToneMenu.tsx +13 -14
  33. package/src/video/OverlayInspector.tsx +7 -7
  34. package/src/video/RenderModal.tsx +76 -78
  35. package/src/video/VersionCompare.tsx +10 -10
  36. package/src/video/VersionPanel.tsx +2 -2
  37. package/src/video/VideoEditor.tsx +48 -23
  38. package/src/video/__tests__/RenderModal.hostTheme.test.tsx +85 -0
  39. package/src/video/__tests__/VideoEditor.context.test.tsx +6 -0
  40. package/src/video/__tests__/VideoEditor.pendingSurface.test.tsx +233 -0
  41. package/src/video/__tests__/VideoEditor.saveError.test.tsx +105 -0
  42. package/src/video/__tests__/export-limits.test.ts +27 -2
  43. package/src/video/__tests__/use-report-context.test.tsx +22 -1
  44. package/src/video/export-limits.ts +22 -1
  45. package/src/video/panels/ClipPropertiesPanel.tsx +4 -4
  46. package/src/video/panels/OverlayContentPanel.tsx +5 -5
  47. package/src/video/preview/OverlayItemsLayer.tsx +7 -2
  48. package/src/video/preview/PreviewPlayer.tsx +14 -0
  49. package/src/video/preview/SocialPreviewMenu.tsx +6 -6
  50. package/src/video/preview/__tests__/PreviewPlayer.playing.test.tsx +62 -0
  51. package/src/video/preview/__tests__/resolveOverlayPropPaths.test.ts +21 -0
  52. package/src/video/timeline/EditableSegment.tsx +1 -1
  53. package/src/video/timeline/Scrubber.tsx +2 -2
  54. package/src/video/timeline/SpeedControl.tsx +3 -3
  55. package/src/video/timeline/Timeline.tsx +3 -3
  56. package/src/video/timeline/TrackGutter.tsx +6 -6
  57. package/src/video/timeline/TrackSettingsPopover.tsx +4 -4
  58. package/src/video/timeline/VolumeControl.tsx +3 -3
  59. package/src/video/use-report-context.ts +10 -7
@@ -174,7 +174,7 @@ export default function TrackSettingsPopover({
174
174
  ref={popoverRef}
175
175
  role="dialog"
176
176
  aria-label={title}
177
- className="fixed z-[100] w-64 rounded-lg border border-[var(--editor-border)] bg-[var(--editor-surface)] shadow-2xl p-3 flex flex-col gap-3"
177
+ className="fixed z-[100] w-64 rounded-lg border border-[var(--editor-border)] bg-[var(--editor-surface)] shadow-2xl p-3 flex flex-col gap-3 text-[var(--editor-text)]"
178
178
  style={{
179
179
  left: position?.left ?? -9999,
180
180
  top: position?.top ?? -9999,
@@ -186,7 +186,7 @@ export default function TrackSettingsPopover({
186
186
  transform: position?.flipped ? 'translateY(-100%)' : undefined,
187
187
  }}
188
188
  >
189
- <p className="text-xs font-semibold text-[var(--editor-text)]/90">{title}</p>
189
+ <p className="text-xs font-semibold text-[color-mix(in_srgb,var(--editor-text)_90%,transparent)]">{title}</p>
190
190
 
191
191
  {showVolume && (
192
192
  <VolumeControl
@@ -211,14 +211,14 @@ export default function TrackSettingsPopover({
211
211
 
212
212
  {showMute && (
213
213
  <div className="flex items-center justify-between">
214
- <span className="text-[11px] text-[var(--editor-text)]/70">Mute</span>
214
+ <span className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]">Mute</span>
215
215
  <Switch checked={!!muted} onCheckedChange={onMutedChange!} aria-label={muteAriaLabel} mode={mode} />
216
216
  </div>
217
217
  )}
218
218
 
219
219
  {skip && (
220
220
  <div className="flex items-center justify-between">
221
- <span className="text-[11px] text-[var(--editor-text)]/70">Skip</span>
221
+ <span className="text-[11px] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)]">Skip</span>
222
222
  <Switch checked={skip.skipped} onCheckedChange={() => skip.onToggle()} aria-label={skip.ariaLabel} mode={mode} />
223
223
  </div>
224
224
  )}
@@ -69,9 +69,9 @@ export default function VolumeControl({
69
69
 
70
70
  return (
71
71
  <div className="flex flex-col gap-2">
72
- <div className={`flex items-center text-[11px] text-[var(--editor-text)]/70 ${label ? 'justify-between' : 'justify-end'}`}>
72
+ <div className={`flex items-center text-[11px] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] ${label ? 'justify-between' : 'justify-end'}`}>
73
73
  {label && <span>{label}</span>}
74
- <span className="font-mono text-[10px] text-[var(--editor-text)]/50">
74
+ <span className="font-mono text-[10px] text-[color-mix(in_srgb,var(--editor-text)_50%,transparent)]">
75
75
  {value.toFixed(2)} ({volumeToDb(value)})
76
76
  </span>
77
77
  </div>
@@ -100,7 +100,7 @@ export default function VolumeControl({
100
100
  className={`rounded-md border px-2 py-0.5 text-[11px] font-mono transition-colors focus:outline-none focus:ring-1 focus:ring-[var(--editor-accent)] ${
101
101
  active
102
102
  ? 'border-[var(--editor-accent)] bg-[var(--editor-accent)] text-[var(--editor-accent-foreground)]'
103
- : 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[var(--editor-text)]/70 hover:text-[var(--editor-text)] hover:border-[var(--editor-accent)]'
103
+ : 'border-[var(--editor-border)] bg-[var(--editor-surface)] text-[color-mix(in_srgb,var(--editor-text)_70%,transparent)] hover:text-[var(--editor-text)] hover:border-[var(--editor-accent)]'
104
104
  }`}
105
105
  >
106
106
  {Math.round(p * 100)}%
@@ -23,6 +23,8 @@ interface Options {
23
23
  clock: PlaybackClock
24
24
  selectedIds: string[]
25
25
  selectedCaptionId: string | null
26
+ /** Transport state. Reported like selection: at once, on change. */
27
+ playing?: boolean
26
28
  }
27
29
 
28
30
  /**
@@ -36,12 +38,12 @@ interface Options {
36
38
  * selection.
37
39
  */
38
40
  export function useReportContext({
39
- adapter, projectId, clock, selectedIds, selectedCaptionId,
41
+ adapter, projectId, clock, selectedIds, selectedCaptionId, playing = false,
40
42
  }: Options): void {
41
43
  // Kept in a ref so the interval effect never re-subscribes on a selection
42
44
  // change — it always reads the latest values without depending on them.
43
- const latest = useRef({ selectedIds, selectedCaptionId })
44
- latest.current = { selectedIds, selectedCaptionId }
45
+ const latest = useRef({ selectedIds, selectedCaptionId, playing })
46
+ latest.current = { selectedIds, selectedCaptionId, playing }
45
47
 
46
48
  const send = useRef<(ctx: EditorContext) => void>(() => {})
47
49
  send.current = (ctx: EditorContext) => {
@@ -58,18 +60,19 @@ export function useReportContext({
58
60
  playheadSec: clock.get(),
59
61
  selectedIds: latest.current.selectedIds,
60
62
  selectedCaptionId: latest.current.selectedCaptionId,
63
+ playing: latest.current.playing,
61
64
  })
62
65
  emit()
63
66
  const id = setInterval(emit, REPORT_INTERVAL_MS)
64
67
  return () => clearInterval(id)
65
68
  }, [adapter, projectId, clock])
66
69
 
67
- // Selection, immediately on change. Skips the mount emit above by comparing
68
- // against what the interval effect already sent.
70
+ // Selection and play/pause, immediately on change. Skips the mount emit
71
+ // above by comparing against what the interval effect already sent.
69
72
  const mounted = useRef(false)
70
73
  useEffect(() => {
71
74
  if (!adapter.reportContext) return
72
75
  if (!mounted.current) { mounted.current = true; return }
73
- send.current({ playheadSec: clock.get(), selectedIds, selectedCaptionId })
74
- }, [adapter, clock, selectedIds, selectedCaptionId])
76
+ send.current({ playheadSec: clock.get(), selectedIds, selectedCaptionId, playing })
77
+ }, [adapter, clock, selectedIds, selectedCaptionId, playing])
75
78
  }