hunk.code_window: 	menuGroup,
   	menuItem,
   	MenuSchema,
   	menuSubmenu,
   	showUiPaste,
   	useAllowGroup,
   	useAllowUngroup,
   	useThreeStackableItems,
   } from './menuHelpers'
  
hunk.labels: keep keep keep keep replace keep keep keep keep
hunk.after_edit: 	showMenuPaste,
  
hunk.file_path: packages/ui/src/lib/hooks/useContextMenuSchema.tsx
hunk.type: replace
file: /* eslint-disable no-inner-declarations */
  import { TLShape } from '@tldraw/tlschema'
  import React, { useCallback, useEffect, useRef } from 'react'
  import { useValue } from 'signia-react'
  import { useApp } from '../../../hooks/useApp'
  import { preventDefault, stopEventPropagation } from '../../../utils/dom'
  import { TextHelpers } from '../TLTextUtil/TextHelpers'
  
  export function useEditableText<T extends Extract<TLShape, { props: { text: string } }>>(
  	id: T['id'],
  	type: T['type'],
  	text: string
  ) {
  	const app = useApp()
  
  	const rInput = useRef<HTMLTextAreaElement>(null)
  
  	const isEditing = useValue('isEditing', () => app.pageState.editingId === id, [app, id])
  
  	const rSkipSelectOnFocus = useRef(false)
  	const rSelectionRanges = useRef<Range[] | null>()
  
  	const isEditableFromHover = useValue(
  		'is editable hovering',
  		() => {
  			if (type === 'text' && app.isIn('text') && app.hoveredId === id) {
  				return true
  			}
  
  			if (app.isIn('select.editing_shape')) {
  				const { editingShape } = app
  				if (!editingShape) return false
  				return (
  					// The shape must be hovered
  					app.hoveredId === id &&
  					// the editing shape must be the same type as this shape
  					editingShape.type === type &&
  					// and this shape must be capable of being editing in its current form
  					app.getShapeUtil(editingShape).canEdit(editingShape)
  				)
  			}
  
  			return false
  		},
  		[type, id]
  	)
  
  	// When the label receives focus, set the value to the most
  	// recent text value and select all of the text
  	const handleFocus = useCallback(() => {
  		if (isEditableFromHover) return
  
  		requestAnimationFrame(() => {
  			const elm = rInput.current
  
  			if (!elm) return
  
  			const shape = app.getShapeById<TLShape & { props: { text: string } }>(id)
  			if (shape) {
  				elm.value = shape.props.text
  				if (elm.value.length && !rSkipSelectOnFocus.current) {
  					elm.select()
  				}
  
  				rSkipSelectOnFocus.current = false
  			}
  		})
  	}, [app, id, isEditableFromHover])
  
  	// When the label blurs, deselect all of the text and complete.
  	// This makes it so that the canvas does not have to be focused
  	// in order to exit the editing state and complete the editing state
  	const handleBlur = useCallback(() => {
  		const ranges = rSelectionRanges.current
  
  		requestAnimationFrame(() => {
  			const elm = rInput.current
  			if (app.isIn('select.editing_shape') && elm) {
  				if (ranges) {
  					if (!ranges.length) {
  						// If we don't have any ranges, restore selection
  						// and select all of the text
  						elm.focus()
  					} else {
  						// Otherwise, skip the select-all-on-focus behavior
  						// and restore the selection
  						rSkipSelectOnFocus.current = true
  						elm.focus()
  						const selection = window.getSelection()
  						if (selection) {
  							ranges.forEach((range) => selection.addRange(range))
  						}
  					}
  				} else {
  					elm.focus()
  				}
  			} else {
  				window.getSelection()?.removeAllRanges()
  				app.complete()
  			}
  		})
  	}, [app])
  
  	// When the user presses ctrl / meta enter, complete the editing state.
  	// When the user presses tab, indent or unindent the text.
  	const handleKeyDown = useCallback(
  		(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
  			if (e.ctrlKey || e.metaKey) stopEventPropagation(e)
  
  			switch (e.key) {
  				case 'Enter': {
  					if (e.ctrlKey || e.metaKey) {
  						app.complete()
  					}
  					break
  				}
  				case 'Tab': {
  					preventDefault(e)
  					if (e.shiftKey) {
  						TextHelpers.unindent(e.currentTarget)
  					} else {
  						TextHelpers.indent(e.currentTarget)
  					}
  					break
  				}
  			}
  		},
  		[app]
  	)
  
  	// When the text changes, update the text value.
  	const handleChange = useCallback(
  		(e: React.ChangeEvent<HTMLTextAreaElement>) => {
  			const text = TextHelpers.normalizeText(e.currentTarget.value)
  			app.updateShapes([{ id, type, props: { text } }])
  		},
  		[app, id, type]
  	)
  
  	const isEmpty = text.trim().length === 0
  
  	useEffect(() => {
  		const elm = rInput.current
  		if (elm) {
  			function updateSelection() {
  				const selection = window.getSelection?.()
  				if (selection && selection.type !== 'None') {
  					const ranges: Range[] = []
  
  					if (selection) {
  						for (let i = 0; i < selection.rangeCount; i++) {
  							ranges.push(selection.getRangeAt?.(i))
  						}
  					}
  
  					rSelectionRanges.current = ranges
  				}
  			}
  
  			document.addEventListener('selectionchange', updateSelection)
  
  			return () => {
  				document.removeEventListener('selectionchange', updateSelection)
  			}
  		}
  	})
  
  	return {
  		rInput,
  		isEditing,
  		isEditableFromHover,
  		handleFocus,
  		handleBlur,
  		handleKeyDown,
  		handleChange,
  		isEmpty,
  	}
  }
  