@bycrux/editor 0.12.0 → 1.0.1
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.
- package/package.json +3 -1
- package/src/ControlsInfoModal.tsx +251 -61
- package/src/__tests__/ControlsInfoModal.test.tsx +43 -0
- package/src/__tests__/adapter.test.ts +59 -1
- package/src/__tests__/schema-assignability.test.ts +93 -0
- package/src/__tests__/schema.test.ts +15 -0
- package/src/__tests__/video-adapter-contract.test.ts +128 -5
- package/src/carousel/AddElementMenu.tsx +10 -4
- package/src/carousel/CarouselEditor.tsx +46 -8
- package/src/carousel/CarouselRenderModal.tsx +15 -10
- package/src/carousel/OverlayPicker.tsx +10 -3
- package/src/carousel/SlidePropertyPanel.tsx +43 -21
- package/src/components/FilmstripScrubber.tsx +277 -0
- package/src/components/__tests__/FilmstripScrubber.test.tsx +216 -0
- package/src/engine/__tests__/audio-clock.test.ts +655 -0
- package/src/engine/__tests__/audio-worklet-source.test.ts +316 -0
- package/src/engine/__tests__/batch-planner.test.ts +298 -0
- package/src/engine/__tests__/decode-worker-source.test.ts +249 -0
- package/src/engine/__tests__/demux-ranged.test.ts +495 -0
- package/src/engine/__tests__/demux-truncated.test.ts +136 -0
- package/src/engine/__tests__/demux.test.ts +305 -0
- package/src/engine/__tests__/eligibility.test.ts +146 -0
- package/src/engine/__tests__/engine-recovery.test.ts +263 -0
- package/src/engine/__tests__/engine.test.ts +326 -0
- package/src/engine/__tests__/frame-server-ranged.test.ts +261 -0
- package/src/engine/__tests__/frame-server.test.ts +326 -0
- package/src/engine/__tests__/media-loader-ranged.test.ts +289 -0
- package/src/engine/__tests__/media-loader.test.ts +100 -0
- package/src/engine/__tests__/scheduler-crop.test.ts +353 -0
- package/src/engine/__tests__/scheduler.test.ts +1310 -0
- package/src/engine/__tests__/scrub-resolve.test.ts +96 -0
- package/src/engine/__tests__/scrub-source.test.ts +195 -0
- package/src/engine/__tests__/source-host.test.ts +455 -0
- package/src/engine/__tests__/time-stretch.test.ts +182 -0
- package/src/engine/audio-clock.ts +1179 -0
- package/src/engine/audio-worklet-source.ts +160 -0
- package/src/engine/batch-planner.ts +308 -0
- package/src/engine/debug-hud.tsx +54 -0
- package/src/engine/decode-worker-source.ts +142 -0
- package/src/engine/demux.ts +900 -0
- package/src/engine/eligibility.ts +140 -0
- package/src/engine/frame-server.ts +644 -0
- package/src/engine/index.ts +950 -0
- package/src/engine/media-loader.ts +380 -0
- package/src/engine/mp4box.d.ts +214 -0
- package/src/engine/scheduler.ts +1324 -0
- package/src/engine/scrub-resolve.ts +66 -0
- package/src/engine/scrub-source.ts +496 -0
- package/src/engine/time-stretch.ts +224 -0
- package/src/index.ts +75 -1
- package/src/preview/OverlayPreview.tsx +2 -24
- package/src/schema.ts +134 -2
- package/src/state/__tests__/use-project-sync.test.tsx +56 -0
- package/src/state/use-project-sync.ts +17 -0
- package/src/test-setup.ts +32 -0
- package/src/text/FontPicker.tsx +85 -51
- package/src/text/TextFormattingToolbar.tsx +6 -1
- package/src/text/__tests__/FontPicker.options.test.ts +43 -0
- package/src/text/__tests__/TextFormattingToolbar.test.tsx +4 -4
- package/src/theme.ts +108 -3
- package/src/types.ts +586 -22
- package/src/ui/Loader.tsx +64 -0
- package/src/ui/NumberField.tsx +254 -0
- package/src/ui/Slider.tsx +128 -0
- package/src/ui/Tooltip.tsx +118 -0
- package/src/ui/__tests__/NumberField.test.tsx +298 -0
- package/src/ui/__tests__/Slider.test.tsx +150 -0
- package/src/ui/__tests__/Tooltip.test.tsx +105 -0
- package/src/ui/__tests__/usePersistentState.test.tsx +112 -0
- package/src/ui/badge.tsx +12 -4
- package/src/ui/index.ts +6 -0
- package/src/ui/input.tsx +1 -1
- package/src/ui/select.tsx +1 -1
- package/src/ui/switch.tsx +12 -2
- package/src/ui/textarea.tsx +1 -1
- package/src/ui/usePersistentState.ts +63 -0
- package/src/video/AudioPolishModal.tsx +983 -0
- package/src/video/CaptionListPanel.test.tsx +944 -0
- package/src/video/CaptionListPanel.tsx +1106 -0
- package/src/video/CaptionRegenModal.tsx +37 -9
- package/src/video/CaptionSpecimen.tsx +160 -0
- package/src/video/CaptionStyleGallery.tsx +466 -0
- package/src/video/CommandPalette.tsx +165 -0
- package/src/video/ImageToneMenu.tsx +6 -2
- package/src/video/OverlayInspector.tsx +977 -0
- package/src/video/RenderModal.tsx +1035 -62
- package/src/video/VersionCompare.tsx +258 -0
- package/src/video/VersionPanel.tsx +117 -42
- package/src/video/VideoEditor.tsx +2202 -251
- package/src/video/__tests__/AudioPolishModal.test.tsx +825 -0
- package/src/video/__tests__/CaptionListPanel.font.test.tsx +397 -0
- package/src/video/__tests__/CaptionListPanel.generate.test.tsx +153 -0
- package/src/video/__tests__/CaptionRegenModal.test.tsx +29 -0
- package/src/video/__tests__/CaptionSpecimen.test.tsx +213 -0
- package/src/video/__tests__/CaptionStyleGallery.test.tsx +362 -0
- package/src/video/__tests__/CommandPalette.test.tsx +119 -0
- package/src/video/__tests__/OverlayInspector.test.tsx +1367 -0
- package/src/video/__tests__/RenderModal.exportControls.test.tsx +319 -0
- package/src/video/__tests__/RenderModal.options.test.tsx +562 -0
- package/src/video/__tests__/RenderModal.progress.test.ts +65 -0
- package/src/video/__tests__/VersionCompare.test.tsx +182 -0
- package/src/video/__tests__/VersionPanel.test.tsx +279 -0
- package/src/video/__tests__/VideoEditor.audioPolish.test.tsx +241 -0
- package/src/video/__tests__/VideoEditor.captionDelete.test.tsx +147 -0
- package/src/video/__tests__/VideoEditor.captionGesture.test.tsx +170 -0
- package/src/video/__tests__/VideoEditor.captionSeam.test.tsx +134 -0
- package/src/video/__tests__/VideoEditor.clipKeyframes.test.tsx +59 -0
- package/src/video/__tests__/VideoEditor.context.test.tsx +44 -0
- package/src/video/__tests__/VideoEditor.editFocus.test.tsx +55 -0
- package/src/video/__tests__/VideoEditor.keymap.test.tsx +725 -0
- package/src/video/__tests__/VideoEditor.layout.test.tsx +338 -0
- package/src/video/__tests__/VideoEditor.propertiesPanel.test.tsx +765 -0
- package/src/video/__tests__/VideoEditor.rippleDeleteCaptions.test.tsx +159 -0
- package/src/video/__tests__/VideoEditor.sourcePreview.test.tsx +122 -0
- package/src/video/__tests__/VideoEditor.test.tsx +403 -50
- package/src/video/__tests__/audioMagnet.test.ts +158 -0
- package/src/video/__tests__/audioPolish.test.ts +1202 -0
- package/src/video/__tests__/captionActiveWord.test.ts +107 -0
- package/src/video/__tests__/captionLanes.test.ts +262 -0
- package/src/video/__tests__/captionPositioning.test.tsx +8 -3
- package/src/video/__tests__/captionWordFloor.test.ts +228 -0
- package/src/video/__tests__/clipboard-ops.test.ts +430 -0
- package/src/video/__tests__/cuts.insert.test.ts +244 -0
- package/src/video/__tests__/cuts.test.ts +1213 -34
- package/src/video/__tests__/export-limits.test.ts +195 -0
- package/src/video/__tests__/hover-scrub.test.ts +163 -0
- package/src/video/__tests__/keyframeOps.canKeyframeProp.test.ts +61 -0
- package/src/video/__tests__/keyframeOps.test.ts +643 -0
- package/src/video/__tests__/keymap.test.tsx +171 -0
- package/src/video/__tests__/render-progress.test.tsx +20 -4
- package/src/video/__tests__/shuttle.test.ts +210 -0
- package/src/video/__tests__/source-preview.test.ts +60 -0
- package/src/video/__tests__/timecode.test.ts +77 -0
- package/src/video/__tests__/use-report-context.test.tsx +101 -0
- package/src/video/audioMagnet.ts +72 -0
- package/src/video/audioPolish.ts +774 -0
- package/src/video/captionActiveWord.ts +74 -0
- package/src/video/captionLanes.ts +202 -0
- package/src/video/captionRepair.ts +9 -5
- package/src/video/captionStyleDefaults.ts +100 -0
- package/src/video/captionWordFloor.ts +83 -0
- package/src/video/clipboard-ops.ts +377 -0
- package/src/video/cuts.ts +713 -37
- package/src/video/export-limits.ts +102 -0
- package/src/video/hover-scrub.ts +102 -0
- package/src/video/keyframeOps.ts +384 -0
- package/src/video/keymap.ts +146 -0
- package/src/video/panels/ClipPropertiesPanel.tsx +706 -0
- package/src/video/panels/LeftPanelTabs.tsx +187 -0
- package/src/video/panels/OverlayContentPanel.tsx +351 -0
- package/src/video/panels/TabNav.tsx +60 -0
- package/src/video/panels/__tests__/ClipPropertiesPanel.test.tsx +645 -0
- package/src/video/panels/__tests__/LeftPanelTabs.test.tsx +189 -0
- package/src/video/panels/__tests__/OverlayContentPanel.test.tsx +222 -0
- package/src/video/panels/__tests__/TabNav.test.tsx +71 -0
- package/src/video/preview/CaptionPreview.tsx +57 -69
- package/src/video/preview/EngineSurface.tsx +103 -0
- package/src/video/preview/OverlayItemsLayer.tsx +315 -103
- package/src/video/preview/PreviewPlayer.tsx +337 -56
- package/src/video/preview/SocialPreviewMenu.tsx +214 -0
- package/src/video/preview/SocialSafeZoneOverlay.tsx +478 -0
- package/src/video/preview/__tests__/CaptionPreview.fonts.test.tsx +97 -0
- package/src/video/preview/__tests__/EngineSurface.test.tsx +114 -0
- package/src/video/preview/__tests__/OverlayItemsLayer.edit.test.tsx +4 -2
- package/src/video/preview/__tests__/OverlayItemsLayer.keyframes.test.tsx +363 -0
- package/src/video/preview/__tests__/OverlayItemsLayer.selection.test.tsx +329 -0
- package/src/video/preview/__tests__/PreviewPlayer.engine.test.tsx +139 -0
- package/src/video/preview/__tests__/SocialPreviewMenu.test.tsx +121 -0
- package/src/video/preview/__tests__/SocialSafeZoneOverlay.test.tsx +165 -0
- package/src/video/preview/__tests__/captionDragState.test.ts +120 -1
- package/src/video/preview/__tests__/latencyCompensation.test.tsx +451 -0
- package/src/video/preview/__tests__/proxySupport.test.ts +75 -0
- package/src/video/preview/__tests__/transformStyle.test.ts +29 -1
- package/src/video/preview/__tests__/useDragOverlay.perAxis.test.ts +197 -0
- package/src/video/preview/__tests__/useEnginePlayback.test.tsx +530 -0
- package/src/video/preview/__tests__/useVideoPlayback.corpus.test.ts +313 -0
- package/src/video/preview/__tests__/useVideoPlayback.test.ts +38 -5
- package/src/video/preview/__tests__/useVideoPlayback.trackAudio.test.ts +278 -0
- package/src/video/preview/audio-context.ts +111 -0
- package/src/video/preview/captionDragState.ts +91 -1
- package/src/video/preview/proxySupport.ts +86 -0
- package/src/video/preview/transformStyle.ts +22 -12
- package/src/video/preview/useDragOverlay.ts +92 -18
- package/src/video/preview/useEnginePlayback.ts +625 -0
- package/src/video/preview/useVideoPlayback.ts +211 -167
- package/src/video/sdrCurves.ts +56 -0
- package/src/video/shuttle.ts +159 -0
- package/src/video/source-preview.ts +66 -0
- package/src/video/timecode.ts +59 -0
- package/src/video/timeline/EditableSegment.tsx +1 -1
- package/src/video/timeline/Scrubber.tsx +46 -174
- package/src/video/timeline/SpeedControl.tsx +95 -0
- package/src/video/timeline/Timeline.tsx +1061 -328
- package/src/video/timeline/TimelineContext.ts +17 -10
- package/src/video/timeline/TrackGutter.tsx +560 -0
- package/src/video/timeline/TrackSettingsPopover.tsx +228 -0
- package/src/video/timeline/VolumeControl.tsx +113 -0
- package/src/video/timeline/__tests__/Timeline.backgroundClick.test.tsx +110 -0
- package/src/video/timeline/__tests__/Timeline.crossfade.test.tsx +104 -0
- package/src/video/timeline/__tests__/Timeline.fadeCurveMenu.test.tsx +174 -0
- package/src/video/timeline/__tests__/Timeline.keyframeDelete.test.tsx +273 -0
- package/src/video/timeline/__tests__/Timeline.keyframeFollow.test.tsx +215 -0
- package/src/video/timeline/__tests__/Timeline.keyframeMenu.test.tsx +253 -0
- package/src/video/timeline/__tests__/Timeline.keymap.test.tsx +372 -0
- package/src/video/timeline/__tests__/Timeline.subcutRegen.test.tsx +351 -0
- package/src/video/timeline/__tests__/TrackGutter.test.tsx +372 -0
- package/src/video/timeline/__tests__/_canvasSelect.test.tsx +273 -0
- package/src/video/timeline/__tests__/_canvasSelect.ts +414 -0
- package/src/video/timeline/__tests__/dragdrop-math.test.ts +135 -0
- package/src/video/timeline/__tests__/effectiveItemAudio.test.ts +50 -0
- package/src/video/timeline/__tests__/enabledTrackItems.test.ts +164 -0
- package/src/video/timeline/__tests__/moveItemAcrossTracks.test.ts +363 -0
- package/src/video/timeline/__tests__/multiSelectOps.test.ts +447 -0
- package/src/video/timeline/__tests__/placement.test.ts +278 -0
- package/src/video/timeline/__tests__/resizeWindowedItem.test.ts +140 -0
- package/src/video/timeline/__tests__/timeline-model.test.ts +576 -0
- package/src/video/timeline/__tests__/visualItemLabel.test.ts +69 -0
- package/src/video/timeline/canvas/TimelineCanvas.tsx +1447 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.drop.test.tsx +439 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.edgeScroll.test.tsx +346 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.panefill.test.tsx +122 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.pendingDrops.test.tsx +316 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.pointer.test.tsx +606 -0
- package/src/video/timeline/canvas/__tests__/TimelineCanvas.test.tsx +407 -0
- package/src/video/timeline/canvas/__tests__/clip-bands.test.ts +77 -0
- package/src/video/timeline/canvas/__tests__/draw.test.ts +2198 -0
- package/src/video/timeline/canvas/__tests__/fade-curve.test.ts +187 -0
- package/src/video/timeline/canvas/__tests__/filmstrips.test.ts +561 -0
- package/src/video/timeline/canvas/__tests__/hit-test.test.ts +818 -0
- package/src/video/timeline/canvas/__tests__/pending-drop.test.ts +210 -0
- package/src/video/timeline/canvas/__tests__/pointer-machine.test.ts +3358 -0
- package/src/video/timeline/canvas/__tests__/snap.test.ts +257 -0
- package/src/video/timeline/canvas/__tests__/viewport.test.ts +399 -0
- package/src/video/timeline/canvas/__tests__/waveforms.test.ts +946 -0
- package/src/video/timeline/canvas/clip-bands.ts +56 -0
- package/src/video/timeline/canvas/draw.ts +2187 -0
- package/src/video/timeline/canvas/fade-curve.ts +111 -0
- package/src/video/timeline/canvas/filmstrips.ts +418 -0
- package/src/video/timeline/canvas/hit-test.ts +501 -0
- package/src/video/timeline/canvas/keyframe-strip.ts +73 -0
- package/src/video/timeline/canvas/pointer-machine.ts +1828 -0
- package/src/video/timeline/canvas/snap.ts +232 -0
- package/src/video/timeline/canvas/viewport.ts +457 -0
- package/src/video/timeline/canvas/waveforms.ts +664 -0
- package/src/video/timeline/makeCaptionEdit.ts +5 -1
- package/src/video/timeline/multiSelectOps.ts +214 -55
- package/src/video/timeline/placement.ts +282 -0
- package/src/video/timeline/timeline-model.ts +919 -0
- package/src/video/timeline/useItemDragDrop.ts +151 -178
- package/src/video/timeline/useTimelineZoom.ts +25 -60
- package/src/video/timeline/utils.ts +0 -12
- package/src/video/use-report-context.ts +75 -0
- package/src/video/preview/OverlayPropsModal.tsx +0 -292
- package/src/video/preview/__tests__/OverlayPropsModal.test.tsx +0 -32
- package/src/video/timeline/AudioTrackRow.tsx +0 -404
- package/src/video/timeline/AudioWaveformLayer.tsx +0 -117
- package/src/video/timeline/CaptionTrackRow.tsx +0 -235
- package/src/video/timeline/PlayheadLine.tsx +0 -18
- package/src/video/timeline/TranscriptModal.tsx +0 -70
- package/src/video/timeline/TranscriptPanel.tsx +0 -273
- package/src/video/timeline/VisualTrackRow.tsx +0 -300
- package/src/video/timeline/__tests__/CaptionTrackRow.test.tsx +0 -241
- package/src/video/timeline/__tests__/PlayheadLine.test.tsx +0 -60
- package/src/video/timeline/__tests__/TranscriptModal.test.tsx +0 -41
- package/src/video/timeline/__tests__/TranscriptPanel.test.tsx +0 -184
- package/src/video/timeline/__tests__/useItemDragDrop.test.ts +0 -72
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The editor's ONE keydown registry — replaces the racing `document`-level
|
|
5
|
+
* listeners that used to live independently in VideoEditor (split, undo/
|
|
6
|
+
* redo) and Timeline (arrows, delete, enter, escape), each with its own
|
|
7
|
+
* slightly different typing-surface guard. `useKeymap` mounts exactly one
|
|
8
|
+
* `document` keydown listener per call site and evaluates bindings in order;
|
|
9
|
+
* the first whose `matches` + `guard` both pass wins.
|
|
10
|
+
*
|
|
11
|
+
* Space is explicitly OUT OF SCOPE — it stays owned by the playback hooks
|
|
12
|
+
* (`useVideoPlayback`/`useEnginePlayback`, both legacy and engine paths) so
|
|
13
|
+
* this registry can never race them. See the hard-coded check in
|
|
14
|
+
* `useKeymap` below.
|
|
15
|
+
*
|
|
16
|
+
* There are two call sites, mirroring where their bindings' dependencies
|
|
17
|
+
* actually live (see T9's report for the full reasoning):
|
|
18
|
+
* - Timeline owns arrows/delete/enter/escape — it mounts its own
|
|
19
|
+
* `useKeymap` with those bindings, in BOTH the pending and review
|
|
20
|
+
* surfaces (exactly where its old internal listener ran).
|
|
21
|
+
* - VideoEditor's ReviewSurface owns split/undo/redo plus every new T9
|
|
22
|
+
* binding (J/K/L shuttle, ripple-delete, command palette) — the
|
|
23
|
+
* editing-only bindings that only ever lived in the review surface.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/** INPUT / TEXTAREA / any contentEditable host — every migrated binding's
|
|
27
|
+
* typing-surface guard, ported verbatim from the handlers it replaces.
|
|
28
|
+
* `!!(...)` coerces to a real boolean: `el.isContentEditable` is a plain
|
|
29
|
+
* DOM boolean in every real browser, but partial event objects in tests
|
|
30
|
+
* (and jsdom's incomplete `isContentEditable` support) can leave it
|
|
31
|
+
* `undefined` — this keeps the guard's return type honest either way. */
|
|
32
|
+
export function isTypingTarget(e: KeyboardEvent): boolean {
|
|
33
|
+
const el = e.target as HTMLElement
|
|
34
|
+
return !!(el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface KeyBinding {
|
|
38
|
+
id: string
|
|
39
|
+
/** Palette label (also the tooltip/description). */
|
|
40
|
+
description: string
|
|
41
|
+
/** Display-only shortcut hint, e.g. ['⌘', 'Z']. Not used for matching. */
|
|
42
|
+
keyHint?: string[]
|
|
43
|
+
/** Whether this keydown event should fire the binding. */
|
|
44
|
+
matches: (e: KeyboardEvent) => boolean
|
|
45
|
+
/** Extra guard beyond the shared base guard (typing surface + modal-open),
|
|
46
|
+
* e.g. "only when there's a selection" or "only when totalDuration > 0". */
|
|
47
|
+
guard?: () => boolean
|
|
48
|
+
/** Defaults to true. */
|
|
49
|
+
preventDefault?: boolean
|
|
50
|
+
action: (e: KeyboardEvent) => void
|
|
51
|
+
/** Exclude from the command palette — context-only bindings (arrow frame-
|
|
52
|
+
* step, Enter/Escape marker placement) aren't "commands" you run once. */
|
|
53
|
+
paletteHidden?: boolean
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface UseKeymapOptions {
|
|
57
|
+
/** True while any dialog/palette is open — suppresses every binding. */
|
|
58
|
+
modalOpen?: boolean
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Mounts one `document` keydown listener for the given bindings for the
|
|
63
|
+
* lifetime of the component. Bindings and options are read through refs so
|
|
64
|
+
* the listener's identity never churns — callers can pass fresh closures
|
|
65
|
+
* every render without re-registering.
|
|
66
|
+
*/
|
|
67
|
+
export function useKeymap(bindings: KeyBinding[], options: UseKeymapOptions = {}) {
|
|
68
|
+
const bindingsRef = useRef(bindings)
|
|
69
|
+
bindingsRef.current = bindings
|
|
70
|
+
const optionsRef = useRef(options)
|
|
71
|
+
optionsRef.current = options
|
|
72
|
+
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
75
|
+
// Never owned by the keymap — see file header.
|
|
76
|
+
if (e.code === 'Space' || e.key === ' ') return
|
|
77
|
+
if (isTypingTarget(e)) return
|
|
78
|
+
if (optionsRef.current.modalOpen) return
|
|
79
|
+
|
|
80
|
+
for (const binding of bindingsRef.current) {
|
|
81
|
+
if (!binding.matches(e)) continue
|
|
82
|
+
if (binding.guard && !binding.guard()) continue
|
|
83
|
+
if (binding.preventDefault !== false) e.preventDefault()
|
|
84
|
+
binding.action(e)
|
|
85
|
+
return
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
document.addEventListener('keydown', onKeyDown)
|
|
89
|
+
return () => document.removeEventListener('keydown', onKeyDown)
|
|
90
|
+
}, [])
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── Combo matchers — small helpers so bindings read declaratively ──────────
|
|
94
|
+
|
|
95
|
+
const mod = (e: KeyboardEvent) => !!(e.metaKey || e.ctrlKey)
|
|
96
|
+
|
|
97
|
+
/** Bare letter key, no modifier check — reproduces the original S-to-split
|
|
98
|
+
* handler's behavior verbatim (it only ever checked `e.key`). */
|
|
99
|
+
export function matchesKey(key: string) {
|
|
100
|
+
const lower = key.toLowerCase()
|
|
101
|
+
return (e: KeyboardEvent) => e.key.toLowerCase() === lower
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Letter key with NO modifier held (mod/alt) — used for the new J/K/L
|
|
105
|
+
* shuttle bindings so they don't fire alongside Cmd/Ctrl/Alt combos. */
|
|
106
|
+
export function matchesPlainKey(key: string) {
|
|
107
|
+
const lower = key.toLowerCase()
|
|
108
|
+
return (e: KeyboardEvent) => e.key.toLowerCase() === lower && !mod(e) && !e.altKey
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function matchesUndo(e: KeyboardEvent): boolean {
|
|
112
|
+
return mod(e) && e.key.toLowerCase() === 'z' && !e.shiftKey
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function matchesRedo(e: KeyboardEvent): boolean {
|
|
116
|
+
return mod(e) && ((e.key.toLowerCase() === 'z' && e.shiftKey) || e.key.toLowerCase() === 'y')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function matchesModKey(key: string) {
|
|
120
|
+
const lower = key.toLowerCase()
|
|
121
|
+
return (e: KeyboardEvent) => mod(e) && e.key.toLowerCase() === lower
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Mod+Alt+key, e.g. Cmd+Opt+V for paste-attributes. NOTE: `matchesModKey`
|
|
125
|
+
* does NOT exclude `altKey`, so a mod+alt chord matches BOTH this and the
|
|
126
|
+
* bare mod+key binding for the same letter — whichever binding is
|
|
127
|
+
* registered FIRST in a `useKeymap` array wins (first match wins), so a
|
|
128
|
+
* mod+alt binding must always be listed before its mod-only sibling. */
|
|
129
|
+
export function matchesModAltKey(key: string) {
|
|
130
|
+
const lower = key.toLowerCase()
|
|
131
|
+
return (e: KeyboardEvent) => mod(e) && e.altKey && e.key.toLowerCase() === lower
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Delete/Backspace WITHOUT Shift — reserves Shift+Delete for ripple-delete. */
|
|
135
|
+
export function matchesDelete(e: KeyboardEvent): boolean {
|
|
136
|
+
return (e.key === 'Delete' || e.key === 'Backspace') && !e.shiftKey
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function matchesShiftDelete(e: KeyboardEvent): boolean {
|
|
140
|
+
return (e.key === 'Delete' || e.key === 'Backspace') && e.shiftKey
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export const matchesEscape = matchesKey('Escape')
|
|
144
|
+
export const matchesEnter = matchesKey('Enter')
|
|
145
|
+
export const matchesArrowLeft = matchesKey('ArrowLeft')
|
|
146
|
+
export const matchesArrowRight = matchesKey('ArrowRight')
|