tldraw 3.16.0-canary.ed8bd30c0f28 → 3.16.0-canary.fa3749606e52
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 +32 -1
- package/dist-cjs/index.js +2 -1
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/tools/SelectTool/childStates/Translating.js.map +2 -2
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js +149 -1
- package/dist-cjs/lib/ui/components/primitives/menus/TldrawUiMenuItem.js.map +2 -2
- package/dist-cjs/lib/ui/context/events.js.map +2 -2
- package/dist-cjs/lib/ui/hooks/useTools.js +76 -9
- package/dist-cjs/lib/ui/hooks/useTools.js.map +2 -2
- package/dist-cjs/lib/ui/version.js +3 -3
- package/dist-cjs/lib/ui/version.js.map +1 -1
- package/dist-esm/index.d.mts +32 -1
- package/dist-esm/index.mjs +3 -1
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/tools/SelectTool/childStates/Translating.mjs.map +2 -2
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs +157 -3
- package/dist-esm/lib/ui/components/primitives/menus/TldrawUiMenuItem.mjs.map +2 -2
- package/dist-esm/lib/ui/context/events.mjs.map +2 -2
- package/dist-esm/lib/ui/hooks/useTools.mjs +83 -10
- package/dist-esm/lib/ui/hooks/useTools.mjs.map +2 -2
- package/dist-esm/lib/ui/version.mjs +3 -3
- package/dist-esm/lib/ui/version.mjs.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +2 -0
- package/src/lib/tools/SelectTool/childStates/Translating.ts +0 -1
- package/src/lib/ui/components/primitives/menus/TldrawUiMenuItem.tsx +213 -2
- package/src/lib/ui/context/events.tsx +1 -0
- package/src/lib/ui/hooks/useTools.tsx +118 -10
- package/src/lib/ui/version.ts +3 -3
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/lib/ui/hooks/useTools.tsx"],
|
|
4
|
-
"sourcesContent": ["import { Editor, GeoShapeGeoStyle, useMaybeEditor } from '@tldraw/editor'\nimport * as React from 'react'\nimport { EmbedDialog } from '../components/EmbedDialog'\nimport { TLUiIconJsx } from '../components/primitives/TldrawUiIcon'\nimport { useA11y } from '../context/a11y'\nimport { TLUiEventSource, useUiEvents } from '../context/events'\nimport { TLUiIconType } from '../icon-types'\nimport { TLUiOverrideHelpers, useDefaultHelpers } from '../overrides'\nimport { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'\nimport { useTranslation } from './useTranslation/useTranslation'\n\n/** @public */\nexport interface TLUiToolItem<\n\tTranslationKey extends string = string,\n\tIconType extends string = string,\n> {\n\tid: string\n\tlabel: TranslationKey\n\tshortcutsLabel?: TranslationKey\n\ticon: IconType | TLUiIconJsx\n\tonSelect(source: TLUiEventSource): void\n\t/**\n\t * The keyboard shortcut for this tool. This is a string that can be a single key,\n\t * or a combination of keys.\n\t * For example, `cmd+z` or `cmd+shift+z` or `cmd+u,ctrl+u`, or just `v` or `a`.\n\t * We have backwards compatibility with the old system, where we used to use\n\t * symbols to denote cmd/alt/shift, using `!` for shift, `$` for cmd, and `?` for alt.\n\t */\n\tkbd?: string\n\treadonlyOk?: boolean\n\tmeta?: {\n\t\t[key: string]: any\n\t}\n}\n\n/** @public */\nexport type TLUiToolsContextType = Record<string, TLUiToolItem>\n\n/** @internal */\nexport const ToolsContext = React.createContext<null | TLUiToolsContextType>(null)\n\n/** @public */\nexport interface TLUiToolsProviderProps {\n\toverrides?(\n\t\teditor: Editor,\n\t\ttools: TLUiToolsContextType,\n\t\thelpers: Partial<TLUiOverrideHelpers>\n\t): TLUiToolsContextType\n\tchildren: React.ReactNode\n}\n\n/** @internal */\nexport function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {\n\tconst editor = useMaybeEditor()\n\tconst trackEvent = useUiEvents()\n\n\tconst a11y = useA11y()\n\tconst msg = useTranslation()\n\tconst helpers = useDefaultHelpers()\n\n\tconst onToolSelect = React.useCallback(\n\t\t(\n\t\t\tsource: TLUiEventSource,\n\t\t\ttool: TLUiToolItem<TLUiTranslationKey, TLUiIconType>,\n\t\t\tid?: string\n\t\t) => {\n\t\t\ta11y.announce({ msg: msg(tool.label) })\n\t\t\ttrackEvent('select-tool', { source, id: id ?? tool.id })\n\t\t},\n\t\t[a11y, msg, trackEvent]\n\t)\n\n\tconst tools = React.useMemo<TLUiToolsContextType>(() => {\n\t\tif (!editor) return {}\n\t\tconst toolsArray: TLUiToolItem<TLUiTranslationKey, TLUiIconType>[] = [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: 'tool.select',\n\t\t\t\ticon: 'tool-pointer',\n\t\t\t\tkbd: 'v',\n\t\t\t\treadonlyOk: true,\n\t\t\t\tonSelect(source) {\n\t\t\t\t\tif (editor.isIn('select')) {\n\t\t\t\t\t\t// There's a quirk of select mode, where editing a shape is a sub-state of select.\n\t\t\t\t\t\t// Because the text tool can be locked/sticky, we need to make sure we exit the\n\t\t\t\t\t\t// text tool.\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// psst, if you're changing this code, also change the code\n\t\t\t\t\t\t// in strange-tools.test.ts! Sadly it's duplicated there.\n\t\t\t\t\t\tconst currentNode = editor.root.getCurrent()!\n\t\t\t\t\t\tcurrentNode.exit({}, currentNode.id)\n\t\t\t\t\t\tcurrentNode.enter({}, currentNode.id)\n\t\t\t\t\t}\n\t\t\t\t\teditor.setCurrentTool('select')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'hand',\n\t\t\t\tlabel: 'tool.hand',\n\t\t\t\ticon: 'tool-hand',\n\t\t\t\tkbd: 'h',\n\t\t\t\treadonlyOk: true,\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('hand')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'eraser',\n\t\t\t\tlabel: 'tool.eraser',\n\t\t\t\ticon: 'tool-eraser',\n\t\t\t\tkbd: 'e',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('eraser')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'draw',\n\t\t\t\tlabel: 'tool.draw',\n\t\t\t\ticon: 'tool-pencil',\n\t\t\t\tkbd: 'd,b,x',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('draw')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t...[...GeoShapeGeoStyle.values].map((id) => ({\n\t\t\t\tid,\n\t\t\t\tlabel: `tool.${id}` as TLUiTranslationKey,\n\t\t\t\tmeta: {\n\t\t\t\t\tgeo: id,\n\t\t\t\t},\n\t\t\t\tkbd: id === 'rectangle' ? 'r' : id === 'ellipse' ? 'o' : undefined,\n\t\t\t\ticon: ('geo-' + id) as TLUiIconType,\n\t\t\t\tonSelect(source: TLUiEventSource) {\n\t\t\t\t\teditor.run(() => {\n\t\t\t\t\t\teditor.setStyleForNextShapes(GeoShapeGeoStyle, id)\n\t\t\t\t\t\teditor.setCurrentTool('geo')\n\t\t\t\t\t\tonToolSelect(source, this, `geo-${id}`)\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t})),\n\t\t\t{\n\t\t\t\tid: 'arrow',\n\t\t\t\tlabel: 'tool.arrow',\n\t\t\t\ticon: 'tool-arrow',\n\t\t\t\tkbd: 'a',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('arrow')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'line',\n\t\t\t\tlabel: 'tool.line',\n\t\t\t\ticon: 'tool-line',\n\t\t\t\tkbd: 'l',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('line')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'frame',\n\t\t\t\tlabel: 'tool.frame',\n\t\t\t\ticon: 'tool-frame',\n\t\t\t\tkbd: 'f',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('frame')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'text',\n\t\t\t\tlabel: 'tool.text',\n\t\t\t\ticon: 'tool-text',\n\t\t\t\tkbd: 't',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('text')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'asset',\n\t\t\t\tlabel: 'tool.media',\n\t\t\t\ticon: 'tool-media',\n\t\t\t\tkbd: 'cmd+u,ctrl+u',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\thelpers.insertMedia()\n\t\t\t\t\tonToolSelect(source, this, 'media')\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'note',\n\t\t\t\tlabel: 'tool.note',\n\t\t\t\ticon: 'tool-note',\n\t\t\t\tkbd: 'n',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('note')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'laser',\n\t\t\t\tlabel: 'tool.laser',\n\t\t\t\treadonlyOk: true,\n\t\t\t\ticon: 'tool-laser',\n\t\t\t\tkbd: 'k',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('laser')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'embed',\n\t\t\t\tlabel: 'tool.embed',\n\t\t\t\ticon: 'dot',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\thelpers.addDialog({ component: EmbedDialog })\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'highlight',\n\t\t\t\tlabel: 'tool.highlight',\n\t\t\t\ticon: 'tool-highlight',\n\t\t\t\t// TODO: pick a better shortcut\n\t\t\t\tkbd: 'shift+d',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('highlight')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t]\n\n\t\ttoolsArray.forEach((t) => (t.onSelect = t.onSelect.bind(t)))\n\n\t\tconst tools = Object.fromEntries(toolsArray.map((t) => [t.id, t]))\n\n\t\tif (overrides) {\n\t\t\treturn overrides(editor, tools, helpers)\n\t\t}\n\n\t\treturn tools\n\t}, [overrides, editor, helpers, onToolSelect])\n\n\treturn <ToolsContext.Provider value={tools}>{children}</ToolsContext.Provider>\n}\n\n/** @public */\nexport function useTools() {\n\tconst ctx = React.useContext(ToolsContext)\n\n\tif (!ctx) {\n\t\tthrow new Error('useTools must be used within a ToolProvider')\n\t}\n\n\treturn ctx\n}\n"],
|
|
5
|
-
"mappings": "
|
|
4
|
+
"sourcesContent": ["import {\n\tassertExists,\n\tcreateShapeId,\n\tEditor,\n\tGeoShapeGeoStyle,\n\tTLPointerEventInfo,\n\tTLShapeId,\n\ttoRichText,\n\tuseMaybeEditor,\n} from '@tldraw/editor'\nimport * as React from 'react'\nimport { EmbedDialog } from '../components/EmbedDialog'\nimport { TLUiIconJsx } from '../components/primitives/TldrawUiIcon'\nimport { useA11y } from '../context/a11y'\nimport { TLUiEventSource, useUiEvents } from '../context/events'\nimport { TLUiIconType } from '../icon-types'\nimport { TLUiOverrideHelpers, useDefaultHelpers } from '../overrides'\nimport { TLUiTranslationKey } from './useTranslation/TLUiTranslationKey'\nimport { useTranslation } from './useTranslation/useTranslation'\n\n/** @public */\nexport interface TLUiToolItem<\n\tTranslationKey extends string = string,\n\tIconType extends string = string,\n> {\n\tid: string\n\tlabel: TranslationKey\n\tshortcutsLabel?: TranslationKey\n\ticon: IconType | TLUiIconJsx\n\tonSelect(source: TLUiEventSource): void\n\tonDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void\n\t/**\n\t * The keyboard shortcut for this tool. This is a string that can be a single key,\n\t * or a combination of keys.\n\t * For example, `cmd+z` or `cmd+shift+z` or `cmd+u,ctrl+u`, or just `v` or `a`.\n\t * We have backwards compatibility with the old system, where we used to use\n\t * symbols to denote cmd/alt/shift, using `!` for shift, `$` for cmd, and `?` for alt.\n\t */\n\tkbd?: string\n\treadonlyOk?: boolean\n\tmeta?: {\n\t\t[key: string]: any\n\t}\n}\n\n/** @public */\nexport type TLUiToolsContextType = Record<string, TLUiToolItem>\n\n/** @internal */\nexport const ToolsContext = React.createContext<null | TLUiToolsContextType>(null)\n\n/** @public */\nexport interface TLUiToolsProviderProps {\n\toverrides?(\n\t\teditor: Editor,\n\t\ttools: TLUiToolsContextType,\n\t\thelpers: Partial<TLUiOverrideHelpers>\n\t): TLUiToolsContextType\n\tchildren: React.ReactNode\n}\n\n/** @internal */\nexport function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {\n\tconst editor = useMaybeEditor()\n\tconst trackEvent = useUiEvents()\n\n\tconst a11y = useA11y()\n\tconst msg = useTranslation()\n\tconst helpers = useDefaultHelpers()\n\n\tconst onToolSelect = React.useCallback(\n\t\t(\n\t\t\tsource: TLUiEventSource,\n\t\t\ttool: TLUiToolItem<TLUiTranslationKey, TLUiIconType>,\n\t\t\tid?: string\n\t\t) => {\n\t\t\ta11y.announce({ msg: msg(tool.label) })\n\t\t\ttrackEvent('select-tool', { source, id: id ?? tool.id })\n\t\t},\n\t\t[a11y, msg, trackEvent]\n\t)\n\n\tconst tools = React.useMemo<TLUiToolsContextType>(() => {\n\t\tif (!editor) return {}\n\t\tconst toolsArray: TLUiToolItem<TLUiTranslationKey, TLUiIconType>[] = [\n\t\t\t{\n\t\t\t\tid: 'select',\n\t\t\t\tlabel: 'tool.select',\n\t\t\t\ticon: 'tool-pointer',\n\t\t\t\tkbd: 'v',\n\t\t\t\treadonlyOk: true,\n\t\t\t\tonSelect(source) {\n\t\t\t\t\tif (editor.isIn('select')) {\n\t\t\t\t\t\t// There's a quirk of select mode, where editing a shape is a sub-state of select.\n\t\t\t\t\t\t// Because the text tool can be locked/sticky, we need to make sure we exit the\n\t\t\t\t\t\t// text tool.\n\t\t\t\t\t\t//\n\t\t\t\t\t\t// psst, if you're changing this code, also change the code\n\t\t\t\t\t\t// in strange-tools.test.ts! Sadly it's duplicated there.\n\t\t\t\t\t\tconst currentNode = editor.root.getCurrent()!\n\t\t\t\t\t\tcurrentNode.exit({}, currentNode.id)\n\t\t\t\t\t\tcurrentNode.enter({}, currentNode.id)\n\t\t\t\t\t}\n\t\t\t\t\teditor.setCurrentTool('select')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'hand',\n\t\t\t\tlabel: 'tool.hand',\n\t\t\t\ticon: 'tool-hand',\n\t\t\t\tkbd: 'h',\n\t\t\t\treadonlyOk: true,\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('hand')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'eraser',\n\t\t\t\tlabel: 'tool.eraser',\n\t\t\t\ticon: 'tool-eraser',\n\t\t\t\tkbd: 'e',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('eraser')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'draw',\n\t\t\t\tlabel: 'tool.draw',\n\t\t\t\ticon: 'tool-pencil',\n\t\t\t\tkbd: 'd,b,x',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('draw')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t...[...GeoShapeGeoStyle.values].map((geo) => ({\n\t\t\t\tid: geo,\n\t\t\t\tlabel: `tool.${geo}` as TLUiTranslationKey,\n\t\t\t\tmeta: {\n\t\t\t\t\tgeo,\n\t\t\t\t},\n\t\t\t\tkbd: geo === 'rectangle' ? 'r' : geo === 'ellipse' ? 'o' : undefined,\n\t\t\t\ticon: ('geo-' + geo) as TLUiIconType,\n\t\t\t\tonSelect(source: TLUiEventSource) {\n\t\t\t\t\teditor.run(() => {\n\t\t\t\t\t\teditor.setStyleForNextShapes(GeoShapeGeoStyle, geo)\n\t\t\t\t\t\teditor.setCurrentTool('geo')\n\t\t\t\t\t\tonToolSelect(source, this, `geo-${geo}`)\n\t\t\t\t\t})\n\t\t\t\t},\n\t\t\t\tonDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {\n\t\t\t\t\tonDragFromToolbarToCreateShape(editor, info, {\n\t\t\t\t\t\tcreateShape: (id) => editor.createShape({ id, type: 'geo', props: { geo } }),\n\t\t\t\t\t})\n\t\t\t\t\ttrackEvent('drag-tool', { source, id: 'geo' })\n\t\t\t\t},\n\t\t\t})),\n\t\t\t{\n\t\t\t\tid: 'arrow',\n\t\t\t\tlabel: 'tool.arrow',\n\t\t\t\ticon: 'tool-arrow',\n\t\t\t\tkbd: 'a',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('arrow')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t\tonDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {\n\t\t\t\t\tonDragFromToolbarToCreateShape(editor, info, {\n\t\t\t\t\t\tcreateShape: (id) =>\n\t\t\t\t\t\t\teditor.createShape({\n\t\t\t\t\t\t\t\tid,\n\t\t\t\t\t\t\t\ttype: 'arrow',\n\t\t\t\t\t\t\t\tprops: { start: { x: 0, y: 0 }, end: { x: 200, y: 0 } },\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t})\n\t\t\t\t\ttrackEvent('drag-tool', { source, id: 'arrow' })\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'line',\n\t\t\t\tlabel: 'tool.line',\n\t\t\t\ticon: 'tool-line',\n\t\t\t\tkbd: 'l',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('line')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'frame',\n\t\t\t\tlabel: 'tool.frame',\n\t\t\t\ticon: 'tool-frame',\n\t\t\t\tkbd: 'f',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('frame')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t\tonDragStart(source, info) {\n\t\t\t\t\tonDragFromToolbarToCreateShape(editor, info, {\n\t\t\t\t\t\tcreateShape: (id) => editor.createShape({ id, type: 'frame' }),\n\t\t\t\t\t})\n\t\t\t\t\ttrackEvent('drag-tool', { source, id: 'frame' })\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'text',\n\t\t\t\tlabel: 'tool.text',\n\t\t\t\ticon: 'tool-text',\n\t\t\t\tkbd: 't',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('text')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t\tonDragStart(source, info) {\n\t\t\t\t\tonDragFromToolbarToCreateShape(editor, info, {\n\t\t\t\t\t\tcreateShape: (id) =>\n\t\t\t\t\t\t\teditor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),\n\t\t\t\t\t\tonDragEnd: (id) => {\n\t\t\t\t\t\t\teditor.emit('select-all-text', { shapeId: id })\n\t\t\t\t\t\t\teditor.setEditingShape(id)\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\ttrackEvent('drag-tool', { source, id: 'text' })\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'asset',\n\t\t\t\tlabel: 'tool.media',\n\t\t\t\ticon: 'tool-media',\n\t\t\t\tkbd: 'cmd+u,ctrl+u',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\thelpers.insertMedia()\n\t\t\t\t\tonToolSelect(source, this, 'media')\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'note',\n\t\t\t\tlabel: 'tool.note',\n\t\t\t\ticon: 'tool-note',\n\t\t\t\tkbd: 'n',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('note')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t\tonDragStart(source, info) {\n\t\t\t\t\tonDragFromToolbarToCreateShape(editor, info, {\n\t\t\t\t\t\tcreateShape: (id) => editor.createShape({ id, type: 'note' }),\n\t\t\t\t\t\tonDragEnd: (id) => {\n\t\t\t\t\t\t\teditor.emit('select-all-text', { shapeId: id })\n\t\t\t\t\t\t\teditor.setEditingShape(id)\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t\ttrackEvent('drag-tool', { source, id: 'note' })\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'laser',\n\t\t\t\tlabel: 'tool.laser',\n\t\t\t\treadonlyOk: true,\n\t\t\t\ticon: 'tool-laser',\n\t\t\t\tkbd: 'k',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('laser')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'embed',\n\t\t\t\tlabel: 'tool.embed',\n\t\t\t\ticon: 'dot',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\thelpers.addDialog({ component: EmbedDialog })\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'highlight',\n\t\t\t\tlabel: 'tool.highlight',\n\t\t\t\ticon: 'tool-highlight',\n\t\t\t\t// TODO: pick a better shortcut\n\t\t\t\tkbd: 'shift+d',\n\t\t\t\tonSelect(source) {\n\t\t\t\t\teditor.setCurrentTool('highlight')\n\t\t\t\t\tonToolSelect(source, this)\n\t\t\t\t},\n\t\t\t},\n\t\t]\n\n\t\ttoolsArray.forEach((t) => (t.onSelect = t.onSelect.bind(t)))\n\n\t\tconst tools = Object.fromEntries(toolsArray.map((t) => [t.id, t]))\n\n\t\tif (overrides) {\n\t\t\treturn overrides(editor, tools, helpers)\n\t\t}\n\n\t\treturn tools\n\t}, [overrides, editor, helpers, onToolSelect, trackEvent])\n\n\treturn <ToolsContext.Provider value={tools}>{children}</ToolsContext.Provider>\n}\n\n/** @public */\nexport function useTools() {\n\tconst ctx = React.useContext(ToolsContext)\n\n\tif (!ctx) {\n\t\tthrow new Error('useTools must be used within a ToolProvider')\n\t}\n\n\treturn ctx\n}\n\n/**\n * Options for {@link onDragFromToolbarToCreateShape}.\n * @public\n */\nexport interface OnDragFromToolbarToCreateShapesOpts {\n\t/**\n\t * Create the shape being dragged. You don't need to worry about positioning it, as it'll be\n\t * immediately updated with the correct position.\n\t */\n\tcreateShape(id: TLShapeId): void\n\t/**\n\t * Called once the drag interaction has finished.\n\t */\n\tonDragEnd?(id: TLShapeId): void\n}\n\n/**\n * A helper method to use in {@link TLUiToolItem#onDragStart} to create a shape by dragging it from\n * the toolbar.\n * @public\n */\nexport function onDragFromToolbarToCreateShape(\n\teditor: Editor,\n\tinfo: TLPointerEventInfo,\n\topts: OnDragFromToolbarToCreateShapesOpts\n) {\n\tconst { x, y } = editor.inputs.currentPagePoint\n\n\tconst stoppingPoint = editor.markHistoryStoppingPoint('drag shape tool')\n\teditor.setCurrentTool('select.translating')\n\n\tconst id = createShapeId()\n\topts.createShape(id)\n\tconst shape = assertExists(editor.getShape(id), 'Shape not found')\n\n\tconst { w, h } = editor.getShapePageBounds(id)!\n\teditor.updateShape({ id, type: shape.type, x: x - w / 2, y: y - h / 2 })\n\teditor.select(id)\n\n\teditor.setCurrentTool('select.translating', {\n\t\t...info,\n\t\ttarget: 'shape',\n\t\tshape: editor.getShape(id),\n\t\tisCreating: true,\n\t\tcreatingMarkId: stoppingPoint,\n\t\tonCreate() {\n\t\t\teditor.setCurrentTool('select.idle')\n\t\t\teditor.select(id)\n\t\t\topts.onDragEnd?.(id)\n\t\t},\n\t})\n\teditor.getCurrentTool().setCurrentToolIdMask(shape.type)\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["tools"]
|
|
7
7
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const version = "3.16.0-canary.
|
|
1
|
+
const version = "3.16.0-canary.fa3749606e52";
|
|
2
2
|
const publishDates = {
|
|
3
3
|
major: "2024-09-13T14:36:29.063Z",
|
|
4
|
-
minor: "2025-
|
|
5
|
-
patch: "2025-
|
|
4
|
+
minor: "2025-08-01T14:30:58.099Z",
|
|
5
|
+
patch: "2025-08-01T14:30:58.099Z"
|
|
6
6
|
};
|
|
7
7
|
export {
|
|
8
8
|
publishDates,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/ui/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.16.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '3.16.0-canary.fa3749606e52'\nexport const publishDates = {\n\tmajor: '2024-09-13T14:36:29.063Z',\n\tminor: '2025-08-01T14:30:58.099Z',\n\tpatch: '2025-08-01T14:30:58.099Z',\n}\n"],
|
|
5
5
|
"mappings": "AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tldraw",
|
|
3
3
|
"description": "A tiny little drawing editor.",
|
|
4
|
-
"version": "3.16.0-canary.
|
|
4
|
+
"version": "3.16.0-canary.fa3749606e52",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "tldraw Inc.",
|
|
7
7
|
"email": "hello@tldraw.com"
|
|
@@ -54,8 +54,8 @@
|
|
|
54
54
|
"@tiptap/pm": "^2.9.1",
|
|
55
55
|
"@tiptap/react": "^2.9.1",
|
|
56
56
|
"@tiptap/starter-kit": "^2.9.1",
|
|
57
|
-
"@tldraw/editor": "3.16.0-canary.
|
|
58
|
-
"@tldraw/store": "3.16.0-canary.
|
|
57
|
+
"@tldraw/editor": "3.16.0-canary.fa3749606e52",
|
|
58
|
+
"@tldraw/store": "3.16.0-canary.fa3749606e52",
|
|
59
59
|
"classnames": "^2.5.1",
|
|
60
60
|
"hotkeys-js": "^3.13.9",
|
|
61
61
|
"idb": "^7.1.1",
|
package/src/index.ts
CHANGED
|
@@ -590,7 +590,9 @@ export { useMenuIsOpen } from './lib/ui/hooks/useMenuIsOpen'
|
|
|
590
590
|
export { useReadonly } from './lib/ui/hooks/useReadonly'
|
|
591
591
|
export { useRelevantStyles } from './lib/ui/hooks/useRelevantStyles'
|
|
592
592
|
export {
|
|
593
|
+
onDragFromToolbarToCreateShape,
|
|
593
594
|
useTools,
|
|
595
|
+
type OnDragFromToolbarToCreateShapesOpts,
|
|
594
596
|
type TLUiToolItem,
|
|
595
597
|
type TLUiToolsContextType,
|
|
596
598
|
type TLUiToolsProviderProps,
|
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
exhaustiveSwitchError,
|
|
3
|
+
getPointerInfo,
|
|
4
|
+
preventDefault,
|
|
5
|
+
TLPointerEventInfo,
|
|
6
|
+
useEditor,
|
|
7
|
+
Vec,
|
|
8
|
+
} from '@tldraw/editor'
|
|
2
9
|
import { ContextMenu as _ContextMenu } from 'radix-ui'
|
|
3
|
-
import { useState } from 'react'
|
|
10
|
+
import { useMemo, useState } from 'react'
|
|
4
11
|
import { unwrapLabel } from '../../../context/actions'
|
|
5
12
|
import { TLUiEventSource } from '../../../context/events'
|
|
6
13
|
import { useReadonly } from '../../../hooks/useReadonly'
|
|
14
|
+
import { TLUiToolItem } from '../../../hooks/useTools'
|
|
7
15
|
import { TLUiTranslationKey } from '../../../hooks/useTranslation/TLUiTranslationKey'
|
|
8
16
|
import { useTranslation } from '../../../hooks/useTranslation/useTranslation'
|
|
9
17
|
import { kbdStr } from '../../../kbd-utils'
|
|
@@ -63,6 +71,10 @@ export interface TLUiMenuItemProps<
|
|
|
63
71
|
* Whether the item is selected.
|
|
64
72
|
*/
|
|
65
73
|
isSelected?: boolean
|
|
74
|
+
/**
|
|
75
|
+
* The function to call when the item is dragged. If this is provided, the item will be draggable.
|
|
76
|
+
*/
|
|
77
|
+
onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void
|
|
66
78
|
}
|
|
67
79
|
|
|
68
80
|
/** @public @react */
|
|
@@ -81,6 +93,7 @@ export function TldrawUiMenuItem<
|
|
|
81
93
|
onSelect,
|
|
82
94
|
noClose,
|
|
83
95
|
isSelected,
|
|
96
|
+
onDragStart,
|
|
84
97
|
}: TLUiMenuItemProps<TranslationKey, IconType>) {
|
|
85
98
|
const { type: menuType, sourceId } = useTldrawUiMenuContext()
|
|
86
99
|
|
|
@@ -207,6 +220,20 @@ export function TldrawUiMenuItem<
|
|
|
207
220
|
)
|
|
208
221
|
}
|
|
209
222
|
case 'toolbar': {
|
|
223
|
+
if (onDragStart) {
|
|
224
|
+
return (
|
|
225
|
+
<DraggableToolbarButton
|
|
226
|
+
id={id}
|
|
227
|
+
icon={icon}
|
|
228
|
+
onSelect={onSelect}
|
|
229
|
+
onDragStart={onDragStart}
|
|
230
|
+
labelToUse={labelToUse}
|
|
231
|
+
titleStr={titleStr}
|
|
232
|
+
disabled={disabled}
|
|
233
|
+
isSelected={isSelected}
|
|
234
|
+
/>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
210
237
|
return (
|
|
211
238
|
<TldrawUiToolbarButton
|
|
212
239
|
aria-label={labelStr}
|
|
@@ -227,6 +254,21 @@ export function TldrawUiMenuItem<
|
|
|
227
254
|
)
|
|
228
255
|
}
|
|
229
256
|
case 'toolbar-overflow': {
|
|
257
|
+
if (onDragStart) {
|
|
258
|
+
return (
|
|
259
|
+
<DraggableToolbarButton
|
|
260
|
+
id={id}
|
|
261
|
+
icon={icon}
|
|
262
|
+
onSelect={onSelect}
|
|
263
|
+
onDragStart={onDragStart}
|
|
264
|
+
labelToUse={labelToUse}
|
|
265
|
+
titleStr={titleStr}
|
|
266
|
+
disabled={disabled}
|
|
267
|
+
isSelected={isSelected}
|
|
268
|
+
overflow
|
|
269
|
+
/>
|
|
270
|
+
)
|
|
271
|
+
}
|
|
230
272
|
return (
|
|
231
273
|
<TldrawUiToolbarButton
|
|
232
274
|
aria-label={labelStr}
|
|
@@ -249,3 +291,172 @@ export function TldrawUiMenuItem<
|
|
|
249
291
|
}
|
|
250
292
|
}
|
|
251
293
|
}
|
|
294
|
+
|
|
295
|
+
function useDraggableEvents(
|
|
296
|
+
onDragStart: TLUiToolItem['onDragStart'],
|
|
297
|
+
onSelect: TLUiToolItem['onSelect']
|
|
298
|
+
) {
|
|
299
|
+
const editor = useEditor()
|
|
300
|
+
const events = useMemo(() => {
|
|
301
|
+
let state = { name: 'idle' } as
|
|
302
|
+
| {
|
|
303
|
+
name: 'idle'
|
|
304
|
+
}
|
|
305
|
+
| {
|
|
306
|
+
name: 'pointing'
|
|
307
|
+
start: Vec
|
|
308
|
+
}
|
|
309
|
+
| {
|
|
310
|
+
name: 'dragging'
|
|
311
|
+
start: Vec
|
|
312
|
+
}
|
|
313
|
+
| {
|
|
314
|
+
name: 'dragged'
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function handlePointerDown(e: React.PointerEvent<HTMLButtonElement>) {
|
|
318
|
+
state = {
|
|
319
|
+
name: 'pointing',
|
|
320
|
+
start: editor.inputs.currentPagePoint.clone(),
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
e.currentTarget.setPointerCapture(e.pointerId)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function handlePointerMove(e: React.PointerEvent<HTMLButtonElement>) {
|
|
327
|
+
if ((e as any).isSpecialRedispatchedEvent) return
|
|
328
|
+
|
|
329
|
+
if (state.name === 'pointing') {
|
|
330
|
+
const distance = Vec.Dist2(state.start, editor.inputs.currentPagePoint)
|
|
331
|
+
if (
|
|
332
|
+
distance >
|
|
333
|
+
(editor.getInstanceState().isCoarsePointer
|
|
334
|
+
? editor.options.coarseDragDistanceSquared
|
|
335
|
+
: editor.options.dragDistanceSquared)
|
|
336
|
+
) {
|
|
337
|
+
const start = state.start
|
|
338
|
+
state = {
|
|
339
|
+
name: 'dragging',
|
|
340
|
+
start,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
editor.run(() => {
|
|
344
|
+
// Set origin point
|
|
345
|
+
editor.dispatch({
|
|
346
|
+
type: 'pointer',
|
|
347
|
+
target: 'canvas',
|
|
348
|
+
name: 'pointer_down',
|
|
349
|
+
...getPointerInfo(e),
|
|
350
|
+
point: start,
|
|
351
|
+
})
|
|
352
|
+
|
|
353
|
+
// Pointer down potentially selects shapes, so we need to deselect them.
|
|
354
|
+
editor.selectNone()
|
|
355
|
+
|
|
356
|
+
// start drag
|
|
357
|
+
onDragStart?.('toolbar', {
|
|
358
|
+
type: 'pointer',
|
|
359
|
+
target: 'canvas',
|
|
360
|
+
name: 'pointer_move',
|
|
361
|
+
...getPointerInfo(e),
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function handlePointerUp(e: React.PointerEvent<HTMLButtonElement>) {
|
|
369
|
+
if ((e as any).isSpecialRedispatchedEvent) return
|
|
370
|
+
|
|
371
|
+
e.currentTarget.releasePointerCapture(e.pointerId)
|
|
372
|
+
|
|
373
|
+
editor.dispatch({
|
|
374
|
+
type: 'pointer',
|
|
375
|
+
target: 'canvas',
|
|
376
|
+
name: 'pointer_up',
|
|
377
|
+
...getPointerInfo(e),
|
|
378
|
+
})
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function handleClick() {
|
|
382
|
+
if (state.name === 'dragging' || state.name === 'dragged') {
|
|
383
|
+
state = { name: 'idle' }
|
|
384
|
+
return true
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
state = { name: 'idle' }
|
|
388
|
+
onSelect?.('toolbar')
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
onPointerDown: handlePointerDown,
|
|
393
|
+
onPointerMove: handlePointerMove,
|
|
394
|
+
onPointerUp: handlePointerUp,
|
|
395
|
+
onClick: handleClick,
|
|
396
|
+
}
|
|
397
|
+
}, [onDragStart, editor, onSelect])
|
|
398
|
+
|
|
399
|
+
return events
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function DraggableToolbarButton({
|
|
403
|
+
id,
|
|
404
|
+
labelToUse,
|
|
405
|
+
titleStr,
|
|
406
|
+
disabled,
|
|
407
|
+
isSelected,
|
|
408
|
+
icon,
|
|
409
|
+
onSelect,
|
|
410
|
+
onDragStart,
|
|
411
|
+
overflow,
|
|
412
|
+
}: {
|
|
413
|
+
id: string
|
|
414
|
+
disabled: boolean
|
|
415
|
+
labelToUse?: string
|
|
416
|
+
titleStr?: string
|
|
417
|
+
isSelected?: boolean
|
|
418
|
+
icon: TLUiMenuItemProps['icon']
|
|
419
|
+
onSelect: TLUiMenuItemProps['onSelect']
|
|
420
|
+
onDragStart: TLUiMenuItemProps['onDragStart']
|
|
421
|
+
overflow?: boolean
|
|
422
|
+
}) {
|
|
423
|
+
const events = useDraggableEvents(onDragStart, onSelect)
|
|
424
|
+
|
|
425
|
+
if (overflow) {
|
|
426
|
+
return (
|
|
427
|
+
<TldrawUiToolbarButton
|
|
428
|
+
aria-label={labelToUse}
|
|
429
|
+
aria-pressed={isSelected ? 'true' : 'false'}
|
|
430
|
+
isActive={isSelected}
|
|
431
|
+
className="tlui-button-grid__button"
|
|
432
|
+
data-testid={`tools.more.${id}`}
|
|
433
|
+
data-value={id}
|
|
434
|
+
disabled={disabled}
|
|
435
|
+
title={titleStr}
|
|
436
|
+
type="icon"
|
|
437
|
+
{...events}
|
|
438
|
+
>
|
|
439
|
+
<TldrawUiButtonIcon icon={icon!} />
|
|
440
|
+
</TldrawUiToolbarButton>
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return (
|
|
445
|
+
<TldrawUiToolbarButton
|
|
446
|
+
aria-label={labelToUse}
|
|
447
|
+
aria-pressed={isSelected ? 'true' : 'false'}
|
|
448
|
+
data-testid={`tools.${id}`}
|
|
449
|
+
data-value={id}
|
|
450
|
+
disabled={disabled}
|
|
451
|
+
onTouchStart={(e) => {
|
|
452
|
+
preventDefault(e)
|
|
453
|
+
onSelect('toolbar')
|
|
454
|
+
}}
|
|
455
|
+
title={titleStr}
|
|
456
|
+
type="tool"
|
|
457
|
+
{...events}
|
|
458
|
+
>
|
|
459
|
+
<TldrawUiButtonIcon icon={icon!} />
|
|
460
|
+
</TldrawUiToolbarButton>
|
|
461
|
+
)
|
|
462
|
+
}
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
assertExists,
|
|
3
|
+
createShapeId,
|
|
4
|
+
Editor,
|
|
5
|
+
GeoShapeGeoStyle,
|
|
6
|
+
TLPointerEventInfo,
|
|
7
|
+
TLShapeId,
|
|
8
|
+
toRichText,
|
|
9
|
+
useMaybeEditor,
|
|
10
|
+
} from '@tldraw/editor'
|
|
2
11
|
import * as React from 'react'
|
|
3
12
|
import { EmbedDialog } from '../components/EmbedDialog'
|
|
4
13
|
import { TLUiIconJsx } from '../components/primitives/TldrawUiIcon'
|
|
@@ -19,6 +28,7 @@ export interface TLUiToolItem<
|
|
|
19
28
|
shortcutsLabel?: TranslationKey
|
|
20
29
|
icon: IconType | TLUiIconJsx
|
|
21
30
|
onSelect(source: TLUiEventSource): void
|
|
31
|
+
onDragStart?(source: TLUiEventSource, info: TLPointerEventInfo): void
|
|
22
32
|
/**
|
|
23
33
|
* The keyboard shortcut for this tool. This is a string that can be a single key,
|
|
24
34
|
* or a combination of keys.
|
|
@@ -126,21 +136,27 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
126
136
|
onToolSelect(source, this)
|
|
127
137
|
},
|
|
128
138
|
},
|
|
129
|
-
...[...GeoShapeGeoStyle.values].map((
|
|
130
|
-
id,
|
|
131
|
-
label: `tool.${
|
|
139
|
+
...[...GeoShapeGeoStyle.values].map((geo) => ({
|
|
140
|
+
id: geo,
|
|
141
|
+
label: `tool.${geo}` as TLUiTranslationKey,
|
|
132
142
|
meta: {
|
|
133
|
-
geo
|
|
143
|
+
geo,
|
|
134
144
|
},
|
|
135
|
-
kbd:
|
|
136
|
-
icon: ('geo-' +
|
|
145
|
+
kbd: geo === 'rectangle' ? 'r' : geo === 'ellipse' ? 'o' : undefined,
|
|
146
|
+
icon: ('geo-' + geo) as TLUiIconType,
|
|
137
147
|
onSelect(source: TLUiEventSource) {
|
|
138
148
|
editor.run(() => {
|
|
139
|
-
editor.setStyleForNextShapes(GeoShapeGeoStyle,
|
|
149
|
+
editor.setStyleForNextShapes(GeoShapeGeoStyle, geo)
|
|
140
150
|
editor.setCurrentTool('geo')
|
|
141
|
-
onToolSelect(source, this, `geo-${
|
|
151
|
+
onToolSelect(source, this, `geo-${geo}`)
|
|
142
152
|
})
|
|
143
153
|
},
|
|
154
|
+
onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
|
|
155
|
+
onDragFromToolbarToCreateShape(editor, info, {
|
|
156
|
+
createShape: (id) => editor.createShape({ id, type: 'geo', props: { geo } }),
|
|
157
|
+
})
|
|
158
|
+
trackEvent('drag-tool', { source, id: 'geo' })
|
|
159
|
+
},
|
|
144
160
|
})),
|
|
145
161
|
{
|
|
146
162
|
id: 'arrow',
|
|
@@ -151,6 +167,17 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
151
167
|
editor.setCurrentTool('arrow')
|
|
152
168
|
onToolSelect(source, this)
|
|
153
169
|
},
|
|
170
|
+
onDragStart(source: TLUiEventSource, info: TLPointerEventInfo) {
|
|
171
|
+
onDragFromToolbarToCreateShape(editor, info, {
|
|
172
|
+
createShape: (id) =>
|
|
173
|
+
editor.createShape({
|
|
174
|
+
id,
|
|
175
|
+
type: 'arrow',
|
|
176
|
+
props: { start: { x: 0, y: 0 }, end: { x: 200, y: 0 } },
|
|
177
|
+
}),
|
|
178
|
+
})
|
|
179
|
+
trackEvent('drag-tool', { source, id: 'arrow' })
|
|
180
|
+
},
|
|
154
181
|
},
|
|
155
182
|
{
|
|
156
183
|
id: 'line',
|
|
@@ -171,6 +198,12 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
171
198
|
editor.setCurrentTool('frame')
|
|
172
199
|
onToolSelect(source, this)
|
|
173
200
|
},
|
|
201
|
+
onDragStart(source, info) {
|
|
202
|
+
onDragFromToolbarToCreateShape(editor, info, {
|
|
203
|
+
createShape: (id) => editor.createShape({ id, type: 'frame' }),
|
|
204
|
+
})
|
|
205
|
+
trackEvent('drag-tool', { source, id: 'frame' })
|
|
206
|
+
},
|
|
174
207
|
},
|
|
175
208
|
{
|
|
176
209
|
id: 'text',
|
|
@@ -181,6 +214,17 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
181
214
|
editor.setCurrentTool('text')
|
|
182
215
|
onToolSelect(source, this)
|
|
183
216
|
},
|
|
217
|
+
onDragStart(source, info) {
|
|
218
|
+
onDragFromToolbarToCreateShape(editor, info, {
|
|
219
|
+
createShape: (id) =>
|
|
220
|
+
editor.createShape({ id, type: 'text', props: { richText: toRichText('Text') } }),
|
|
221
|
+
onDragEnd: (id) => {
|
|
222
|
+
editor.emit('select-all-text', { shapeId: id })
|
|
223
|
+
editor.setEditingShape(id)
|
|
224
|
+
},
|
|
225
|
+
})
|
|
226
|
+
trackEvent('drag-tool', { source, id: 'text' })
|
|
227
|
+
},
|
|
184
228
|
},
|
|
185
229
|
{
|
|
186
230
|
id: 'asset',
|
|
@@ -201,6 +245,16 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
201
245
|
editor.setCurrentTool('note')
|
|
202
246
|
onToolSelect(source, this)
|
|
203
247
|
},
|
|
248
|
+
onDragStart(source, info) {
|
|
249
|
+
onDragFromToolbarToCreateShape(editor, info, {
|
|
250
|
+
createShape: (id) => editor.createShape({ id, type: 'note' }),
|
|
251
|
+
onDragEnd: (id) => {
|
|
252
|
+
editor.emit('select-all-text', { shapeId: id })
|
|
253
|
+
editor.setEditingShape(id)
|
|
254
|
+
},
|
|
255
|
+
})
|
|
256
|
+
trackEvent('drag-tool', { source, id: 'note' })
|
|
257
|
+
},
|
|
204
258
|
},
|
|
205
259
|
{
|
|
206
260
|
id: 'laser',
|
|
@@ -244,7 +298,7 @@ export function ToolsProvider({ overrides, children }: TLUiToolsProviderProps) {
|
|
|
244
298
|
}
|
|
245
299
|
|
|
246
300
|
return tools
|
|
247
|
-
}, [overrides, editor, helpers, onToolSelect])
|
|
301
|
+
}, [overrides, editor, helpers, onToolSelect, trackEvent])
|
|
248
302
|
|
|
249
303
|
return <ToolsContext.Provider value={tools}>{children}</ToolsContext.Provider>
|
|
250
304
|
}
|
|
@@ -259,3 +313,57 @@ export function useTools() {
|
|
|
259
313
|
|
|
260
314
|
return ctx
|
|
261
315
|
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Options for {@link onDragFromToolbarToCreateShape}.
|
|
319
|
+
* @public
|
|
320
|
+
*/
|
|
321
|
+
export interface OnDragFromToolbarToCreateShapesOpts {
|
|
322
|
+
/**
|
|
323
|
+
* Create the shape being dragged. You don't need to worry about positioning it, as it'll be
|
|
324
|
+
* immediately updated with the correct position.
|
|
325
|
+
*/
|
|
326
|
+
createShape(id: TLShapeId): void
|
|
327
|
+
/**
|
|
328
|
+
* Called once the drag interaction has finished.
|
|
329
|
+
*/
|
|
330
|
+
onDragEnd?(id: TLShapeId): void
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* A helper method to use in {@link TLUiToolItem#onDragStart} to create a shape by dragging it from
|
|
335
|
+
* the toolbar.
|
|
336
|
+
* @public
|
|
337
|
+
*/
|
|
338
|
+
export function onDragFromToolbarToCreateShape(
|
|
339
|
+
editor: Editor,
|
|
340
|
+
info: TLPointerEventInfo,
|
|
341
|
+
opts: OnDragFromToolbarToCreateShapesOpts
|
|
342
|
+
) {
|
|
343
|
+
const { x, y } = editor.inputs.currentPagePoint
|
|
344
|
+
|
|
345
|
+
const stoppingPoint = editor.markHistoryStoppingPoint('drag shape tool')
|
|
346
|
+
editor.setCurrentTool('select.translating')
|
|
347
|
+
|
|
348
|
+
const id = createShapeId()
|
|
349
|
+
opts.createShape(id)
|
|
350
|
+
const shape = assertExists(editor.getShape(id), 'Shape not found')
|
|
351
|
+
|
|
352
|
+
const { w, h } = editor.getShapePageBounds(id)!
|
|
353
|
+
editor.updateShape({ id, type: shape.type, x: x - w / 2, y: y - h / 2 })
|
|
354
|
+
editor.select(id)
|
|
355
|
+
|
|
356
|
+
editor.setCurrentTool('select.translating', {
|
|
357
|
+
...info,
|
|
358
|
+
target: 'shape',
|
|
359
|
+
shape: editor.getShape(id),
|
|
360
|
+
isCreating: true,
|
|
361
|
+
creatingMarkId: stoppingPoint,
|
|
362
|
+
onCreate() {
|
|
363
|
+
editor.setCurrentTool('select.idle')
|
|
364
|
+
editor.select(id)
|
|
365
|
+
opts.onDragEnd?.(id)
|
|
366
|
+
},
|
|
367
|
+
})
|
|
368
|
+
editor.getCurrentTool().setCurrentToolIdMask(shape.type)
|
|
369
|
+
}
|
package/src/lib/ui/version.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
// This file is automatically generated by internal/scripts/refresh-assets.ts.
|
|
2
2
|
// Do not edit manually. Or do, I'm a comment, not a cop.
|
|
3
3
|
|
|
4
|
-
export const version = '3.16.0-canary.
|
|
4
|
+
export const version = '3.16.0-canary.fa3749606e52'
|
|
5
5
|
export const publishDates = {
|
|
6
6
|
major: '2024-09-13T14:36:29.063Z',
|
|
7
|
-
minor: '2025-
|
|
8
|
-
patch: '2025-
|
|
7
|
+
minor: '2025-08-01T14:30:58.099Z',
|
|
8
|
+
patch: '2025-08-01T14:30:58.099Z',
|
|
9
9
|
}
|