diff --git a/packages/dev/src/components/editor.tsx b/packages/dev/src/components/editor.tsx
index c283f90a3..c699c8e7a 100644
--- a/packages/dev/src/components/editor.tsx
+++ b/packages/dev/src/components/editor.tsx
@@ -86,22 +86,25 @@ const initialDoc: TLDrawDocument = {
}
export default function Editor(): JSX.Element {
- const { value, setValue, status } = usePersistence('doc', initialDoc)
+ // const { value, setValue, status } = usePersistence('doc', initialDoc)
- const handleChange = React.useCallback(
- (tlstate: TLDrawState, patch: TLDrawPatch, reason: string) => {
- if (reason.startsWith('session')) {
- return
- }
+ // const handleChange = React.useCallback(
+ // (tlstate: TLDrawState, patch: TLDrawPatch, reason: string) => {
+ // if (reason.startsWith('session')) {
+ // return
+ // }
- setValue(tlstate.document)
- },
- [setValue]
- )
+ // setValue(tlstate.document)
+ // },
+ // [setValue]
+ // )
- if (status === 'loading' || value === null) {
- return
- }
+ // if (status === 'loading' || value === null) {
+ // return
+ // }
- return
+ // return
+
+ // Will automatically persist data under the provided id, too
+ return
}
diff --git a/packages/tldraw/package.json b/packages/tldraw/package.json
index f57e80e2e..950e01736 100644
--- a/packages/tldraw/package.json
+++ b/packages/tldraw/package.json
@@ -58,7 +58,7 @@
"ismobilejs": "^1.1.1",
"perfect-freehand": "^0.5.2",
"react-hotkeys-hook": "^3.4.0",
- "rko": "^0.5.18"
+ "rko": "^0.5.19"
},
"gitHead": "4a7439ddf81b615ee49fddbe00802699975f9375"
-}
\ No newline at end of file
+}
diff --git a/packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx b/packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx
index 78cd03999..91c6709c2 100644
--- a/packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx
+++ b/packages/tldraw/src/components/page-options-dialog/page-options-dialog.tsx
@@ -7,8 +7,6 @@ import {
DialogOverlay,
DialogContent,
RowButton,
- MenuTextInput,
- DialogInputWrapper,
Divider,
} from '~components/shared'
import type { Data, TLDrawPage } from '~types'
@@ -21,9 +19,10 @@ const canDeleteSelector = (s: Data) => {
interface PageOptionsDialogProps {
page: TLDrawPage
onOpen?: () => void
+ onClose?: () => void
}
-export function PageOptionsDialog({ page, onOpen }: PageOptionsDialogProps): JSX.Element {
+export function PageOptionsDialog({ page, onOpen, onClose }: PageOptionsDialogProps): JSX.Element {
const { tlstate, useSelector } = useTLDrawContext()
const [isOpen, setIsOpen] = React.useState(false)
@@ -32,18 +31,16 @@ export function PageOptionsDialog({ page, onOpen }: PageOptionsDialogProps): JSX
const rInput = React.useRef(null)
- const [name, setName] = React.useState(page.name || 'Page')
-
- const handleNameChange = React.useCallback((e: React.ChangeEvent) => {
- setName(e.currentTarget.value)
- }, [])
-
const handleDuplicate = React.useCallback(() => {
tlstate.duplicatePage(page.id)
+ onClose?.()
}, [tlstate])
const handleDelete = React.useCallback(() => {
- tlstate.deletePage(page.id)
+ if (window.confirm(`Are you sure you want to delete this page?`)) {
+ tlstate.deletePage(page.id)
+ onClose?.()
+ }
}, [tlstate])
const handleOpenChange = React.useCallback(
@@ -54,27 +51,18 @@ export function PageOptionsDialog({ page, onOpen }: PageOptionsDialogProps): JSX
onOpen?.()
return
}
-
- if (name.length === 0) {
- tlstate.renamePage(page.id, 'Page')
- }
},
[tlstate, name]
)
- const handleSave = React.useCallback(() => {
- tlstate.renamePage(page.id, name)
- }, [tlstate, name])
-
function stopPropagation(e: React.KeyboardEvent) {
e.stopPropagation()
}
- function handleKeydown(e: React.KeyboardEvent) {
- if (e.key === 'Enter') {
- handleSave()
- setIsOpen(false)
- }
+ // TODO: Replace with text input
+ function handleRename() {
+ const nextName = window.prompt('New name:', page.name)
+ tlstate.renamePage(page.id, nextName || page.name || 'Page')
}
React.useEffect(() => {
@@ -93,15 +81,9 @@ export function PageOptionsDialog({ page, onOpen }: PageOptionsDialogProps): JSX
-
-
-
-
+
+ Rename
+
Duplicate
@@ -115,9 +97,6 @@ export function PageOptionsDialog({ page, onOpen }: PageOptionsDialogProps): JSX
Delete
-
- Save
-
Cancel
diff --git a/packages/tldraw/src/components/page-panel/page-panel.tsx b/packages/tldraw/src/components/page-panel/page-panel.tsx
index 4eaac39fe..32f350140 100644
--- a/packages/tldraw/src/components/page-panel/page-panel.tsx
+++ b/packages/tldraw/src/components/page-panel/page-panel.tsx
@@ -100,7 +100,7 @@ function PageMenuContent({ onClose }: { onClose: () => void }) {
-
+
))}
diff --git a/packages/tldraw/src/components/tldraw/tldraw.tsx b/packages/tldraw/src/components/tldraw/tldraw.tsx
index 5cefe949c..f52d3cb74 100644
--- a/packages/tldraw/src/components/tldraw/tldraw.tsx
+++ b/packages/tldraw/src/components/tldraw/tldraw.tsx
@@ -12,25 +12,51 @@ import { ToolsPanel } from '~components/tools-panel'
import { PagePanel } from '~components/page-panel'
import { Menu } from '~components/menu'
-export interface TLDrawProps {
- document?: TLDrawDocument
- currentPageId?: string
- onMount?: (state: TLDrawState) => void
- onChange?: TLDrawState['_onChange']
-}
-
+// Selectors
const isInSelectSelector = (s: Data) => s.appState.activeTool === 'select'
+
const isSelectedShapeWithHandlesSelector = (s: Data) => {
const { shapes } = s.document.pages[s.appState.currentPageId]
const { selectedIds } = s.document.pageStates[s.appState.currentPageId]
return selectedIds.length === 1 && selectedIds.every((id) => shapes[id].handles !== undefined)
}
+
const pageSelector = (s: Data) => s.document.pages[s.appState.currentPageId]
+
const pageStateSelector = (s: Data) => s.document.pageStates[s.appState.currentPageId]
+
const isDarkModeSelector = (s: Data) => s.settings.isDarkMode
-export function TLDraw({ document, currentPageId, onMount, onChange: _onChange }: TLDrawProps) {
- const [tlstate] = React.useState(() => new TLDrawState())
+export interface TLDrawProps {
+ /**
+ * (optional) If provided, the component will load / persist state under this key.
+ */
+ id?: string
+ /**
+ * (optional) The document to load or update from.
+ */
+ document?: TLDrawDocument
+ /**
+ * (optional) The current page id.
+ */
+ currentPageId?: string
+ /**
+ * (optional) A callback to run when the component mounts.
+ */
+ onMount?: (state: TLDrawState) => void
+ /**
+ * (optional) A callback to run when the component's state changes.
+ */
+ onChange?: TLDrawState['_onChange']
+}
+
+export function TLDraw({ id, document, currentPageId, onMount, onChange: _onChange }: TLDrawProps) {
+ const [tlstate, setTlstate] = React.useState(() => new TLDrawState(id))
+
+ React.useEffect(() => {
+ setTlstate(new TLDrawState(id))
+ }, [id])
+
const [context] = React.useState(() => {
return { tlstate, useSelector: tlstate.useStore }
})
@@ -38,10 +64,15 @@ export function TLDraw({ document, currentPageId, onMount, onChange: _onChange }
useKeyboardShortcuts(tlstate)
const page = context.useSelector(pageSelector)
+
const pageState = context.useSelector(pageStateSelector)
+
const isDarkMode = context.useSelector(isDarkModeSelector)
+
const isSelecting = context.useSelector(isInSelectSelector)
+
const isSelectedHandlesShape = context.useSelector(isSelectedShapeWithHandlesSelector)
+
const isInSession = !!tlstate.session
// Hide bounds when not using the select tool, or when the only selected shape has handles
diff --git a/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx b/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx
index db1ce173a..b9649b3c1 100644
--- a/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx
+++ b/packages/tldraw/src/hooks/useKeyboardShortcuts.tsx
@@ -200,7 +200,8 @@ export function useKeyboardShortcuts(tlstate: TLDrawState) {
tlstate.moveToFront()
})
- useHotkeys('command+shift+backspace', () => {
+ useHotkeys('command+shift+backspace', (e) => {
tlstate.reset()
+ e.preventDefault()
})
}
diff --git a/packages/tldraw/src/hooks/useTheme.ts b/packages/tldraw/src/hooks/useTheme.ts
index 9667d184d..667a99fac 100644
--- a/packages/tldraw/src/hooks/useTheme.ts
+++ b/packages/tldraw/src/hooks/useTheme.ts
@@ -1,9 +1,25 @@
-import type { Theme } from '~types'
+import React from 'react'
+import type { Data, Theme } from '~types'
+import { useTLDrawContext } from './useTLDrawContext'
+import { dark } from '~styles'
+
+const themeSelector = (data: Data): Theme => (data.settings.isDarkMode ? 'dark' : 'light')
export function useTheme() {
+ const { tlstate, useSelector } = useTLDrawContext()
+
+ const theme = useSelector(themeSelector)
+
+ React.useEffect(() => {
+ if (theme === 'dark') {
+ document.body.classList.add(dark)
+ } else {
+ document.body.classList.remove(dark)
+ }
+ }, [theme])
+
return {
- theme: 'light' as Theme,
- toggle: () => null,
- setTheme: (theme: Theme) => void theme,
+ theme,
+ toggle: tlstate.toggleDarkMode,
}
}
diff --git a/packages/tldraw/src/shape/shapes/draw/draw.tsx b/packages/tldraw/src/shape/shapes/draw/draw.tsx
index 39ba2e884..f58e0147f 100644
--- a/packages/tldraw/src/shape/shapes/draw/draw.tsx
+++ b/packages/tldraw/src/shape/shapes/draw/draw.tsx
@@ -47,7 +47,9 @@ export class Draw extends TLDrawShapeUtil {
// For very short lines, draw a point instead of a line
const bounds = this.getBounds(shape)
- if (!isEditing && bounds.width < strokeWidth / 2 && bounds.height < strokeWidth / 2) {
+ const verySmall = bounds.width < strokeWidth / 2 && bounds.height < strokeWidth / 2
+
+ if (!isEditing && verySmall) {
const sw = strokeWidth * 0.618
return (
@@ -151,6 +153,14 @@ export class Draw extends TLDrawShapeUtil {
renderIndicator(shape: DrawShape): JSX.Element {
const { points } = shape
+ const bounds = this.getBounds(shape)
+
+ const verySmall = bounds.width < 4 && bounds.height < 4
+
+ if (verySmall) {
+ return
+ }
+
const path = Utils.getFromCache(this.simplePathCache, points, () => getSolidStrokePath(shape))
return
diff --git a/packages/tldraw/src/state/session/sessions/arrow/arrow.session.spec.ts b/packages/tldraw/src/state/session/sessions/arrow/arrow.session.spec.ts
index 62ed485c1..d8ce73242 100644
--- a/packages/tldraw/src/state/session/sessions/arrow/arrow.session.spec.ts
+++ b/packages/tldraw/src/state/session/sessions/arrow/arrow.session.spec.ts
@@ -157,17 +157,14 @@ describe('Arrow session', () => {
expect(tlstate.getShape('arrow1').point).toStrictEqual([116, 116])
expect(tlstate.getShape('arrow1').handles.start.point).toStrictEqual([0, 0])
expect(tlstate.getShape('arrow1').handles.end.point).toStrictEqual([85, 85])
- // tlstate
- // .select('target1')
- // .startTranslateSession([50, 50])
- // .updateTranslateSession([300, 0])
- // .completeSession()
- // expect(tlstate.getShape('arrow1').handles.start.point).toStrictEqual([66.493, 0])
- // expect(tlstate.getShape('arrow1').handles.end.point).toStrictEqual([0, 135])
})
it('updates the arrow when bound on both sides', () => {
// TODO
})
+
+ it('snaps the bend to zero when dragging the bend handle toward the center', () => {
+ // TODO
+ })
})
})
diff --git a/packages/tldraw/src/state/tlstate.ts b/packages/tldraw/src/state/tlstate.ts
index 00cb2f71f..daf8ae074 100644
--- a/packages/tldraw/src/state/tlstate.ts
+++ b/packages/tldraw/src/state/tlstate.ts
@@ -91,8 +91,8 @@ const initialData: Data = {
export class TLDrawState extends StateManager {
_onChange?: (tlstate: TLDrawState, data: Data, reason: string) => void
- constructor() {
- super(initialData, 'tlstate', 1)
+ constructor(id = Utils.uniqueId()) {
+ super(initialData, id, 1)
}
selectHistory: SelectHistory = {
@@ -258,25 +258,31 @@ export class TLDrawState extends StateManager {
}
toggleDarkMode = (): this => {
- return this.patchState(
+ this.patchState(
{ settings: { isDarkMode: !this.state.settings.isDarkMode } },
`settings:toggled_dark_mode`
)
+ this.persist()
+ return this
}
toggleDebugMode = () => {
- return this.patchState(
+ this.patchState(
{ settings: { isDebugMode: !this.state.settings.isDebugMode } },
`settings:toggled_debug`
)
+ this.persist()
+ return this
}
/* ----------------------- UI ----------------------- */
toggleStylePanel = (): this => {
- return this.patchState(
+ this.patchState(
{ appState: { isStyleOpen: !this.appState.isStyleOpen } },
'ui:toggled_style_panel'
)
+ this.persist()
+ return this
}
selectTool = (tool: TLDrawShapeType | 'select'): this => {
diff --git a/packages/www/styles/stitches.config.ts b/packages/www/styles/stitches.config.ts
index 9534c65d9..d48dfb840 100644
--- a/packages/www/styles/stitches.config.ts
+++ b/packages/www/styles/stitches.config.ts
@@ -99,8 +99,6 @@ const { styled, css, theme, getCssString } = createCss({
},
})
-const light = theme({})
-
const dark = theme({
colors: {
brushFill: 'rgba(180, 180, 180, .05)',
@@ -138,4 +136,4 @@ const dark = theme({
export default styled
-export { css, getCssString, light, dark }
+export { css, getCssString, dark }
diff --git a/yarn.lock b/yarn.lock
index 0e20f93f9..e06ba9f4d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9469,10 +9469,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
-rko@^0.5.18:
- version "0.5.18"
- resolved "https://registry.yarnpkg.com/rko/-/rko-0.5.18.tgz#cbbc45f073b1db1884112479b18ed04a799065ec"
- integrity sha512-zh6/NRIZi0gApYMyyJjTLpni/98dmkj/oZhD/KRS2TYZD0V63iIopBWUmD5wSNVVqyOv5o/HHsM1Q4EzdTGeZA==
+rko@^0.5.19:
+ version "0.5.19"
+ resolved "https://registry.yarnpkg.com/rko/-/rko-0.5.19.tgz#33577596167178abc30063b6dd0a8bbde0362c27"
+ integrity sha512-0KSdDnbhD11GCDvZFARvCJedJuwWIh5F1cCqTGljFF/wQi9PUVu019qH4ME4LdPF3HotMLcdQsxEXmIDeeD0zQ==
dependencies:
idb-keyval "^5.1.3"
zustand "^3.5.9"