StateNode atoms (#2213)

This PR extracts some improvements from #2198 into a separate PR.

### Release Notes
- adds computed `StateNode.getPath`
- adds computed StateNode.getCurrent`
- adds computed StateNode.getIsActive`
- adds computed `Editor.getPath()`
- makes transition's second property optional

### Change Type

- [x] `minor` — New feature

### Test Plan

- [x] Unit Tests
- [x] End to end tests
This commit is contained in:
Steve Ruiz 2023-11-14 13:02:50 +00:00 committed by GitHub
parent 6f872c796a
commit 7186368f0d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
60 changed files with 562 additions and 468 deletions

View file

@ -5,18 +5,6 @@ export function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
// export async function expectPathToBe(page: Page, path: string) {
// expect(await page.evaluate(() => editor.root.path.value)).toBe(path)
// }
// export async function expectToHaveNShapes(page: Page, numberOfShapes: number) {
// expect(await page.evaluate(() => editor.currentPageShapes.length)).toBe(numberOfShapes)
// }
// export async function expectToHaveNSelectedShapes(page: Page, numberOfSelectedShapes: number) {
// expect(await page.evaluate(() => editor.selectedShapeIds.length)).toBe(numberOfSelectedShapes)
// }
declare const editor: Editor
export async function setup({ page }: PlaywrightTestArgs & PlaywrightWorkerArgs) {

View file

@ -711,6 +711,7 @@ export class Editor extends EventEmitter<TLEventMap> {
getPage(page: TLPage | TLPageId): TLPage | undefined;
getPageShapeIds(page: TLPage | TLPageId): Set<TLShapeId>;
getPageStates(): TLInstancePageState[];
getPath(): string;
getPointInParentSpace(shape: TLShape | TLShapeId, point: VecLike): Vec2d;
getPointInShapeSpace(shape: TLShape | TLShapeId, point: VecLike): Vec2d;
getSelectedShapeAtPoint(point: VecLike): TLShape | undefined;
@ -1848,8 +1849,6 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// (undocumented)
children?: Record<string, StateNode>;
// (undocumented)
current: Atom<StateNode | undefined>;
// (undocumented)
get currentToolIdMask(): string | undefined;
set currentToolIdMask(id: string | undefined);
_currentToolIdMask: Atom<string | undefined, unknown>;
@ -1859,6 +1858,9 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
enter: (info: any, from: string) => void;
// (undocumented)
exit: (info: any, from: string) => void;
getCurrent(): StateNode | undefined;
getIsActive(): boolean;
getPath(): string;
// (undocumented)
handleEvent: (info: Exclude<TLEventInfo, TLPinchEventInfo>) => void;
// (undocumented)
@ -1870,8 +1872,6 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// (undocumented)
initial?: string;
// (undocumented)
isActive: boolean;
// (undocumented)
onCancel?: TLEventHandlers['onCancel'];
// (undocumented)
onComplete?: TLEventHandlers['onComplete'];
@ -1908,11 +1908,10 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// (undocumented)
parent: StateNode;
// (undocumented)
path: Computed<string>;
_path: Computed<string>;
// (undocumented)
shapeType?: string;
// (undocumented)
transition: (id: string, info: any) => this;
transition: (id: string, info?: any) => this;
// (undocumented)
type: TLStateNodeType;
}

View file

@ -11223,6 +11223,37 @@
"isAbstract": false,
"name": "getPageStates"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getPath:member(1)",
"docComment": "/**\n * The editor's current path of active states.\n *\n * @example\n * ```ts\n * editor.path // \"select.idle\"\n * ```\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getPath(): "
},
{
"kind": "Content",
"text": "string"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getPath"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!Editor#getPointInParentSpace:member(1)",
@ -34285,6 +34316,41 @@
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#_path:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "_path: "
},
{
"kind": "Reference",
"text": "Computed",
"canonicalReference": "@tldraw/state!Computed:interface"
},
{
"kind": "Content",
"text": "<string>"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "_path",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 3
},
"isStatic": false,
"isProtected": false,
"isAbstract": false
},
{
"kind": "Constructor",
"canonicalReference": "@tldraw/editor!StateNode:constructor(1)",
@ -34418,50 +34484,6 @@
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#current:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "current: "
},
{
"kind": "Reference",
"text": "Atom",
"canonicalReference": "@tldraw/state!Atom:interface"
},
{
"kind": "Content",
"text": "<"
},
{
"kind": "Reference",
"text": "StateNode",
"canonicalReference": "@tldraw/editor!StateNode:class"
},
{
"kind": "Content",
"text": " | undefined>"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "current",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 5
},
"isStatic": false,
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#currentToolIdMask:member",
@ -34583,6 +34605,104 @@
"isProtected": false,
"isAbstract": false
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!StateNode#getCurrent:member(1)",
"docComment": "/**\n * This node's current active child node, if any.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getCurrent(): "
},
{
"kind": "Reference",
"text": "StateNode",
"canonicalReference": "@tldraw/editor!StateNode:class"
},
{
"kind": "Content",
"text": " | undefined"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 3
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getCurrent"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!StateNode#getIsActive:member(1)",
"docComment": "/**\n * Whether this node is active.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getIsActive(): "
},
{
"kind": "Content",
"text": "boolean"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getIsActive"
},
{
"kind": "Method",
"canonicalReference": "@tldraw/editor!StateNode#getPath:member(1)",
"docComment": "/**\n * This node's path of active state nodes\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "getPath(): "
},
{
"kind": "Content",
"text": "string"
},
{
"kind": "Content",
"text": ";"
}
],
"isStatic": false,
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"isProtected": false,
"overloadIndex": 1,
"parameters": [],
"isOptional": false,
"isAbstract": false,
"name": "getPath"
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#handleEvent:member",
@ -34760,36 +34880,6 @@
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#isActive:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "isActive: "
},
{
"kind": "Content",
"text": "boolean"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "isActive",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isStatic": false,
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#onCancel:member",
@ -35408,41 +35498,6 @@
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#path:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "path: "
},
{
"kind": "Reference",
"text": "Computed",
"canonicalReference": "@tldraw/state!Computed:interface"
},
{
"kind": "Content",
"text": "<string>"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": false,
"releaseTag": "Public",
"name": "path",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 3
},
"isStatic": false,
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#shapeType:member",
@ -35476,7 +35531,7 @@
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#transition:member",
"docComment": "",
"docComment": "/**\n * Transition to a new active child state node.\n *\n * @param id - The id of the child state node to transition to.\n *\n * @param info - Any data to pass to the `onEnter` and `onExit` handlers.\n *\n * @example\n * ```ts\n * parentState.transition('childStateA')\n * parentState.transition('childStateB', { myData: 4 })\n * ```\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
@ -35484,7 +35539,7 @@
},
{
"kind": "Content",
"text": "(id: string, info: any) => this"
"text": "(id: string, info?: any) => this"
},
{
"kind": "Content",

View file

@ -1008,7 +1008,7 @@ export class Editor extends EventEmitter<TLEventMap> {
willCrashApp,
},
extras: {
activeStateNode: this.root.path.get(),
activeStateNode: this.root.getPath(),
selectedShapes: this.getSelectedShapes(),
editingShape: editingShapeId ? this.getShape(editingShapeId) : undefined,
inputs: this.inputs,
@ -1051,6 +1051,20 @@ export class Editor extends EventEmitter<TLEventMap> {
/* ------------------- Statechart ------------------- */
/**
* The editor's current path of active states.
*
* @example
* ```ts
* editor.path // "select.idle"
* ```
*
* @public
*/
@computed getPath() {
return this.root.getPath().split('root.')[1]
}
/**
* Get whether a certain tool (or other state node) is currently active.
*
@ -1070,7 +1084,7 @@ export class Editor extends EventEmitter<TLEventMap> {
while (ids.length > 0) {
const id = ids.pop()
if (!id) return true
const current = state.current.get()
const current = state.getCurrent()
if (current?.id === id) {
if (ids.length === 0) return true
state = current
@ -1119,7 +1133,7 @@ export class Editor extends EventEmitter<TLEventMap> {
* @public
*/
@computed getCurrentTool(): StateNode | undefined {
return this.root.current.get()
return this.root.getCurrent()
}
/**
@ -7476,7 +7490,7 @@ export class Editor extends EventEmitter<TLEventMap> {
// If the current tool is associated with a shape, return the styles for that shape.
// Otherwise, just return an empty map.
const currentTool = this.root.current.get()!
const currentTool = this.root.getCurrent()!
const styles = new SharedStyleMap()
if (currentTool.shapeType) {
for (const style of this.styleProps[currentTool.shapeType].keys()) {

View file

@ -112,13 +112,13 @@ export class Pointing extends StateNode {
this.editor.setSelectedShapes([id])
if (this.editor.getInstanceState().isToolLocked) {
this.parent.transition('idle', {})
this.parent.transition('idle')
} else {
this.editor.setCurrentTool('select.idle')
}
}
cancel() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -7,11 +7,12 @@ export class RootState extends StateNode {
static override children = () => []
override onKeyDown: TLEventHandlers['onKeyDown'] = (info) => {
// todo: move this logic up to the @tldraw/tldraw library, as the "zoom" tool only exists there
switch (info.code) {
case 'KeyZ': {
if (!(info.shiftKey || info.ctrlKey)) {
const currentTool = this.current.get()
if (currentTool && currentTool.current.get()?.id === 'idle') {
const currentTool = this.getCurrent()
if (currentTool && currentTool.getCurrent()?.id === 'idle') {
if (this.children!['zoom']) {
this.editor.setCurrentTool('zoom', { ...info, onInteractionEnd: currentTool.id })
}

View file

@ -25,11 +25,12 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
const { id, children, initial } = this.constructor as TLStateNodeConstructor
this.id = id
this.current = atom<StateNode | undefined>('toolState' + this.id, undefined)
this._isActive = atom<boolean>('toolIsActive' + this.id, false)
this._current = atom<StateNode | undefined>('toolState' + this.id, undefined)
this.path = computed('toolPath' + this.id, () => {
const current = this.current.get()
return this.id + (current ? `.${current.path.get()}` : '')
this._path = computed('toolPath' + this.id, () => {
const current = this.getCurrent()
return this.id + (current ? `.${current.getPath()}` : '')
})
this.parent = parent ?? ({} as any)
@ -41,7 +42,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
this.children = Object.fromEntries(
children().map((Ctor) => [Ctor.id, new Ctor(this.editor, this)])
)
this.current.set(this.children[this.initial])
this._current.set(this.children[this.initial])
} else {
this.type = 'leaf'
}
@ -53,35 +54,74 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
this.children = Object.fromEntries(
children().map((Ctor) => [Ctor.id, new Ctor(this.editor, this)])
)
this.current.set(this.children[this.initial])
this._current.set(this.children[this.initial])
}
}
}
path: Computed<string>
static id: string
static initial?: string
static children?: () => TLStateNodeConstructor[]
id: string
current: Atom<StateNode | undefined>
type: TLStateNodeType
shapeType?: string
initial?: string
children?: Record<string, StateNode>
parent: StateNode
isActive = false
/**
* This node's path of active state nodes
*
* @public
*/
@computed getPath() {
return this._path.get()
}
_path: Computed<string>
transition = (id: string, info: any) => {
/**
* This node's current active child node, if any.
*
* @public
*/
@computed getCurrent() {
return this._current.get()
}
private _current: Atom<StateNode | undefined>
/**
* Whether this node is active.
*
* @public
*/
@computed getIsActive() {
return this._isActive.get()
}
private _isActive: Atom<boolean>
/**
* Transition to a new active child state node.
*
* @example
* ```ts
* parentState.transition('childStateA')
* parentState.transition('childStateB', { myData: 4 })
*```
*
* @param id - The id of the child state node to transition to.
* @param info - Any data to pass to the `onEnter` and `onExit` handlers.
*
* @public
*/
transition = (id: string, info: any = {}) => {
const path = id.split('.')
let currState = this as StateNode
for (let i = 0; i < path.length; i++) {
const id = path[i]
const prevChildState = currState.current.get()
const prevChildState = currState.getCurrent()
const nextChildState = currState.children?.[id]
if (!nextChildState) {
@ -90,9 +130,9 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
if (prevChildState?.id !== nextChildState.id) {
prevChildState?.exit(info, id)
currState.current.set(nextChildState)
currState._current.set(nextChildState)
nextChildState.enter(info, prevChildState?.id || 'initial')
if (!nextChildState.isActive) break
if (!nextChildState.getIsActive()) break
}
currState = nextChildState
@ -103,28 +143,30 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
handleEvent = (info: Exclude<TLEventInfo, TLPinchEventInfo>) => {
const cbName = EVENT_NAME_MAP[info.name]
const x = this.current.get()
const x = this.getCurrent()
this[cbName]?.(info as any)
if (this.current.get() === x && this.isActive) {
if (this.getCurrent() === x && this.getIsActive()) {
x?.handleEvent(info)
}
}
// todo: move this logic into transition
enter = (info: any, from: string) => {
this.isActive = true
this._isActive.set(true)
this.onEnter?.(info, from)
if (this.children && this.initial && this.isActive) {
if (this.children && this.initial && this.getIsActive()) {
const initial = this.children[this.initial]
this.current.set(initial)
this._current.set(initial)
initial.enter(info, from)
}
}
// todo: move this logic into transition
exit = (info: any, from: string) => {
this.isActive = false
this._isActive.set(false)
this.onExit?.(info, from)
if (!this.isActive) {
this.current.get()?.exit(info, from)
if (!this.getIsActive()) {
this.getCurrent()?.exit(info, from)
}
}

View file

@ -55,7 +55,7 @@ export function useDocumentEvents() {
if (
e.altKey &&
// todo: When should we allow the alt key to be used? Perhaps states should declare which keys matter to them?
(editor.isIn('zoom') || !editor.root.path.get().endsWith('.idle')) &&
(editor.isIn('zoom') || !editor.root.getPath().endsWith('.idle')) &&
!isFocusingInput()
) {
// On windows the alt key opens the menu bar.

View file

@ -34,7 +34,7 @@ beforeEach(() => {
it('enters the arrow state', () => {
editor.setCurrentTool('arrow')
expect(editor.getCurrentToolId()).toBe('arrow')
editor.expectPathToBe('root.arrow.idle')
editor.expectToBeIn('arrow.idle')
})
describe('When in the idle state', () => {
@ -43,7 +43,7 @@ describe('When in the idle state', () => {
editor.setCurrentTool('arrow').pointerDown(0, 0)
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore + 1)
editor.expectPathToBe('root.arrow.pointing')
editor.expectToBeIn('arrow.pointing')
})
it('returns to select on cancel', () => {
@ -60,7 +60,7 @@ describe('When in the pointing state', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.arrow.idle')
editor.expectToBeIn('arrow.idle')
})
it('bails on cancel', () => {
@ -69,12 +69,12 @@ describe('When in the pointing state', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.arrow.idle')
editor.expectToBeIn('arrow.idle')
})
it('enters the dragging state on pointer move', () => {
editor.setCurrentTool('arrow').pointerDown(0, 0).pointerMove(10, 10)
editor.expectPathToBe('root.select.dragging_handle')
editor.expectToBeIn('select.dragging_handle')
})
})
@ -93,7 +93,7 @@ describe('When dragging the arrow', () => {
end: { type: 'point', x: 10, y: 10 },
},
})
editor.expectPathToBe('root.select.dragging_handle')
editor.expectToBeIn('select.dragging_handle')
})
it('returns to select.idle, keeping shape, on pointer up', () => {
@ -102,7 +102,7 @@ describe('When dragging the arrow', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore + 1)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('returns to arrow.idle, keeping shape, on pointer up when tool lock is active', () => {
@ -112,7 +112,7 @@ describe('When dragging the arrow', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore + 1)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.arrow.idle')
editor.expectToBeIn('arrow.idle')
})
it('bails on cancel', () => {
@ -120,7 +120,7 @@ describe('When dragging the arrow', () => {
editor.setCurrentTool('arrow').pointerDown(0, 0).pointerMove(10, 10).cancel()
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore)
editor.expectPathToBe('root.arrow.idle')
editor.expectToBeIn('arrow.idle')
})
})
@ -409,10 +409,10 @@ describe('reparenting issue', () => {
handle: { id: 'end', type: 'vertex', index: 'a0', x: 100, y: 100 },
shape: editor.getShape(arrowId)!,
})
editor.expectPathToBe('root.select.pointing_handle')
editor.expectToBeIn('select.pointing_handle')
editor.pointerMove(320, 320) // over box 2
editor.expectPathToBe('root.select.dragging_handle')
editor.expectToBeIn('select.dragging_handle')
editor.expectShapeToMatch({
id: arrowId,
index: 'a3V',

View file

@ -379,7 +379,7 @@ describe('resizing', () => {
.pointerDown(150, 300, { target: 'selection', handle: 'bottom' })
.pointerMove(150, 600)
.expectPathToBe('root.select.resizing')
.expectToBeIn('select.resizing')
expect(editor.getShape(arrow1.id)).toMatchObject({
x: 0,
@ -436,7 +436,7 @@ describe('resizing', () => {
.pointerDown(150, 300, { target: 'selection', handle: 'bottom' })
.pointerMove(150, -300)
.expectPathToBe('root.select.resizing')
.expectToBeIn('select.resizing')
expect(editor.getShape(arrow1.id)).toCloselyMatchObject({
props: {

View file

@ -26,7 +26,7 @@ export class Idle extends StateNode {
) {
this.editor.setCurrentTool('select')
this.editor.setEditingShape(onlySelectedShape.id)
this.editor.root.current.get()!.transition('editing_shape', {
this.editor.root.getCurrent()!.transition('editing_shape', {
...info,
target: 'shape',
shape: onlySelectedShape,

View file

@ -75,7 +75,7 @@ export class Pointing extends StateNode {
this.editor.bailToMark(this.markId)
}
this.editor.setHintingShapes([])
this.parent.transition('idle', {})
this.parent.transition('idle')
}
createArrowShape() {
@ -180,7 +180,7 @@ export class Pointing extends StateNode {
private didTimeout = false
private startPreciseTimeout() {
this.preciseTimeout = window.setTimeout(() => {
if (!this.isActive) return
if (!this.getIsActive()) return
this.didTimeout = true
}, 320)
}

View file

@ -17,15 +17,15 @@ describe(DrawShapeTool, () => {
describe('When in the idle state', () => {
it('Returns to select on cancel', () => {
editor.setCurrentTool('draw')
editor.expectPathToBe('root.draw.idle')
editor.expectToBeIn('draw.idle')
editor.cancel()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('Enters the drawing state on pointer down', () => {
editor.setCurrentTool('draw')
editor.pointerDown(50, 50)
editor.expectPathToBe('root.draw.drawing')
editor.expectToBeIn('draw.drawing')
})
})
@ -34,19 +34,19 @@ describe('When in the drawing state', () => {
editor.setCurrentTool('draw')
editor.pointerDown(50, 50)
editor.cancel()
editor.expectPathToBe('root.draw.idle')
editor.expectToBeIn('draw.idle')
})
it('Returns to idle on complete', () => {
editor.setCurrentTool('draw')
editor.pointerDown(50, 50)
editor.pointerUp(50, 50)
editor.expectPathToBe('root.draw.idle')
editor.expectToBeIn('draw.idle')
editor.pointerDown(50, 50)
editor.pointerMove(55, 55)
editor.pointerMove(60, 60)
editor.pointerUp(60, 60)
editor.expectPathToBe('root.draw.idle')
editor.expectToBeIn('draw.idle')
})
})

View file

@ -713,7 +713,7 @@ export class Drawing extends StateNode {
{ id: initialShape.id, type: initialShape.type, props: { isComplete: true } },
])
this.parent.transition('idle', {})
this.parent.transition('idle')
}
cancel() {

View file

@ -56,7 +56,7 @@ describe(FrameShapeTool, () => {
describe('When selecting the tool', () => {
it('selects the tool and enters the idle state', () => {
editor.setCurrentTool('frame')
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})
})
@ -64,19 +64,19 @@ describe('When in the idle state', () => {
it('Enters pointing state on pointer down', () => {
editor.setCurrentTool('frame')
editor.pointerDown(100, 100)
editor.expectPathToBe('root.frame.pointing')
editor.expectToBeIn('frame.pointing')
})
it('Switches back to select tool on cancel', () => {
editor.setCurrentTool('frame')
editor.cancel()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('Does nothing on interrupt', () => {
editor.setCurrentTool('frame')
editor.interrupt()
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})
})
@ -84,25 +84,25 @@ describe('When in the pointing state', () => {
it('Switches back to idle on cancel', () => {
editor.setCurrentTool('frame')
editor.pointerDown(50, 50)
editor.expectPathToBe('root.frame.pointing')
editor.expectToBeIn('frame.pointing')
editor.cancel()
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})
it('Enters the select.resizing state on drag start', () => {
editor.setCurrentTool('frame')
editor.pointerDown(50, 50)
editor.pointerMove(51, 51) // not far enough!
editor.expectPathToBe('root.frame.pointing')
editor.expectToBeIn('frame.pointing')
editor.pointerMove(55, 55)
editor.expectPathToBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
})
it('Enters the select.resizing state on pointer move', () => {
editor.setCurrentTool('frame')
editor.pointerDown(50, 50)
editor.cancel()
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})
it('Returns to the frame state on cancel', () => {
@ -110,7 +110,7 @@ describe('When in the pointing state', () => {
editor.pointerDown(50, 50)
editor.pointerMove(100, 100)
editor.cancel()
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})
it('Creates a frame and returns to select tool on pointer up', () => {
@ -118,7 +118,7 @@ describe('When in the pointing state', () => {
editor.setCurrentTool('frame')
editor.pointerDown(50, 50)
editor.pointerUp(50, 50)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
expect(editor.currentPageShapes.length).toBe(1)
})
@ -128,7 +128,7 @@ describe('When in the pointing state', () => {
editor.setCurrentTool('frame')
editor.pointerDown(50, 50)
editor.pointerUp(50, 50)
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
expect(editor.currentPageShapes.length).toBe(1)
})
})
@ -138,9 +138,9 @@ describe('When in the resizing state', () => {
editor.setCurrentTool('frame')
editor.pointerDown(50, 50)
editor.pointerMove(55, 55)
editor.expectPathToBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
editor.cancel()
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})
it('Returns to select.idle on complete', () => {
@ -148,7 +148,7 @@ describe('When in the resizing state', () => {
editor.pointerDown(50, 50)
editor.pointerMove(100, 100)
editor.pointerUp(100, 100)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('Returns to frame.idle on complete if tool lock is enabled', () => {
@ -157,7 +157,7 @@ describe('When in the resizing state', () => {
editor.pointerDown(50, 50)
editor.pointerMove(100, 100)
editor.pointerUp(100, 100)
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})
})
@ -165,5 +165,5 @@ it('Returns to the idle state on interrupt', () => {
editor.setCurrentTool('frame')
editor.pointerDown(50, 50)
editor.interrupt()
editor.expectPathToBe('root.frame.idle')
editor.expectToBeIn('frame.idle')
})

View file

@ -56,7 +56,7 @@ describe(GeoShapeTool, () => {
describe('When selecting the tool', () => {
it('selects the tool and enters the idle state', () => {
editor.setCurrentTool('geo')
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
})
})
@ -64,19 +64,19 @@ describe('When in the idle state', () => {
it('Enters pointing state on pointer down', () => {
editor.setCurrentTool('geo')
editor.pointerDown(100, 100)
editor.expectPathToBe('root.geo.pointing')
editor.expectToBeIn('geo.pointing')
})
it('Switches back to select tool on cancel', () => {
editor.setCurrentTool('geo')
editor.cancel()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('Does nothing on interrupt', () => {
editor.setCurrentTool('geo')
editor.interrupt()
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
})
it('Enters edit shape state on "Enter" key up when we have one geo shape', () => {
@ -86,7 +86,7 @@ describe('When in the idle state', () => {
editor.pointerUp(100, 100)
editor.keyUp('Enter')
editor.expectPathToBe('root.select.editing_shape')
editor.expectToBeIn('select.editing_shape')
})
it('Does not enter edit shape state on "Enter" key up when multiple geo shapes are selected', () => {
@ -106,7 +106,7 @@ describe('When in the idle state', () => {
expect(editor.getSelectedShapes().length).toBe(2)
editor.keyUp('Enter')
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
})
@ -114,32 +114,32 @@ describe('When in the pointing state', () => {
it('Switches back to idle on cancel', () => {
editor.setCurrentTool('geo')
editor.pointerDown(50, 50)
editor.expectPathToBe('root.geo.pointing')
editor.expectToBeIn('geo.pointing')
editor.cancel()
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
})
it('Enters the select.resizing state on drag start', () => {
editor.setCurrentTool('geo')
editor.pointerDown(50, 50)
editor.pointerMove(51, 51) // not far enough!
editor.expectPathToBe('root.geo.pointing')
editor.expectToBeIn('geo.pointing')
editor.pointerMove(55, 55)
editor.expectPathToBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
})
it('Enters the select.resizing state on pointer move', () => {
editor.setCurrentTool('geo')
editor.pointerDown(50, 50)
editor.cancel()
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
})
it('Returns to the idle state on interrupt', () => {
editor.setCurrentTool('geo')
editor.pointerDown(50, 50)
editor.interrupt()
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
})
it('Creates a geo and returns to select tool on pointer up', () => {
@ -147,7 +147,7 @@ describe('When in the pointing state', () => {
editor.setCurrentTool('geo')
editor.pointerDown(50, 50)
editor.pointerUp(50, 50)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
expect(editor.currentPageShapes.length).toBe(1)
})
@ -157,7 +157,7 @@ describe('When in the pointing state', () => {
editor.setCurrentTool('geo')
editor.pointerDown(50, 50)
editor.pointerUp(50, 50)
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
expect(editor.currentPageShapes.length).toBe(1)
})
})
@ -167,9 +167,9 @@ describe('When in the resizing state while creating a geo shape', () => {
editor.setCurrentTool('geo')
editor.pointerDown(50, 50)
editor.pointerMove(55, 55)
editor.expectPathToBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
editor.cancel()
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
})
it('Returns to select.idle on complete', () => {
@ -177,7 +177,7 @@ describe('When in the resizing state while creating a geo shape', () => {
editor.pointerDown(50, 50)
editor.pointerMove(100, 100)
editor.pointerUp(100, 100)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('Returns to geo.idle on complete if tool lock is enabled', () => {
@ -186,6 +186,6 @@ describe('When in the resizing state while creating a geo shape', () => {
editor.pointerDown(50, 50)
editor.pointerMove(100, 100)
editor.pointerUp(100, 100)
editor.expectPathToBe('root.geo.idle')
editor.expectToBeIn('geo.idle')
})
})

View file

@ -23,7 +23,7 @@ export class Idle extends StateNode {
) {
this.editor.setCurrentTool('select')
this.editor.setEditingShape(onlySelectedShape.id)
this.editor.root.current.get()!.transition('editing_shape', {
this.editor.root.getCurrent()!.transition('editing_shape', {
...info,
target: 'shape',
shape: onlySelectedShape,

View file

@ -118,7 +118,7 @@ export class Pointing extends StateNode {
])
if (this.editor.getInstanceState().isToolLocked) {
this.parent.transition('idle', {})
this.parent.transition('idle')
} else {
this.editor.setCurrentTool('select', {})
}
@ -126,6 +126,6 @@ export class Pointing extends StateNode {
private cancel() {
// we should not have created any shapes yet, so no need to bail
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -10,7 +10,7 @@ beforeEach(() => {
it('enters the line state', () => {
editor.setCurrentTool('line')
expect(editor.getCurrentToolId()).toBe('line')
editor.expectPathToBe('root.line.idle')
editor.expectToBeIn('line.idle')
})
describe('When in the idle state', () => {
@ -19,7 +19,7 @@ describe('When in the idle state', () => {
editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' })
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore + 1)
editor.expectPathToBe('root.line.pointing')
editor.expectToBeIn('line.pointing')
})
it('returns to select on cancel', () => {
@ -36,7 +36,7 @@ describe('When in the pointing state', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore + 1)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.line.idle')
editor.expectToBeIn('line.idle')
})
it('bails on cancel', () => {
@ -45,12 +45,12 @@ describe('When in the pointing state', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.line.idle')
editor.expectToBeIn('line.idle')
})
it('enters the dragging state on pointer move', () => {
editor.setCurrentTool('line').pointerDown(0, 0, { target: 'canvas' }).pointerMove(10, 10)
editor.expectPathToBe('root.select.dragging_handle')
editor.expectToBeIn('select.dragging_handle')
})
})
@ -71,7 +71,7 @@ describe('When dragging the line', () => {
},
},
})
editor.expectPathToBe('root.select.dragging_handle')
editor.expectToBeIn('select.dragging_handle')
})
it('returns to select.idle, keeping shape, on pointer up', () => {
@ -84,7 +84,7 @@ describe('When dragging the line', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore + 1)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('returns to line.idle, keeping shape, on pointer up if tool lock is enabled', () => {
@ -98,7 +98,7 @@ describe('When dragging the line', () => {
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore + 1)
expect(editor.getHintingShapeIds().length).toBe(0)
editor.expectPathToBe('root.line.idle')
editor.expectToBeIn('line.idle')
})
it('bails on cancel', () => {
@ -110,7 +110,7 @@ describe('When dragging the line', () => {
.cancel()
const shapesAfter = editor.currentPageShapes.length
expect(shapesAfter).toBe(shapesBefore)
editor.expectPathToBe('root.line.idle')
editor.expectToBeIn('line.idle')
})
})

View file

@ -142,7 +142,7 @@ describe('Misc', () => {
editor
.pointerDown(150, 0, { target: 'selection', handle: 'bottom' })
.pointerMove(150, 600) // Resize shape by 0, 600
.expectPathToBe('root.select.resizing')
.expectToBeIn('select.resizing')
expect(editor.getShape(id)!).toMatchSnapshot('line shape after resize')
})

View file

@ -147,7 +147,7 @@ export class Pointing extends StateNode {
}
override onInterrupt: TLInterruptEvent = () => {
this.parent.transition('idle', {})
this.parent.transition('idle')
if (this.markId) this.editor.bailToMark(this.markId)
this.editor.snaps.clear()
}

View file

@ -59,7 +59,7 @@ describe(NoteShapeTool, () => {
describe('When selecting the tool', () => {
it('selects the tool and enters the idle state', () => {
editor.setCurrentTool('note')
editor.expectPathToBe('root.note.idle')
editor.expectToBeIn('note.idle')
})
})
@ -67,19 +67,19 @@ describe('When in the idle state', () => {
it('Enters pointing state on pointer down', () => {
editor.setCurrentTool('note')
editor.pointerDown(100, 100)
editor.expectPathToBe('root.note.pointing')
editor.expectToBeIn('note.pointing')
})
it('Switches back to select tool on cancel', () => {
editor.setCurrentTool('note')
editor.cancel()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('Does nothing on interrupt', () => {
editor.setCurrentTool('note')
editor.interrupt()
editor.expectPathToBe('root.note.idle')
editor.expectToBeIn('note.idle')
})
})
@ -87,18 +87,18 @@ describe('When in the pointing state', () => {
it('Switches back to idle on cancel', () => {
editor.setCurrentTool('note')
editor.pointerDown(50, 50)
editor.expectPathToBe('root.note.pointing')
editor.expectToBeIn('note.pointing')
editor.cancel()
editor.expectPathToBe('root.note.idle')
editor.expectToBeIn('note.idle')
})
it('Enters the select.translating state on drag start', () => {
editor.setCurrentTool('note')
editor.pointerDown(50, 50)
editor.pointerMove(51, 51) // not far enough!
editor.expectPathToBe('root.note.pointing')
editor.expectToBeIn('note.pointing')
editor.pointerMove(55, 55)
editor.expectPathToBe('root.select.translating')
editor.expectToBeIn('select.translating')
})
it('Returns to the note tool on cancel from translating', () => {
@ -106,7 +106,7 @@ describe('When in the pointing state', () => {
editor.pointerDown(50, 50)
editor.pointerMove(55, 55)
editor.cancel()
editor.expectPathToBe('root.note.idle')
editor.expectToBeIn('note.idle')
})
it('Returns to the note tool on complete from translating when tool lock is enabled', () => {
@ -115,14 +115,14 @@ describe('When in the pointing state', () => {
editor.pointerDown(50, 50)
editor.pointerMove(55, 55)
editor.pointerUp()
editor.expectPathToBe('root.note.idle')
editor.expectToBeIn('note.idle')
})
it('Returns to the idle state on interrupt', () => {
editor.setCurrentTool('note')
editor.pointerDown(50, 50)
editor.interrupt()
editor.expectPathToBe('root.note.idle')
editor.expectToBeIn('note.idle')
})
it('Creates a note and begins editing on pointer up', () => {
@ -130,7 +130,7 @@ describe('When in the pointing state', () => {
editor.setCurrentTool('note')
editor.pointerDown(50, 50)
editor.pointerUp(50, 50)
editor.expectPathToBe('root.select.editing_shape')
editor.expectToBeIn('select.editing_shape')
expect(editor.currentPageShapes.length).toBe(1)
})
@ -140,7 +140,7 @@ describe('When in the pointing state', () => {
editor.setCurrentTool('note')
editor.pointerDown(50, 50)
editor.pointerUp(50, 50)
editor.expectPathToBe('root.note.idle')
editor.expectToBeIn('note.idle')
expect(editor.currentPageShapes.length).toBe(1)
})
})

View file

@ -63,7 +63,7 @@ export class Pointing extends StateNode {
private complete() {
if (this.wasFocusedOnEnter) {
if (this.editor.getInstanceState().isToolLocked) {
this.parent.transition('idle', {})
this.parent.transition('idle')
} else {
this.editor.setEditingShape(this.shape.id)
this.editor.setCurrentTool('select.editing_shape', {

View file

@ -32,7 +32,7 @@ export class Idle extends StateNode {
) {
this.editor.setCurrentTool('select')
this.editor.setEditingShape(onlySelectedShape.id)
this.editor.root.current.get()!.transition('editing_shape', {
this.editor.root.getCurrent()!.transition('editing_shape', {
...info,
target: 'shape',
shape: onlySelectedShape,

View file

@ -90,11 +90,11 @@ export class Pointing extends StateNode {
this.editor.setEditingShape(id)
this.editor.setCurrentTool('select')
this.editor.root.current.get()?.transition('editing_shape', {})
this.editor.root.getCurrent()?.transition('editing_shape')
}
private cancel() {
this.parent.transition('idle', {})
this.parent.transition('idle')
this.editor.bailToMark(this.markId)
}
}

View file

@ -119,7 +119,7 @@ export class Erasing extends StateNode {
complete() {
this.editor.deleteShapes(this.editor.getCurrentPageState().erasingShapeIds)
this.editor.setErasingShapes([])
this.parent.transition('idle', {})
this.parent.transition('idle')
}
cancel() {

View file

@ -83,11 +83,11 @@ export class Pointing extends StateNode {
}
this.editor.setErasingShapes([])
this.parent.transition('idle', {})
this.parent.transition('idle')
}
cancel() {
this.editor.setErasingShapes([])
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -40,6 +40,6 @@ export class Dragging extends StateNode {
friction: CAMERA_SLIDE_FRICTION,
})
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -34,6 +34,6 @@ export class Pointing extends StateNode {
}
private complete() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -44,10 +44,10 @@ export class Lasering extends StateNode {
}
private complete() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
private cancel() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -89,7 +89,7 @@ export class Brushing extends StateNode {
}
private complete() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
private hitTestShapes() {

View file

@ -209,7 +209,7 @@ export class Cropping extends StateNode {
this.editor.setCurrentTool(this.info.onInteractionEnd, this.info)
} else {
this.editor.setCroppingShape(null)
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
@ -219,7 +219,7 @@ export class Cropping extends StateNode {
this.editor.setCurrentTool(this.info.onInteractionEnd, this.info)
} else {
this.editor.setCroppingShape(null)
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -124,7 +124,7 @@ export class DraggingHandle extends StateNode {
}
this.exactTimeout = setTimeout(() => {
if (this.isActive && !this.isPrecise) {
if (this.getIsActive() && !this.isPrecise) {
this.isPrecise = true
this.isPreciseId = this.pointingId
this.update()
@ -186,7 +186,7 @@ export class DraggingHandle extends StateNode {
return
}
this.parent.transition('idle', {})
this.parent.transition('idle')
}
private cancel() {
@ -201,7 +201,7 @@ export class DraggingHandle extends StateNode {
return
}
this.parent.transition('idle', {})
this.parent.transition('idle')
}
private update() {

View file

@ -32,10 +32,10 @@ export class PointingCanvas extends StateNode {
}
override onInterrupt = () => {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
private complete() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -56,7 +56,7 @@ export class PointingCropHandle extends StateNode {
this.editor.setCurrentTool(this.info.onInteractionEnd, this.info)
} else {
this.editor.setCroppingShape(null)
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
@ -77,7 +77,7 @@ export class PointingCropHandle extends StateNode {
this.editor.setCurrentTool(this.info.onInteractionEnd, this.info)
} else {
this.editor.setCroppingShape(null)
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
}

View file

@ -51,6 +51,6 @@ export class PointingHandle extends StateNode {
}
private cancel() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -76,7 +76,7 @@ export class PointingResizeHandle extends StateNode {
if (this.info.onInteractionEnd) {
this.editor.setCurrentTool(this.info.onInteractionEnd, {})
} else {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
@ -84,7 +84,7 @@ export class PointingResizeHandle extends StateNode {
if (this.info.onInteractionEnd) {
this.editor.setCurrentTool(this.info.onInteractionEnd, {})
} else {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
}

View file

@ -62,7 +62,7 @@ export class PointingRotateHandle extends StateNode {
if (this.info.onInteractionEnd) {
this.editor.setCurrentTool(this.info.onInteractionEnd, {})
} else {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
@ -70,7 +70,7 @@ export class PointingRotateHandle extends StateNode {
if (this.info.onInteractionEnd) {
this.editor.setCurrentTool(this.info.onInteractionEnd, {})
} else {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
}

View file

@ -43,7 +43,7 @@ export class PointingSelection extends StateNode {
if (hitShape) {
// todo: extract the double click shape logic from idle so that we can share it here
this.parent.transition('idle', {})
this.parent.transition('idle')
this.parent.onDoubleClick?.({
...info,
target: 'shape',
@ -66,6 +66,6 @@ export class PointingSelection extends StateNode {
}
private cancel() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -213,6 +213,6 @@ export class PointingShape extends StateNode {
}
private cancel() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -102,7 +102,7 @@ export class Resizing extends StateNode {
if (this.info.onInteractionEnd) {
this.editor.setCurrentTool(this.info.onInteractionEnd, {})
} else {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
@ -121,7 +121,7 @@ export class Resizing extends StateNode {
return
}
this.parent.transition('idle', {})
this.parent.transition('idle')
}
private handleResizeStart() {

View file

@ -65,7 +65,7 @@ export class ScribbleBrushing extends StateNode {
override onKeyUp = () => {
if (!this.editor.inputs.altKey) {
this.parent.transition('brushing', {})
this.parent.transition('brushing')
} else {
this.updateScribbleSelection(false)
}
@ -157,11 +157,11 @@ export class ScribbleBrushing extends StateNode {
}
private complete() {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
private cancel() {
this.editor.setSelectedShapes([...this.initialSelectedShapeIds], { squashing: true })
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}

View file

@ -172,7 +172,7 @@ export class Translating extends StateNode {
this.editor.setCurrentTool('select.editing_shape')
}
} else {
this.parent.transition('idle', {})
this.parent.transition('idle')
}
}
}

View file

@ -47,7 +47,7 @@ export class ZoomTool extends StateNode {
if (this.info.onInteractionEnd && this.info.onInteractionEnd !== 'select') {
this.editor.setCurrentTool(this.info.onInteractionEnd, this.info)
} else {
this.parent.transition('select', {})
this.parent.transition('select')
}
}

View file

@ -65,7 +65,7 @@ export const DebugPanel = React.memo(function DebugPanel({
const CurrentState = track(function CurrentState() {
const editor = useEditor()
return <div className="tlui-debug-panel__current-state">{editor.root.path.get()}</div>
return <div className="tlui-debug-panel__current-state">{editor.root.getPath()}</div>
})
const ShapeCount = function ShapeCount() {

View file

@ -279,12 +279,12 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
readonlyOk: true,
kbd: 'z',
onSelect(source) {
if (editor.root.current.get()?.id === 'zoom') return
if (editor.root.getCurrent()?.id === 'zoom') return
trackEvent('zoom-tool', { source })
if (!(editor.inputs.shiftKey || editor.inputs.ctrlKey)) {
const currentTool = editor.root.current.get()
if (currentTool && currentTool.current.get()?.id === 'idle') {
const currentTool = editor.root.getCurrent()
if (currentTool && currentTool.getCurrent()?.id === 'idle') {
editor.setCurrentTool('zoom', { onInteractionEnd: currentTool.id, maskAs: 'zoom' })
}
}

View file

@ -22,7 +22,7 @@ export function useRelevantStyles(): {
() => {
const styles = new SharedStyleMap(editor.sharedStyles)
const hasShape =
editor.getSelectedShapeIds().length > 0 || !!editor.root.current.get()?.shapeType
editor.getSelectedShapeIds().length > 0 || !!editor.root.getCurrent()?.shapeType
if (styles.size === 0 && editor.isIn('select') && editor.getSelectedShapeIds().length === 0) {
for (const style of selectToolStyles) {

View file

@ -322,7 +322,7 @@ describe('currentToolId', () => {
editor.pointerMove(100, 100)
expect(editor.getCurrentToolId()).toBe('geo')
expect(editor.root.path.get()).toBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
})
it('reverts back to select if we finish the interaction', () => {
@ -333,7 +333,7 @@ describe('currentToolId', () => {
editor.pointerMove(100, 100)
expect(editor.getCurrentToolId()).toBe('geo')
expect(editor.root.path.get()).toBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
editor.pointerUp(100, 100)
@ -348,7 +348,7 @@ describe('currentToolId', () => {
editor.pointerMove(100, 100)
expect(editor.getCurrentToolId()).toBe('geo')
expect(editor.root.path.get()).toBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
editor.cancel()

View file

@ -97,14 +97,14 @@ describe('When clicking', () => {
editor.setCurrentTool('eraser')
// Starts in idle
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
const shapesBeforeCount = editor.currentPageShapes.length
editor.pointerDown(0, 0) // near enough to box1
// Enters the pointing state
editor.expectPathToBe('root.eraser.pointing')
editor.expectToBeIn('eraser.pointing')
// Sets the erasingShapeIds array
expect(editor.getErasingShapeIds()).toEqual([ids.box1])
@ -121,7 +121,7 @@ describe('When clicking', () => {
expect(editor.getErasingShapeIds()).toEqual([])
// Returns to idle
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
editor.undo()
@ -240,12 +240,12 @@ describe('When clicking', () => {
it('Clears erasing ids and does not erase shapes on cancel', () => {
editor.setCurrentTool('eraser')
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
const shapesBeforeCount = editor.currentPageShapes.length
editor.pointerDown(0, 0) // in box1
editor.expectPathToBe('root.eraser.pointing')
editor.expectToBeIn('eraser.pointing')
expect(editor.getErasingShapeIds()).toEqual([ids.box1])
@ -255,7 +255,7 @@ describe('When clicking', () => {
const shapesAfterCount = editor.currentPageShapes.length
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
// Does NOT erase the shape
expect(editor.getErasingShapeIds()).toEqual([])
@ -265,12 +265,12 @@ describe('When clicking', () => {
it('Clears erasing ids and does not erase shapes on interrupt', () => {
editor.setCurrentTool('eraser')
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
const shapesBeforeCount = editor.currentPageShapes.length
editor.pointerDown(0, 0) // near to box1
editor.expectPathToBe('root.eraser.pointing')
editor.expectToBeIn('eraser.pointing')
expect(editor.getErasingShapeIds()).toEqual([ids.box1])
@ -280,7 +280,7 @@ describe('When clicking', () => {
const shapesAfterCount = editor.currentPageShapes.length
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
// Does NOT erase the shape
expect(editor.getErasingShapeIds()).toEqual([])
@ -293,16 +293,16 @@ describe('When clicking and dragging', () => {
it('Enters erasing state on pointer move, adds contacted shapes to the apps.erasingShapeIds array, deletes them and clears erasingShapeIds on pointer up, restores shapes on undo and deletes again on redo', () => {
editor.setCurrentTool('eraser')
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
editor.pointerDown(-100, -100) // outside of any shapes
editor.expectPathToBe('root.eraser.pointing')
editor.expectToBeIn('eraser.pointing')
expect(editor.getInstanceState().scribbles.length).toBe(0)
editor.pointerMove(50, 50) // inside of box1
editor.expectPathToBe('root.eraser.erasing')
editor.expectToBeIn('eraser.erasing')
jest.advanceTimersByTime(16)
expect(editor.getInstanceState().scribbles.length).toBe(1)
@ -310,7 +310,7 @@ describe('When clicking and dragging', () => {
expect(editor.getErasingShapeIds()).toEqual([ids.box1])
// editor.pointerUp()
// editor.expectPathToBe('root.eraser.idle')
// editor.expectToBeIn('eraser.idle')
// expect(editor.erasingShapeIds).toEqual([])
// expect(editor.getShape(ids.box1)).not.toBeDefined()
@ -327,14 +327,14 @@ describe('When clicking and dragging', () => {
it('Clears erasing ids and does not erase shapes on cancel', () => {
editor.setCurrentTool('eraser')
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
editor.pointerDown(-100, -100) // outside of any shapes
editor.pointerMove(50, 50) // inside of box1
jest.advanceTimersByTime(16)
expect(editor.getInstanceState().scribbles.length).toBe(1)
expect(editor.getErasingShapeIds()).toEqual([ids.box1])
editor.cancel()
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
expect(editor.getErasingShapeIds()).toEqual([])
expect(editor.getShape(ids.box1)).toBeDefined()
})
@ -342,7 +342,7 @@ describe('When clicking and dragging', () => {
it('Excludes a group if it was hovered when the drag started', () => {
editor.groupShapes([ids.box2, ids.box3], ids.group1)
editor.setCurrentTool('eraser')
editor.expectPathToBe('root.eraser.idle')
editor.expectToBeIn('eraser.idle')
editor.pointerDown(275, 275) // in between box2 AND box3, so over of the new group
editor.pointerMove(280, 280) // still outside of the new group
jest.advanceTimersByTime(16)
@ -394,7 +394,7 @@ describe('When clicking and dragging', () => {
editor.pointerDown(-100, -100)
editor.pointerMove(50, 50)
editor.interrupt()
editor.expectPathToBe('root.eraser.erasing')
editor.expectToBeIn('eraser.erasing')
})
it('Starts a scribble on pointer down, updates it on pointer move, stops it on exit', () => {
@ -439,8 +439,8 @@ describe('When shift clicking', () => {
describe('When in the idle state', () => {
it('Returns to select on cancel', () => {
editor.setCurrentTool('hand')
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
editor.cancel()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
})

View file

@ -74,16 +74,16 @@ describe(HandTool, () => {
describe('When in the idle state', () => {
it('Returns to select on cancel', () => {
editor.setCurrentTool('hand')
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
editor.cancel()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
})
describe('When selecting the tool', () => {
it('selects the tool and enters the idle state', () => {
editor.setCurrentTool('hand')
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
})
})
@ -91,19 +91,19 @@ describe('When in the idle state', () => {
it('Enters pointing state on pointer down', () => {
editor.setCurrentTool('hand')
editor.pointerDown(100, 100)
editor.expectPathToBe('root.hand.pointing')
editor.expectToBeIn('hand.pointing')
})
it('Switches back to select tool on cancel', () => {
editor.setCurrentTool('hand')
editor.cancel()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
it('Does nothing on interrupt', () => {
editor.setCurrentTool('hand')
editor.interrupt()
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
})
})
@ -111,32 +111,32 @@ describe('When in the pointing state', () => {
it('Switches back to idle on cancel', () => {
editor.setCurrentTool('hand')
editor.pointerDown(50, 50)
editor.expectPathToBe('root.hand.pointing')
editor.expectToBeIn('hand.pointing')
editor.cancel()
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
})
it('Enters the dragging state on drag start', () => {
editor.setCurrentTool('hand')
editor.pointerDown(50, 50)
editor.pointerMove(51, 51) // not far enough!
editor.expectPathToBe('root.hand.pointing')
editor.expectToBeIn('hand.pointing')
editor.pointerMove(55, 55)
editor.expectPathToBe('root.hand.dragging')
editor.expectToBeIn('hand.dragging')
})
it('Returns to the idle state on cancel', () => {
editor.setCurrentTool('hand')
editor.pointerDown(50, 50)
editor.cancel()
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
})
it('Returns to the idle state on interrupt', () => {
editor.setCurrentTool('hand')
editor.pointerDown(50, 50)
editor.interrupt()
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
})
})
@ -146,11 +146,11 @@ describe('When in the dragging state', () => {
expect(editor.getCamera().x).toBe(0)
expect(editor.getCamera().y).toBe(0)
editor.pointerDown(50, 50)
editor.expectPathToBe('root.hand.pointing')
editor.expectToBeIn('hand.pointing')
editor.pointerMove(75, 75)
expect(editor.getCamera().x).toBe(25)
expect(editor.getCamera().y).toBe(25)
editor.expectPathToBe('root.hand.dragging')
editor.expectToBeIn('hand.dragging')
editor.pointerMove(100, 100)
expect(editor.getCamera().x).toBe(50)
expect(editor.getCamera().y).toBe(50)
@ -186,6 +186,6 @@ describe('When in the dragging state', () => {
editor.pointerDown(50, 50)
editor.pointerMove(100, 100)
editor.cancel()
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
})
})

View file

@ -189,12 +189,7 @@ export class TestEditor extends Editor {
}
expectToBeIn = (path: string) => {
expect(this.root.current.get()!.path.get()).toBe(path)
return this
}
expectPathToBe = (path: string) => {
expect(this.root.path.get()).toBe(path)
expect(this.getPath()).toBe(path)
return this
}

View file

@ -13,11 +13,11 @@ afterEach(() => {
describe('Set selected tool', () => {
it('Selects a tool by its name', () => {
editor.setCurrentTool('hand')
editor.expectPathToBe('root.hand.idle')
editor.expectToBeIn('hand.idle')
})
it('Selects a tool by its deep path', () => {
editor.setCurrentTool('hand.pointing')
editor.expectPathToBe('root.hand.pointing')
editor.expectToBeIn('hand.pointing')
})
})

View file

@ -90,12 +90,12 @@ describe('When in the select.idle state', () => {
it('double clicking an image should transition to select.crop', () => {
editor.select(ids.boxA)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
expect(editor.getSelectedShapeIds()).toMatchObject([ids.boxA])
expect(editor.croppingShapeId).toBe(null)
editor.doubleClick(550, 550, ids.imageB)
editor.expectPathToBe('root.select.crop.idle')
editor.expectToBeIn('select.crop.idle')
expect(editor.getSelectedShapeIds()).toMatchObject([ids.imageB])
expect(editor.croppingShapeId).toBe(ids.imageB)
@ -103,14 +103,14 @@ describe('When in the select.idle state', () => {
editor.undo()
// first selection should have been a mark
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
expect(editor.getSelectedShapeIds()).toMatchObject([ids.imageB])
expect(editor.croppingShapeId).toBe(null)
editor.undo()
// back to start
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
expect(editor.getSelectedShapeIds()).toMatchObject([ids.boxA])
expect(editor.croppingShapeId).toBe(null)
@ -118,7 +118,7 @@ describe('When in the select.idle state', () => {
.redo() // select again
.redo() // crop again
editor.expectPathToBe('root.select.crop.idle')
editor.expectToBeIn('select.crop.idle')
expect(editor.getSelectedShapeIds()).toMatchObject([ids.imageB])
expect(editor.croppingShapeId).toBe(ids.imageB)
})
@ -128,49 +128,49 @@ describe('When in the select.idle state', () => {
// corner (two shapes)
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageA, ids.imageB)
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom_right',
})
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
expect(editor.croppingShapeId).toBe(null)
// edge (two shapes)
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom',
})
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
expect(editor.croppingShapeId).toBe(null)
// corner (one shape)
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom_right',
})
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
expect(editor.croppingShapeId).toBe(ids.imageB)
// edge (one shape)
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, {
target: 'selection',
handle: 'bottom',
})
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
expect(editor.croppingShapeId).toBe(ids.imageB)
})
@ -178,19 +178,19 @@ describe('When in the select.idle state', () => {
it('when only an image is selected pressing enter should transition to select.crop', () => {
// two shapes
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageA, ids.imageB)
.keyDown('Enter')
.keyUp('Enter')
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
// one image
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.keyDown('Enter')
.keyUp('Enter')
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
expect(editor.croppingShapeId).toBe(ids.imageB)
})
@ -199,101 +199,101 @@ describe('When in the select.idle state', () => {
// two shapes / edge
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageA, ids.imageB)
.pointerDown(500, 550, { target: 'selection', handle: 'bottom', ctrlKey: true })
.expectPathToBe('root.select.brushing')
.expectToBeIn('select.brushing')
// two shapes / corner
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageA, ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.brushing')
.expectToBeIn('select.brushing')
// one shape / edge
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.pointerDown(500, 550, { target: 'selection', handle: 'bottom', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
// one shape / corner
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
})
})
describe('When in the crop.idle state', () => {
it('pressing escape should transition to select.idle', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
})
it('pressing enter should transition to select.idle', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.keyDown('Enter')
.keyUp('Enter')
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
})
it('pointing the canvas should point canvas', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.pointerMove(-100, -100)
.pointerDown()
.expectPathToBe('root.select.pointing_canvas')
.expectToBeIn('select.pointing_canvas')
})
it('pointing some other shape should start pointing the shape', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.pointerMove(550, 500)
.pointerDown()
.expectPathToBe('root.select.pointing_shape')
.expectToBeIn('select.pointing_shape')
})
it('pointing a selection handle should enter the select.pointing_crop_handle state', () => {
// corner
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
//reset
editor.cancel().cancel()
// edge
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
})
it('pointing the cropping image should enter the select.crop.translating_crop state', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(500, 600, { target: 'shape', shape: editor.getShape(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.expectToBeIn('select.crop.pointing_crop')
expect(editor.croppingShapeId).toBe(ids.imageB)
expect(editor.getSelectedShapeIds()).toMatchObject([ids.imageB])
@ -301,11 +301,11 @@ describe('When in the crop.idle state', () => {
it('clicking another image shape should set that shape as the new cropping shape and transition to pointing_crop', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(100, 100, { target: 'shape', shape: editor.getShape(ids.imageA) })
.expectPathToBe('root.select.crop.pointing_crop')
.expectToBeIn('select.crop.pointing_crop')
expect(editor.croppingShapeId).toBe(ids.imageA)
expect(editor.getSelectedShapeIds()).toMatchObject([ids.imageA])
@ -313,22 +313,22 @@ describe('When in the crop.idle state', () => {
it('rotating will return to select.crop.idle', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'top_left_rotate' })
.expectPathToBe('root.select.pointing_rotate_handle')
.expectToBeIn('select.pointing_rotate_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.rotating')
.expectToBeIn('select.rotating')
.pointerUp()
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
})
it('nudges the cropped image', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
const crop = () => editor.getShape<TLImageShape>(ids.imageB)!.props.crop!
@ -374,34 +374,34 @@ describe('When in the crop.idle state', () => {
describe('When in the select.crop.pointing_crop state', () => {
it('pressing escape / cancel returns to select.crop.idle', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShape(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.expectToBeIn('select.crop.pointing_crop')
.cancel()
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
})
it('dragging enters select.crop.translating_crop', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShape(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.expectToBeIn('select.crop.pointing_crop')
.pointerMove(560, 560)
.expectPathToBe('root.select.crop.translating_crop')
.expectToBeIn('select.crop.translating_crop')
})
})
describe('When in the select.crop.translating_crop state', () => {
it('moving the pointer should adjust the crop', () => {
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShape(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.expectToBeIn('select.crop.pointing_crop')
const before = editor.getShape<TLImageShape>(ids.imageB)!.props.crop!
@ -413,7 +413,7 @@ describe('When in the select.crop.translating_crop state', () => {
// Move the pointer to the left
editor
.pointerMove(550 - imageWidth / 4, 550 - imageHeight / 4)
.expectPathToBe('root.select.crop.translating_crop')
.expectToBeIn('select.crop.translating_crop')
// Update should have run right away
const afterFirst = editor.getShape<TLImageShape>(ids.imageB)!.props.crop!
@ -456,7 +456,7 @@ describe('When in the select.crop.translating_crop state', () => {
.pointerDown(550, 550, { target: 'shape', shape: editor.getShape(ids.imageB) })
.keyDown('Shift')
.pointerMove(550 - imageWidth / 8, 550 - imageHeight / 8)
.expectPathToBe('root.select.crop.translating_crop')
.expectToBeIn('select.crop.translating_crop')
// Update should have run right away
const afterShiftDown = editor.getShape<TLImageShape>(ids.imageB)!.props.crop!
@ -487,17 +487,17 @@ describe('When in the select.crop.translating_crop state', () => {
const before = editor.getShape<TLImageShape>(ids.imageB)!.props.crop!
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShape(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.expectToBeIn('select.crop.pointing_crop')
.pointerMove(250, 250)
.expectPathToBe('root.select.crop.translating_crop')
.expectToBeIn('select.crop.translating_crop')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.cancel().expectPathToBe('root.select.crop.idle')
editor.cancel().expectToBeIn('select.crop.idle')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
})
@ -506,17 +506,17 @@ describe('When in the select.crop.translating_crop state', () => {
const before = editor.getShape<TLImageShape>(ids.imageB)!.props.crop!
editor
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(550, 550, { target: 'shape', shape: editor.getShape(ids.imageB) })
.expectPathToBe('root.select.crop.pointing_crop')
.expectToBeIn('select.crop.pointing_crop')
.pointerMove(250, 250)
.expectPathToBe('root.select.crop.translating_crop')
.expectToBeIn('select.crop.translating_crop')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.keyDown('Enter').keyUp('Enter').expectPathToBe('root.select.crop.idle')
editor.keyDown('Enter').keyUp('Enter').expectToBeIn('select.crop.idle')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
})
@ -526,24 +526,24 @@ describe('When in the select.pointing_crop_handle state', () => {
it('moving the pointer should transition to select.cropping', () => {
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.expectToBeIn('select.cropping')
})
it('when entered from select.idle, pressing escape / cancel should return to idle and clear cropping idle', () => {
// coming from select.idle
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
expect(editor.croppingShapeId).toBe(null)
})
@ -552,13 +552,13 @@ describe('When in the select.pointing_crop_handle state', () => {
// coming from idle
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.cancel()
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
expect(editor.croppingShapeId).toBe(ids.imageB)
})
@ -570,12 +570,12 @@ describe('When in the select.cropping state', () => {
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.expectToBeIn('select.cropping')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
})
@ -585,14 +585,14 @@ describe('When in the select.cropping state', () => {
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.expectToBeIn('select.cropping')
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
})
@ -602,15 +602,15 @@ describe('When in the select.cropping state', () => {
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.expectToBeIn('select.cropping')
.cancel()
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).toMatchObject(before)
})
@ -620,15 +620,15 @@ describe('When in the select.cropping state', () => {
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.doubleClick(550, 550, ids.imageB)
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
.pointerDown(500, 600, { target: 'selection', handle: 'bottom', ctrlKey: false })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.expectToBeIn('select.cropping')
.pointerUp()
.expectPathToBe('root.select.crop.idle')
.expectToBeIn('select.crop.idle')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.undo()
@ -642,14 +642,14 @@ describe('When in the select.cropping state', () => {
editor
.cancel()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
.select(ids.imageB)
.pointerDown(500, 600, { target: 'selection', handle: 'bottom_left', ctrlKey: true })
.expectPathToBe('root.select.pointing_crop_handle')
.expectToBeIn('select.pointing_crop_handle')
.pointerMove(510, 590)
.expectPathToBe('root.select.cropping')
.expectToBeIn('select.cropping')
.pointerUp()
.expectPathToBe('root.select.idle')
.expectToBeIn('select.idle')
expect(editor.getShape<TLImageShape>(ids.imageB)!.props.crop!).not.toMatchObject(before)
editor.undo()

View file

@ -49,7 +49,7 @@ describe('creating frames', () => {
it('can be canceled while dragging', () => {
editor.setCurrentTool('frame')
editor.pointerDown(100, 100).pointerMove(200, 200)
editor.expectPathToBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
editor.cancel()
editor.pointerUp()
expect(editor.getOnlySelectedShape()?.type).toBe(undefined)
@ -139,7 +139,7 @@ describe('creating frames', () => {
it('switches back to the select tool after creating', () => {
editor.setCurrentTool('frame')
editor.pointerDown(100, 100).pointerMove(49, 149).pointerUp()
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
})
})
@ -534,7 +534,7 @@ describe('frame shapes', () => {
// select from outside the frame
editor.setCurrentTool('select')
editor.pointerDown(50, 50).pointerMove(150, 150)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
expect(editor.getSelectedShapeIds()).toHaveLength(0)
@ -547,13 +547,13 @@ describe('frame shapes', () => {
it('can be selected with scribble brushing only if the drag starts outside the frame', () => {
editor.setCurrentTool('frame')
editor.pointerDown(100, 100).pointerMove(200, 200).pointerUp(200, 200)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
// select from inside the frame
editor.selectNone()
editor.setCurrentTool('select')
editor.pointerDown(150, 150).pointerMove(250, 250)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
expect(editor.getSelectedShapeIds()).toHaveLength(0)
})
@ -590,7 +590,7 @@ describe('frame shapes', () => {
editor.keyDown('alt').pointerDown(500, 500).pointerMove(300, 300)
// Check if in scribble brushing mode
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
// Check if the inner box was selected
editor.pointerUp(300, 300)

View file

@ -1034,7 +1034,7 @@ describe('the select tool', () => {
// it('should work while focused in a group if you start the drag from within the group', () => {
// editor.select(ids.boxA)
// editor.pointerDown(15, 5, groupAId).pointerMove(25, 9, ids.boxB)
// expect(editor.root.path.value).toBe(`root.select.brushing`)
// expect(editor.getPath()).toBe(`select.brushing`)
// expect(editor.selectedShapeIds.includes(ids.boxA)).toBe(false)
// expect(editor.selectedShapeIds.includes(ids.boxB)).toBe(true)
@ -1048,7 +1048,7 @@ describe('the select tool', () => {
expect(editor.getShapesAtPoint({ x: -305, y: -5 })).toMatchObject([])
editor.pointerDown(-305, -5, { target: 'canvas' }).pointerMove(35, 9, ids.boxB)
expect(editor.root.path.get()).toBe(`root.select.brushing`)
editor.expectToBeIn(`select.brushing`)
expect(editor.getSelectedShapeIds().includes(ids.boxA)).toBe(true)
expect(editor.getSelectedShapeIds().includes(ids.boxB)).toBe(true)

View file

@ -862,7 +862,7 @@ describe('When resizing a shape with children', () => {
handle: 'top_left',
})
.pointerMove(0, 0)
.expectPathToBe('root.select.resizing')
.expectToBeIn('select.resizing')
// A's model should have changed by the offset
.expectShapeToMatch({
id: ids.boxA,
@ -926,7 +926,7 @@ describe('When resizing a shape with children', () => {
})
.pointerMove(0, 0)
// .pointerMove(10, 10)
.expectPathToBe('root.select.resizing')
.expectToBeIn('select.resizing')
// A's model should have changed by the offset
.expectShapeToMatch({
id: ids.boxB,

View file

@ -19,7 +19,7 @@ describe(SelectTool, () => {
editor._transformPointerDownSpy.mockRestore()
editor._transformPointerUpSpy.mockRestore()
editor.setCurrentTool('select')
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
editor.doubleClick(50, 50, shapeId)
expect(editor.getCurrentPageState().editingShapeId).toBe(shapeId)
@ -37,7 +37,7 @@ describe(SelectTool, () => {
editor.pointerDown(150, 150).pointerUp()
expect(editor.getCurrentPageState().editingShapeId).toBe(null)
expect(editor.root.path.get()).toEqual('root.select.idle')
editor.expectToBeIn('select.idle')
})
})
it('does not allow pressing undo to end up in the editing state', () => {
@ -55,7 +55,7 @@ describe(SelectTool, () => {
editor.pointerDown(150, 150).pointerUp()
expect(editor.getCurrentPageState().editingShapeId).toBe(null)
expect(editor.root.path.get()).toEqual('root.select.idle')
editor.expectToBeIn('select.idle')
editor.undo()
@ -144,7 +144,7 @@ describe('When brushing arrows', () => {
editor.setCurrentTool('select')
editor.pointerDown(0, 45)
editor.pointerMove(100, 55)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
expect(editor.getSelectedShapeIds()).toStrictEqual([ids.arrow1])
})
@ -166,7 +166,7 @@ describe('When brushing arrows', () => {
editor.setCurrentTool('select')
editor.pointerDown(55, 45)
editor.pointerMove(45, 55)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
expect(editor.getSelectedShapeIds()).toStrictEqual([])
})
})

View file

@ -663,7 +663,7 @@ describe('when a frame has multiple children', () => {
expect(editor.getHoveredShapeId()).toBe(null)
editor.pointerDown()
editor.pointerMove(160, 160)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
editor.pointerUp()
expect(editor.getSelectedShapeIds()).toEqual([])
})
@ -673,7 +673,7 @@ describe('when a frame has multiple children', () => {
expect(editor.getHoveredShapeId()).toBe(null)
editor.pointerDown()
editor.pointerMove(30, 30)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
editor.pointerUp()
expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
})
@ -683,7 +683,7 @@ describe('when a frame has multiple children', () => {
expect(editor.getHoveredShapeId()).toBe(null)
editor.pointerDown()
editor.pointerMove(30, 30)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
editor.pointerUp()
expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
})
@ -696,7 +696,7 @@ describe('when a frame has multiple children', () => {
expect(editor.getHoveredShapeId()).toBe(null)
editor.pointerDown()
editor.pointerMove(99, 99)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
editor.pointerUp()
expect(editor.getSelectedShapeIds()).toEqual([ids.box1, ids.box2])
})
@ -709,7 +709,7 @@ describe('when a frame has multiple children', () => {
expect(editor.getHoveredShapeId()).toBe(null)
editor.pointerDown()
editor.pointerMove(150, 150)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
editor.pointerUp()
expect(editor.getSelectedShapeIds()).toEqual([ids.box1, ids.box2])
})
@ -722,7 +722,7 @@ describe('when a frame has multiple children', () => {
expect(editor.getHoveredShapeId()).toBe(null)
editor.pointerDown()
editor.pointerMove(150, 150)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
editor.pointerUp()
expect(editor.getSelectedShapeIds()).toEqual([ids.frame1])
})
@ -732,7 +732,7 @@ describe('when a frame has multiple children', () => {
expect(editor.getHoveredShapeId()).toBe(null)
editor.pointerDown()
editor.pointerMove(150, 150)
editor.expectPathToBe('root.select.brushing')
editor.expectToBeIn('select.brushing')
editor.pointerUp()
expect(editor.getSelectedShapeIds()).toEqual([ids.frame1])
})

View file

@ -122,12 +122,12 @@ describe('When interacting with a shape...', () => {
handle: 'bottom_right',
})
editor.expectPathToBe('root.select.pointing_resize_handle')
editor.expectToBeIn('select.pointing_resize_handle')
editor.pointerMove(200, 200)
editor.expectPathToBe('root.select.resizing')
editor.expectToBeIn('select.resizing')
editor.pointerMove(200, 210)
editor.pointerUp(200, 210)
editor.expectPathToBe('root.select.idle')
editor.expectToBeIn('select.idle')
// Once on start (for frame only)
expect(fnStart).toHaveBeenCalledTimes(1)

View file

@ -280,15 +280,15 @@ describe('When cloning...', () => {
const count1 = editor.currentPageShapes.length
editor.pointerDown(50, 50, { shape: editor.getShape(groupId)!, target: 'shape' })
editor.expectPathToBe('root.select.pointing_shape')
editor.expectToBeIn('select.pointing_shape')
editor.pointerMove(199, 199)
editor.expectPathToBe('root.select.translating')
editor.expectToBeIn('select.translating')
expect(editor.currentPageShapes.length).toBe(count1) // 2 new box and group
editor.keyDown('Alt')
editor.expectPathToBe('root.select.translating')
editor.expectToBeIn('select.translating')
expect(editor.currentPageShapes.length).toBe(count1 + 3) // 2 new box and group
editor.keyUp('Alt')