changes alt to ctrl for copying
This commit is contained in:
@@ -3,8 +3,9 @@
|
||||
## Coding Style
|
||||
|
||||
- **Never use single-letter abbreviations in lambdas.** Use descriptive names like `entry`, `text`, `value`, `event`, etc.
|
||||
- **Use types from `@opendaw/lib-std` instead of inline union types:**
|
||||
- **Use types and functions from `@opendaw/lib-std` instead of inline checks:**
|
||||
- Use `Optional<T>` instead of `T | undefined`
|
||||
- Use `Nullable<T>` instead of `T | null`
|
||||
- Use `isDefined()` instead of `!== undefined`
|
||||
- Use `isDefined(value)` instead of `value !== undefined` or `value !== null`
|
||||
- Use `!isDefined(value)` instead of `value === undefined` or `value === null`
|
||||
- Never write `| null` or `| undefined` inline - always use the lib-std types.
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Terminable
|
||||
} from "@opendaw/lib-std"
|
||||
import {AnyDragData, DragFile} from "@/ui/AnyDragData"
|
||||
import {Events, Keyboard} from "@opendaw/lib-dom"
|
||||
import {Events} from "@opendaw/lib-dom"
|
||||
|
||||
export namespace DragAndDrop {
|
||||
let dragging: Option<AnyDragData> = Option.None
|
||||
@@ -86,7 +86,7 @@ export namespace DragAndDrop {
|
||||
some: data => {
|
||||
if (process.drag(event, data)) {
|
||||
event.preventDefault()
|
||||
dataTransfer.dropEffect = Keyboard.isCopyKey(event) || data.copy === true ? "copy" : "move"
|
||||
dataTransfer.dropEffect = event.altKey || data.copy === true ? "copy" : "move"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -71,8 +71,7 @@ const createInstrumentList = (lifecycle: Lifecycle, project: Project) => (
|
||||
lifecycle.ownAll(
|
||||
DragAndDrop.installSource(element, () => ({
|
||||
type: "instrument",
|
||||
device: key as InstrumentFactories.Keys,
|
||||
copy: true
|
||||
device: key as InstrumentFactories.Keys
|
||||
} satisfies DragDevice)),
|
||||
TextTooltip.simple(element, () => {
|
||||
const {bottom, left} = element.getBoundingClientRect()
|
||||
|
||||
@@ -85,7 +85,7 @@ export const BusySlot = ({
|
||||
<Icon symbol={IconSymbol.Focus} className="edit"/>
|
||||
)
|
||||
const element: HTMLElement = (
|
||||
<div className={className} tabIndex={-1} draggable>
|
||||
<div className={className} tabIndex={-1} data-slot-index={octave.getValue() * 12 + semitone}>
|
||||
{header}
|
||||
<div className="waveform">
|
||||
{waveform}
|
||||
@@ -150,12 +150,19 @@ export const BusySlot = ({
|
||||
}
|
||||
}
|
||||
})),
|
||||
SlotDragAndDrop.install({
|
||||
octave.catchupAndSubscribe(owner => {
|
||||
const slotIndex = owner.getValue() * 12 + semitone
|
||||
element.setAttribute("data-slot-index", String(slotIndex))
|
||||
}),
|
||||
SlotDragAndDrop.installSource({
|
||||
element,
|
||||
sample,
|
||||
getSlotIndex: () => octave.getValue() * 12 + semitone
|
||||
}),
|
||||
SlotDragAndDrop.installTarget({
|
||||
element,
|
||||
project: service.project,
|
||||
sample: Option.wrap(sample),
|
||||
octave,
|
||||
semitone
|
||||
getSlotIndex: () => octave.getValue() * 12 + semitone
|
||||
}),
|
||||
Events.subscribe(iconEdit, "click", () => userEditingManager.audioUnit.edit(sample.box)),
|
||||
Events.subscribe(header, "pointerdown", (event: PointerEvent) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import css from "./EmptySlot.sass?inline"
|
||||
import {Html} from "@opendaw/lib-dom"
|
||||
import {int, Lifecycle, ObservableValue, Option} from "@opendaw/lib-std"
|
||||
import {int, Lifecycle, ObservableValue} from "@opendaw/lib-std"
|
||||
import {createElement} from "@opendaw/lib-jsx"
|
||||
import {StudioService} from "@/service/StudioService.ts"
|
||||
import {SampleSelector} from "@/ui/devices/SampleSelector"
|
||||
@@ -30,7 +30,7 @@ export const EmptySlot = (
|
||||
</div>
|
||||
)
|
||||
const element: HTMLElement = (
|
||||
<div className={className}>
|
||||
<div className={className} data-slot-index={octave.getValue() * 12 + semitone}>
|
||||
<header/>
|
||||
{browseButton}
|
||||
<footer>
|
||||
@@ -39,12 +39,14 @@ export const EmptySlot = (
|
||||
</div>
|
||||
)
|
||||
lifecycle.ownAll(
|
||||
SlotDragAndDrop.install({
|
||||
octave.catchupAndSubscribe(owner => {
|
||||
const slotIndex = owner.getValue() * 12 + semitone
|
||||
element.setAttribute("data-slot-index", String(slotIndex))
|
||||
}),
|
||||
SlotDragAndDrop.installTarget({
|
||||
element,
|
||||
project,
|
||||
sample: Option.None,
|
||||
octave,
|
||||
semitone
|
||||
getSlotIndex: () => octave.getValue() * 12 + semitone
|
||||
}),
|
||||
sampleSelector.configureDrop(element),
|
||||
sampleSelector.configureBrowseClick(browseButton),
|
||||
|
||||
+92
-77
@@ -1,84 +1,99 @@
|
||||
import {asInstanceOf, int, ObservableValue, Option, Terminable, UUID} from "@opendaw/lib-std"
|
||||
import {DragAndDrop} from "@/ui/DragAndDrop"
|
||||
import {AnyDragData} from "@/ui/AnyDragData"
|
||||
import {PlayfieldSampleBox} from "@opendaw/studio-boxes"
|
||||
import {Keyboard} from "@opendaw/lib-dom"
|
||||
import {PlayfieldSampleBoxAdapter} from "@opendaw/studio-adapters"
|
||||
import {byte, int, Option, Terminable, UUID} from "@opendaw/lib-std"
|
||||
import {AudioFileBox, PlayfieldDeviceBox, PlayfieldSampleBox} from "@opendaw/studio-boxes"
|
||||
import {Project} from "@opendaw/studio-core"
|
||||
import {DragAndDrop} from "@/ui/DragAndDrop"
|
||||
import {AnyDragData, DragDevice} from "@/ui/AnyDragData"
|
||||
import {PlayfieldSampleBoxAdapter} from "@opendaw/studio-adapters"
|
||||
|
||||
export namespace SlotDragAndDrop {
|
||||
type Construct = {
|
||||
element: HTMLElement
|
||||
project: Project
|
||||
sample: Option<PlayfieldSampleBoxAdapter>
|
||||
octave: ObservableValue<int>
|
||||
semitone: int
|
||||
const findSampleByIndex = (project: Project, index: int): Option<PlayfieldSampleBox> => {
|
||||
for (const box of project.boxGraph.boxes()) {
|
||||
if (box instanceof PlayfieldSampleBox && box.index.getValue() === index) {
|
||||
return Option.wrap(box)
|
||||
}
|
||||
}
|
||||
return Option.None
|
||||
}
|
||||
|
||||
export const install = ({element, project, sample, octave, semitone}: Construct): Terminable => {
|
||||
const uuid = sample.mapOr(({address}) => address.toString(), "")
|
||||
return Terminable.many(
|
||||
DragAndDrop.installSource(element, () => ({
|
||||
type: "playfield-slot",
|
||||
index: octave.getValue() * 12 + semitone,
|
||||
uuid
|
||||
})),
|
||||
DragAndDrop.installTarget(element, {
|
||||
drag: (_event: DragEvent, dragData: AnyDragData): boolean => {
|
||||
if (dragData.type !== "playfield-slot") {return false}
|
||||
return uuid !== dragData.uuid
|
||||
},
|
||||
drop: (event: DragEvent, dragData: AnyDragData): void => {
|
||||
if (dragData.type !== "playfield-slot") {return}
|
||||
if (uuid === dragData.uuid) {return}
|
||||
const {editing, boxGraph, boxAdapters} = project
|
||||
const copyMode = Keyboard.isCopyKey(event)
|
||||
const resolveBox = (uuid: string): Option<PlayfieldSampleBox> => uuid === ""
|
||||
? Option.None
|
||||
: boxGraph
|
||||
.findBox(UUID.parse(uuid))
|
||||
.assert(() => `Could not find box for ${uuid}`)
|
||||
.map(box => asInstanceOf(box, PlayfieldSampleBox))
|
||||
const target = resolveBox(uuid)
|
||||
const source = resolveBox(dragData.uuid)
|
||||
const newIndex = octave.getValue() * 12 + semitone
|
||||
if (target.isEmpty()) {
|
||||
if (source.nonEmpty()) {
|
||||
if (copyMode) {
|
||||
editing.modify(() => boxAdapters.adapterFor(source.unwrap(), PlayfieldSampleBoxAdapter)
|
||||
.copyToIndex(newIndex))
|
||||
} else {
|
||||
editing.modify(() => source.unwrap().index.setValue(newIndex))
|
||||
}
|
||||
} else {
|
||||
// else: move or copy empty slot to empty slot has no effect
|
||||
}
|
||||
} else {
|
||||
if (source.isEmpty()) {
|
||||
if (copyMode) {
|
||||
editing.modify(() => target.unwrap().delete())
|
||||
} else {
|
||||
editing.modify(() => target.unwrap().index.setValue(dragData.index))
|
||||
}
|
||||
} else {
|
||||
editing.modify(() => {
|
||||
if (copyMode) {
|
||||
target.unwrap().delete()
|
||||
boxAdapters.adapterFor(source.unwrap(), PlayfieldSampleBoxAdapter)
|
||||
.copyToIndex(newIndex)
|
||||
} else {
|
||||
source.unwrap().index.setValue(newIndex)
|
||||
target.unwrap().index.setValue(dragData.index)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
enter: (allowDrop: boolean): void => {
|
||||
if (allowDrop) {element.classList.add("swap")}
|
||||
},
|
||||
leave: (): void => element.classList.remove("swap")
|
||||
const executeCopy = (project: Project, sourceIndex: int, targetIndex: int): void => {
|
||||
if (sourceIndex === targetIndex) {return}
|
||||
const {editing, boxGraph} = project
|
||||
const source = findSampleByIndex(project, sourceIndex)
|
||||
const target = findSampleByIndex(project, targetIndex)
|
||||
source.ifSome(sourceBox => {
|
||||
editing.modify(() => {
|
||||
// If target has a sample, delete it first
|
||||
target.ifSome(targetBox => targetBox.delete())
|
||||
// Get the source file (AudioFileBox)
|
||||
const sourceFile = sourceBox.file.targetVertex
|
||||
.map(vertex => vertex.box instanceof AudioFileBox ? vertex.box : null)
|
||||
// Get the device box and its samples hub
|
||||
const deviceBox = sourceBox.device.targetVertex
|
||||
.map(vertex => vertex.box instanceof PlayfieldDeviceBox ? vertex.box : null)
|
||||
if (sourceFile.nonEmpty() && deviceBox.nonEmpty()) {
|
||||
PlayfieldSampleBox.create(boxGraph, UUID.generate(), box => {
|
||||
box.file.refer(sourceFile.unwrap())
|
||||
box.device.refer(deviceBox.unwrap().samples)
|
||||
box.index.setValue(targetIndex)
|
||||
})
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const executeSwap = (project: Project, sourceIndex: int, targetIndex: int): void => {
|
||||
if (sourceIndex === targetIndex) {return}
|
||||
const {editing} = project
|
||||
const source = findSampleByIndex(project, sourceIndex)
|
||||
const target = findSampleByIndex(project, targetIndex)
|
||||
editing.modify(() => {
|
||||
if (source.nonEmpty() && target.isEmpty()) {
|
||||
source.unwrap().index.setValue(targetIndex)
|
||||
} else if (source.isEmpty() && target.nonEmpty()) {
|
||||
target.unwrap().index.setValue(sourceIndex)
|
||||
} else if (source.nonEmpty() && target.nonEmpty()) {
|
||||
source.unwrap().index.setValue(targetIndex)
|
||||
target.unwrap().index.setValue(sourceIndex)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type SourceConstruct = {
|
||||
element: HTMLElement
|
||||
sample: PlayfieldSampleBoxAdapter
|
||||
getSlotIndex: () => int
|
||||
}
|
||||
|
||||
export const installSource = ({element, sample, getSlotIndex}: SourceConstruct): Terminable => {
|
||||
return DragAndDrop.installSource(element, () => ({
|
||||
type: "playfield-slot",
|
||||
index: getSlotIndex() as byte,
|
||||
uuid: sample.address.uuid.toString()
|
||||
} satisfies DragDevice))
|
||||
}
|
||||
|
||||
type TargetConstruct = {
|
||||
element: HTMLElement
|
||||
project: Project
|
||||
getSlotIndex: () => int
|
||||
}
|
||||
|
||||
export const installTarget = ({element, project, getSlotIndex}: TargetConstruct): Terminable => {
|
||||
return DragAndDrop.installTarget(element, {
|
||||
drag: (_event: DragEvent, data: AnyDragData): boolean => {
|
||||
return data.type === "playfield-slot" && data.index !== getSlotIndex()
|
||||
},
|
||||
drop: (event: DragEvent, data: AnyDragData): void => {
|
||||
if (data.type !== "playfield-slot") {return}
|
||||
const targetIndex = getSlotIndex()
|
||||
if (event.altKey) {
|
||||
executeCopy(project, data.index, targetIndex)
|
||||
} else {
|
||||
executeSwap(project, data.index, targetIndex)
|
||||
}
|
||||
},
|
||||
enter: (allowDrop: boolean) => element.classList.toggle("drop-target", allowDrop),
|
||||
leave: () => element.classList.remove("drop-target")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export class NoteMoveModifier implements NoteModifier {
|
||||
selectedModifyStrategy(): NoteModifyStrategy {return this.#selectedModifyStrategy}
|
||||
unselectedModifyStrategy(): NoteModifyStrategy {return NoteModifyStrategy.Identity}
|
||||
|
||||
update({clientX, clientY, altKey, shiftKey}: Dragging.Event): void {
|
||||
update({clientX, clientY, ctrlKey, shiftKey}: Dragging.Event): void {
|
||||
const clientRect = this.#element.getBoundingClientRect()
|
||||
const deltaPitch: int = this.#positioner
|
||||
.yToPitch(clientY - clientRect.top) - this.#pointerPitch
|
||||
@@ -117,8 +117,8 @@ export class NoteMoveModifier implements NoteModifier {
|
||||
this.#pitchChanged.notify(this.#reference.pitch + deltaPitch)
|
||||
change = true
|
||||
}
|
||||
if (this.#copy !== altKey) {
|
||||
this.#copy = altKey
|
||||
if (this.#copy !== ctrlKey) {
|
||||
this.#copy = ctrlKey
|
||||
change = true
|
||||
}
|
||||
if (change) {this.#dispatchChange()}
|
||||
|
||||
@@ -38,7 +38,7 @@ import {NoteEventBox} from "@opendaw/studio-boxes"
|
||||
import {NoteCreateModifier} from "@/ui/timeline/editors/notes/NoteCreateModifier.ts"
|
||||
import {CanvasPainter} from "@/ui/canvas/painter.ts"
|
||||
import {NoteEventOwnerReader} from "@/ui/timeline/editors/EventOwnerReader.ts"
|
||||
import {CssUtils, Dragging, Events, Html, Keyboard, ShortcutManager} from "@opendaw/lib-dom"
|
||||
import {CssUtils, Dragging, Events, Html, ShortcutManager} from "@opendaw/lib-dom"
|
||||
import {PPQN, ppqn} from "@opendaw/lib-dsp"
|
||||
import {Surface} from "@/ui/surface/Surface"
|
||||
import {NoteEditorShortcuts} from "@/ui/shortcuts/NoteEditorShortcuts"
|
||||
@@ -116,7 +116,7 @@ export const PitchEditor = ({
|
||||
}))
|
||||
}, {permanentUpdates: true}),
|
||||
Dragging.attach(canvas, event => {
|
||||
if (!Keyboard.isControlKey(event)) {return Option.None}
|
||||
if (!event.altKey) {return Option.None}
|
||||
const target = capturing.captureEvent(event)
|
||||
if (target !== null) {return Option.None}
|
||||
const clientRect = canvas.getBoundingClientRect()
|
||||
@@ -281,7 +281,7 @@ export const PitchEditor = ({
|
||||
modifyContext.subscribeUpdate(renderer.requestUpdate),
|
||||
Events.subscribe(canvas, "pointermove", event => {
|
||||
canvas.focus({preventScroll: true})
|
||||
if (Keyboard.isControlKey(event) && event.buttons === 0) {
|
||||
if (event.altKey && event.buttons === 0) {
|
||||
updatePreview()
|
||||
}
|
||||
}),
|
||||
@@ -291,7 +291,7 @@ export const PitchEditor = ({
|
||||
}),
|
||||
installCursor(canvas, capturing, {
|
||||
get: (target, event) =>
|
||||
target === null ? Keyboard.isControlKey(event) && event.buttons === 0
|
||||
target === null ? event.altKey && event.buttons === 0
|
||||
? Cursor.Pencil
|
||||
: null : CursorMap[target.type]
|
||||
}),
|
||||
@@ -300,7 +300,7 @@ export const PitchEditor = ({
|
||||
renderer.requestUpdate()
|
||||
}),
|
||||
Events.subscribe(canvas, "keydown", event => {
|
||||
if (Keyboard.isControlKey(event)) {
|
||||
if (event.altKey) {
|
||||
updatePreview()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ import {installValueInput} from "@/ui/timeline/editors/ValueInput.ts"
|
||||
import {ValueEventOwnerReader} from "@/ui/timeline/editors/EventOwnerReader.ts"
|
||||
import {installEditorBody} from "../EditorBody"
|
||||
import {ValueContentDurationModifier} from "./ValueContentDurationModifier"
|
||||
import {Dragging, Events, Html, Keyboard, ShortcutManager} from "@opendaw/lib-dom"
|
||||
import {Dragging, Events, Html, ShortcutManager} from "@opendaw/lib-dom"
|
||||
import {ValueTooltip} from "./ValueTooltip"
|
||||
import {ValueEventEditing} from "./ValueEventEditing"
|
||||
import {TimelineRange} from "@opendaw/studio-core"
|
||||
@@ -107,7 +107,7 @@ export const ValueEditor = ({lifecycle, service, range, snapping, eventMapping,
|
||||
let lastDownTime = 0
|
||||
return (event: PointerEvent) => {
|
||||
const target: Nullable<ValueCaptureTarget> = capturing.captureEvent(event)
|
||||
const controlKey = Keyboard.isControlKey(event)
|
||||
const altKey = event.altKey
|
||||
const now = Date.now()
|
||||
const dblclck = now - lastDownTime < Events.DOUBLE_DOWN_THRESHOLD
|
||||
lastDownTime = now
|
||||
@@ -150,7 +150,7 @@ export const ValueEditor = ({lifecycle, service, range, snapping, eventMapping,
|
||||
}
|
||||
}
|
||||
if (target === null) {
|
||||
if (controlKey) {
|
||||
if (altKey) {
|
||||
return modifyContext.startModifier(ValuePaintModifier.create({
|
||||
element: canvas,
|
||||
reader,
|
||||
@@ -260,7 +260,7 @@ export const ValueEditor = ({lifecycle, service, range, snapping, eventMapping,
|
||||
installCursor(canvas, capturing, {
|
||||
get: (target, event) => {
|
||||
const onCurve = target?.type === "curve" || target?.type === "midpoint"
|
||||
const controlKey = Keyboard.isControlKey(event) && event.buttons === 0
|
||||
const controlKey = event.altKey && event.buttons === 0
|
||||
if (target === null) {
|
||||
if (controlKey) {return Cursor.Pencil}
|
||||
} else if (target.type === "event") {
|
||||
|
||||
@@ -125,7 +125,7 @@ export class ValueMoveModifier implements ValueModifier {
|
||||
}
|
||||
readContentDuration(owner: ValueEventOwnerReader): number {return owner.contentDuration}
|
||||
update(event: Dragging.Event): void {
|
||||
const {clientX, clientY, altKey, ctrlKey: freezeMode, shiftKey} = event
|
||||
const {clientX, clientY, altKey: freezeMode, ctrlKey, shiftKey} = event
|
||||
const clientRect = this.#element.getBoundingClientRect()
|
||||
const localX = clientX - clientRect.left
|
||||
const localY = clientY - clientRect.top
|
||||
@@ -159,8 +159,8 @@ export class ValueMoveModifier implements ValueModifier {
|
||||
this.#deltaValue = deltaValue
|
||||
change = true
|
||||
}
|
||||
if (this.#copy !== altKey) {
|
||||
this.#copy = altKey
|
||||
if (this.#copy !== ctrlKey) {
|
||||
this.#copy = ctrlKey
|
||||
change = true
|
||||
}
|
||||
if (this.#snapValue !== snapValue) {
|
||||
|
||||
@@ -101,7 +101,7 @@ export class RegionMoveModifier implements RegionModifier {
|
||||
selectedModifyStrategy(): RegionModifyStrategy {return this.#selectedModifyStrategy}
|
||||
unselectedModifyStrategy(): RegionModifyStrategy {return this.#unselectedModifyStrategy}
|
||||
|
||||
update({clientX, clientY, altKey, shiftKey}: Dragging.Event): void {
|
||||
update({clientX, clientY, ctrlKey, shiftKey}: Dragging.Event): void {
|
||||
const adapters = this.#selection.selected().filter(adapter => adapter.trackBoxAdapter.nonEmpty())
|
||||
if (adapters.length === 0) {return}
|
||||
const maxIndex = this.#manager.numTracks() - 1
|
||||
@@ -123,8 +123,8 @@ export class RegionMoveModifier implements RegionModifier {
|
||||
this.#deltaIndex = deltaIndex
|
||||
change = true
|
||||
}
|
||||
if (this.#copy !== altKey) {
|
||||
this.#copy = altKey
|
||||
if (this.#copy !== ctrlKey) {
|
||||
this.#copy = ctrlKey
|
||||
change = true
|
||||
}
|
||||
if (this.#mirroredCopy !== shiftKey) {
|
||||
|
||||
@@ -140,7 +140,7 @@ export const RegionsArea = ({lifecycle, service, manager, scrollModel, scrollCon
|
||||
} else {
|
||||
return Option.None
|
||||
}
|
||||
} else if (target.type === "region" && Keyboard.isControlKey(event)) {
|
||||
} else if (target.type === "region" && event.altKey) {
|
||||
if (!regionSelection.isSelected(target.region)) {
|
||||
regionSelection.deselectAll()
|
||||
regionSelection.select(target.region)
|
||||
@@ -222,13 +222,13 @@ export const RegionsArea = ({lifecycle, service, manager, scrollModel, scrollCon
|
||||
get: (target: Nullable<RegionCaptureTarget>, event: CursorEvent) => {
|
||||
const units = snapping.xToUnitRound(event.clientX - element.getBoundingClientRect().left)
|
||||
markerPosition.setValue(
|
||||
Keyboard.isControlKey(event) && target !== null && target.type === "region"
|
||||
event.altKey && target !== null && target.type === "region"
|
||||
&& target.region.position < units && units < target.region.complete
|
||||
? units
|
||||
: null)
|
||||
return target === null || target.type === "track"
|
||||
? null
|
||||
: Keyboard.isControlKey(event)
|
||||
: event.altKey
|
||||
? Cursor.Scissors
|
||||
: CursorMap[target.part]
|
||||
},
|
||||
|
||||
@@ -5,7 +5,6 @@ export namespace Keyboard {
|
||||
ctrlKey: boolean,
|
||||
metaKey: boolean
|
||||
}) => Browser.isMacOS() ? metaKey : ctrlKey
|
||||
export const isCopyKey = ({altKey}: { altKey: boolean }) => altKey
|
||||
export const isDelete = (event: KeyboardEvent) => event.code === "Delete" || event.code === "Backspace"
|
||||
export const isSelectAll = (event: KeyboardEvent) => isControlKey(event) && !event.shiftKey && event.code === "KeyA"
|
||||
export const isDeselectAll = (event: KeyboardEvent) => isControlKey(event) && event.shiftKey && event.code === "KeyA"
|
||||
|
||||
Reference in New Issue
Block a user