@tldraw/editor 5.4.0-next.ef99ab47e5ce → 5.5.0-canary.078a77321dc3
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/dist-cjs/index.d.ts +119 -49
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/TldrawEditor.js +7 -2
- package/dist-cjs/lib/TldrawEditor.js.map +2 -2
- package/dist-cjs/lib/components/MenuClickCapture.js +6 -8
- package/dist-cjs/lib/components/MenuClickCapture.js.map +2 -2
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js +1 -3
- package/dist-cjs/lib/components/default-components/CanvasOverlays.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultErrorFallback.js.map +1 -1
- package/dist-cjs/lib/config/TLUserPreferences.js +6 -2
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/config/createTLStore.js +1 -1
- package/dist-cjs/lib/config/createTLStore.js.map +2 -2
- package/dist-cjs/lib/constants.js +2 -0
- package/dist-cjs/lib/constants.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +190 -300
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js +7 -3
- package/dist-cjs/lib/editor/derivations/shapeIdsInCurrentPage.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js +3 -3
- package/dist-cjs/lib/editor/managers/ClickManager/ClickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js +8 -8
- package/dist-cjs/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js +10 -6
- package/dist-cjs/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js +2 -1
- package/dist-cjs/lib/editor/managers/FontManager/FontManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js +79 -6
- package/dist-cjs/lib/editor/managers/InputsManager/InputsManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js +7 -0
- package/dist-cjs/lib/editor/managers/ScribbleManager/ScribbleManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js +0 -2
- package/dist-cjs/lib/editor/managers/SnapManager/BoundsSnaps.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +1 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js +7 -3
- package/dist-cjs/lib/editor/managers/ThemeManager/ThemeManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js +6 -3
- package/dist-cjs/lib/editor/managers/TickManager/TickManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +12 -1
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/OverlayManager.js +3 -7
- package/dist-cjs/lib/editor/overlays/OverlayManager.js.map +2 -2
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js +0 -1
- package/dist-cjs/lib/editor/overlays/strokeShapeIndicators.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js +3 -1
- package/dist-cjs/lib/editor/shapes/BaseFrameLikeShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js +1 -0
- package/dist-cjs/lib/editor/shapes/shared/getPerfectDashProps.js.map +2 -2
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js +1 -1
- package/dist-cjs/lib/editor/tools/BaseBoxShapeTool/children/Pointing.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +3 -2
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/globals/editors.js +57 -0
- package/dist-cjs/lib/globals/editors.js.map +7 -0
- package/dist-cjs/lib/globals/menus.js +9 -4
- package/dist-cjs/lib/globals/menus.js.map +2 -2
- package/dist-cjs/lib/hooks/useCanvasEvents.js +2 -0
- package/dist-cjs/lib/hooks/useCanvasEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useDocumentEvents.js +39 -9
- package/dist-cjs/lib/hooks/useDocumentEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/useLocalStore.js +10 -6
- package/dist-cjs/lib/hooks/useLocalStore.js.map +2 -2
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js +1 -0
- package/dist-cjs/lib/hooks/usePassThroughWheelEvents.js.map +2 -2
- package/dist-cjs/lib/hooks/usePeerIds.js +2 -1
- package/dist-cjs/lib/hooks/usePeerIds.js.map +2 -2
- package/dist-cjs/lib/license/LicenseManager.js +9 -2
- package/dist-cjs/lib/license/LicenseManager.js.map +2 -2
- package/dist-cjs/lib/license/LicenseProvider.js +16 -3
- package/dist-cjs/lib/license/LicenseProvider.js.map +2 -2
- package/dist-cjs/lib/license/useLicenseManagerState.js +5 -4
- package/dist-cjs/lib/license/useLicenseManagerState.js.map +2 -2
- package/dist-cjs/lib/primitives/Box.js +2 -1
- package/dist-cjs/lib/primitives/Box.js.map +2 -2
- package/dist-cjs/lib/primitives/Vec.js +4 -3
- package/dist-cjs/lib/primitives/Vec.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Edge2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Edge2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Group2d.js +71 -26
- package/dist-cjs/lib/primitives/geometry/Group2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Point2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Point2d.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +2 -1
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/deepLinks.js +5 -1
- package/dist-cjs/lib/utils/deepLinks.js.map +2 -2
- package/dist-cjs/lib/utils/normalizeWheel.js +2 -0
- package/dist-cjs/lib/utils/normalizeWheel.js.map +2 -2
- package/dist-cjs/lib/utils/rotation.js +1 -9
- package/dist-cjs/lib/utils/rotation.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +17 -0
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/lib/utils/sync/alerts.js +1 -1
- package/dist-cjs/lib/utils/sync/alerts.js.map +1 -1
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +119 -49
- package/dist-esm/index.mjs +9 -2
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/TldrawEditor.mjs +9 -4
- package/dist-esm/lib/TldrawEditor.mjs.map +2 -2
- package/dist-esm/lib/components/MenuClickCapture.mjs +6 -8
- package/dist-esm/lib/components/MenuClickCapture.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs +1 -3
- package/dist-esm/lib/components/default-components/CanvasOverlays.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultErrorFallback.mjs.map +1 -1
- package/dist-esm/lib/config/TLUserPreferences.mjs +6 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/config/createTLStore.mjs +1 -1
- package/dist-esm/lib/config/createTLStore.mjs.map +2 -2
- package/dist-esm/lib/constants.mjs +2 -0
- package/dist-esm/lib/constants.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +199 -301
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs +7 -3
- package/dist-esm/lib/editor/derivations/shapeIdsInCurrentPage.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs +3 -3
- package/dist-esm/lib/editor/managers/ClickManager/ClickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs +9 -9
- package/dist-esm/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs +10 -6
- package/dist-esm/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs +2 -1
- package/dist-esm/lib/editor/managers/FontManager/FontManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs +81 -8
- package/dist-esm/lib/editor/managers/InputsManager/InputsManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs +7 -0
- package/dist-esm/lib/editor/managers/ScribbleManager/ScribbleManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs +0 -2
- package/dist-esm/lib/editor/managers/SnapManager/BoundsSnaps.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +2 -3
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs +7 -3
- package/dist-esm/lib/editor/managers/ThemeManager/ThemeManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs +6 -3
- package/dist-esm/lib/editor/managers/TickManager/TickManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +12 -1
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs +3 -7
- package/dist-esm/lib/editor/overlays/OverlayManager.mjs.map +2 -2
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs +0 -1
- package/dist-esm/lib/editor/overlays/strokeShapeIndicators.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs +3 -1
- package/dist-esm/lib/editor/shapes/BaseFrameLikeShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs +1 -0
- package/dist-esm/lib/editor/shapes/shared/getPerfectDashProps.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs +1 -1
- package/dist-esm/lib/editor/tools/BaseBoxShapeTool/children/Pointing.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +3 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/globals/editors.mjs +37 -0
- package/dist-esm/lib/globals/editors.mjs.map +7 -0
- package/dist-esm/lib/globals/menus.mjs +9 -4
- package/dist-esm/lib/globals/menus.mjs.map +2 -2
- package/dist-esm/lib/hooks/useCanvasEvents.mjs +2 -0
- package/dist-esm/lib/hooks/useCanvasEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useDocumentEvents.mjs +40 -10
- package/dist-esm/lib/hooks/useDocumentEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/useLocalStore.mjs +11 -7
- package/dist-esm/lib/hooks/useLocalStore.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs +1 -0
- package/dist-esm/lib/hooks/usePassThroughWheelEvents.mjs.map +2 -2
- package/dist-esm/lib/hooks/usePeerIds.mjs +2 -1
- package/dist-esm/lib/hooks/usePeerIds.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseManager.mjs +9 -2
- package/dist-esm/lib/license/LicenseManager.mjs.map +2 -2
- package/dist-esm/lib/license/LicenseProvider.mjs +16 -3
- package/dist-esm/lib/license/LicenseProvider.mjs.map +2 -2
- package/dist-esm/lib/license/useLicenseManagerState.mjs +5 -4
- package/dist-esm/lib/license/useLicenseManagerState.mjs.map +2 -2
- package/dist-esm/lib/primitives/Box.mjs +2 -1
- package/dist-esm/lib/primitives/Box.mjs.map +2 -2
- package/dist-esm/lib/primitives/Vec.mjs +4 -3
- package/dist-esm/lib/primitives/Vec.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Edge2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Group2d.mjs +75 -27
- package/dist-esm/lib/primitives/geometry/Group2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Point2d.mjs +1 -1
- package/dist-esm/lib/primitives/geometry/Point2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +2 -1
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/deepLinks.mjs +5 -1
- package/dist-esm/lib/utils/deepLinks.mjs.map +2 -2
- package/dist-esm/lib/utils/normalizeWheel.mjs +2 -0
- package/dist-esm/lib/utils/normalizeWheel.mjs.map +2 -2
- package/dist-esm/lib/utils/rotation.mjs +1 -9
- package/dist-esm/lib/utils/rotation.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +17 -0
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/alerts.mjs +1 -1
- package/dist-esm/lib/utils/sync/alerts.mjs.map +1 -1
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +13 -7
- package/src/index.ts +6 -1
- package/src/lib/TldrawEditor.tsx +16 -4
- package/src/lib/components/MenuClickCapture.tsx +19 -18
- package/src/lib/components/default-components/CanvasOverlays.tsx +1 -3
- package/src/lib/components/default-components/DefaultErrorFallback.tsx +1 -1
- package/src/lib/config/TLUserPreferences.test.ts +18 -1
- package/src/lib/config/TLUserPreferences.ts +8 -4
- package/src/lib/config/createTLStore.ts +1 -1
- package/src/lib/constants.ts +8 -0
- package/src/lib/editor/Editor.ts +285 -392
- package/src/lib/editor/derivations/shapeIdsInCurrentPage.ts +7 -3
- package/src/lib/editor/managers/ClickManager/ClickManager.ts +5 -3
- package/src/lib/editor/managers/CollaboratorsManager/CollaboratorsManager.ts +9 -9
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.test.ts +35 -4
- package/src/lib/editor/managers/EdgeScrollManager/EdgeScrollManager.ts +13 -7
- package/src/lib/editor/managers/FontManager/FontManager.ts +2 -1
- package/src/lib/editor/managers/InputsManager/InputsManager.test.ts +199 -3
- package/src/lib/editor/managers/InputsManager/InputsManager.ts +128 -9
- package/src/lib/editor/managers/ScribbleManager/ScribbleManager.ts +12 -1
- package/src/lib/editor/managers/SnapManager/BoundsSnaps.ts +0 -2
- package/src/lib/editor/managers/TextManager/TextManager.ts +2 -6
- package/src/lib/editor/managers/ThemeManager/ThemeManager.test.ts +53 -0
- package/src/lib/editor/managers/ThemeManager/ThemeManager.ts +9 -3
- package/src/lib/editor/managers/TickManager/TickManager.test.ts +13 -0
- package/src/lib/editor/managers/TickManager/TickManager.ts +9 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +64 -3
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +18 -1
- package/src/lib/editor/overlays/OverlayManager.ts +3 -7
- package/src/lib/editor/overlays/strokeShapeIndicators.ts +0 -1
- package/src/lib/editor/shapes/BaseFrameLikeShapeUtil.tsx +5 -1
- package/src/lib/editor/shapes/shared/getPerfectDashProps.ts +3 -0
- package/src/lib/editor/tools/BaseBoxShapeTool/children/Pointing.ts +4 -1
- package/src/lib/editor/tools/StateNode.test.ts +64 -0
- package/src/lib/editor/tools/StateNode.ts +12 -2
- package/src/lib/globals/editors.test.ts +76 -0
- package/src/lib/globals/editors.ts +53 -0
- package/src/lib/globals/menus.test.ts +33 -0
- package/src/lib/globals/menus.ts +11 -4
- package/src/lib/hooks/useCanvasEvents.ts +2 -0
- package/src/lib/hooks/useDocumentEvents.ts +60 -16
- package/src/lib/hooks/useLocalStore.ts +16 -8
- package/src/lib/hooks/usePassThroughWheelEvents.ts +3 -0
- package/src/lib/hooks/usePeerIds.ts +2 -1
- package/src/lib/license/LicenseManager.test.ts +160 -0
- package/src/lib/license/LicenseManager.ts +13 -4
- package/src/lib/license/LicenseProvider.tsx +24 -3
- package/src/lib/license/useLicenseManagerState.ts +8 -6
- package/src/lib/primitives/Box.test.ts +9 -1
- package/src/lib/primitives/Box.ts +3 -1
- package/src/lib/primitives/Vec.test.ts +10 -0
- package/src/lib/primitives/Vec.ts +4 -4
- package/src/lib/primitives/geometry/Arc2d.test.ts +235 -3
- package/src/lib/primitives/geometry/Arc2d.ts +1 -1
- package/src/lib/primitives/geometry/CubicBezier2d.test.ts +186 -3
- package/src/lib/primitives/geometry/CubicBezier2d.ts +1 -1
- package/src/lib/primitives/geometry/CubicSpline2d.test.ts +160 -3
- package/src/lib/primitives/geometry/Edge2d.test.ts +21 -3
- package/src/lib/primitives/geometry/Edge2d.ts +1 -1
- package/src/lib/primitives/geometry/Ellipse2d.test.ts +243 -3
- package/src/lib/primitives/geometry/Geometry2d.test.ts +553 -1
- package/src/lib/primitives/geometry/Group2d.test.ts +419 -3
- package/src/lib/primitives/geometry/Group2d.ts +81 -34
- package/src/lib/primitives/geometry/Point2d.test.ts +14 -3
- package/src/lib/primitives/geometry/Point2d.ts +1 -1
- package/src/lib/primitives/geometry/Polyline2d.test.ts +207 -0
- package/src/lib/primitives/geometry/Stadium2d.test.ts +191 -3
- package/src/lib/primitives/utils.test.ts +10 -1
- package/src/lib/primitives/utils.ts +4 -1
- package/src/lib/test/useCanvasEvents.test.tsx +74 -0
- package/src/lib/test/useDocumentEvents.test.tsx +107 -0
- package/src/lib/test/usePassThroughWheelEvents.test.tsx +25 -2
- package/src/lib/utils/deepLinks.test.ts +15 -0
- package/src/lib/utils/deepLinks.ts +7 -1
- package/src/lib/utils/normalizeWheel.test.ts +39 -0
- package/src/lib/utils/normalizeWheel.ts +6 -0
- package/src/lib/utils/rotation.ts +4 -12
- package/src/lib/utils/sync/TLLocalSyncClient.test.ts +56 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +24 -0
- package/src/lib/utils/sync/alerts.ts +1 -1
- package/src/version.ts +3 -3
- package/src/lib/primitives/geometry/Polyline.test.ts +0 -5
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { TLTheme } from '@tldraw/tlschema'
|
|
2
|
+
import { MockInstance, vi } from 'vitest'
|
|
3
|
+
import { TestEditor } from '../../../test/TestEditor'
|
|
4
|
+
import { DEFAULT_THEME } from './defaultThemes'
|
|
5
|
+
|
|
6
|
+
// Optional so that other tests' theme objects still satisfy TLThemes.
|
|
7
|
+
declare module '@tldraw/tlschema' {
|
|
8
|
+
interface TLThemes {
|
|
9
|
+
ocean?: TLTheme
|
|
10
|
+
sunset?: TLTheme
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const oceanTheme: TLTheme = { ...DEFAULT_THEME, id: 'ocean' }
|
|
15
|
+
|
|
16
|
+
let editor: TestEditor
|
|
17
|
+
let warn: MockInstance
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
editor = new TestEditor({ themes: { ocean: oceanTheme } })
|
|
21
|
+
warn = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
warn.mockRestore()
|
|
26
|
+
editor.dispose()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
describe('setCurrentTheme', () => {
|
|
30
|
+
it('switches to a registered theme', () => {
|
|
31
|
+
editor.setCurrentTheme('ocean')
|
|
32
|
+
|
|
33
|
+
expect(editor.getCurrentThemeId()).toBe('ocean')
|
|
34
|
+
expect(editor.getCurrentTheme()).toBe(oceanTheme)
|
|
35
|
+
expect(warn).not.toHaveBeenCalled()
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('warns and keeps the current theme when the id is not registered', () => {
|
|
39
|
+
editor.setCurrentTheme('sunset')
|
|
40
|
+
|
|
41
|
+
expect(warn).toHaveBeenCalledWith("Theme 'sunset' not found. Available themes: default, ocean")
|
|
42
|
+
expect(editor.getCurrentThemeId()).toBe('default')
|
|
43
|
+
expect(editor.getCurrentTheme()).toBe(DEFAULT_THEME)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('keeps the previous theme rather than falling back to default', () => {
|
|
47
|
+
editor.setCurrentTheme('ocean')
|
|
48
|
+
editor.setCurrentTheme('sunset')
|
|
49
|
+
|
|
50
|
+
expect(editor.getCurrentThemeId()).toBe('ocean')
|
|
51
|
+
expect(editor.getCurrentTheme()).toBe(oceanTheme)
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -69,14 +69,20 @@ export class ThemeManager {
|
|
|
69
69
|
return this._themes.get()[this.getCurrentThemeId()]!
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Set the current theme by id. The theme must have been previously registered; an
|
|
74
|
+
* unregistered id is ignored and the current theme is kept.
|
|
75
|
+
*/
|
|
73
76
|
setCurrentTheme(id: TLThemeId): void {
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
// Storing an unregistered id would make getCurrentTheme() return
|
|
78
|
+
// undefined despite its TLTheme return type.
|
|
79
|
+
if (!(id in this._themes.get())) {
|
|
80
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
76
81
|
console.warn(
|
|
77
82
|
`Theme '${id}' not found. Available themes: ${Object.keys(this._themes.get()).join(', ')}`
|
|
78
83
|
)
|
|
79
84
|
}
|
|
85
|
+
return
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
this._currentThemeId.set(id)
|
|
@@ -142,6 +142,19 @@ describe('TickManager', () => {
|
|
|
142
142
|
expect(mockEmit).toHaveBeenCalledWith('frame', 0)
|
|
143
143
|
expect(mockEmit).toHaveBeenCalledWith('tick', 0)
|
|
144
144
|
})
|
|
145
|
+
|
|
146
|
+
it('should schedule the next frame even when a listener throws', () => {
|
|
147
|
+
const staleCancel = vi.fn()
|
|
148
|
+
tickManager.cancelRaf = staleCancel
|
|
149
|
+
mockEmit.mockImplementation(() => {
|
|
150
|
+
throw new Error('listener blew up')
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// the error still reaches the caller, but the loop lives on
|
|
154
|
+
expect(() => tickManager.tick()).toThrow('listener blew up')
|
|
155
|
+
expect(mockRequestAnimationFrame).toHaveBeenCalled()
|
|
156
|
+
expect(tickManager.cancelRaf).not.toBe(staleCancel)
|
|
157
|
+
})
|
|
145
158
|
})
|
|
146
159
|
|
|
147
160
|
describe('dispose method', () => {
|
|
@@ -41,9 +41,15 @@ export class TickManager extends EditorManager {
|
|
|
41
41
|
const elapsed = now - this.now
|
|
42
42
|
this.now = now
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
// Schedule the next frame even if a listener throws. Otherwise a single throwing listener
|
|
45
|
+
// ends the loop for good, which silently stops every frame-driven behavior — camera
|
|
46
|
+
// animations, edge scrolling, pointer velocity, following — instead of failing once.
|
|
47
|
+
try {
|
|
48
|
+
this.editor.emit('frame', elapsed)
|
|
49
|
+
this.editor.emit('tick', elapsed)
|
|
50
|
+
} finally {
|
|
51
|
+
this.cancelRaf = throttleToNextFrame(this.tick)
|
|
52
|
+
}
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
dispose() {
|
|
@@ -113,16 +113,16 @@ describe('UserPreferencesManager', () => {
|
|
|
113
113
|
const mockAddEventListener = vi.fn()
|
|
114
114
|
let changeHandler: (e: MediaQueryListEvent) => void
|
|
115
115
|
|
|
116
|
-
mockMatchMedia.
|
|
116
|
+
mockMatchMedia.mockImplementation((query: string) => ({
|
|
117
117
|
matches: false,
|
|
118
118
|
addEventListener: (event: string, handler: any) => {
|
|
119
|
-
if (event === 'change') {
|
|
119
|
+
if (event === 'change' && query === '(prefers-color-scheme: dark)') {
|
|
120
120
|
changeHandler = handler
|
|
121
121
|
}
|
|
122
122
|
mockAddEventListener(event, handler)
|
|
123
123
|
},
|
|
124
124
|
removeEventListener: vi.fn(),
|
|
125
|
-
})
|
|
125
|
+
}))
|
|
126
126
|
|
|
127
127
|
userPreferencesManager = new UserPreferencesManager(mockUser, 'system')
|
|
128
128
|
|
|
@@ -367,6 +367,67 @@ describe('UserPreferencesManager', () => {
|
|
|
367
367
|
defaultUserPreferences.animationSpeed
|
|
368
368
|
)
|
|
369
369
|
})
|
|
370
|
+
|
|
371
|
+
describe('following the OS reduce-motion setting', () => {
|
|
372
|
+
let reducedMotionChangeHandler: ((e: MediaQueryListEvent) => void) | undefined
|
|
373
|
+
let osPrefersReducedMotion: boolean
|
|
374
|
+
|
|
375
|
+
const mountWithOs = (prefersReducedMotion: boolean) => {
|
|
376
|
+
osPrefersReducedMotion = prefersReducedMotion
|
|
377
|
+
reducedMotionChangeHandler = undefined
|
|
378
|
+
mockMatchMedia.mockImplementation((query: string) => {
|
|
379
|
+
const isReducedMotion = query === '(prefers-reduced-motion: reduce)'
|
|
380
|
+
return {
|
|
381
|
+
matches: isReducedMotion ? osPrefersReducedMotion : false,
|
|
382
|
+
addEventListener: (event: string, handler: any) => {
|
|
383
|
+
if (isReducedMotion && event === 'change') reducedMotionChangeHandler = handler
|
|
384
|
+
},
|
|
385
|
+
removeEventListener: vi.fn(),
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
it('resolves an unset speed to 0 when the OS prefers reduced motion', () => {
|
|
392
|
+
userPreferencesAtom.set({ ...mockUserPreferences, animationSpeed: null })
|
|
393
|
+
mountWithOs(true)
|
|
394
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(0)
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('tracks a live OS change while the speed is unset', () => {
|
|
398
|
+
userPreferencesAtom.set({ ...mockUserPreferences, animationSpeed: null })
|
|
399
|
+
mountWithOs(false)
|
|
400
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(1)
|
|
401
|
+
|
|
402
|
+
reducedMotionChangeHandler!({ matches: true } as MediaQueryListEvent)
|
|
403
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(0)
|
|
404
|
+
|
|
405
|
+
reducedMotionChangeHandler!({ matches: false } as MediaQueryListEvent)
|
|
406
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(1)
|
|
407
|
+
})
|
|
408
|
+
|
|
409
|
+
it('ignores the OS once the user has set a speed explicitly', () => {
|
|
410
|
+
userPreferencesAtom.set({ ...mockUserPreferences, animationSpeed: 1 })
|
|
411
|
+
mountWithOs(true)
|
|
412
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(1)
|
|
413
|
+
|
|
414
|
+
userPreferencesAtom.set({ ...mockUserPreferences, animationSpeed: 0 })
|
|
415
|
+
reducedMotionChangeHandler!({ matches: false } as MediaQueryListEvent)
|
|
416
|
+
expect(userPreferencesManager.getAnimationSpeed()).toBe(0)
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
it('removes the reduce-motion listener on dispose', () => {
|
|
420
|
+
const removeEventListener = vi.fn()
|
|
421
|
+
mockMatchMedia.mockReturnValue({
|
|
422
|
+
matches: false,
|
|
423
|
+
addEventListener: vi.fn(),
|
|
424
|
+
removeEventListener,
|
|
425
|
+
})
|
|
426
|
+
userPreferencesManager = new UserPreferencesManager(mockUser, 'light')
|
|
427
|
+
userPreferencesManager.dispose()
|
|
428
|
+
expect(removeEventListener).toHaveBeenCalledTimes(2)
|
|
429
|
+
})
|
|
430
|
+
})
|
|
370
431
|
})
|
|
371
432
|
|
|
372
433
|
describe('getAreKeyboardShortcutsEnabled', () => {
|
|
@@ -7,9 +7,11 @@ import { getGlobalWindow } from '../../../utils/dom'
|
|
|
7
7
|
/** @public */
|
|
8
8
|
export class UserPreferencesManager {
|
|
9
9
|
systemColorScheme = atom<'dark' | 'light'>('systemColorScheme', 'light')
|
|
10
|
+
private readonly systemPrefersReducedMotion = atom('systemPrefersReducedMotion', false)
|
|
10
11
|
disposables = new Set<() => void>()
|
|
11
12
|
dispose() {
|
|
12
13
|
this.disposables.forEach((d) => d())
|
|
14
|
+
this.disposables.clear()
|
|
13
15
|
}
|
|
14
16
|
constructor(
|
|
15
17
|
private readonly user: TLCurrentUser,
|
|
@@ -30,6 +32,16 @@ export class UserPreferencesManager {
|
|
|
30
32
|
}
|
|
31
33
|
darkModeMediaQuery?.addEventListener('change', handleChange)
|
|
32
34
|
this.disposables.add(() => darkModeMediaQuery?.removeEventListener('change', handleChange))
|
|
35
|
+
|
|
36
|
+
const reducedMotionMediaQuery = getGlobalWindow().matchMedia('(prefers-reduced-motion: reduce)')
|
|
37
|
+
this.systemPrefersReducedMotion.set(!!reducedMotionMediaQuery?.matches)
|
|
38
|
+
const handleReducedMotionChange = (e: MediaQueryListEvent) => {
|
|
39
|
+
this.systemPrefersReducedMotion.set(e.matches)
|
|
40
|
+
}
|
|
41
|
+
reducedMotionMediaQuery?.addEventListener('change', handleReducedMotionChange)
|
|
42
|
+
this.disposables.add(() =>
|
|
43
|
+
reducedMotionMediaQuery?.removeEventListener('change', handleReducedMotionChange)
|
|
44
|
+
)
|
|
33
45
|
}
|
|
34
46
|
|
|
35
47
|
updateUserPreferences(userPreferences: Partial<TLUserPreferences>) {
|
|
@@ -80,7 +92,12 @@ export class UserPreferencesManager {
|
|
|
80
92
|
}
|
|
81
93
|
|
|
82
94
|
@computed getAnimationSpeed() {
|
|
83
|
-
|
|
95
|
+
// Until the user explicitly sets a speed, follow the OS reduce-motion setting live rather
|
|
96
|
+
// than the value `defaultUserPreferences` captured at module load.
|
|
97
|
+
return (
|
|
98
|
+
this.user.userPreferences.get().animationSpeed ??
|
|
99
|
+
(this.systemPrefersReducedMotion.get() ? 0 : 1)
|
|
100
|
+
)
|
|
84
101
|
}
|
|
85
102
|
|
|
86
103
|
@computed getAreKeyboardShortcutsEnabled() {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { atom, computed } from '@tldraw/state'
|
|
2
|
+
import { WeakCache } from '@tldraw/utils'
|
|
2
3
|
import { Geometry2d } from '../../primitives/geometry/Geometry2d'
|
|
3
4
|
import { VecLike } from '../../primitives/Vec'
|
|
4
5
|
import type { Editor } from '../Editor'
|
|
@@ -119,7 +120,7 @@ export class OverlayManager {
|
|
|
119
120
|
// while getActiveOverlayEntries() keeps returning the same overlay
|
|
120
121
|
// instances; when its reactive deps change, getOverlays() emits fresh
|
|
121
122
|
// objects and stale entries fall out by GC.
|
|
122
|
-
private _geometryCache = new
|
|
123
|
+
private _geometryCache = new WeakCache<TLOverlay, Geometry2d | null>()
|
|
123
124
|
|
|
124
125
|
/**
|
|
125
126
|
* Get hit-test geometry for an overlay, cached by overlay identity. Lets
|
|
@@ -130,12 +131,7 @@ export class OverlayManager {
|
|
|
130
131
|
* @public
|
|
131
132
|
*/
|
|
132
133
|
getOverlayGeometry(overlay: TLOverlay): Geometry2d | null {
|
|
133
|
-
|
|
134
|
-
if (cached !== undefined) return cached
|
|
135
|
-
const util = this.getOverlayUtil(overlay)
|
|
136
|
-
const geometry = util.getGeometry(overlay)
|
|
137
|
-
this._geometryCache.set(overlay, geometry)
|
|
138
|
-
return geometry
|
|
134
|
+
return this._geometryCache.get(overlay, (o) => this.getOverlayUtil(o).getGeometry(o))
|
|
139
135
|
}
|
|
140
136
|
|
|
141
137
|
/**
|
|
@@ -40,7 +40,6 @@ export function strokeShapeIndicators(
|
|
|
40
40
|
if (!shape || shape.isLocked) continue
|
|
41
41
|
|
|
42
42
|
const pageTransform = editor.getShapePageTransform(shape)
|
|
43
|
-
if (!pageTransform) continue
|
|
44
43
|
|
|
45
44
|
const indicatorPath = indicatorPathCache.get(editor, shape.id)
|
|
46
45
|
if (!indicatorPath) continue
|
|
@@ -93,7 +93,11 @@ export abstract class BaseFrameLikeShapeUtil<
|
|
|
93
93
|
const currentChildren = compact(
|
|
94
94
|
editor.getSortedChildIdsForParent(shape).map((id) => editor.getShape(id))
|
|
95
95
|
)
|
|
96
|
-
if (
|
|
96
|
+
if (
|
|
97
|
+
previousChildren.every(
|
|
98
|
+
(s) => !currentChildren.find((c) => c.index === initialIndices.get(s.id))
|
|
99
|
+
)
|
|
100
|
+
) {
|
|
97
101
|
canRestoreOriginalIndices = true
|
|
98
102
|
}
|
|
99
103
|
}
|
|
@@ -87,6 +87,9 @@ export function getPerfectDashProps(
|
|
|
87
87
|
|
|
88
88
|
dashCount = Math.floor(totalLength / dashLength / (2 * ratio))
|
|
89
89
|
dashCount -= dashCount % snap
|
|
90
|
+
// A path shorter than one dash period (or of zero length, where the count is NaN) would
|
|
91
|
+
// otherwise divide by zero below and produce an invalid dasharray; draw a single dash instead
|
|
92
|
+
if (!(dashCount >= 1)) dashCount = 1
|
|
90
93
|
|
|
91
94
|
if (dashCount < 3 && style === 'dashed') {
|
|
92
95
|
if (totalLength / strokeWidth < 4) {
|
|
@@ -75,7 +75,10 @@ export class Pointing extends StateNode {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
override onComplete() {
|
|
78
|
-
|
|
78
|
+
// `complete` arrives mid-press from undo, menus opening, page changes, and focus loss,
|
|
79
|
+
// none of which is a release; committing here would drop a default-size shape the user
|
|
80
|
+
// never asked for. Match the text and arrow tools and leave nothing behind.
|
|
81
|
+
this.cancel()
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
override onInterrupt() {
|
|
@@ -238,6 +238,70 @@ describe('StateNode.addChild', () => {
|
|
|
238
238
|
})
|
|
239
239
|
})
|
|
240
240
|
|
|
241
|
+
describe('StateNode.enter', () => {
|
|
242
|
+
let idleExits = 0
|
|
243
|
+
|
|
244
|
+
class Idle extends StateNode {
|
|
245
|
+
static override id = 'idle'
|
|
246
|
+
override onExit() {
|
|
247
|
+
idleExits++
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
class Pointing extends StateNode {
|
|
252
|
+
static override id = 'pointing'
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
class ToolWithEnterTransition extends StateNode {
|
|
256
|
+
static override id = 'enterTransition'
|
|
257
|
+
static override initial = 'idle'
|
|
258
|
+
static override children() {
|
|
259
|
+
return [Idle, Pointing]
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
override onEnter(info: { startPointing?: boolean }) {
|
|
263
|
+
if (info?.startPointing) this.transition('pointing', info)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
class OtherTool extends StateNode {
|
|
268
|
+
static override id = 'other'
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let editor: Editor
|
|
272
|
+
|
|
273
|
+
beforeEach(() => {
|
|
274
|
+
idleExits = 0
|
|
275
|
+
editor = new Editor({
|
|
276
|
+
initialState: 'other',
|
|
277
|
+
shapeUtils: [],
|
|
278
|
+
bindingUtils: [],
|
|
279
|
+
tools: [OtherTool, ToolWithEnterTransition],
|
|
280
|
+
store: createTLStore({ shapeUtils: [], bindingUtils: [] }),
|
|
281
|
+
getContainer: () => document.body,
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('enters the initial child when onEnter does not transition', () => {
|
|
286
|
+
editor.setCurrentTool('enterTransition')
|
|
287
|
+
expect(editor.root.getPath()).toBe('root.enterTransition.idle')
|
|
288
|
+
expect(editor.root.children!['enterTransition'].children!['idle'].getIsActive()).toBe(true)
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
it('does not enter the initial child on top of a child that onEnter transitioned to', () => {
|
|
292
|
+
editor.setCurrentTool('enterTransition', { startPointing: true })
|
|
293
|
+
const tool = editor.root.children!['enterTransition']
|
|
294
|
+
expect(editor.root.getPath()).toBe('root.enterTransition.pointing')
|
|
295
|
+
expect(tool.children!['pointing'].getIsActive()).toBe(true)
|
|
296
|
+
expect(tool.children!['idle'].getIsActive()).toBe(false)
|
|
297
|
+
// The stale (never re-entered) idle child must not get a spurious onExit
|
|
298
|
+
expect(idleExits).toBe(0)
|
|
299
|
+
|
|
300
|
+
editor.setCurrentTool('other')
|
|
301
|
+
expect(tool.children!['pointing'].getIsActive()).toBe(false)
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
241
305
|
describe('current tool id mask', () => {
|
|
242
306
|
// Tool mask test classes
|
|
243
307
|
class ToolA extends StateNode {
|
|
@@ -152,7 +152,9 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
if (prevChildState?.id !== nextChildState.id) {
|
|
155
|
-
|
|
155
|
+
// The previous child is inactive when transitioning from a parent's onEnter (or from a
|
|
156
|
+
// node that was already exited); exiting it again would fire a spurious onExit
|
|
157
|
+
if (prevChildState?.getIsActive()) prevChildState.exit(info, id)
|
|
156
158
|
currState._current.set(nextChildState)
|
|
157
159
|
nextChildState.enter(info, prevChildState?.id || 'initial')
|
|
158
160
|
if (!nextChildState.getIsActive()) break
|
|
@@ -188,10 +190,18 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
|
|
|
188
190
|
this.editor.performance._notifyInteractionStart(this.id, this.getPath())
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
const currentBeforeEnter = this.getCurrent()
|
|
191
194
|
this._isActive.set(true)
|
|
192
195
|
this.onEnter?.(info, from)
|
|
193
196
|
|
|
194
|
-
|
|
197
|
+
// If onEnter already transitioned to a child, entering the initial child on top of it would
|
|
198
|
+
// leave that child active but orphaned
|
|
199
|
+
if (
|
|
200
|
+
this.children &&
|
|
201
|
+
this.initial &&
|
|
202
|
+
this.getIsActive() &&
|
|
203
|
+
this.getCurrent() === currentBeforeEnter
|
|
204
|
+
) {
|
|
195
205
|
const initial = this.children[this.initial]
|
|
196
206
|
this._current.set(initial)
|
|
197
207
|
initial.enter(info, from)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createTLStore } from '../..'
|
|
2
|
+
import { Editor } from '../editor/Editor'
|
|
3
|
+
import { tleditors } from './editors'
|
|
4
|
+
|
|
5
|
+
const editors: Editor[] = []
|
|
6
|
+
|
|
7
|
+
function createTestEditor() {
|
|
8
|
+
const editor = new Editor({
|
|
9
|
+
shapeUtils: [],
|
|
10
|
+
bindingUtils: [],
|
|
11
|
+
tools: [],
|
|
12
|
+
store: createTLStore({ shapeUtils: [], bindingUtils: [] }),
|
|
13
|
+
getContainer: () => document.body,
|
|
14
|
+
})
|
|
15
|
+
editors.push(editor)
|
|
16
|
+
return editor
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
for (const editor of editors) {
|
|
21
|
+
editor.dispose()
|
|
22
|
+
}
|
|
23
|
+
editors.length = 0
|
|
24
|
+
expect(tleditors.getMounted()).toEqual([])
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
describe('tleditors', () => {
|
|
28
|
+
it('tracks editors as they mount and unmount', () => {
|
|
29
|
+
const editor = createTestEditor()
|
|
30
|
+
expect(tleditors.getMounted()).toEqual([])
|
|
31
|
+
|
|
32
|
+
editor.emit('mount')
|
|
33
|
+
expect(tleditors.getMounted()).toEqual([editor])
|
|
34
|
+
|
|
35
|
+
editor.emit('unmount')
|
|
36
|
+
expect(tleditors.getMounted()).toEqual([])
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('does not add the same editor twice', () => {
|
|
40
|
+
const editor = createTestEditor()
|
|
41
|
+
editor.emit('mount')
|
|
42
|
+
editor.emit('mount')
|
|
43
|
+
expect(tleditors.getMounted()).toEqual([editor])
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('removes an editor when it is disposed while mounted', () => {
|
|
47
|
+
const editor = createTestEditor()
|
|
48
|
+
editor.emit('mount')
|
|
49
|
+
expect(tleditors.getMounted()).toEqual([editor])
|
|
50
|
+
|
|
51
|
+
editor.dispose()
|
|
52
|
+
expect(tleditors.getMounted()).toEqual([])
|
|
53
|
+
expect(editor.getIsMounted()).toBe(false)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
it('removes an editor on dispose even if the unmount listener was removed', () => {
|
|
57
|
+
const editor = createTestEditor()
|
|
58
|
+
editor.emit('mount')
|
|
59
|
+
editor.removeAllListeners('unmount')
|
|
60
|
+
|
|
61
|
+
editor.dispose()
|
|
62
|
+
expect(tleditors.getMounted()).toEqual([])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('tracks multiple mounted editors', () => {
|
|
66
|
+
const editorA = createTestEditor()
|
|
67
|
+
const editorB = createTestEditor()
|
|
68
|
+
|
|
69
|
+
editorA.emit('mount')
|
|
70
|
+
editorB.emit('mount')
|
|
71
|
+
expect(tleditors.getMounted()).toEqual([editorA, editorB])
|
|
72
|
+
|
|
73
|
+
editorA.emit('unmount')
|
|
74
|
+
expect(tleditors.getMounted()).toEqual([editorB])
|
|
75
|
+
})
|
|
76
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Signal, atom } from '@tldraw/state'
|
|
2
|
+
import type { Editor } from '../editor/Editor'
|
|
3
|
+
|
|
4
|
+
const mounted = atom<readonly Editor[]>('mounted editors', [])
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A global registry of currently mounted editor instances. Use it to discover live editors from
|
|
8
|
+
* outside the React tree, for example from sidebar chrome, keyboard shortcuts, or multi-editor
|
|
9
|
+
* layouts.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* tleditors.getMounted() // readonly Editor[]
|
|
14
|
+
*
|
|
15
|
+
* const editors = useValue('mounted editors', () => tleditors.getMounted(), [])
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
export const tleditors = {
|
|
21
|
+
/**
|
|
22
|
+
* A reactive list of currently mounted editor instances.
|
|
23
|
+
*
|
|
24
|
+
* An editor is added when it emits the `mount` event and removed when it emits `unmount`
|
|
25
|
+
* (including when the editor is disposed while still mounted).
|
|
26
|
+
*
|
|
27
|
+
* @public
|
|
28
|
+
*/
|
|
29
|
+
mounted: mounted as Signal<readonly Editor[]>,
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the currently mounted editor instances.
|
|
33
|
+
*
|
|
34
|
+
* @public
|
|
35
|
+
*/
|
|
36
|
+
getMounted(): readonly Editor[] {
|
|
37
|
+
return mounted.get()
|
|
38
|
+
},
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @internal */
|
|
42
|
+
export function registerMountedEditor(editor: Editor) {
|
|
43
|
+
const current = mounted.get()
|
|
44
|
+
if (current.includes(editor)) return
|
|
45
|
+
mounted.set([...current, editor])
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** @internal */
|
|
49
|
+
export function unregisterMountedEditor(editor: Editor) {
|
|
50
|
+
const current = mounted.get()
|
|
51
|
+
if (!current.includes(editor)) return
|
|
52
|
+
mounted.set(current.filter((e) => e !== editor))
|
|
53
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { tlmenus } from './menus'
|
|
2
|
+
|
|
3
|
+
describe('tlmenus', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
tlmenus.clearOpenMenus()
|
|
6
|
+
tlmenus._hiddenMenus = []
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
it('reports whether a menu is open with and without a context', () => {
|
|
10
|
+
tlmenus.addOpenMenu('main')
|
|
11
|
+
tlmenus.addOpenMenu('main', 'editor-1')
|
|
12
|
+
|
|
13
|
+
expect(tlmenus.isMenuOpen('main')).toBe(true)
|
|
14
|
+
expect(tlmenus.isMenuOpen('main', 'editor-1')).toBe(true)
|
|
15
|
+
expect(tlmenus.isMenuOpen('main', 'editor-2')).toBe(false)
|
|
16
|
+
expect(tlmenus.isMenuOpen('other')).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('hides and restores the open menus of a context', () => {
|
|
20
|
+
tlmenus.addOpenMenu('main', 'editor-1')
|
|
21
|
+
tlmenus.addOpenMenu('main', 'editor-2')
|
|
22
|
+
|
|
23
|
+
tlmenus.hideOpenMenus('editor-1')
|
|
24
|
+
expect(tlmenus.getOpenMenus()).toEqual(['main-editor-2'])
|
|
25
|
+
|
|
26
|
+
tlmenus.showOpenMenus('editor-1')
|
|
27
|
+
expect(tlmenus.getOpenMenus().sort()).toEqual(['main-editor-1', 'main-editor-2'])
|
|
28
|
+
|
|
29
|
+
// closing the real menu afterwards leaves nothing behind
|
|
30
|
+
tlmenus.deleteOpenMenu('main', 'editor-1')
|
|
31
|
+
expect(tlmenus.hasOpenMenus('editor-1')).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
})
|
package/src/lib/globals/menus.ts
CHANGED
|
@@ -106,10 +106,12 @@ export const tlmenus = {
|
|
|
106
106
|
* @public
|
|
107
107
|
*/
|
|
108
108
|
hideOpenMenus(contextId?: string) {
|
|
109
|
+
// ids read back from the store already carry their context suffix, so they are deleted
|
|
110
|
+
// (and later re-added) as-is rather than suffixed again
|
|
109
111
|
this._hiddenMenus = [...this.getOpenMenus(contextId)]
|
|
110
112
|
if (this._hiddenMenus.length === 0) return
|
|
111
113
|
for (const menu of this._hiddenMenus) {
|
|
112
|
-
this.deleteOpenMenu(menu
|
|
114
|
+
this.deleteOpenMenu(menu)
|
|
113
115
|
}
|
|
114
116
|
},
|
|
115
117
|
|
|
@@ -128,10 +130,15 @@ export const tlmenus = {
|
|
|
128
130
|
*/
|
|
129
131
|
showOpenMenus(contextId?: string) {
|
|
130
132
|
if (this._hiddenMenus.length === 0) return
|
|
133
|
+
const stillHidden: string[] = []
|
|
131
134
|
for (const menu of this._hiddenMenus) {
|
|
132
|
-
|
|
135
|
+
if (!contextId || menu.endsWith('-' + contextId)) {
|
|
136
|
+
this.addOpenMenu(menu)
|
|
137
|
+
} else {
|
|
138
|
+
stillHidden.push(menu)
|
|
139
|
+
}
|
|
133
140
|
}
|
|
134
|
-
this._hiddenMenus =
|
|
141
|
+
this._hiddenMenus = stillHidden
|
|
135
142
|
},
|
|
136
143
|
|
|
137
144
|
/**
|
|
@@ -148,7 +155,7 @@ export const tlmenus = {
|
|
|
148
155
|
* @public
|
|
149
156
|
*/
|
|
150
157
|
isMenuOpen(id: string, contextId?: string): boolean {
|
|
151
|
-
return this.getOpenMenus(contextId).includes(`${id}-${contextId}`)
|
|
158
|
+
return this.getOpenMenus(contextId).includes(contextId ? `${id}-${contextId}` : id)
|
|
152
159
|
},
|
|
153
160
|
|
|
154
161
|
/**
|
|
@@ -153,6 +153,7 @@ export function useCanvasEvents() {
|
|
|
153
153
|
if (e.dataTransfer?.files?.length) {
|
|
154
154
|
const files = Array.from(e.dataTransfer.files)
|
|
155
155
|
|
|
156
|
+
editor.markHistoryStoppingPoint('drop')
|
|
156
157
|
await editor.putExternalContent({
|
|
157
158
|
type: 'files',
|
|
158
159
|
files,
|
|
@@ -163,6 +164,7 @@ export function useCanvasEvents() {
|
|
|
163
164
|
|
|
164
165
|
const url = e.dataTransfer.getData('url')
|
|
165
166
|
if (url) {
|
|
167
|
+
editor.markHistoryStoppingPoint('drop')
|
|
166
168
|
await editor.putExternalContent({
|
|
167
169
|
type: 'url',
|
|
168
170
|
url,
|