@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
|
-
import { useEffect } from 'react'
|
|
2
|
+
import { useEffect, useRef } from 'react'
|
|
3
3
|
import { Editor } from '../editor/Editor'
|
|
4
4
|
import { TLKeyboardEventInfo } from '../editor/types/event-types'
|
|
5
5
|
import { activeElementShouldCaptureKeys, preventDefault } from '../utils/dom'
|
|
@@ -14,6 +14,10 @@ export function useDocumentEvents() {
|
|
|
14
14
|
const isEditing = useValue('isEditing', () => editor.getEditingShapeId(), [editor])
|
|
15
15
|
const isAppFocused = useValue('isFocused', () => editor.getIsFocused(), [editor])
|
|
16
16
|
|
|
17
|
+
// `inputs.keys` only stores codes, but tools match `onKeyUp` on `info.key`, so the window
|
|
18
|
+
// blur release below needs the key remembered per code.
|
|
19
|
+
const heldKeysRef = useRef(new Map<string, string>())
|
|
20
|
+
|
|
17
21
|
// Prevent the browser's default drag and drop behavior on our container (UI, etc)
|
|
18
22
|
useEffect(() => {
|
|
19
23
|
if (!container) return
|
|
@@ -29,8 +33,12 @@ export function useDocumentEvents() {
|
|
|
29
33
|
// re-dispatched, which would lead to an infinite loop.
|
|
30
34
|
if ((e as any).isSpecialRedispatchedEvent) return
|
|
31
35
|
preventDefault(e)
|
|
32
|
-
e.stopPropagation()
|
|
33
36
|
const cvs = container.querySelector('.tl-canvas')
|
|
37
|
+
// A drop on the canvas itself already reaches the canvas (and any shape's own React
|
|
38
|
+
// drop handlers) by bubbling; stopping it here would hide it from React, which
|
|
39
|
+
// delegates from the root above this container.
|
|
40
|
+
if (cvs?.contains(e.target as Node | null)) return
|
|
41
|
+
e.stopPropagation()
|
|
34
42
|
if (!cvs) return
|
|
35
43
|
const newEvent = new DragEvent(e.type, e)
|
|
36
44
|
;(newEvent as any).isSpecialRedispatchedEvent = true
|
|
@@ -208,6 +216,7 @@ export function useDocumentEvents() {
|
|
|
208
216
|
accelKey: isAccelKey(e),
|
|
209
217
|
}
|
|
210
218
|
|
|
219
|
+
heldKeysRef.current.set(e.code, e.key)
|
|
211
220
|
editor.dispatch(info)
|
|
212
221
|
}
|
|
213
222
|
|
|
@@ -235,24 +244,26 @@ export function useDocumentEvents() {
|
|
|
235
244
|
accelKey: isAccelKey(e),
|
|
236
245
|
}
|
|
237
246
|
|
|
247
|
+
heldKeysRef.current.delete(e.code)
|
|
238
248
|
editor.dispatch(info)
|
|
239
249
|
}
|
|
240
250
|
|
|
241
251
|
function handleTouchStart(e: TouchEvent) {
|
|
242
252
|
if (container.contains(e.target as Node)) {
|
|
243
|
-
// Center point of the touch area
|
|
244
|
-
|
|
253
|
+
// Center point of the touch area, measured from the edge of the window the touch
|
|
254
|
+
// has to reach to trigger the navigation
|
|
255
|
+
const touchXPosition = e.touches[0].clientX
|
|
245
256
|
// Size of the touch area
|
|
246
257
|
const touchXRadius = e.touches[0].radiusX || 0
|
|
247
258
|
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
|
|
259
|
+
// If the touch area overlaps with the screen edges it's likely to trigger the
|
|
260
|
+
// navigation. We prevent the touchstart event in that case. The gesture belongs to
|
|
261
|
+
// the window, so an editor inset from the window's edges — beside a sidebar, say —
|
|
262
|
+
// is not near an edge the system reacts to.
|
|
263
|
+
const windowWidth = editor.getContainerWindow().innerWidth
|
|
253
264
|
if (
|
|
254
265
|
touchXPosition - touchXRadius < 10 ||
|
|
255
|
-
touchXPosition + touchXRadius >
|
|
266
|
+
touchXPosition + touchXRadius > windowWidth - 10
|
|
256
267
|
) {
|
|
257
268
|
if ((e.target as HTMLElement)?.tagName === 'BUTTON') {
|
|
258
269
|
// Force a click before bailing
|
|
@@ -276,10 +287,13 @@ export function useDocumentEvents() {
|
|
|
276
287
|
|
|
277
288
|
container.addEventListener('wheel', handleWheel, { passive: false })
|
|
278
289
|
|
|
290
|
+
// Not the shared `preventDefault` reference: the DOM dedupes identical listeners, so with two
|
|
291
|
+
// editors in one document the first cleanup would otherwise remove it for both.
|
|
292
|
+
const handleGesture = (e: Event) => preventDefault(e)
|
|
279
293
|
const ownerDoc = container.ownerDocument
|
|
280
|
-
ownerDoc.addEventListener('gesturestart',
|
|
281
|
-
ownerDoc.addEventListener('gesturechange',
|
|
282
|
-
ownerDoc.addEventListener('gestureend',
|
|
294
|
+
ownerDoc.addEventListener('gesturestart', handleGesture)
|
|
295
|
+
ownerDoc.addEventListener('gesturechange', handleGesture)
|
|
296
|
+
ownerDoc.addEventListener('gestureend', handleGesture)
|
|
283
297
|
|
|
284
298
|
container.addEventListener('keydown', handleKeyDown)
|
|
285
299
|
container.addEventListener('keyup', handleKeyUp)
|
|
@@ -289,14 +303,44 @@ export function useDocumentEvents() {
|
|
|
289
303
|
|
|
290
304
|
container.removeEventListener('wheel', handleWheel)
|
|
291
305
|
|
|
292
|
-
ownerDoc.removeEventListener('gesturestart',
|
|
293
|
-
ownerDoc.removeEventListener('gesturechange',
|
|
294
|
-
ownerDoc.removeEventListener('gestureend',
|
|
306
|
+
ownerDoc.removeEventListener('gesturestart', handleGesture)
|
|
307
|
+
ownerDoc.removeEventListener('gesturechange', handleGesture)
|
|
308
|
+
ownerDoc.removeEventListener('gestureend', handleGesture)
|
|
295
309
|
|
|
296
310
|
container.removeEventListener('keydown', handleKeyDown)
|
|
297
311
|
container.removeEventListener('keyup', handleKeyUp)
|
|
298
312
|
}
|
|
299
313
|
}, [editor, container, isAppFocused, isEditing])
|
|
314
|
+
|
|
315
|
+
// Alt+Tab / Cmd+Tab away while holding a key delivers the keyup to the other app, so without
|
|
316
|
+
// this Space-panning (grab cursor, drags pan) stuck until the key was pressed again (#10442).
|
|
317
|
+
// Replaying a key_up per held key runs the normal release path, including tool onKeyUp.
|
|
318
|
+
useEffect(() => {
|
|
319
|
+
const win = editor.getContainerWindow()
|
|
320
|
+
|
|
321
|
+
const handleWindowBlur = () => {
|
|
322
|
+
const heldKeys = heldKeysRef.current
|
|
323
|
+
for (const code of [...editor.inputs.keys]) {
|
|
324
|
+
editor.dispatch({
|
|
325
|
+
type: 'keyboard',
|
|
326
|
+
name: 'key_up',
|
|
327
|
+
key: heldKeys.get(code) ?? code,
|
|
328
|
+
code,
|
|
329
|
+
shiftKey: false,
|
|
330
|
+
altKey: false,
|
|
331
|
+
ctrlKey: false,
|
|
332
|
+
metaKey: false,
|
|
333
|
+
accelKey: false,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
heldKeys.clear()
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
win.addEventListener('blur', handleWindowBlur)
|
|
340
|
+
return () => {
|
|
341
|
+
win.removeEventListener('blur', handleWindowBlur)
|
|
342
|
+
}
|
|
343
|
+
}, [editor])
|
|
300
344
|
}
|
|
301
345
|
|
|
302
346
|
function areShortcutsDisabled(editor: Editor) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { TLAssetId, TLAssetStore, TLStoreSnapshot } from '@tldraw/tlschema'
|
|
2
|
+
import { noop } from '@tldraw/utils'
|
|
3
3
|
import { useEffect } from 'react'
|
|
4
4
|
import { TLStoreOptions, createTLStore } from '../config/createTLStore'
|
|
5
5
|
import { TLEditorSnapshot } from '../config/TLEditorSnapshot'
|
|
@@ -33,7 +33,9 @@ export function useLocalStore(
|
|
|
33
33
|
|
|
34
34
|
setState({ status: 'loading' })
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
// Keyed by asset id rather than record identity so that a record update doesn't mint
|
|
37
|
+
// another object url for the same blob; every url minted here is revoked on cleanup.
|
|
38
|
+
const objectURLCache = new Map<TLAssetId, Promise<string | null>>()
|
|
37
39
|
const assets: TLAssetStore = {
|
|
38
40
|
upload: async (asset, file) => {
|
|
39
41
|
await client.db.storeAsset(asset.id, file)
|
|
@@ -43,11 +45,14 @@ export function useLocalStore(
|
|
|
43
45
|
if (!asset.props.src) return null
|
|
44
46
|
|
|
45
47
|
if (asset.props.src.startsWith('asset:')) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
let objectURL = objectURLCache.get(asset.id)
|
|
49
|
+
if (!objectURL) {
|
|
50
|
+
objectURL = client.db
|
|
51
|
+
.getAsset(asset.id)
|
|
52
|
+
.then((blob) => (blob ? URL.createObjectURL(blob) : null))
|
|
53
|
+
objectURLCache.set(asset.id, objectURL)
|
|
54
|
+
}
|
|
55
|
+
return await objectURL
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
return asset.props.src
|
|
@@ -78,6 +83,9 @@ export function useLocalStore(
|
|
|
78
83
|
return () => {
|
|
79
84
|
isClosed = true
|
|
80
85
|
client.close()
|
|
86
|
+
for (const objectURL of objectURLCache.values()) {
|
|
87
|
+
objectURL.then((url) => url && URL.revokeObjectURL(url), noop)
|
|
88
|
+
}
|
|
81
89
|
}
|
|
82
90
|
}, [options, setState])
|
|
83
91
|
|
|
@@ -70,6 +70,9 @@ export function usePassThroughWheelEvents(ref: RefObject<HTMLElement | null>) {
|
|
|
70
70
|
const newEvent = new WheelEvent('wheel', e as any)
|
|
71
71
|
;(newEvent as any).isSpecialRedispatchedEvent = true
|
|
72
72
|
cvs.dispatchEvent(newEvent)
|
|
73
|
+
// Flag the original too: it keeps bubbling, and an enclosing pass-through root (the
|
|
74
|
+
// navigation panel around the minimap) would otherwise redispatch it a second time.
|
|
75
|
+
;(e as any).isSpecialRedispatchedEvent = true
|
|
73
76
|
})
|
|
74
77
|
|
|
75
78
|
// The element the listener is currently on, which isn't necessarily `ref.current`: a `RefObject`
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useComputed, useValue } from '@tldraw/state-react'
|
|
2
|
+
import { areArraysShallowEqual } from '@tldraw/utils'
|
|
2
3
|
import { uniq } from '../utils/uniq'
|
|
3
4
|
import { useEditor } from './useEditor'
|
|
4
5
|
|
|
@@ -17,7 +18,7 @@ export function usePeerIds() {
|
|
|
17
18
|
const $userIds = useComputed(
|
|
18
19
|
'userIds',
|
|
19
20
|
() => uniq(editor.getVisibleCollaborators().map((p) => p.userId)).sort(),
|
|
20
|
-
{ isEqual:
|
|
21
|
+
{ isEqual: areArraysShallowEqual },
|
|
21
22
|
[editor]
|
|
22
23
|
)
|
|
23
24
|
|
|
@@ -316,6 +316,42 @@ describe('LicenseManager', () => {
|
|
|
316
316
|
expect(result.isDomainValid).toBe(true)
|
|
317
317
|
})
|
|
318
318
|
|
|
319
|
+
it('Fails if the wildcard domain is a prefix of the current hostname', async () => {
|
|
320
|
+
// @ts-ignore
|
|
321
|
+
delete window.location
|
|
322
|
+
// @ts-ignore
|
|
323
|
+
window.location = new URL('https://sub.example.com.other.io')
|
|
324
|
+
|
|
325
|
+
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
326
|
+
permissiveHostsInfo[PROPERTIES.HOSTS] = ['*.example.com']
|
|
327
|
+
const permissiveLicenseKey = await generateLicenseKey(
|
|
328
|
+
JSON.stringify(permissiveHostsInfo),
|
|
329
|
+
keyPair
|
|
330
|
+
)
|
|
331
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
332
|
+
permissiveLicenseKey
|
|
333
|
+
)) as ValidLicenseKeyResult
|
|
334
|
+
expect(result.isDomainValid).toBe(false)
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
it('Fails if the current hostname only ends with the wildcard domain', async () => {
|
|
338
|
+
// @ts-ignore
|
|
339
|
+
delete window.location
|
|
340
|
+
// @ts-ignore
|
|
341
|
+
window.location = new URL('https://notexample.com')
|
|
342
|
+
|
|
343
|
+
const permissiveHostsInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
344
|
+
permissiveHostsInfo[PROPERTIES.HOSTS] = ['*.example.com']
|
|
345
|
+
const permissiveLicenseKey = await generateLicenseKey(
|
|
346
|
+
JSON.stringify(permissiveHostsInfo),
|
|
347
|
+
keyPair
|
|
348
|
+
)
|
|
349
|
+
const result = (await licenseManager.getLicenseFromKey(
|
|
350
|
+
permissiveLicenseKey
|
|
351
|
+
)) as ValidLicenseKeyResult
|
|
352
|
+
expect(result.isDomainValid).toBe(false)
|
|
353
|
+
})
|
|
354
|
+
|
|
319
355
|
it('Fails if has a subdomain wildcard isnt for the same base domain', async () => {
|
|
320
356
|
// @ts-ignore
|
|
321
357
|
delete window.location
|
|
@@ -422,6 +458,107 @@ describe('LicenseManager', () => {
|
|
|
422
458
|
expect(result.isDomainValid).toBe(true)
|
|
423
459
|
})
|
|
424
460
|
|
|
461
|
+
it('Is not development mode on a custom protocol', async () => {
|
|
462
|
+
process.env.NODE_ENV = 'production'
|
|
463
|
+
try {
|
|
464
|
+
// @ts-ignore
|
|
465
|
+
delete window.location
|
|
466
|
+
// @ts-ignore
|
|
467
|
+
window.location = new URL('app-bundle://app/index.html')
|
|
468
|
+
|
|
469
|
+
const nativeLicenseInfo = JSON.parse(STANDARD_LICENSE_INFO)
|
|
470
|
+
nativeLicenseInfo[PROPERTIES.FLAGS] = FLAGS.NATIVE_LICENSE
|
|
471
|
+
nativeLicenseInfo[PROPERTIES.HOSTS] = ['app-bundle:']
|
|
472
|
+
const nativeLicenseKey = await generateLicenseKey(
|
|
473
|
+
JSON.stringify(nativeLicenseInfo),
|
|
474
|
+
keyPair
|
|
475
|
+
)
|
|
476
|
+
const nativeLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
477
|
+
const result = (await nativeLicenseManager.getLicenseFromKey(
|
|
478
|
+
nativeLicenseKey
|
|
479
|
+
)) as ValidLicenseKeyResult
|
|
480
|
+
expect(result).toMatchObject({
|
|
481
|
+
isLicenseParseable: true,
|
|
482
|
+
isNativeLicense: true,
|
|
483
|
+
isDomainValid: true,
|
|
484
|
+
isDevelopment: false,
|
|
485
|
+
})
|
|
486
|
+
expect(getLicenseState(result, () => {}, result.isDevelopment)).toBe('licensed')
|
|
487
|
+
} finally {
|
|
488
|
+
process.env.NODE_ENV = 'test'
|
|
489
|
+
}
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
it('Validates the domain on a custom protocol', async () => {
|
|
493
|
+
process.env.NODE_ENV = 'production'
|
|
494
|
+
try {
|
|
495
|
+
// @ts-ignore
|
|
496
|
+
delete window.location
|
|
497
|
+
// @ts-ignore
|
|
498
|
+
window.location = new URL('app-bundle://app/index.html')
|
|
499
|
+
|
|
500
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
501
|
+
const customProtocolLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
502
|
+
const result = (await customProtocolLicenseManager.getLicenseFromKey(
|
|
503
|
+
licenseKey
|
|
504
|
+
)) as ValidLicenseKeyResult
|
|
505
|
+
expect(result).toMatchObject({ isDomainValid: false, isDevelopment: false })
|
|
506
|
+
expect(getLicenseState(result, () => {}, result.isDevelopment)).toBe(
|
|
507
|
+
'unlicensed-production'
|
|
508
|
+
)
|
|
509
|
+
} finally {
|
|
510
|
+
process.env.NODE_ENV = 'test'
|
|
511
|
+
}
|
|
512
|
+
})
|
|
513
|
+
|
|
514
|
+
it('Is development mode over http on a non-loopback host', async () => {
|
|
515
|
+
process.env.NODE_ENV = 'production'
|
|
516
|
+
try {
|
|
517
|
+
// @ts-ignore
|
|
518
|
+
delete window.location
|
|
519
|
+
// @ts-ignore
|
|
520
|
+
window.location = new URL('http://www.example.com')
|
|
521
|
+
|
|
522
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
523
|
+
const httpLicenseManager = new LicenseManager('', keyPair.publicKey)
|
|
524
|
+
const result = (await httpLicenseManager.getLicenseFromKey(
|
|
525
|
+
licenseKey
|
|
526
|
+
)) as ValidLicenseKeyResult
|
|
527
|
+
expect(result.isDevelopment).toBe(true)
|
|
528
|
+
} finally {
|
|
529
|
+
process.env.NODE_ENV = 'test'
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
|
|
533
|
+
it.each([
|
|
534
|
+
['http://localhost:5173/', true],
|
|
535
|
+
['http://127.0.0.1:3000/', true],
|
|
536
|
+
['https://localhost:8443/', true],
|
|
537
|
+
['https://[::1]:8443/', true],
|
|
538
|
+
['http://192.168.1.5:3000/', true],
|
|
539
|
+
['http://staging-box/', true],
|
|
540
|
+
['https://www.example.com/', false],
|
|
541
|
+
['tauri://localhost/index.html', false],
|
|
542
|
+
['http://tauri.localhost/index.html', false],
|
|
543
|
+
['app://tauri.localhost/com.example.app', false],
|
|
544
|
+
['app-bundle://localhost/index.html', false],
|
|
545
|
+
])('Development mode at %s is %s', async (href, expected) => {
|
|
546
|
+
process.env.NODE_ENV = 'production'
|
|
547
|
+
try {
|
|
548
|
+
// @ts-ignore
|
|
549
|
+
delete window.location
|
|
550
|
+
// @ts-ignore
|
|
551
|
+
window.location = new URL(href)
|
|
552
|
+
|
|
553
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
554
|
+
const manager = new LicenseManager('', keyPair.publicKey)
|
|
555
|
+
const result = (await manager.getLicenseFromKey(licenseKey)) as ValidLicenseKeyResult
|
|
556
|
+
expect(result.isDevelopment).toBe(expected)
|
|
557
|
+
} finally {
|
|
558
|
+
process.env.NODE_ENV = 'test'
|
|
559
|
+
}
|
|
560
|
+
})
|
|
561
|
+
|
|
425
562
|
it('Fails if it is a native app with the wrong protocol', async () => {
|
|
426
563
|
// @ts-ignore
|
|
427
564
|
delete window.location
|
|
@@ -437,6 +574,29 @@ describe('LicenseManager', () => {
|
|
|
437
574
|
)) as ValidLicenseKeyResult
|
|
438
575
|
expect(result.isDomainValid).toBe(false)
|
|
439
576
|
})
|
|
577
|
+
|
|
578
|
+
it.each(['tauri://localhost/index.html', 'http://tauri.localhost/index.html'])(
|
|
579
|
+
'Does not license an unrelated key at %s',
|
|
580
|
+
async (href) => {
|
|
581
|
+
process.env.NODE_ENV = 'production'
|
|
582
|
+
try {
|
|
583
|
+
// @ts-ignore
|
|
584
|
+
delete window.location
|
|
585
|
+
// @ts-ignore
|
|
586
|
+
window.location = new URL(href)
|
|
587
|
+
|
|
588
|
+
const licenseKey = await generateLicenseKey(STANDARD_LICENSE_INFO, keyPair)
|
|
589
|
+
const manager = new LicenseManager('', keyPair.publicKey)
|
|
590
|
+
const result = (await manager.getLicenseFromKey(licenseKey)) as ValidLicenseKeyResult
|
|
591
|
+
expect(result).toMatchObject({ isDevelopment: false, isDomainValid: false })
|
|
592
|
+
expect(getLicenseState(result, () => {}, result.isDevelopment)).toBe(
|
|
593
|
+
'unlicensed-production'
|
|
594
|
+
)
|
|
595
|
+
} finally {
|
|
596
|
+
process.env.NODE_ENV = 'test'
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
)
|
|
440
600
|
})
|
|
441
601
|
|
|
442
602
|
describe('License types and flags', () => {
|
|
@@ -174,10 +174,17 @@ export class LicenseManager {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
private getIsDevelopment() {
|
|
177
|
-
|
|
177
|
+
const protocol = window.location.protocol
|
|
178
|
+
const hostname = window.location.hostname
|
|
179
|
+
|
|
180
|
+
// Tauri uses `tauri://localhost` on macOS and Linux and `http://tauri.localhost` on Windows.
|
|
181
|
+
if (hostname.toLowerCase().endsWith('.localhost')) {
|
|
182
|
+
return process.env.NODE_ENV !== 'production'
|
|
183
|
+
}
|
|
184
|
+
|
|
178
185
|
return (
|
|
179
|
-
|
|
180
|
-
this.isLoopbackHost(
|
|
186
|
+
protocol === 'http:' ||
|
|
187
|
+
(protocol === 'https:' && this.isLoopbackHost(hostname)) ||
|
|
181
188
|
process.env.NODE_ENV !== 'production'
|
|
182
189
|
)
|
|
183
190
|
}
|
|
@@ -407,7 +414,9 @@ export class LicenseManager {
|
|
|
407
414
|
|
|
408
415
|
// Glob testing, we only support '*.somedomain.com' right now.
|
|
409
416
|
if (host.includes('*')) {
|
|
410
|
-
const globToRegex = new RegExp(
|
|
417
|
+
const globToRegex = new RegExp(
|
|
418
|
+
normalizedHostOrUrlRegex.replace(/\./g, '\\.').replace(/\*/g, '.*?') + '$'
|
|
419
|
+
)
|
|
411
420
|
return globToRegex.test(currentHostname) || globToRegex.test(`www.${currentHostname}`)
|
|
412
421
|
}
|
|
413
422
|
|
|
@@ -1,13 +1,34 @@
|
|
|
1
1
|
import { useValue } from '@tldraw/state-react'
|
|
2
2
|
import { createContext, ReactNode, useContext, useEffect, useState } from 'react'
|
|
3
|
+
import { useMaybeEditor } from '../hooks/useEditor'
|
|
3
4
|
import { LicenseManager } from './LicenseManager'
|
|
4
5
|
|
|
5
6
|
/** @internal */
|
|
6
|
-
export const LicenseContext = createContext
|
|
7
|
+
export const LicenseContext = createContext<LicenseManager | null>(null)
|
|
7
8
|
|
|
8
9
|
/** @internal */
|
|
9
|
-
export function useLicenseContext() {
|
|
10
|
-
|
|
10
|
+
export function useLicenseContext(): LicenseManager {
|
|
11
|
+
const licenseManager = useMaybeLicenseManager()
|
|
12
|
+
if (!licenseManager) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
'useLicenseContext must be used inside of the <Tldraw /> or <TldrawEditor /> components, or inside an <EditorProvider /> wrapping an editor created by them'
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
return licenseManager
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the license manager for the current editor, or `null` if there is none. Reads the
|
|
22
|
+
* license context when inside `<TldrawEditor />`, and otherwise falls back to the license manager
|
|
23
|
+
* of the nearest editor, so UI mounted outside the editor tree (via `EditorProvider`) resolves the
|
|
24
|
+
* same license as the editor it belongs to.
|
|
25
|
+
*
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
export function useMaybeLicenseManager(): LicenseManager | null {
|
|
29
|
+
const licenseManager = useContext(LicenseContext)
|
|
30
|
+
const editor = useMaybeEditor()
|
|
31
|
+
return licenseManager ?? editor?.licenseManager ?? null
|
|
11
32
|
}
|
|
12
33
|
|
|
13
34
|
function shouldHideEditorAfterDelay(licenseState: string): boolean {
|
|
@@ -8,16 +8,18 @@ export function useLicenseManagerState(licenseManager: LicenseManager): LicenseS
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Reactively reads whether a licensable feature is enabled for the current license. Re-renders when
|
|
11
|
-
* license validation resolves.
|
|
11
|
+
* license validation resolves. Returns `false` when there is no license manager, so gated UI stays
|
|
12
|
+
* hidden when mounted without an editor or license context.
|
|
12
13
|
*
|
|
13
14
|
* @internal
|
|
14
15
|
*/
|
|
15
16
|
export function useLicenseFeatureFlag(
|
|
16
|
-
licenseManager: LicenseManager,
|
|
17
|
+
licenseManager: LicenseManager | null,
|
|
17
18
|
feature: LicenseFeatureName
|
|
18
19
|
): boolean {
|
|
19
|
-
return useValue(
|
|
20
|
-
|
|
21
|
-
feature,
|
|
22
|
-
|
|
20
|
+
return useValue(
|
|
21
|
+
'licenseFeature',
|
|
22
|
+
() => (licenseManager ? licenseManager.isFeatureEnabled(feature) : false),
|
|
23
|
+
[licenseManager, feature]
|
|
24
|
+
)
|
|
23
25
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box } from './Box'
|
|
1
|
+
import { Box, rotateSelectionHandle } from './Box'
|
|
2
2
|
import { Vec } from './Vec'
|
|
3
3
|
|
|
4
4
|
describe('Box', () => {
|
|
@@ -759,3 +759,11 @@ describe('Box', () => {
|
|
|
759
759
|
})
|
|
760
760
|
})
|
|
761
761
|
})
|
|
762
|
+
|
|
763
|
+
describe('rotateSelectionHandle', () => {
|
|
764
|
+
it('wraps around for negative rotations', () => {
|
|
765
|
+
expect(rotateSelectionHandle('top', Math.PI / 2)).toBe('right')
|
|
766
|
+
expect(rotateSelectionHandle('top', -Math.PI / 2)).toBe('left')
|
|
767
|
+
expect(rotateSelectionHandle('top_left', -Math.PI)).toBe('bottom_right')
|
|
768
|
+
})
|
|
769
|
+
})
|
|
@@ -678,7 +678,9 @@ export function rotateSelectionHandle(handle: SelectionHandle, rotation: number)
|
|
|
678
678
|
const numSteps = Math.round(rotation / (PI / 4))
|
|
679
679
|
|
|
680
680
|
const currentIndex = ORDERED_SELECTION_HANDLES.indexOf(handle)
|
|
681
|
-
|
|
681
|
+
// numSteps is negative for negative rotations, so wrap the index into range
|
|
682
|
+
const n = ORDERED_SELECTION_HANDLES.length
|
|
683
|
+
return ORDERED_SELECTION_HANDLES[(((currentIndex + numSteps) % n) + n) % n]
|
|
682
684
|
}
|
|
683
685
|
|
|
684
686
|
/** @public */
|
|
@@ -336,6 +336,16 @@ describe('Vec.Slope', () => {
|
|
|
336
336
|
expect(Vec.Slope(new Vec(0, 0), new Vec(50, 100))).toEqual(2)
|
|
337
337
|
expect(Vec.Slope(new Vec(0, 0), new Vec(-50, 100))).toEqual(-2)
|
|
338
338
|
expect(Vec.Slope(new Vec(123, 456), new Vec(789, 24))).toEqual(-0.6486486486486487)
|
|
339
|
+
// A.x happened to equal B.y here, which used to trip the vertical guard
|
|
340
|
+
expect(Vec.Slope(new Vec(1, 0), new Vec(0, 1))).toEqual(-1)
|
|
341
|
+
expect(Vec.Slope(new Vec(5, 0), new Vec(5, 10))).toBeNaN()
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
describe('Vec.cross', () => {
|
|
346
|
+
it('Matches the static Vec.Cross.', () => {
|
|
347
|
+
expect(Vec.Cross(new Vec(1, 2, 3), new Vec(4, 5, 6))).toMatchObject({ x: -3, y: 6 })
|
|
348
|
+
expect(new Vec(1, 2, 3).cross(new Vec(4, 5, 6))).toMatchObject({ x: -3, y: 6 })
|
|
339
349
|
})
|
|
340
350
|
})
|
|
341
351
|
|
|
@@ -164,9 +164,9 @@ export class Vec {
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
cross(V: VecLike) {
|
|
167
|
-
|
|
168
|
-
this.
|
|
169
|
-
|
|
167
|
+
const { x, y, z } = this
|
|
168
|
+
this.x = y * V.z! - z * V.y
|
|
169
|
+
this.y = z * V.x - x * V.z!
|
|
170
170
|
return this
|
|
171
171
|
}
|
|
172
172
|
|
|
@@ -497,7 +497,7 @@ export class Vec {
|
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
static Slope(A: VecLike, B: VecLike): number {
|
|
500
|
-
if (A.x === B.
|
|
500
|
+
if (A.x === B.x) return NaN
|
|
501
501
|
return (A.y - B.y) / (A.x - B.x)
|
|
502
502
|
}
|
|
503
503
|
|