tldraw
Version:
A tiny little drawing editor.
8 lines (7 loc) • 28.2 kB
Source Map (JSON)
{
"version": 3,
"sources": ["../../../../../src/lib/ui/components/Toolbar/DefaultRichTextToolbar.tsx"],
"sourcesContent": ["import { getMarkRange, Range } from '@tiptap/core'\nimport { MarkType } from '@tiptap/pm/model'\nimport {\n\tBox,\n\tclamp,\n\tdebounce,\n\tEditor,\n\tTiptapEditor,\n\ttltime,\n\ttrack,\n\tuseAtom,\n\tuseEditor,\n\tuseQuickReactor,\n\tuseReactor,\n\tuseValue,\n\tVec,\n} from '@tldraw/editor'\nimport React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { useTranslation } from '../../hooks/useTranslation/useTranslation'\nimport { TldrawUiContextualToolbar } from '../primitives/TldrawUiContextualToolbar'\nimport { DefaultRichTextToolbarContent } from './DefaultRichTextToolbarContent'\nimport { LinkEditor } from './LinkEditor'\n\nconst MOVE_TIMEOUT = 150\nconst HIDE_VISIBILITY_TIMEOUT = 16\nconst SHOW_VISIBILITY_TIMEOUT = 16\nconst TOOLBAR_GAP = 8\nconst SCREEN_MARGIN = 16\nconst MIN_DISTANCE_TO_REPOSITION_SQUARED = 16 ** 2\nconst HIDE_TOOLBAR_WHEN_CAMERA_IS_MOVING = true\nconst CHANGE_ONLY_WHEN_Y_CHANGES = true\nconst LEFT_ALIGN_TOOLBAR = false\n\n/** @public */\nexport interface TLUiRichTextToolbarProps {\n\tchildren?: React.ReactNode\n}\n\n/**\n * The default rich text toolbar.\n *\n * @public @react\n */\nexport const DefaultRichTextToolbar = track(function DefaultRichTextToolbar({\n\tchildren,\n}: TLUiRichTextToolbarProps) {\n\tconst editor = useEditor()\n\n\tconst textEditor = useValue('textEditor', () => editor.getRichTextEditor(), [editor])\n\n\tif (editor.getInstanceState().isCoarsePointer || !textEditor) return null\n\n\treturn <ContextualToolbarInner textEditor={textEditor}>{children}</ContextualToolbarInner>\n})\n\nfunction ContextualToolbarInner({\n\ttextEditor,\n\tchildren,\n}: {\n\tchildren?: React.ReactNode\n\ttextEditor: TiptapEditor\n}) {\n\tconst editor = useEditor()\n\tconst msg = useTranslation()\n\n\tconst rToolbar = useRef<HTMLDivElement>(null)\n\n\tconst { isVisible, isInteractive, hide, show, position, move } =\n\t\tuseToolbarVisibilityStateMachine()\n\n\tconst { isEditingLink, onEditLinkStart, onEditLinkComplete } = useEditingLinkBehavior(textEditor)\n\n\t// We use an atom to force the toolbar position to update\n\t// This gets triggered when:\n\t// - the selection changes\n\t// - the shape changes\n\tconst forcePositionUpdateAtom = useAtom('force toolbar position update', 0)\n\n\tuseEffect(\n\t\tfunction forceUpdateWhenSelectionUpdates() {\n\t\t\tfunction handleSelectionUpdate() {\n\t\t\t\tforcePositionUpdateAtom.update((t) => t + 1)\n\t\t\t}\n\t\t\t// Run me once after a raf to force the toolbar position to update immediately.\n\t\t\t// This is needed in order to capture a \"select all\" moment, e.g. when\n\t\t\t// double clicking a geo shape to edit its text. We need the raf to let the selection occur.\n\t\t\ttltime.requestAnimationFrame('first forced update', handleSelectionUpdate)\n\t\t\ttextEditor.on('selectionUpdate', handleSelectionUpdate)\n\t\t\treturn () => {\n\t\t\t\ttextEditor.off('selectionUpdate', handleSelectionUpdate)\n\t\t\t}\n\t\t},\n\t\t[textEditor, forcePositionUpdateAtom]\n\t)\n\n\tuseReactor(\n\t\t'shape change',\n\t\tfunction forceUpdateOnNextFrameWhenShapeChanges() {\n\t\t\t// Ok, this is crazy bullshit but here's what's happening:\n\t\t\t// 1. the editing shape updates\n\t\t\t// 2. the shape's position changes (maybe) based on its new size\n\t\t\t// 3. we force an update\n\t\t\t// 4. we update the toolbar position\n\t\t\t// It's IMPORTANT that this is a normal \"useReactor\" and not a \"useQuickReactor\",\n\t\t\t// so that the force update happens on the NEXT FRAME after the change. It takes a\n\t\t\t// frame between 2 and 3 for the shape to update its position. If we don't wait, then\n\t\t\t// we race the shape's position update and the measurement of the selection screen rects.\n\t\t\t// If you really want to test this, try changing this to a \"useQuickReactor\", select a\n\t\t\t// shape's text, and then use the style panel to change the shape's size.\n\n\t\t\teditor.getEditingShape() // capture the editing shape\n\t\t\tforcePositionUpdateAtom.update((t) => t + 1)\n\t\t},\n\t\t[editor]\n\t)\n\n\t// annoying react stuff: we don't want the toolbar position function to depend on the react state so we'll double with a ref\n\tconst rCouldShowToolbar = useRef(false)\n\tconst [hasValidToolbarPosition, setHasValidToolbarPosition] = useState(false)\n\n\tuseQuickReactor(\n\t\t'toolbar position',\n\t\tfunction updateToolbarPositionAndDisplay() {\n\t\t\tconst toolbarElm = rToolbar.current\n\t\t\tif (!toolbarElm) return\n\n\t\t\t// capture / force this to update when...\n\t\t\teditor.getCamera() // the camera moves\n\t\t\tforcePositionUpdateAtom.get() // the selection changes\n\n\t\t\t// undefined here means that we can't show the toolbar due to an incompatible position\n\t\t\tconst position = getToolbarScreenPosition(editor, toolbarElm)\n\n\t\t\t// todo: when the toolbar is hidden due to the selection being off screen, it should be hidden immediately\n\t\t\t// rather than waiting for the position to settle. This is different than when the position changes due to\n\t\t\t// a change in the user's selection.\n\t\t\tif (!position) {\n\t\t\t\tif (rCouldShowToolbar.current) {\n\t\t\t\t\t// If we don't have a position, then we're not showing the toolbar\n\t\t\t\t\trCouldShowToolbar.current = false\n\t\t\t\t\tsetHasValidToolbarPosition(false)\n\t\t\t\t}\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// If the camera state is moving, we want to immediately update the position\n\t\t\t// todo: consider hiding the toolbar while the camera is moving\n\t\t\tconst cameraState = editor.getCameraState()\n\t\t\tif (cameraState === 'moving') {\n\t\t\t\t// ...if we wanted this to avoid prematurely updating any positions, we'd need\n\t\t\t\t// to have the last updated position in page space, so that we could convert\n\t\t\t\t// it to screen space and update it here\n\t\t\t\tconst elm = rToolbar.current\n\t\t\t\telm.style.setProperty('transform', `translate(${position.x}px, ${position.y}px)`)\n\t\t\t} else {\n\t\t\t\t// Schedule a move to its next location\n\t\t\t\tmove(position.x, position.y)\n\t\t\t}\n\n\t\t\t// Finally, if the toolbar was previously hidden, show it again\n\t\t\tif (!rCouldShowToolbar.current) {\n\t\t\t\trCouldShowToolbar.current = true\n\t\t\t\tsetHasValidToolbarPosition(true)\n\t\t\t}\n\t\t},\n\t\t[editor, textEditor, forcePositionUpdateAtom]\n\t)\n\n\tconst cameraState = useValue('camera state', () => editor.getCameraState(), [editor])\n\tconst isMousingDown = useIsMousingDownOnTextEditor(textEditor)\n\n\t// Send the hide or show events based on whether the user is clicking\n\t// and whether the toolbar's position is valid\n\tuseEffect(() => {\n\t\tif (cameraState === 'moving' && HIDE_TOOLBAR_WHEN_CAMERA_IS_MOVING) {\n\t\t\thide(true)\n\t\t\treturn\n\t\t}\n\n\t\tif (isMousingDown || !hasValidToolbarPosition) {\n\t\t\thide()\n\t\t\treturn\n\t\t}\n\n\t\tshow()\n\t}, [hasValidToolbarPosition, cameraState, isMousingDown, show, hide])\n\n\t// When the visibility changes, update the toolbar's visibility\n\tuseLayoutEffect(() => {\n\t\tconst elm = rToolbar.current\n\t\tif (!elm) return\n\t\telm.dataset.visible = `${isVisible}`\n\t}, [isVisible, position])\n\n\t// When the position changes, update the toolbar's position on screen\n\tuseLayoutEffect(() => {\n\t\tconst elm = rToolbar.current\n\t\tif (!elm) return\n\t\telm.style.setProperty('transform', `translate(${position.x}px, ${position.y}px)`)\n\t}, [position])\n\n\t// When the interactivity changes, update the toolbar's interactivity\n\tuseLayoutEffect(() => {\n\t\tconst elm = rToolbar.current\n\t\tif (!elm) return\n\t\telm.dataset.interactive = `${isInteractive}`\n\t}, [isInteractive])\n\n\treturn (\n\t\t<TldrawUiContextualToolbar\n\t\t\tref={rToolbar}\n\t\t\tclassName=\"tlui-rich-text__toolbar\"\n\t\t\tdata-interactive={false}\n\t\t\tdata-visible={false}\n\t\t\tlabel={msg('tool.rich-text-toolbar-title')}\n\t\t>\n\t\t\t{children ? (\n\t\t\t\tchildren\n\t\t\t) : isEditingLink ? (\n\t\t\t\t<LinkEditor\n\t\t\t\t\ttextEditor={textEditor}\n\t\t\t\t\tvalue={textEditor.isActive('link') ? textEditor.getAttributes('link').href : ''}\n\t\t\t\t\tonComplete={onEditLinkComplete}\n\t\t\t\t/>\n\t\t\t) : (\n\t\t\t\t<DefaultRichTextToolbarContent textEditor={textEditor} onEditLinkStart={onEditLinkStart} />\n\t\t\t)}\n\t\t</TldrawUiContextualToolbar>\n\t)\n}\n\n// For convenience, let's work just with boxes here\nfunction rectToBox(rect: DOMRect): Box {\n\treturn new Box(rect.x, rect.y, rect.width, rect.height)\n}\n\n// Extracted here\nfunction getToolbarScreenPosition(editor: Editor, toolbarElm: HTMLElement) {\n\t// Get the text selection rects as a box. This will be undefined if there are no selections.\n\tconst selection = window.getSelection()\n\n\t// If there are no selections, don't return a box\n\tif (!selection || selection.rangeCount === 0 || selection.isCollapsed) return\n\n\t// Get a common box from all of the ranges' screen rects\n\tconst rangeBoxes: Box[] = []\n\tfor (let i = 0; i < selection.rangeCount; i++) {\n\t\tconst range = selection.getRangeAt(i)\n\t\trangeBoxes.push(rectToBox(range.getBoundingClientRect()))\n\t}\n\n\tconst selectionBounds = Box.Common(rangeBoxes)\n\n\t// Offset the selection bounds by the viewport screen bounds (if the editor is scrolled or inset, etc)\n\tconst vsb = editor.getViewportScreenBounds()\n\tselectionBounds.x -= vsb.x\n\tselectionBounds.y -= vsb.y\n\n\t// If the selection bounds are too far off of the screen, don't show the toolbar\n\tif (\n\t\tselectionBounds.midY < SCREEN_MARGIN ||\n\t\tselectionBounds.midY > vsb.h - SCREEN_MARGIN ||\n\t\tselectionBounds.midX < SCREEN_MARGIN ||\n\t\tselectionBounds.midX > vsb.w - SCREEN_MARGIN\n\t) {\n\t\treturn\n\t}\n\n\t// Get the toolbar's screen rect as a box. Do this after we verify that there is at least one selection.\n\tconst toolbarBounds = rectToBox(toolbarElm.getBoundingClientRect())\n\n\t// Chance these are NaN? Rare case.\n\tif (!toolbarBounds.width || !toolbarBounds.height) return\n\n\t// Thrashy, only do this if we're showing the toolbar\n\t// ! this might not be needed, the container never scrolls\n\tconst { scrollLeft, scrollTop } = editor.getContainer()\n\n\t// We want to position the toolbar so that it is centered over the selection\n\t// except in the cases where it would extend off the edge of the screen.\n\n\t// Start by placing the top left corner of the toolbar so that the\n\t// toolbar would be centered above the section bounds, bumped up by the\n\n\tlet x = LEFT_ALIGN_TOOLBAR ? selectionBounds.x : selectionBounds.midX - toolbarBounds.w / 2\n\tlet y = selectionBounds.y - toolbarBounds.h - TOOLBAR_GAP\n\n\t// Clamp the position on screen.\n\tx = clamp(x, SCREEN_MARGIN, vsb.w - toolbarBounds.w - SCREEN_MARGIN)\n\ty = clamp(y, SCREEN_MARGIN, vsb.h - toolbarBounds.h - SCREEN_MARGIN)\n\n\t// Offset the position by the container's scroll position\n\tx += scrollLeft\n\ty += scrollTop\n\n\t// Round the position to the nearest pixel\n\tx = Math.round(x)\n\ty = Math.round(y)\n\n\treturn { x, y }\n}\n\nfunction useEditingLinkBehavior(textEditor?: TiptapEditor) {\n\tconst [isEditingLink, setIsEditingLink] = useState(false)\n\n\t// Set up text editor event listeners.\n\tuseEffect(() => {\n\t\tif (!textEditor) {\n\t\t\tsetIsEditingLink(false)\n\t\t\treturn\n\t\t}\n\n\t\tconst handleClick = () => {\n\t\t\tconst isLinkActive = textEditor.isActive('link')\n\t\t\tsetIsEditingLink(isLinkActive)\n\t\t}\n\n\t\ttextEditor.view.dom.addEventListener('click', handleClick)\n\t\treturn () => {\n\t\t\ttextEditor.view.dom.removeEventListener('click', handleClick)\n\t\t}\n\t}, [textEditor, isEditingLink])\n\n\t// If we're editing a link, select the entire link.\n\t// This can happen via a click or via keyboarding over to the link and then\n\t// clicking the toolbar button.\n\tuseEffect(() => {\n\t\tif (!textEditor) {\n\t\t\treturn\n\t\t}\n\n\t\t// N.B. This specifically isn't checking the isEditingLink state but\n\t\t// the current active state of the text editor. This is because there's\n\t\t// a subtelty where when going edit-to-edit, that is text editor-to-text editor\n\t\t// in different shapes, the isEditingLink state doesn't get reset quickly enough.\n\t\tif (textEditor.isActive('link')) {\n\t\t\ttry {\n\t\t\t\tconst { from, to } = getMarkRange(\n\t\t\t\t\ttextEditor.state.doc.resolve(textEditor.state.selection.from),\n\t\t\t\t\ttextEditor.schema.marks.link as MarkType\n\t\t\t\t) as Range\n\t\t\t\t// Select the entire link if we just clicked on it while in edit mode, but not if there's\n\t\t\t\t// a specific selection.\n\t\t\t\tif (textEditor.state.selection.empty) {\n\t\t\t\t\ttextEditor.commands.setTextSelection({ from, to })\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Sometimes getMarkRange throws an error when the selection is the entire document.\n\t\t\t\t// This is somewhat mysterious but it's harmless. We just need to ignore it.\n\t\t\t\t// Also, this seems to have recently broken with the React 19 preparation changes.\n\t\t\t}\n\t\t}\n\t}, [textEditor, isEditingLink])\n\n\tconst onEditLinkStart = useCallback(() => {\n\t\tsetIsEditingLink(true)\n\t}, [])\n\n\tconst onEditLinkCancel = useCallback(() => {\n\t\tsetIsEditingLink(false)\n\t}, [])\n\n\tconst onEditLinkComplete = useCallback(() => {\n\t\tsetIsEditingLink(false)\n\t\tif (!textEditor) return\n\t\tconst from = textEditor.state.selection.from\n\t\ttextEditor.commands.setTextSelection({ from, to: from })\n\t}, [textEditor])\n\n\treturn { isEditingLink, onEditLinkStart, onEditLinkComplete, onEditLinkCancel }\n}\n\nfunction sufficientlyDistant(curr: Vec, next: Vec) {\n\tif (CHANGE_ONLY_WHEN_Y_CHANGES) {\n\t\treturn Vec.Sub(next, curr).y ** 2 >= MIN_DISTANCE_TO_REPOSITION_SQUARED\n\t}\n\treturn Vec.Len2(Vec.Sub(next, curr)) >= MIN_DISTANCE_TO_REPOSITION_SQUARED\n}\n\nfunction useToolbarVisibilityStateMachine() {\n\tconst editor = useEditor()\n\n\tconst rState = useRef<\n\t\t{ name: 'hidden' } | { name: 'showing' } | { name: 'shown' } | { name: 'hiding' }\n\t>({ name: 'hidden' })\n\n\t// The toolbar should only be interactive when in the 'shown' state\n\tconst [isInteractive, setIsInteractive] = useState(false)\n\n\t// The toolbar is visible in the 'shown' and 'hiding' states\n\tconst [isVisible, setIsVisible] = useState(false)\n\n\t// The position is updated when entering the 'shown' state or when moving while in the 'shown' state\n\tconst [position, setPosition] = useState({ x: -1000, y: -1000 })\n\n\t// The toolbar's current position\n\tconst rCurrPosition = useRef(new Vec(-1000, -1000))\n\n\t// The toolbar's proposed next position\n\tconst rNextPosition = useRef(new Vec(-1000, -1000))\n\n\t// A timeout needs to be completed before the toolbar is shown or hidden\n\tconst rStableVisibilityTimeout = useRef<any>(-1)\n\n\t// A timeout needs to be completed before the toolbar's position changes moved\n\tconst rStablePositionTimeout = useRef<any>(-1)\n\n\t/**\n\t * Send the 'move' event whenever something happens that would cause the toolbar's position to change.\n\t * Any update here will cause\n\t * If the state is 'shown', it will start a new timeout that will update the toolbar's position after it completes.\n\t */\n\tconst move = useCallback(\n\t\t(x: number, y: number) => {\n\t\t\t// Update the next proposed position\n\t\t\trNextPosition.current.x = x\n\t\t\trNextPosition.current.y = y\n\n\t\t\t// If the toolbar is not yet visible, don't do anything\n\t\t\tif (rState.current.name === 'hidden' || rState.current.name === 'showing') return\n\n\t\t\t// If showing or hiding, cancel the position timeout and start a new one.\n\t\t\t// When the timeout ends, if we're in the 'shown' state and the position has changed sufficiently\n\t\t\t// from the last visible position, update the position.\n\t\t\tclearTimeout(rStablePositionTimeout.current)\n\t\t\trStablePositionTimeout.current = editor.timers.setTimeout(() => {\n\t\t\t\tif (\n\t\t\t\t\trState.current.name === 'shown' &&\n\t\t\t\t\tsufficientlyDistant(rNextPosition.current, rCurrPosition.current)\n\t\t\t\t) {\n\t\t\t\t\tconst { x, y } = rNextPosition.current\n\t\t\t\t\trCurrPosition.current = new Vec(x, y)\n\t\t\t\t\tsetPosition({ x, y })\n\t\t\t\t}\n\t\t\t}, MOVE_TIMEOUT)\n\t\t},\n\t\t[editor]\n\t)\n\n\t/**\n\t * Send the hide event whenever a change occurs that would cause the toolbar to become invisible.\n\t * If the state is 'shown', it will enter 'hiding' and then 'hidden' after a timeout completes.\n\t * If the state is 'showing', it will cancel the visibility timeout and enter 'hidden' immediately.\n\t */\n\tconst hide = useCallback(\n\t\t(immediate = false) => {\n\t\t\tswitch (rState.current.name) {\n\t\t\t\tcase 'showing': {\n\t\t\t\t\tclearTimeout(rStableVisibilityTimeout.current)\n\t\t\t\t\trState.current = { name: 'hidden' }\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'shown': {\n\t\t\t\t\trState.current = { name: 'hiding' }\n\t\t\t\t\tsetIsInteractive(false) // when leaving shown, turn back on interactions\n\n\t\t\t\t\tif (immediate) {\n\t\t\t\t\t\trState.current = { name: 'hidden' }\n\t\t\t\t\t\tsetIsVisible(false)\n\t\t\t\t\t} else {\n\t\t\t\t\t\trStableVisibilityTimeout.current = editor.timers.setTimeout(() => {\n\t\t\t\t\t\t\trState.current = { name: 'hidden' }\n\t\t\t\t\t\t\tsetIsVisible(false)\n\t\t\t\t\t\t}, HIDE_VISIBILITY_TIMEOUT)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tdefault: {\n\t\t\t\t\t// noop\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t[editor]\n\t)\n\n\t/**\n\t * Send the show event whenever a change occurs that would cause the toolbar to become visible.\n\t * If the state is 'hidden', it will enter 'showing' and then 'shown' after a timeout completes.\n\t * If the state is 'hiding', it will cancel the visibility timeout and enter 'shown' immediately.\n\t */\n\tconst show = useCallback(() => {\n\t\tswitch (rState.current.name) {\n\t\t\tcase 'hidden': {\n\t\t\t\trState.current = { name: 'showing' }\n\t\t\t\trStableVisibilityTimeout.current = editor.timers.setTimeout(() => {\n\t\t\t\t\t// position\n\t\t\t\t\tconst { x, y } = rNextPosition.current\n\t\t\t\t\trCurrPosition.current = new Vec(x, y)\n\t\t\t\t\tsetPosition({ x, y })\n\n\t\t\t\t\trState.current = { name: 'shown' }\n\t\t\t\t\tsetIsVisible(true)\n\t\t\t\t\tsetIsInteractive(true)\n\t\t\t\t}, SHOW_VISIBILITY_TIMEOUT)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tcase 'hiding': {\n\t\t\t\t// Go back to shown immediately\n\t\t\t\tclearTimeout(rStableVisibilityTimeout.current)\n\t\t\t\trState.current = { name: 'shown' }\n\t\t\t\tsetIsInteractive(true) // when entering shown, turn back on interactions\n\t\t\t\tmove(rNextPosition.current.x, rNextPosition.current.y)\n\t\t\t\tbreak\n\t\t\t}\n\t\t\tdefault: {\n\t\t\t\t// noop\n\t\t\t}\n\t\t}\n\t}, [editor, move])\n\n\treturn { isVisible, isInteractive, show, hide, move, position }\n}\n\nfunction useIsMousingDownOnTextEditor(textEditor: TiptapEditor) {\n\tconst [isMousingDown, setIsMousingDown] = useState(false)\n\n\t// Set up general event listeners for text selection.\n\tuseEffect(() => {\n\t\tif (!textEditor) return\n\n\t\tconst handlePointingStateChange = debounce(({ isPointing }: { isPointing: boolean }) => {\n\t\t\tsetIsMousingDown(isPointing)\n\t\t}, 16)\n\t\tconst handlePointingDown = () => handlePointingStateChange({ isPointing: true })\n\t\tconst handlePointingUp = () => handlePointingStateChange({ isPointing: false })\n\n\t\tconst touchDownEvents = ['touchstart', 'pointerdown', 'mousedown']\n\t\tconst touchUpEvents = ['touchend', 'pointerup', 'mouseup']\n\t\ttouchDownEvents.forEach((eventName: string) => {\n\t\t\ttextEditor.view.dom.addEventListener(eventName, handlePointingDown)\n\t\t})\n\t\ttouchUpEvents.forEach((eventName: string) => {\n\t\t\tdocument.body.addEventListener(eventName, handlePointingUp)\n\t\t})\n\t\treturn () => {\n\t\t\ttouchDownEvents.forEach((eventName: string) => {\n\t\t\t\ttextEditor.view.dom.removeEventListener(eventName, handlePointingDown)\n\t\t\t})\n\t\t\ttouchUpEvents.forEach((eventName: string) => {\n\t\t\t\tdocument.body.removeEventListener(eventName, handlePointingUp)\n\t\t\t})\n\t\t}\n\t}, [textEditor])\n\n\treturn isMousingDown\n}\n"],
"mappings": "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",
"names": ["DefaultRichTextToolbar", "position", "cameraState", "x", "y"]
}