tool-with-ui
Steve Ruiz 2024-05-16 15:06:48 -07:00
rodzic 706dfb1055
commit 001f9e70c2
12 zmienionych plików z 271 dodań i 282 usunięć

Wyświetl plik

@ -18,6 +18,7 @@ import {
SharedStyleMap,
TLArrowShape,
TLClickEventInfo,
TLCursorType,
TLEventInfo,
TLFrameShape,
TLGroupShape,
@ -28,12 +29,14 @@ import {
TLNoteShape,
TLPageId,
TLRotationSnapshot,
TLSelectionHandle,
TLShape,
TLShapeId,
TLShapePartial,
TLTextShape,
ToolUtil,
Vec,
VecLike,
applyRotationToSnapshotShapes,
createShapeId,
debugFlags,
@ -42,6 +45,7 @@ import {
getRotationSnapshot,
intersectLineSegmentPolygon,
isPageId,
isShapeId,
kickoutOccludedShapes,
moveCameraWhenCloseToEdge,
pointInPolygon,
@ -62,21 +66,13 @@ import { DragAndDropManager } from './selection-logic/DragAndDropManager'
import {
MIN_CROP_SIZE,
ShapeWithCrop,
getCroppingSnapshot,
getTranslateCroppedImageChange,
} from './selection-logic/cropping'
import { cursorTypeMap } from './selection-logic/cursorTypeMap'
import { getCroppingSnapshot } from './selection-logic/getCroppingSnapshot'
import { getHitShapeOnCanvasPointerDown } from './selection-logic/getHitShapeOnCanvasPointerDown'
import { getNoteForPit } from './selection-logic/getNoteForPits'
import { getShouldEnterCropMode } from './selection-logic/getShouldEnterCropModeOnPointerDown'
import { getTextLabels } from './selection-logic/getTextLabels'
import { isOverArrowLabel } from './selection-logic/isOverArrowLabel'
import { isPointInRotatedSelectionBounds } from './selection-logic/isPointInRotatedSelectionBounds'
import { NOTE_CENTER_OFFSET } from './selection-logic/noteHelpers'
import { NOTE_CENTER_OFFSET, getNoteForPit } from './selection-logic/noteHelpers'
import { ResizingSnapshot, getResizingSnapshot } from './selection-logic/resizing'
import { getRotationFromPointerPosition } from './selection-logic/rotating'
import { zoomToShapeIfOffscreen } from './selection-logic/selectHelpers'
import { selectOnCanvasPointerUp } from './selection-logic/selectOnCanvasPointerUp'
import {
TranslatingSnapshot,
getTranslatingSnapshot,
@ -401,7 +397,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
} else if (info.name === 'pointer_down') {
if (editor.getIsMenuOpen()) return
const shouldEnterCropMode = info.ctrlKey && getShouldEnterCropMode(editor)
const shouldEnterCropMode = info.ctrlKey && this.getShouldEnterCropMode()
if (info.ctrlKey && !shouldEnterCropMode) {
// On Mac, you can right click using the Control keys + Click.
@ -430,7 +426,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
case 'canvas': {
// Check to see if we hit any shape under the pointer; if so,
// handle this as a pointer down on the shape instead of the canvas
const hitShape = getHitShapeOnCanvasPointerDown(editor)
const hitShape = this.getHitShapeOnCanvasPointerDown()
if (hitShape && !hitShape.isLocked) {
this.onEvent({
...info,
@ -451,7 +447,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
(onlySelectedShape &&
!editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape))
) {
if (isPointInRotatedSelectionBounds(editor, currentPagePoint)) {
if (this.isPointInRotatedSelectionBounds(currentPagePoint)) {
this.onEvent({
...info,
target: 'selection',
@ -465,24 +461,32 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
}
case 'shape': {
const { shape } = info
if (isOverArrowLabel(editor, shape)) {
// We're moving the label on a shape.
const geometry = editor.getShapeGeometry<Group2d>(shape)
const labelGeometry = geometry.children[1]
if (!labelGeometry) {
throw Error(`Expected to find an arrow label geometry for shape: ${shape.id}`)
}
const { currentPagePoint } = editor.inputs
const pointInShapeSpace = editor.getPointInShapeSpace(shape, currentPagePoint)
this.setState({
name: 'pointing_arrow_label',
shapeId: shape.id,
wasAlreadySelected: editor.getOnlySelectedShapeId() === shape.id,
didDrag: false,
labelDragOffset: Vec.Sub(labelGeometry.center, pointInShapeSpace),
})
break
// Check if we're over an arrow label.
// How should we handle multiple labels? Do shapes ever have multiple labels?
// Knowing what we know about arrows... if the shape has no text in its label,
// then the label geometry should not be there.
if (editor.isShapeOfType<TLArrowShape>(shape, 'arrow')) {
const labelGeometry = editor.getShapeGeometry<Group2d>(shape).children[1]
const pointInShapeSpace = editor.getPointInShapeSpace(
shape,
editor.inputs.currentPagePoint
)
if (labelGeometry && pointInPolygon(pointInShapeSpace, labelGeometry.vertices)) {
// We're moving the label on a shape.
const labelGeometry = editor.getShapeGeometry<Group2d>(shape).children[1]
if (!labelGeometry) {
throw Error(`Expected to find an arrow label geometry for shape: ${shape.id}`)
}
this.setState({
name: 'pointing_arrow_label',
shapeId: shape.id,
wasAlreadySelected: editor.getOnlySelectedShapeId() === shape.id,
didDrag: false,
labelDragOffset: Vec.Sub(labelGeometry.center, pointInShapeSpace),
})
break
}
}
if (editor.isShapeOrAncestorLocked(shape)) {
@ -598,7 +602,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
if (hitShape) {
if (editor.isShapeOfType<TLGroupShape>(hitShape, 'group')) {
// Probably select the shape
selectOnCanvasPointerUp(editor)
this.selectOnCanvasPointerUp()
return
} else {
const parent = editor.getShape(hitShape.parentId)
@ -611,7 +615,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
} else {
// The shape is the child of some group other than our current
// focus layer. We should probably select the group instead.
selectOnCanvasPointerUp(editor)
this.selectOnCanvasPointerUp()
return
}
}
@ -777,7 +781,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
(onlySelectedShape &&
!editor.getShapeUtil(onlySelectedShape).hideSelectionBoundsBg(onlySelectedShape))
) {
if (isPointInRotatedSelectionBounds(editor, currentPagePoint)) {
if (this.isPointInRotatedSelectionBounds(currentPagePoint)) {
this.onEvent({
...info,
target: 'selection',
@ -892,7 +896,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
}
// If the only selected shape is croppable, then begin cropping it
if (getShouldEnterCropMode(editor)) {
if (this.getShouldEnterCropMode()) {
this.setState({ name: 'crop_idle' })
}
break
@ -921,7 +925,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
} else if (editor.inputs.isDragging) {
this.startBrushing(state)
} else if (info.name === 'pointer_up') {
selectOnCanvasPointerUp(editor)
this.selectOnCanvasPointerUp()
this.setState({ name: 'idle' })
} else if (info.name === 'cancel' || info.name === 'complete' || info.name === 'interrupt') {
this.setState({ name: 'idle' })
@ -1220,7 +1224,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
// enter
}
} else if (info.name === 'pointer_up') {
selectOnCanvasPointerUp(editor)
this.selectOnCanvasPointerUp()
this.setState({ name: 'idle' })
} else if (info.name === 'double_click') {
const hoveredShape = editor.getHoveredShape()
@ -1614,7 +1618,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
switch (info.target) {
case 'canvas': {
const hitShape = getHitShapeOnCanvasPointerDown(editor)
const hitShape = this.getHitShapeOnCanvasPointerDown()
if (hitShape && !editor.isShapeOfType<TLGroupShape>(hitShape, 'group')) {
this.onEvent({
...info,
@ -2119,7 +2123,7 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
this.setState({ ...state, hitShapeForPointerUp: null })
switch (info.target) {
case 'canvas': {
const hitShape = getHitShapeOnCanvasPointerDown(editor, true /* hitLabels */)
const hitShape = this.getHitShapeOnCanvasPointerDown(true /* hitLabels */)
if (hitShape) {
this.onEvent({
...info,
@ -2346,6 +2350,102 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
})
}
private selectOnCanvasPointerUp() {
const { editor } = this
const selectedShapeIds = editor.getSelectedShapeIds()
const { shiftKey, altKey, currentPagePoint } = editor.inputs
const hitShape = editor.getShapeAtPoint(currentPagePoint, {
hitInside: false,
margin: HIT_TEST_MARGIN / editor.getZoomLevel(),
hitLabels: true,
renderingOnly: true,
filter: (shape) => !shape.isLocked,
})
// Note at the start: if we select a shape that is inside of a group,
// the editor will automatically adjust the selection to the outermost
// selectable shape (the group)
// If the shape's outermost selected id (e.g. the group that contains
// the shape) is not the same as the editor's only selected shape, then
// we want to select the outermost selected shape instead of the shape
if (hitShape) {
const outermostSelectableShape = editor.getOutermostSelectableShape(hitShape)
// If the user is holding shift, they're either adding to or removing from
// their selection.
if (shiftKey && !altKey) {
editor.cancelDoubleClick() // fuckin eh
if (selectedShapeIds.includes(outermostSelectableShape.id)) {
// Remove it from selected shapes
editor.mark('deselecting shape')
editor.deselect(outermostSelectableShape)
} else {
// Add it to selected shapes
editor.mark('shift selecting shape')
editor.setSelectedShapes([...selectedShapeIds, outermostSelectableShape.id])
}
} else {
let shapeToSelect: TLShape | undefined = undefined
if (outermostSelectableShape === hitShape) {
// There's no group around the shape, so we can select it.
shapeToSelect = hitShape
} else {
// There's a group around the hit shape.
// If the group is the current focus layer, OR if the group is
// already selected, then we can select the shape inside the group.
// Otherwise, if the group isn't selected and isn't our current
// focus layer, then we need to select the group instead.
if (
outermostSelectableShape.id === editor.getFocusedGroupId() ||
selectedShapeIds.includes(outermostSelectableShape.id)
) {
shapeToSelect = hitShape
} else {
shapeToSelect = outermostSelectableShape
}
}
if (shapeToSelect && !selectedShapeIds.includes(shapeToSelect.id)) {
editor.mark('selecting shape')
editor.select(shapeToSelect.id)
}
}
} else {
// We didn't hit anything...
if (shiftKey) {
// If we were holding shift, then it's a noop. We keep the
// current selection because we didn't add anything to it
return
} else {
// Otherwise, we clear the selction because the user selected
// nothing instead of their current selection.
if (selectedShapeIds.length > 0) {
editor.mark('selecting none')
editor.selectNone()
}
// If the click was inside of the current focused group, then
// we keep that focused group; otherwise we clear the focused
// group (reset it to the page)
const focusedGroupId = editor.getFocusedGroupId()
if (isShapeId(focusedGroupId)) {
const groupShape = editor.getShape(focusedGroupId)!
if (
!editor.isPointInShape(groupShape, currentPagePoint, { margin: 0, hitInside: true })
) {
editor.setFocusedGroup(null)
}
}
}
}
}
private cleanUpDuplicateProps() {
const { editor } = this
// Clean up the duplicate props when the selection changes
@ -3324,6 +3424,62 @@ export class SimpleSelectToolUtil extends ToolUtil<SimpleSelectState, SimpleSele
editor.setSelectedShapes(Array.from(next))
}
}
isPointInRotatedSelectionBounds(point: VecLike) {
const { editor } = this
const selectionBounds = editor.getSelectionRotatedPageBounds()
if (!selectionBounds) return false
const selectionRotation = editor.getSelectionRotation()
if (!selectionRotation) return selectionBounds.containsPoint(point)
return pointInPolygon(
point,
selectionBounds.corners.map((c) => Vec.RotWith(c, selectionBounds.point, selectionRotation))
)
}
getShouldEnterCropMode(): boolean {
const { editor } = this
const onlySelectedShape = editor.getOnlySelectedShape()
return !!(
onlySelectedShape &&
!editor.isShapeOrAncestorLocked(onlySelectedShape) &&
editor.getShapeUtil(onlySelectedShape).canCrop(onlySelectedShape)
)
}
getHitShapeOnCanvasPointerDown(hitLabels = false): TLShape | undefined {
const { editor } = this
const zoomLevel = editor.getZoomLevel()
const {
inputs: { currentPagePoint },
} = editor
return (
// hovered shape at point
editor.getShapeAtPoint(currentPagePoint, {
hitInside: false,
hitLabels,
margin: HIT_TEST_MARGIN / zoomLevel,
renderingOnly: true,
}) ??
// selected shape at point
editor.getSelectedShapeAtPoint(currentPagePoint)
)
}
}
function getTextLabels(geometry: Geometry2d) {
if (geometry.isLabel) {
return [geometry]
}
if (geometry instanceof Group2d) {
return geometry.children.filter((child) => child.isLabel)
}
return []
}
const MAJOR_NUDGE_FACTOR = 10
@ -3340,3 +3496,19 @@ const SKIPPED_KEYS_FOR_AUTO_EDITING = [
'Shift',
'Tab',
]
const cursorTypeMap: Record<TLSelectionHandle, TLCursorType> = {
bottom: 'ns-resize',
top: 'ns-resize',
left: 'ew-resize',
right: 'ew-resize',
bottom_left: 'nesw-resize',
bottom_right: 'nwse-resize',
top_left: 'nwse-resize',
top_right: 'nesw-resize',
bottom_left_rotate: 'swne-rotate',
bottom_right_rotate: 'senw-rotate',
top_left_rotate: 'nwse-rotate',
top_right_rotate: 'nesw-rotate',
mobile_rotate: 'grabbing',
}

Wyświetl plik

@ -1,10 +1,43 @@
import { Editor, TLBaseShape, TLImageShapeCrop, TLShapePartial, Vec, structuredClone } from 'tldraw'
import {
Editor,
SelectionHandle,
TLBaseShape,
TLImageShape,
TLImageShapeCrop,
TLShapePartial,
Vec,
structuredClone,
} from 'tldraw'
/** @internal */
export const MIN_CROP_SIZE = 8
export type ShapeWithCrop = TLBaseShape<string, { w: number; h: number; crop: TLImageShapeCrop }>
export function getCroppingSnapshot(editor: Editor, handle: SelectionHandle) {
const selectionRotation = editor.getSelectionRotation()
const {
inputs: { originPagePoint },
} = editor
const shape = editor.getOnlySelectedShape() as TLImageShape
const selectionBounds = editor.getSelectionRotatedPageBounds()!
const dragHandlePoint = Vec.RotWith(
selectionBounds.getHandlePoint(handle),
selectionBounds.point,
selectionRotation
)
const cursorHandleOffset = Vec.Sub(originPagePoint, dragHandlePoint)
return {
shape,
cursorHandleOffset,
}
}
export function getTranslateCroppedImageChange(
editor: Editor,
shape: TLBaseShape<string, { w: number; h: number; crop: TLImageShapeCrop }>,

Wyświetl plik

@ -1,17 +0,0 @@
import { TLCursorType, TLSelectionHandle } from 'tldraw'
export const cursorTypeMap: Record<TLSelectionHandle, TLCursorType> = {
bottom: 'ns-resize',
top: 'ns-resize',
left: 'ew-resize',
right: 'ew-resize',
bottom_left: 'nesw-resize',
bottom_right: 'nwse-resize',
top_left: 'nwse-resize',
top_right: 'nesw-resize',
bottom_left_rotate: 'swne-rotate',
bottom_right_rotate: 'senw-rotate',
top_left_rotate: 'nwse-rotate',
top_right_rotate: 'nesw-rotate',
mobile_rotate: 'grabbing',
}

Wyświetl plik

@ -1,25 +0,0 @@
import { Editor, SelectionHandle, TLImageShape, Vec } from 'tldraw'
export function getCroppingSnapshot(editor: Editor, handle: SelectionHandle) {
const selectionRotation = editor.getSelectionRotation()
const {
inputs: { originPagePoint },
} = editor
const shape = editor.getOnlySelectedShape() as TLImageShape
const selectionBounds = editor.getSelectionRotatedPageBounds()!
const dragHandlePoint = Vec.RotWith(
selectionBounds.getHandlePoint(handle),
selectionBounds.point,
selectionRotation
)
const cursorHandleOffset = Vec.Sub(originPagePoint, dragHandlePoint)
return {
shape,
cursorHandleOffset,
}
}

Wyświetl plik

@ -1,23 +0,0 @@
import { Editor, HIT_TEST_MARGIN, TLShape } from 'tldraw'
export function getHitShapeOnCanvasPointerDown(
editor: Editor,
hitLabels = false
): TLShape | undefined {
const zoomLevel = editor.getZoomLevel()
const {
inputs: { currentPagePoint },
} = editor
return (
// hovered shape at point
editor.getShapeAtPoint(currentPagePoint, {
hitInside: false,
hitLabels,
margin: HIT_TEST_MARGIN / zoomLevel,
renderingOnly: true,
}) ??
// selected shape at point
editor.getSelectedShapeAtPoint(currentPagePoint)
)
}

Wyświetl plik

@ -1,19 +0,0 @@
import { Editor, TLHandle, TLNoteShape } from 'tldraw'
import { getNoteAdjacentPositions, getNoteShapeForAdjacentPosition } from './noteHelpers'
export function getNoteForPit(
editor: Editor,
shape: TLNoteShape,
handle: TLHandle,
forceNew: boolean
) {
const pageTransform = editor.getShapePageTransform(shape.id)!
const pagePoint = pageTransform.point()
const pageRotation = pageTransform.rotation()
const pits = getNoteAdjacentPositions(pagePoint, pageRotation, shape.props.growY, 0)
const pit = pits[handle.index]
if (pit) {
return getNoteShapeForAdjacentPosition(editor, shape, pit, pageRotation, forceNew)
}
return
}

Wyświetl plik

@ -1,10 +0,0 @@
import { Editor } from 'tldraw'
export function getShouldEnterCropMode(editor: Editor): boolean {
const onlySelectedShape = editor.getOnlySelectedShape()
return !!(
onlySelectedShape &&
!editor.isShapeOrAncestorLocked(onlySelectedShape) &&
editor.getShapeUtil(onlySelectedShape).canCrop(onlySelectedShape)
)
}

Wyświetl plik

@ -1,20 +0,0 @@
import { Geometry2d, Group2d } from 'tldraw'
/**
* Return all the text labels in a geometry.
*
* @param geometry - The geometry to get the text labels from.
*
* @public
*/
export function getTextLabels(geometry: Geometry2d) {
if (geometry.isLabel) {
return [geometry]
}
if (geometry instanceof Group2d) {
return geometry.children.filter((child) => child.isLabel)
}
return []
}

Wyświetl plik

@ -1,20 +0,0 @@
import { Editor, Group2d, TLArrowShape, TLShape, pointInPolygon } from 'tldraw'
export function isOverArrowLabel(editor: Editor, shape: TLShape | undefined) {
if (!shape) return false
const pointInShapeSpace = editor.getPointInShapeSpace(shape, editor.inputs.currentPagePoint)
// todo: Extract into general hit test for arrows
if (editor.isShapeOfType<TLArrowShape>(shape, 'arrow')) {
// How should we handle multiple labels? Do shapes ever have multiple labels?
const labelGeometry = editor.getShapeGeometry<Group2d>(shape).children[1]
// Knowing what we know about arrows... if the shape has no text in its label,
// then the label geometry should not be there.
if (labelGeometry && pointInPolygon(pointInShapeSpace, labelGeometry.vertices)) {
return true
}
}
return false
}

Wyświetl plik

@ -1,14 +0,0 @@
import { Editor, Vec, VecLike, pointInPolygon } from 'tldraw'
export function isPointInRotatedSelectionBounds(editor: Editor, point: VecLike) {
const selectionBounds = editor.getSelectionRotatedPageBounds()
if (!selectionBounds) return false
const selectionRotation = editor.getSelectionRotation()
if (!selectionRotation) return selectionBounds.containsPoint(point)
return pointInPolygon(
point,
selectionBounds.corners.map((c) => Vec.RotWith(c, selectionBounds.point, selectionRotation))
)
}

Wyświetl plik

@ -1,4 +1,13 @@
import { Editor, IndexKey, TLNoteShape, TLShape, Vec, compact, createShapeId } from 'tldraw'
import {
Editor,
IndexKey,
TLHandle,
TLNoteShape,
TLShape,
Vec,
compact,
createShapeId,
} from 'tldraw'
import { zoomToShapeIfOffscreen } from './selectHelpers'
/** @internal */
@ -201,3 +210,20 @@ export function getNoteShapeForAdjacentPosition(
zoomToShapeIfOffscreen(editor)
return nextNote
}
export function getNoteForPit(
editor: Editor,
shape: TLNoteShape,
handle: TLHandle,
forceNew: boolean
) {
const pageTransform = editor.getShapePageTransform(shape.id)!
const pagePoint = pageTransform.point()
const pageRotation = pageTransform.rotation()
const pits = getNoteAdjacentPositions(pagePoint, pageRotation, shape.props.growY, 0)
const pit = pits[handle.index]
if (pit) {
return getNoteShapeForAdjacentPosition(editor, shape, pit, pageRotation, forceNew)
}
return
}

Wyświetl plik

@ -1,94 +0,0 @@
import { Editor, HIT_TEST_MARGIN, TLShape, isShapeId } from 'tldraw'
export function selectOnCanvasPointerUp(editor: Editor) {
const selectedShapeIds = editor.getSelectedShapeIds()
const { shiftKey, altKey, currentPagePoint } = editor.inputs
const hitShape = editor.getShapeAtPoint(currentPagePoint, {
hitInside: false,
margin: HIT_TEST_MARGIN / editor.getZoomLevel(),
hitLabels: true,
renderingOnly: true,
filter: (shape) => !shape.isLocked,
})
// Note at the start: if we select a shape that is inside of a group,
// the editor will automatically adjust the selection to the outermost
// selectable shape (the group)
// If the shape's outermost selected id (e.g. the group that contains
// the shape) is not the same as the editor's only selected shape, then
// we want to select the outermost selected shape instead of the shape
if (hitShape) {
const outermostSelectableShape = editor.getOutermostSelectableShape(hitShape)
// If the user is holding shift, they're either adding to or removing from
// their selection.
if (shiftKey && !altKey) {
editor.cancelDoubleClick() // fuckin eh
if (selectedShapeIds.includes(outermostSelectableShape.id)) {
// Remove it from selected shapes
editor.mark('deselecting shape')
editor.deselect(outermostSelectableShape)
} else {
// Add it to selected shapes
editor.mark('shift selecting shape')
editor.setSelectedShapes([...selectedShapeIds, outermostSelectableShape.id])
}
} else {
let shapeToSelect: TLShape | undefined = undefined
if (outermostSelectableShape === hitShape) {
// There's no group around the shape, so we can select it.
shapeToSelect = hitShape
} else {
// There's a group around the hit shape.
// If the group is the current focus layer, OR if the group is
// already selected, then we can select the shape inside the group.
// Otherwise, if the group isn't selected and isn't our current
// focus layer, then we need to select the group instead.
if (
outermostSelectableShape.id === editor.getFocusedGroupId() ||
selectedShapeIds.includes(outermostSelectableShape.id)
) {
shapeToSelect = hitShape
} else {
shapeToSelect = outermostSelectableShape
}
}
if (shapeToSelect && !selectedShapeIds.includes(shapeToSelect.id)) {
editor.mark('selecting shape')
editor.select(shapeToSelect.id)
}
}
} else {
// We didn't hit anything...
if (shiftKey) {
// If we were holding shift, then it's a noop. We keep the
// current selection because we didn't add anything to it
return
} else {
// Otherwise, we clear the selction because the user selected
// nothing instead of their current selection.
if (selectedShapeIds.length > 0) {
editor.mark('selecting none')
editor.selectNone()
}
// If the click was inside of the current focused group, then
// we keep that focused group; otherwise we clear the focused
// group (reset it to the page)
const focusedGroupId = editor.getFocusedGroupId()
if (isShapeId(focusedGroupId)) {
const groupShape = editor.getShape(focusedGroupId)!
if (!editor.isPointInShape(groupShape, currentPagePoint, { margin: 0, hitInside: true })) {
editor.setFocusedGroup(null)
}
}
}
}
}