@tldraw/editor 5.2.0-next.e2b8d10bf10e → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -1
- package/dist-cjs/index.d.ts +112 -53
- package/dist-cjs/index.js +7 -8
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +8 -5
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +47 -1
- package/dist-cjs/lib/components/Shape.js.map +3 -3
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -16
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +97 -45
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +3 -3
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +28 -4
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +3 -3
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js +24 -2
- package/dist-cjs/lib/editor/managers/HistoryManager/HistoryManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +14 -3
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js +4 -2
- package/dist-cjs/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +7 -3
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +0 -1
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +15 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +79 -0
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +7 -0
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +4 -0
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/{hooks/useTransform.js → editor/tools/cancelShapeCreationOnLongPress.js} +8 -23
- package/dist-cjs/lib/editor/tools/cancelShapeCreationOnLongPress.js.map +7 -0
- package/dist-cjs/lib/editor/types/event-types.js.map +2 -2
- package/dist-cjs/lib/globals/environment.js +6 -1
- package/dist-cjs/lib/globals/environment.js.map +2 -2
- package/dist-cjs/lib/hooks/EditorComponentsContext.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +17 -8
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useEditorComponents.js +0 -1
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePresence.js.map +2 -2
- package/dist-cjs/lib/hooks/useScreenBounds.js +1 -0
- package/dist-cjs/lib/hooks/useScreenBounds.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +5 -1
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +3 -2
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/utils/dom.js +12 -0
- package/dist-cjs/lib/utils/dom.js.map +2 -2
- package/dist-cjs/lib/utils/getPointerInfo.js +2 -1
- package/dist-cjs/lib/utils/getPointerInfo.js.map +2 -2
- package/dist-cjs/lib/utils/pointer.js +37 -0
- package/dist-cjs/lib/utils/pointer.js.map +7 -0
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +112 -53
- package/dist-esm/index.mjs +9 -13
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +8 -5
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +48 -2
- package/dist-esm/lib/components/Shape.mjs.map +3 -3
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -16
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +97 -45
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +28 -4
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +3 -3
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs +24 -2
- package/dist-esm/lib/editor/managers/HistoryManager/HistoryManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +14 -3
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs +4 -2
- package/dist-esm/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +7 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +0 -1
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +15 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +59 -0
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +7 -0
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +4 -0
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs +8 -0
- package/dist-esm/lib/editor/tools/cancelShapeCreationOnLongPress.mjs.map +7 -0
- package/dist-esm/lib/editor/types/event-types.mjs.map +2 -2
- package/dist-esm/lib/globals/environment.mjs +6 -1
- package/dist-esm/lib/globals/environment.mjs.map +2 -2
- package/dist-esm/lib/hooks/EditorComponentsContext.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +17 -8
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +0 -1
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePresence.mjs.map +2 -2
- package/dist-esm/lib/hooks/useScreenBounds.mjs +1 -0
- package/dist-esm/lib/hooks/useScreenBounds.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +5 -1
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +3 -2
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/utils/dom.mjs +12 -0
- package/dist-esm/lib/utils/dom.mjs.map +2 -2
- package/dist-esm/lib/utils/getPointerInfo.mjs +2 -1
- package/dist-esm/lib/utils/getPointerInfo.mjs.map +2 -2
- package/dist-esm/lib/utils/pointer.mjs +17 -0
- package/dist-esm/lib/utils/pointer.mjs.map +7 -0
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +15 -3
- package/package.json +11 -8
- package/src/index.ts +4 -10
- package/src/lib/components/MenuClickCapture.tsx +8 -4
- package/src/lib/components/Shape.tsx +88 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -17
- package/src/lib/editor/Editor.test.ts +36 -0
- package/src/lib/editor/Editor.ts +145 -55
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.test.ts +43 -16
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +8 -5
- package/src/lib/editor/managers/FontManager/FontManager.test.ts +37 -10
- package/src/lib/editor/managers/FontManager/FontManager.ts +46 -1
- package/src/lib/editor/managers/HistoryManager/HistoryManager.test.ts +32 -0
- package/src/lib/editor/managers/HistoryManager/HistoryManager.ts +34 -4
- package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +61 -0
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +16 -4
- package/src/lib/editor/managers/SpatialIndexManager/SpatialIndexManager.ts +9 -2
- package/src/lib/editor/managers/TextManager/TextManager.test.ts +16 -14
- package/src/lib/editor/managers/TextManager/TextManager.ts +17 -2
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +0 -40
- package/src/lib/editor/managers/TickManager/TickManager.ts +0 -1
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +12 -2
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +27 -2
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +86 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +41 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +5 -0
- package/src/lib/editor/tools/cancelShapeCreationOnLongPress.ts +31 -0
- package/src/lib/editor/types/event-types.ts +7 -0
- package/src/lib/globals/environment.ts +7 -0
- package/src/lib/hooks/EditorComponentsContext.tsx +0 -2
- package/src/lib/hooks/useCanvasEvents.ts +24 -12
- package/src/lib/hooks/useEditorComponents.tsx +0 -1
- package/src/lib/hooks/usePresence.ts +2 -2
- package/src/lib/hooks/useScreenBounds.ts +3 -0
- package/src/lib/license/LicenseManager.test.ts +24 -16
- package/src/lib/license/LicenseManager.ts +8 -2
- package/src/lib/primitives/Vec.ts +5 -2
- package/src/lib/test/ContentElementSlot.test.tsx +201 -0
- package/src/lib/utils/dom.ts +34 -0
- package/src/lib/utils/getPointerInfo.ts +2 -1
- package/src/lib/utils/pointer.test.ts +76 -0
- package/src/lib/utils/pointer.ts +42 -0
- package/src/version.ts +3 -3
- package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js +0 -49
- package/dist-cjs/lib/components/default-components/DefaultSelectionBackground.js.map +0 -7
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js +0 -161
- package/dist-cjs/lib/editor/overlays/ShapeIndicatorOverlayUtil.js.map +0 -7
- package/dist-cjs/lib/hooks/useTransform.js.map +0 -7
- package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs +0 -19
- package/dist-esm/lib/components/default-components/DefaultSelectionBackground.mjs.map +0 -7
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs +0 -141
- package/dist-esm/lib/editor/overlays/ShapeIndicatorOverlayUtil.mjs.map +0 -7
- package/dist-esm/lib/hooks/useTransform.mjs +0 -23
- package/dist-esm/lib/hooks/useTransform.mjs.map +0 -7
- package/src/lib/components/default-components/DefaultSelectionBackground.tsx +0 -25
- package/src/lib/editor/overlays/ShapeIndicatorOverlayUtil.ts +0 -216
- package/src/lib/hooks/useTransform.ts +0 -30
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Mock, Mocked, vi } from 'vitest'
|
|
2
|
-
import { Vec } from '../../../primitives/Vec'
|
|
3
2
|
import { Editor } from '../../Editor'
|
|
4
3
|
import { TickManager } from './TickManager'
|
|
5
4
|
|
|
@@ -21,17 +20,6 @@ describe('TickManager', () => {
|
|
|
21
20
|
let tickManager: TickManager
|
|
22
21
|
let mockEmit: Mock
|
|
23
22
|
let mockDisposablesAdd: Mock
|
|
24
|
-
let mockInputs: {
|
|
25
|
-
_currentScreenPoint: Vec
|
|
26
|
-
getCurrentScreenPoint(): Vec
|
|
27
|
-
currentScreenPoint: Vec
|
|
28
|
-
setCurrentScreenPoint(value: Vec): void
|
|
29
|
-
_pointerVelocity: Vec
|
|
30
|
-
getPointerVelocity(): Vec
|
|
31
|
-
pointerVelocity: Vec
|
|
32
|
-
setPointerVelocity(value: Vec): void
|
|
33
|
-
updatePointerVelocity(elapsed: number): void
|
|
34
|
-
}
|
|
35
23
|
|
|
36
24
|
beforeEach(() => {
|
|
37
25
|
vi.clearAllMocks()
|
|
@@ -52,39 +40,11 @@ describe('TickManager', () => {
|
|
|
52
40
|
mockEmit = vi.fn()
|
|
53
41
|
mockDisposablesAdd = vi.fn()
|
|
54
42
|
|
|
55
|
-
// Create a mock inputs object with getters and setters
|
|
56
|
-
mockInputs = {
|
|
57
|
-
_currentScreenPoint: new Vec(100, 100),
|
|
58
|
-
getCurrentScreenPoint() {
|
|
59
|
-
return this._currentScreenPoint
|
|
60
|
-
},
|
|
61
|
-
get currentScreenPoint() {
|
|
62
|
-
return this.getCurrentScreenPoint()
|
|
63
|
-
},
|
|
64
|
-
setCurrentScreenPoint(value: Vec) {
|
|
65
|
-
this._currentScreenPoint = value
|
|
66
|
-
},
|
|
67
|
-
_pointerVelocity: new Vec(0, 0),
|
|
68
|
-
getPointerVelocity() {
|
|
69
|
-
return this._pointerVelocity
|
|
70
|
-
},
|
|
71
|
-
get pointerVelocity() {
|
|
72
|
-
return this.getPointerVelocity()
|
|
73
|
-
},
|
|
74
|
-
setPointerVelocity(value: Vec) {
|
|
75
|
-
this._pointerVelocity = value
|
|
76
|
-
},
|
|
77
|
-
updatePointerVelocity(_elapsed: number) {
|
|
78
|
-
// Mock implementation - no-op for tests
|
|
79
|
-
},
|
|
80
|
-
}
|
|
81
|
-
|
|
82
43
|
editor = {
|
|
83
44
|
emit: mockEmit,
|
|
84
45
|
disposables: {
|
|
85
46
|
add: mockDisposablesAdd,
|
|
86
47
|
},
|
|
87
|
-
inputs: mockInputs as unknown as Editor['inputs'],
|
|
88
48
|
} as unknown as Mocked<Editor>
|
|
89
49
|
|
|
90
50
|
tickManager = new TickManager(editor)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { atom } from '@tldraw/state'
|
|
2
|
+
import { createUserId } from '@tldraw/tlschema'
|
|
2
3
|
import { Mocked, vi } from 'vitest'
|
|
3
4
|
import { TLCurrentUser } from '../../../config/createTLCurrentUser'
|
|
4
5
|
import { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'
|
|
@@ -296,8 +297,17 @@ describe('UserPreferencesManager', () => {
|
|
|
296
297
|
userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
|
|
297
298
|
})
|
|
298
299
|
|
|
299
|
-
describe('
|
|
300
|
-
it('should return user id', () => {
|
|
300
|
+
describe('getExternalId / getRecordId', () => {
|
|
301
|
+
it('should return the raw external user id', () => {
|
|
302
|
+
expect(userPreferencesManager.getExternalId()).toBe(mockUserPreferences.id)
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('should return the prefixed record id', () => {
|
|
306
|
+
expect(userPreferencesManager.getRecordId()).toBe(createUserId(mockUserPreferences.id))
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('getId() still returns the external id', () => {
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
301
311
|
expect(userPreferencesManager.getId()).toBe(mockUserPreferences.id)
|
|
302
312
|
})
|
|
303
313
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { atom, computed } from '@tldraw/state'
|
|
2
|
+
import { createUserId, TLUserId } from '@tldraw/tlschema'
|
|
2
3
|
import { TLCurrentUser } from '../../../config/createTLCurrentUser'
|
|
3
4
|
import { TLUserPreferences, defaultUserPreferences } from '../../../config/TLUserPreferences'
|
|
4
5
|
import { getGlobalWindow } from '../../../utils/dom'
|
|
@@ -39,7 +40,7 @@ export class UserPreferencesManager {
|
|
|
39
40
|
}
|
|
40
41
|
@computed getUserPreferences() {
|
|
41
42
|
return {
|
|
42
|
-
id: this.
|
|
43
|
+
id: this.getExternalId(),
|
|
43
44
|
name: this.getName(),
|
|
44
45
|
locale: this.getLocale(),
|
|
45
46
|
color: this.getColor(),
|
|
@@ -89,10 +90,34 @@ export class UserPreferencesManager {
|
|
|
89
90
|
)
|
|
90
91
|
}
|
|
91
92
|
|
|
92
|
-
|
|
93
|
+
/**
|
|
94
|
+
* The current user's raw, app-provided id — the value set in the user's
|
|
95
|
+
* {@link @tldraw/editor#TLUserPreferences}. Use this when you need the id your application
|
|
96
|
+
* assigned to the user. To compare against or look up store records, use
|
|
97
|
+
* {@link UserPreferencesManager.getRecordId} instead.
|
|
98
|
+
*/
|
|
99
|
+
@computed getExternalId(): string {
|
|
93
100
|
return this.user.userPreferences.get().id
|
|
94
101
|
}
|
|
95
102
|
|
|
103
|
+
/**
|
|
104
|
+
* @deprecated Use {@link UserPreferencesManager.getExternalId} for the raw app-provided id, or
|
|
105
|
+
* {@link UserPreferencesManager.getRecordId} for the prefixed `TLUserId` record id.
|
|
106
|
+
*/
|
|
107
|
+
@computed getId() {
|
|
108
|
+
return this.getExternalId()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The current user's id as a tldraw {@link @tldraw/tlschema#TLUserId} record id (prefixed
|
|
113
|
+
* with `user:`). Use this when comparing against or looking up store records, such as a
|
|
114
|
+
* presence record's `userId` or `followingUserId`. For the raw, app-provided id, use
|
|
115
|
+
* {@link UserPreferencesManager.getExternalId}.
|
|
116
|
+
*/
|
|
117
|
+
@computed getRecordId(): TLUserId {
|
|
118
|
+
return createUserId(this.getExternalId())
|
|
119
|
+
}
|
|
120
|
+
|
|
96
121
|
@computed getName() {
|
|
97
122
|
return this.user.userPreferences.get().name?.trim() ?? defaultUserPreferences.name
|
|
98
123
|
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { createComputedCache } from '@tldraw/store'
|
|
2
|
+
import { TLShape, TLShapeId } from '@tldraw/tlschema'
|
|
3
|
+
import type { Editor } from '../Editor'
|
|
4
|
+
|
|
5
|
+
const indicatorPathCache = createComputedCache(
|
|
6
|
+
'shapeIndicatorPath',
|
|
7
|
+
(editor: Editor, shape: TLShape) => {
|
|
8
|
+
const util = editor.getShapeUtil(shape)
|
|
9
|
+
return util.getIndicatorPath(shape)
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
areRecordsEqual(a, b) {
|
|
13
|
+
return a.props === b.props
|
|
14
|
+
},
|
|
15
|
+
}
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Combine every batchable shape indicator into a single page-space `Path2D` and
|
|
20
|
+
* emit one stroke call. Shapes whose indicator needs an evenodd clip (e.g.
|
|
21
|
+
* arrows with labels or complex arrowheads) can't be batched — they still
|
|
22
|
+
* stroke individually inside a save/restore with `ctx.clip` applied.
|
|
23
|
+
*
|
|
24
|
+
* Shared by any overlay util that paints shape indicators (e.g. collaborator
|
|
25
|
+
* selections).
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
export function strokeShapeIndicators(
|
|
30
|
+
editor: Editor,
|
|
31
|
+
ctx: CanvasRenderingContext2D,
|
|
32
|
+
shapeIds: TLShapeId[]
|
|
33
|
+
): void {
|
|
34
|
+
if (shapeIds.length === 0) return
|
|
35
|
+
|
|
36
|
+
const batched = new Path2D()
|
|
37
|
+
|
|
38
|
+
for (const shapeId of shapeIds) {
|
|
39
|
+
const shape = editor.getShape(shapeId)
|
|
40
|
+
if (!shape || shape.isLocked) continue
|
|
41
|
+
|
|
42
|
+
const pageTransform = editor.getShapePageTransform(shape)
|
|
43
|
+
if (!pageTransform) continue
|
|
44
|
+
|
|
45
|
+
const indicatorPath = indicatorPathCache.get(editor, shape.id)
|
|
46
|
+
if (!indicatorPath) continue
|
|
47
|
+
|
|
48
|
+
if (indicatorPath instanceof Path2D) {
|
|
49
|
+
batched.addPath(indicatorPath, pageTransform)
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { path, clipPath, additionalPaths } = indicatorPath
|
|
54
|
+
|
|
55
|
+
if (!clipPath) {
|
|
56
|
+
batched.addPath(path, pageTransform)
|
|
57
|
+
if (additionalPaths) {
|
|
58
|
+
for (const p of additionalPaths) batched.addPath(p, pageTransform)
|
|
59
|
+
}
|
|
60
|
+
continue
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Clipped case: fall back to an individual stroke. Rare (arrows with
|
|
64
|
+
// labels / complex arrowheads), so the extra save/restore/stroke
|
|
65
|
+
// pair per such shape isn't worth batching away.
|
|
66
|
+
ctx.save()
|
|
67
|
+
ctx.transform(
|
|
68
|
+
pageTransform.a,
|
|
69
|
+
pageTransform.b,
|
|
70
|
+
pageTransform.c,
|
|
71
|
+
pageTransform.d,
|
|
72
|
+
pageTransform.e,
|
|
73
|
+
pageTransform.f
|
|
74
|
+
)
|
|
75
|
+
ctx.save()
|
|
76
|
+
ctx.clip(clipPath, 'evenodd')
|
|
77
|
+
ctx.stroke(path)
|
|
78
|
+
ctx.restore()
|
|
79
|
+
if (additionalPaths) {
|
|
80
|
+
for (const p of additionalPaths) ctx.stroke(p)
|
|
81
|
+
}
|
|
82
|
+
ctx.restore()
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
ctx.stroke(batched)
|
|
86
|
+
}
|
|
@@ -982,6 +982,47 @@ export abstract class ShapeUtil<Shape extends TLShape = TLShape> {
|
|
|
982
982
|
* @public
|
|
983
983
|
*/
|
|
984
984
|
onEditEnd?(shape: Shape): void
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Provide an app-owned element to be rendered inside the shape, alongside the output of
|
|
988
|
+
* {@link ShapeUtil.component}. While the shape remains mounted, tldraw guarantees the
|
|
989
|
+
* element keeps the same DOM position: it is never unmounted, recreated, or relocated by
|
|
990
|
+
* reordering, reparenting, culling, or re-renders. When adopting the element, tldraw uses
|
|
991
|
+
* `Node.moveBefore` where available so stateful content like cross-origin iframes keeps
|
|
992
|
+
* its state across the move, falling back to `appendChild` elsewhere.
|
|
993
|
+
*
|
|
994
|
+
* Pair this with {@link ShapeUtil.onReleaseAppOwnedElement} to reclaim the element before
|
|
995
|
+
* the shape or editor unmounts.
|
|
996
|
+
*
|
|
997
|
+
* This is called once per shape mount, not on every prop change, so the adopted element is
|
|
998
|
+
* not refreshed when the shape's props change. If the element's content depends on props,
|
|
999
|
+
* return a stable element and mutate it in place (for example from {@link ShapeUtil.component}
|
|
1000
|
+
* or an effect) rather than returning a different element.
|
|
1001
|
+
*
|
|
1002
|
+
* @param shape - The shape.
|
|
1003
|
+
* @returns The element to adopt, or null to render nothing.
|
|
1004
|
+
* @public
|
|
1005
|
+
*/
|
|
1006
|
+
getAppOwnedElement?(shape: Shape): HTMLElement | null
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* A callback called before the shape's app-owned element slot is destroyed: when the shape
|
|
1010
|
+
* unmounts (for example when it is deleted or the current page changes) or when the whole
|
|
1011
|
+
* editor unmounts, including error teardown. The slot is still connected to the document
|
|
1012
|
+
* when this is called, so the app can move the element to another connected parent with
|
|
1013
|
+
* `Node.moveBefore` to preserve its state. An element left in the slot is destroyed along
|
|
1014
|
+
* with it.
|
|
1015
|
+
*
|
|
1016
|
+
* This is not only a "shape deleted" signal: it also fires on page changes, editor unmount,
|
|
1017
|
+
* and (in dev under React StrictMode) on the throwaway mount/unmount cycle while the shape
|
|
1018
|
+
* still exists. Make it safe to call when the shape is still present, and check the store
|
|
1019
|
+
* (`this.editor.getShape(shape.id)`) if you need to distinguish deletion from a remount.
|
|
1020
|
+
*
|
|
1021
|
+
* @param shape - The shape.
|
|
1022
|
+
* @param element - The element returned by {@link ShapeUtil.getAppOwnedElement}.
|
|
1023
|
+
* @public
|
|
1024
|
+
*/
|
|
1025
|
+
onReleaseAppOwnedElement?(shape: Shape, element: HTMLElement): void
|
|
985
1026
|
}
|
|
986
1027
|
|
|
987
1028
|
/**
|
|
@@ -4,6 +4,7 @@ import { Vec } from '../../../../primitives/Vec'
|
|
|
4
4
|
import type { Editor } from '../../../Editor'
|
|
5
5
|
import { TLBaseBoxShape } from '../../../shapes/BaseBoxShapeUtil'
|
|
6
6
|
import { TLPointerEventInfo } from '../../../types/event-types'
|
|
7
|
+
import { cancelShapeCreationOnLongPress } from '../../cancelShapeCreationOnLongPress'
|
|
7
8
|
import { StateNode } from '../../StateNode'
|
|
8
9
|
import type { BaseBoxShapeTool } from '../BaseBoxShapeTool'
|
|
9
10
|
|
|
@@ -65,6 +66,10 @@ export class Pointing extends StateNode {
|
|
|
65
66
|
this.complete()
|
|
66
67
|
}
|
|
67
68
|
|
|
69
|
+
override onLongPress() {
|
|
70
|
+
cancelShapeCreationOnLongPress(this.editor, () => this.cancel())
|
|
71
|
+
}
|
|
72
|
+
|
|
68
73
|
override onCancel() {
|
|
69
74
|
this.cancel()
|
|
70
75
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Editor } from '../Editor'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared handler for a long-press in a discrete shape-creation tool's pointing
|
|
5
|
+
* state (geo, note, line, text, arrow, frame). On a coarse pointer this cancels
|
|
6
|
+
* any pending creation so the long-press leaves no shape behind (issue #8277).
|
|
7
|
+
* It does not open a context menu — that is a select-tool surface, reached on
|
|
8
|
+
* touch only from the select tool, and on desktop from a right-click (which the
|
|
9
|
+
* editor routes through the select tool).
|
|
10
|
+
*
|
|
11
|
+
* Gated on `isCoarsePointer`: on a fine pointer (mouse) the long-press timer
|
|
12
|
+
* powers the deliberate "pause before drag to start a precise arrow" gesture,
|
|
13
|
+
* which we must not disturb. The bug only manifests on coarse pointers, because
|
|
14
|
+
* that is also where the browser fires `contextmenu` at the long-press mark.
|
|
15
|
+
*
|
|
16
|
+
* The `cancelPendingCreation` callback is the tool's own cleanup — it knows
|
|
17
|
+
* whether it needs to `bailToMark` a shape it created early or simply transition
|
|
18
|
+
* back to idle. Continuous-gesture tools (draw, highlight, eraser, laser) are
|
|
19
|
+
* excluded: their press is their own action, so they handle (or ignore) the
|
|
20
|
+
* long-press themselves.
|
|
21
|
+
*
|
|
22
|
+
* @internal
|
|
23
|
+
*/
|
|
24
|
+
export function cancelShapeCreationOnLongPress(
|
|
25
|
+
editor: Editor,
|
|
26
|
+
cancelPendingCreation: () => void
|
|
27
|
+
): void {
|
|
28
|
+
if (!editor.getInstanceState().isCoarsePointer) return
|
|
29
|
+
|
|
30
|
+
cancelPendingCreation()
|
|
31
|
+
}
|
|
@@ -63,6 +63,13 @@ export type TLPointerEventInfo = TLBaseEventInfo & {
|
|
|
63
63
|
pointerId: number
|
|
64
64
|
button: number
|
|
65
65
|
isPen: boolean
|
|
66
|
+
/**
|
|
67
|
+
* Whether this pen event appears to be direct manipulation on the display (e.g. Apple Pencil on
|
|
68
|
+
* an iPad or a Surface Pen on a touchscreen) rather than indirect input from a desktop graphics
|
|
69
|
+
* tablet (e.g. a Wacom Intuos). Only direct-display pens should auto-enable pen mode. Drawing and
|
|
70
|
+
* pressure behavior is driven by `isPen` and applies to all pens regardless of this flag.
|
|
71
|
+
*/
|
|
72
|
+
isPenDirect?: boolean
|
|
66
73
|
} & TLPointerEventTarget
|
|
67
74
|
|
|
68
75
|
/** @public */
|
|
@@ -17,6 +17,10 @@ const tlenv = {
|
|
|
17
17
|
isWebview: false,
|
|
18
18
|
isDarwin: false,
|
|
19
19
|
hasCanvasSupport: false,
|
|
20
|
+
// Whether the device has a touch screen (an integrated coarse pointer, e.g. an iPad or a
|
|
21
|
+
// touchscreen laptop). Unlike `isCoarsePointer`, this reflects the device's hardware rather than
|
|
22
|
+
// the pointer currently in use, so it stays stable when a pen or mouse is used.
|
|
23
|
+
isTouchDevice: false,
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
let isForcedFinePointer = false
|
|
@@ -32,6 +36,9 @@ if (typeof window !== 'undefined') {
|
|
|
32
36
|
}
|
|
33
37
|
tlenv.hasCanvasSupport = 'Promise' in window && 'HTMLCanvasElement' in window
|
|
34
38
|
isForcedFinePointer = tlenv.isFirefox && !tlenv.isAndroid && !tlenv.isIos
|
|
39
|
+
tlenv.isTouchDevice =
|
|
40
|
+
('navigator' in window && window.navigator.maxTouchPoints > 0) ||
|
|
41
|
+
(typeof window.matchMedia === 'function' && window.matchMedia('(any-pointer: coarse)').matches)
|
|
35
42
|
}
|
|
36
43
|
|
|
37
44
|
/**
|
|
@@ -2,7 +2,6 @@ import { ComponentType, RefAttributes, createContext, useContext } from 'react'
|
|
|
2
2
|
import type { TLCanvasComponentProps } from '../components/default-components/DefaultCanvas'
|
|
3
3
|
import type { TLErrorFallbackComponent } from '../components/default-components/DefaultErrorFallback'
|
|
4
4
|
import type { TLGridProps } from '../components/default-components/DefaultGrid'
|
|
5
|
-
import type { TLSelectionBackgroundProps } from '../components/default-components/DefaultSelectionBackground'
|
|
6
5
|
import type { TLShapeErrorFallbackComponent } from '../components/default-components/DefaultShapeErrorFallback'
|
|
7
6
|
import type { TLShapeWrapperProps } from '../components/default-components/DefaultShapeWrapper'
|
|
8
7
|
|
|
@@ -14,7 +13,6 @@ export interface TLEditorComponents {
|
|
|
14
13
|
InFrontOfTheCanvas?: ComponentType | null
|
|
15
14
|
LoadingScreen?: ComponentType | null
|
|
16
15
|
OnTheCanvas?: ComponentType | null
|
|
17
|
-
SelectionBackground?: ComponentType<TLSelectionBackgroundProps> | null
|
|
18
16
|
ShapeWrapper?: ComponentType<TLShapeWrapperProps & RefAttributes<HTMLDivElement>> | null
|
|
19
17
|
Spinner?: ComponentType<React.SVGProps<SVGSVGElement>> | null
|
|
20
18
|
SvgDefs?: ComponentType | null
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
setPointerCapture,
|
|
9
9
|
} from '../utils/dom'
|
|
10
10
|
import { getPointerInfo } from '../utils/getPointerInfo'
|
|
11
|
+
import { getPointerEventButton, isDirectDisplayPen, isSecondaryClickEvent } from '../utils/pointer'
|
|
11
12
|
import { useEditor } from './useEditor'
|
|
12
13
|
|
|
13
14
|
export function useCanvasEvents() {
|
|
@@ -17,12 +18,16 @@ export function useCanvasEvents() {
|
|
|
17
18
|
|
|
18
19
|
const events = useMemo(
|
|
19
20
|
function canvasEvents() {
|
|
21
|
+
let isSecondaryClickPointerDown = false
|
|
22
|
+
|
|
20
23
|
function onPointerDown(e: React.PointerEvent) {
|
|
21
24
|
if (editor.wasEventAlreadyHandled(e)) return
|
|
25
|
+
const button = getPointerEventButton(e)
|
|
26
|
+
isSecondaryClickPointerDown = button === 2
|
|
22
27
|
|
|
23
28
|
// With right-click panning disabled, fire right_click on press and let the
|
|
24
29
|
// native contextmenu through so the menu opens at the pointer-down location.
|
|
25
|
-
if (
|
|
30
|
+
if (button === 2 && !editor.options.rightClickPanning) {
|
|
26
31
|
editor.dispatch({
|
|
27
32
|
type: 'pointer',
|
|
28
33
|
target: 'canvas',
|
|
@@ -32,7 +37,11 @@ export function useCanvasEvents() {
|
|
|
32
37
|
return
|
|
33
38
|
}
|
|
34
39
|
|
|
35
|
-
if (
|
|
40
|
+
if (button !== 0 && button !== 1 && button !== 2 && button !== 5) return
|
|
41
|
+
|
|
42
|
+
// Detect direct-display pen input (Apple Pencil, Surface Pen on a touchscreen) so we
|
|
43
|
+
// only auto-enable pen mode for it, not for an indirect desktop tablet stylus.
|
|
44
|
+
const isPenDirect = isDirectDisplayPen(e)
|
|
36
45
|
|
|
37
46
|
setPointerCapture(e.currentTarget, e)
|
|
38
47
|
|
|
@@ -41,17 +50,19 @@ export function useCanvasEvents() {
|
|
|
41
50
|
target: 'canvas',
|
|
42
51
|
name: 'pointer_down',
|
|
43
52
|
...getPointerInfo(editor, e),
|
|
53
|
+
isPenDirect,
|
|
44
54
|
})
|
|
45
55
|
}
|
|
46
56
|
|
|
47
57
|
function onPointerUp(e: React.PointerEvent) {
|
|
48
58
|
if (editor.wasEventAlreadyHandled(e)) return
|
|
49
|
-
|
|
59
|
+
const button = isSecondaryClickPointerDown ? 2 : getPointerEventButton(e)
|
|
60
|
+
if (button !== 0 && button !== 1 && button !== 2 && button !== 5) return
|
|
50
61
|
|
|
51
62
|
const rightClickPanning = editor.options.rightClickPanning
|
|
52
63
|
// Check before dispatch (which resets isPanning)
|
|
53
64
|
const wasRightClickPanning =
|
|
54
|
-
rightClickPanning &&
|
|
65
|
+
rightClickPanning && button === 2 && editor.inputs.getIsPanning()
|
|
55
66
|
|
|
56
67
|
releasePointerCapture(e.currentTarget, e)
|
|
57
68
|
|
|
@@ -60,10 +71,11 @@ export function useCanvasEvents() {
|
|
|
60
71
|
target: 'canvas',
|
|
61
72
|
name: 'pointer_up',
|
|
62
73
|
...getPointerInfo(editor, e),
|
|
74
|
+
button,
|
|
63
75
|
})
|
|
64
76
|
|
|
65
77
|
// Static right-click: fire contextmenu at the pointer-up location
|
|
66
|
-
if (rightClickPanning &&
|
|
78
|
+
if (rightClickPanning && button === 2 && !wasRightClickPanning) {
|
|
67
79
|
const contextMenuEvent = new PointerEvent('contextmenu', {
|
|
68
80
|
bubbles: true,
|
|
69
81
|
clientX: e.clientX,
|
|
@@ -76,6 +88,7 @@ export function useCanvasEvents() {
|
|
|
76
88
|
})
|
|
77
89
|
e.currentTarget.dispatchEvent(contextMenuEvent)
|
|
78
90
|
}
|
|
91
|
+
isSecondaryClickPointerDown = false
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
function onPointerEnter(e: React.PointerEvent) {
|
|
@@ -171,16 +184,15 @@ export function useCanvasEvents() {
|
|
|
171
184
|
// Synthetic events — our own dispatch from onPointerUp, or tests using
|
|
172
185
|
// fireEvent.contextMenu — pass through so Radix can open the menu.
|
|
173
186
|
if (!e.nativeEvent.isTrusted) return
|
|
174
|
-
// Only suppress the native browser contextmenu when it follows a
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
187
|
+
// Only suppress the native browser contextmenu when it follows a
|
|
188
|
+
// secondary click. For those, our pointer handling has already
|
|
189
|
+
// decided what to do (either we'll dispatch a synthetic contextmenu on
|
|
190
|
+
// pointerup to open the menu at the release position, or we panned and
|
|
191
|
+
// don't want a menu at all).
|
|
179
192
|
//
|
|
180
193
|
// Other contextmenu sources must reach Radix so the menu opens:
|
|
181
|
-
// - ctrl+click on macOS (button=0, or button=2 with ctrlKey=true)
|
|
182
194
|
// - long-press on touch devices (button=0, pointerType=touch)
|
|
183
|
-
if (e
|
|
195
|
+
if (!isSecondaryClickEvent(e)) return
|
|
184
196
|
preventDefault(e)
|
|
185
197
|
}
|
|
186
198
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
|
-
import { TLInstancePresence } from '@tldraw/tlschema'
|
|
2
|
+
import { TLInstancePresence, TLUserId } from '@tldraw/tlschema'
|
|
3
3
|
import { useEditor } from './useEditor'
|
|
4
4
|
|
|
5
5
|
// TODO: maybe move this to a computed property on the App class?
|
|
@@ -7,7 +7,7 @@ import { useEditor } from './useEditor'
|
|
|
7
7
|
* @returns The latest presence of the user matching userId
|
|
8
8
|
* @public
|
|
9
9
|
*/
|
|
10
|
-
export function usePresence(userId:
|
|
10
|
+
export function usePresence(userId: TLUserId): TLInstancePresence | null {
|
|
11
11
|
const editor = useEditor()
|
|
12
12
|
|
|
13
13
|
const latestPresence = useValue(
|
|
@@ -11,6 +11,9 @@ export function useScreenBounds(ref: React.RefObject<HTMLElement | null>) {
|
|
|
11
11
|
const updateBounds = throttle(
|
|
12
12
|
() => {
|
|
13
13
|
if (!ref.current) return
|
|
14
|
+
// If the container's document has been torn down (e.g. an iframe
|
|
15
|
+
// being removed), there's nothing to measure — bail.
|
|
16
|
+
if (!ref.current.ownerDocument.body) return
|
|
14
17
|
editor.updateViewportScreenBounds(ref.current)
|
|
15
18
|
},
|
|
16
19
|
200,
|
|
@@ -60,22 +60,30 @@ describe('LicenseManager', () => {
|
|
|
60
60
|
expect(result).toMatchObject({ isLicenseParseable: false, reason: 'no-key-provided' })
|
|
61
61
|
})
|
|
62
62
|
|
|
63
|
-
it('Signals that it is development mode
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
it('Signals that it is development mode for loopback hosts', async () => {
|
|
64
|
+
process.env.NODE_ENV = 'production'
|
|
65
|
+
try {
|
|
66
|
+
const schemes = ['http', 'https']
|
|
67
|
+
const hosts = ['localhost', '127.0.0.1', '127.0.0.2', '[::1]']
|
|
68
|
+
for (const scheme of schemes) {
|
|
69
|
+
for (const host of hosts) {
|
|
70
|
+
// @ts-ignore
|
|
71
|
+
delete window.location
|
|
72
|
+
// @ts-ignore
|
|
73
|
+
window.location = new URL(`${scheme}://${host}:3000`)
|
|
74
|
+
|
|
75
|
+
const testEnvLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
76
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
77
|
+
const result = await testEnvLicenseManager.getLicenseFromKey(licenseKey)
|
|
78
|
+
expect(result).toMatchObject({
|
|
79
|
+
isLicenseParseable: true,
|
|
80
|
+
isDomainValid: false,
|
|
81
|
+
isDevelopment: true,
|
|
82
|
+
})
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
} finally {
|
|
86
|
+
process.env.NODE_ENV = 'test'
|
|
79
87
|
}
|
|
80
88
|
})
|
|
81
89
|
|
|
@@ -125,14 +125,20 @@ export class LicenseManager {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
private getIsDevelopment() {
|
|
128
|
-
// If we are using https on a non-
|
|
128
|
+
// If we are using https on a non-loopback domain we assume it's a production env and a development one otherwise
|
|
129
129
|
return (
|
|
130
130
|
!['https:', 'vscode-webview:'].includes(window.location.protocol) ||
|
|
131
|
-
window.location.hostname
|
|
131
|
+
this.isLoopbackHost(window.location.hostname) ||
|
|
132
132
|
process.env.NODE_ENV !== 'production'
|
|
133
133
|
)
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
+
private isLoopbackHost(hostname: string) {
|
|
137
|
+
// localhost, IPv4 loopback (127.0.0.0/8) and IPv6 loopback (::1) are all local development hosts
|
|
138
|
+
const host = hostname.toLowerCase().replace(/^\[|\]$/g, '')
|
|
139
|
+
return host === 'localhost' || host === '::1' || /^127(?:\.\d{1,3}){3}$/.test(host)
|
|
140
|
+
}
|
|
141
|
+
|
|
136
142
|
private getTrackType(result: LicenseFromKeyResult, licenseState: LicenseState): TrackType {
|
|
137
143
|
// Track watermark for unlicensed production deployments
|
|
138
144
|
if (licenseState === 'unlicensed-production') {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { VecModel } from '@tldraw/tlschema'
|
|
2
2
|
import { EASINGS } from './easings'
|
|
3
|
+
|
|
4
|
+
const { easeInQuad, easeInOutQuad } = EASINGS
|
|
5
|
+
|
|
3
6
|
function clamp(n: number, min: number, max?: number): number {
|
|
4
7
|
return Math.max(min, typeof max !== 'undefined' ? Math.min(n, max) : n)
|
|
5
8
|
}
|
|
@@ -634,13 +637,13 @@ export class Vec {
|
|
|
634
637
|
* @param steps - The number of points to return.
|
|
635
638
|
* @param ease - The easing to use.
|
|
636
639
|
*/
|
|
637
|
-
static PointsBetween(A: VecModel, B: VecModel, steps = 6, ease =
|
|
640
|
+
static PointsBetween(A: VecModel, B: VecModel, steps = 6, ease = easeInQuad): Vec[] {
|
|
638
641
|
const results: Vec[] = []
|
|
639
642
|
|
|
640
643
|
for (let i = 0; i < steps; i++) {
|
|
641
644
|
const t = ease(i / (steps - 1))
|
|
642
645
|
const point = Vec.Lrp(A, B, t)
|
|
643
|
-
point.z = Math.min(1, 0.5 + Math.abs(0.5 -
|
|
646
|
+
point.z = Math.min(1, 0.5 + Math.abs(0.5 - easeInOutQuad(t)) * 0.65)
|
|
644
647
|
results.push(point)
|
|
645
648
|
}
|
|
646
649
|
|