rich-textarea
Version:
A small customizable textarea for React to colorize, highlight, decorate texts, offer autocomplete and much more.
1 lines • 56.7 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":[],"sources":["../src/utils.tsx","../src/vendor/range-at-index.js","../src/dom.ts","../src/selection.ts","../src/useIsomorphicLayoutEffect.ts","../src/useStatic.ts","../src/observer.ts","../src/textarea.tsx","../src/input.tsx","../src/renderers/regex/utils.ts","../src/renderers/regex/index.tsx"],"sourcesContent":["/**\n * @internal\n */\nexport const { min, max } = Math;\n\n/**\n * @internal\n */\nexport const refKey = \"current\";\n\n/**\n * for caret position detection\n * @internal\n */\nexport const CARET_DETECTOR = (\n <span style={{ color: \"transparent\" }}>{\"\\u200b\"}</span>\n);\n","import { max } from \"../utils.js\";\n\n/**\n * range-at-index\n * https://github.com/webmodules/range-at-index/\n * Nathan Rajlich\n *\n * Forked from version 1.0.4; includes the following modifications:\n * 1) Change module.exports to export default.\n * 2) Fix for https://github.com/inokawa/rich-textarea/issues/160\n **/\n\n/**\n * Returns a Range instance selecting text within HTML Element `el`,\n * at the given `start` and `end` offsets.\n *\n * @param {HTMLElement} el - DOM element to select text within\n * @public\n */\n\nfunction RangeAtIndex(el, index, offset, range) {\n var doc = el.ownerDocument;\n if (!range) range = doc.createRange();\n\n let iterator = doc.createNodeIterator(el, NodeFilter.SHOW_TEXT, null, false);\n\n let start = {};\n let end = {};\n let node, val, len;\n\n while ((node = iterator.nextNode())) {\n val = node.nodeValue;\n len = val.length;\n\n if (!start.node && len > index) {\n start.node = node;\n start.offset = index;\n }\n\n if (!end.node && len >= offset) {\n end.node = node;\n end.offset = offset;\n }\n\n index -= len;\n offset -= len;\n }\n\n // update the range with the start and end offsets\n if (start.node) range.setStart(start.node, max(start.offset, 0));\n if (end.node) range.setEnd(end.node, max(end.offset, 0));\n\n return range;\n}\n\nexport default RangeAtIndex;\n","import { refKey } from \"./utils.js\";\n\n/**\n * @internal\n */\nexport const isSafari = (): boolean => {\n if (typeof navigator === \"undefined\") {\n return false;\n }\n const ua = navigator.userAgent.toLowerCase();\n return ua.indexOf(\"safari\") > -1 && ua.indexOf(\"chrome\") <= -1;\n};\n\nconst TEXT_STYLE_KEYS: (keyof React.CSSProperties)[] = [\n \"direction\",\n \"padding\",\n \"paddingTop\",\n \"paddingBottom\",\n \"paddingLeft\",\n \"paddingRight\",\n \"margin\",\n \"marginTop\",\n \"marginBottom\",\n \"marginLeft\",\n \"marginRight\",\n \"border\",\n \"borderWidth\",\n \"borderTopWidth\",\n \"borderBottomWidth\",\n \"borderLeftWidth\",\n \"borderRightWidth\",\n \"borderStyle\",\n \"borderTopStyle\",\n \"borderBottomStyle\",\n \"borderLeftStyle\",\n \"borderRightStyle\",\n \"fontSize\",\n \"fontFamily\",\n \"fontStyle\",\n \"fontVariant\",\n \"fontWeight\",\n \"fontStretch\",\n \"fontSizeAdjust\",\n \"textAlign\",\n \"textTransform\",\n \"textIndent\",\n \"letterSpacing\",\n \"wordSpacing\",\n \"lineHeight\",\n \"whiteSpace\",\n \"wordBreak\",\n \"overflowWrap\",\n \"tabSize\",\n \"MozTabSize\",\n];\n\nconst getPropertyValue = (style: CSSStyleDeclaration, key: string): string => {\n return style.getPropertyValue(key);\n};\nconst setProperty = (\n style: CSSStyleDeclaration,\n key: string,\n value: string\n) => {\n style.setProperty(key, value);\n};\n\nconst getValueFromStyle = (style: CSSStyleDeclaration, key: string): number => {\n const value = getPropertyValue(style, key);\n if (!value) {\n return 0;\n } else {\n return parseInt(value, 10);\n }\n};\n\n/**\n * @internal\n */\nexport const getStyle = (e: Element) => getComputedStyle(e);\n\n/**\n * @internal\n */\nexport const hasPercentageUnit = (\n widthOrHeight: React.CSSProperties[\"width\"] | React.CSSProperties[\"height\"]\n): boolean => {\n return typeof widthOrHeight === \"string\" && widthOrHeight.endsWith(\"%\");\n};\n\n/**\n * @internal\n */\nexport const getVerticalPadding = (style: CSSStyleDeclaration): number => {\n return (\n getValueFromStyle(style, \"padding-top\") +\n getValueFromStyle(style, \"padding-bottom\") +\n getValueFromStyle(style, \"border-top\") +\n getValueFromStyle(style, \"border-bottom\")\n );\n};\n\n/**\n * @internal\n */\nexport const getHorizontalPadding = (style: CSSStyleDeclaration): number => {\n return (\n getValueFromStyle(style, \"padding-left\") +\n getValueFromStyle(style, \"padding-right\") +\n getValueFromStyle(style, \"border-left\") +\n getValueFromStyle(style, \"border-right\")\n );\n};\n\n/**\n * @internal\n */\nexport const getPointedElement = (\n textarea: HTMLElement,\n backdrop: HTMLElement,\n e: MouseEvent\n): HTMLElement | null => {\n const POINTER_EVENTS = \"pointer-events\";\n\n const textareaStyle = textarea.style;\n const backdropStyle = backdrop.style;\n const prev = getPropertyValue(textareaStyle, POINTER_EVENTS);\n const backPrev = getPropertyValue(backdropStyle, POINTER_EVENTS);\n setProperty(textareaStyle, POINTER_EVENTS, \"none\");\n setProperty(backdropStyle, POINTER_EVENTS, \"auto\");\n\n const pointed = document.elementFromPoint(\n e.clientX,\n e.clientY\n ) as HTMLElement | null;\n\n setProperty(textareaStyle, POINTER_EVENTS, prev);\n setProperty(backdropStyle, POINTER_EVENTS, backPrev);\n\n if (isInsideBackdrop(pointed, backdrop)) {\n return pointed;\n } else {\n return null;\n }\n};\n\nconst isInsideBackdrop = (\n pointed: HTMLElement | null,\n backdrop: HTMLElement\n): boolean => !!pointed && backdrop !== pointed && backdrop.contains(pointed);\n\nconst dispatchMouseEvent = (\n target: HTMLElement,\n type: string,\n init: MouseEventInit\n) => {\n target.dispatchEvent(new MouseEvent(type, init));\n};\n\n/**\n * @internal\n */\nexport const dispatchClonedMouseEvent = (\n pointed: HTMLElement,\n e: MouseEvent\n) => {\n dispatchMouseEvent(pointed, e.type, e);\n};\n\n/**\n * @internal\n */\nexport const dispatchMouseMoveEvent = (\n pointed: HTMLElement | null,\n prevPointed: HTMLElement | null,\n e: MouseEvent\n) => {\n if (pointed) {\n dispatchClonedMouseEvent(pointed, e);\n }\n\n if (prevPointed !== pointed) {\n dispatchMouseOutEvent(prevPointed, e);\n if (pointed) {\n dispatchMouseEvent(pointed, \"mouseover\", e);\n }\n }\n};\n\n/**\n * @internal\n */\nexport const dispatchMouseOutEvent = (\n prevPointed: HTMLElement | null,\n e: MouseEvent\n) => {\n if (prevPointed) {\n dispatchMouseEvent(prevPointed, \"mouseout\", e);\n }\n};\n\n/**\n * @internal\n */\nexport const stopPropagation = (event: React.MouseEvent) => {\n event.stopPropagation();\n};\n\n/**\n * @internal\n */\nexport const syncBackdropStyle = (\n textarea: HTMLElement,\n backdrop: HTMLElement,\n caretColorRef: React.MutableRefObject<string>,\n style?: React.CSSProperties\n) => {\n const computedTextAreaStyle = getStyle(textarea);\n const textareaStyle = textarea.style;\n const backdropStyle = backdrop.style;\n if (!caretColorRef[refKey]) {\n caretColorRef[refKey] = getPropertyValue(computedTextAreaStyle, \"color\");\n }\n\n TEXT_STYLE_KEYS.forEach((k) => {\n backdropStyle[k as any] = computedTextAreaStyle[k as any]!;\n });\n textareaStyle.color = backdropStyle.borderColor = \"transparent\";\n textareaStyle.caretColor = style?.caretColor || caretColorRef[refKey];\n};\n\n/**\n * @internal\n */\nexport const listenEvent = <\n E extends HTMLElement | Document,\n K extends keyof GlobalEventHandlersEventMap,\n>(\n el: E,\n key: K,\n cb: (this: E, ev: GlobalEventHandlersEventMap[K]) => void\n) => {\n el.addEventListener(key, cb as EventListenerOrEventListenerObject);\n return () => {\n el.removeEventListener(key, cb as EventListenerOrEventListenerObject);\n };\n};\n","import { min, refKey } from \"./utils.js\";\n\n/**\n * @internal\n */\nexport type SelectionRange = [number | null, number | null];\n\n/**\n * @internal\n */\nexport const initSelectionStore = (\n ref: React.RefObject<HTMLTextAreaElement | HTMLInputElement | null>,\n onSelectionUpdate: (range: SelectionRange) => void\n) => {\n let prevSelection: [number | null, number | null] = [null, null];\n let compositionEvent: CompositionEvent | void;\n const getSelection = (): [number | null, number | null] => {\n const selectionStart = handle._getSelectionStart();\n const selectionEnd = handle._getSelectionEnd();\n if (\n prevSelection[0] === selectionStart &&\n prevSelection[1] === selectionEnd\n ) {\n return prevSelection;\n }\n prevSelection = [selectionStart, selectionEnd];\n return prevSelection;\n };\n const handle = {\n _updateSeletion() {\n setTimeout(() => {\n onSelectionUpdate(getSelection());\n });\n },\n _setComposition(event: CompositionEvent | void) {\n compositionEvent = event;\n },\n _getSelectionStart(): number {\n const el = ref[refKey];\n if (!el) return 0;\n const pos = el.selectionStart!;\n if (!compositionEvent) return pos;\n // compensate selection range during compositioning\n return min(pos, el.selectionEnd! - compositionEvent.data.length);\n },\n _getSelectionEnd(): number {\n const el = ref[refKey];\n if (!el) return 0;\n const pos = el.selectionEnd!;\n // compensate selection range during compositioning\n if (!compositionEvent) return pos;\n return min(pos, el.selectionStart! + compositionEvent.data.length);\n },\n };\n return handle;\n};\n\n/**\n * @internal\n */\nexport type SelectionStore = ReturnType<typeof initSelectionStore>;\n","import { useEffect, useLayoutEffect } from \"react\";\n\n/**\n * https://gist.github.com/gaearon/e7d97cdf38a2907924ea12e4ebdf3c85\n * @internal\n */\nexport const useIsomorphicLayoutEffect =\n typeof window !== \"undefined\" ? useLayoutEffect : useEffect;\n","import { useRef } from \"react\";\nimport { refKey } from \"./utils.js\";\n\n/**\n * @internal\n */\nexport const useStatic = <T>(init: () => T): T => {\n const ref = useRef<T>(undefined);\n return ref[refKey] || (ref[refKey] = init());\n};\n","import {\n dispatchClonedMouseEvent,\n dispatchMouseMoveEvent,\n dispatchMouseOutEvent,\n getHorizontalPadding,\n getPointedElement,\n getStyle,\n getVerticalPadding,\n listenEvent,\n} from \"./dom.js\";\nimport { type SelectionStore } from \"./selection.js\";\n\n/**\n * @internal\n */\nexport type TextareaRect = [\n width: number,\n height: number,\n hPadding: number,\n vPadding: number,\n];\n\n/**\n * @internal\n */\nexport const createTextareaObserver = (\n textarea: HTMLTextAreaElement | HTMLInputElement,\n backdrop: HTMLDivElement,\n selectionStore: SelectionStore,\n onResize: (size: TextareaRect) => void,\n onFocus: (value: boolean) => void\n) => {\n let prevPointed: HTMLElement | null = null;\n\n const observer = new ResizeObserver(([entry]) => {\n const {\n contentRect: { width, height },\n borderBoxSize,\n } = entry!;\n if (borderBoxSize && borderBoxSize[0]) {\n onResize([\n width,\n height,\n borderBoxSize[0].inlineSize - width,\n borderBoxSize[0].blockSize - height,\n ]);\n return;\n }\n const style = getStyle(textarea);\n onResize([\n width,\n height,\n getHorizontalPadding(style),\n getVerticalPadding(style),\n ]);\n });\n\n const cleanUpOnFocus = listenEvent(textarea, \"focus\", () => {\n onFocus(true);\n });\n const cleanUpOnBlur = listenEvent(textarea, \"blur\", () => {\n onFocus(false);\n });\n const cleanUpOnScroll = listenEvent(textarea, \"scroll\", () => {\n const { scrollTop, scrollLeft } = textarea;\n backdrop.style.transform = `translate(${-scrollLeft}px, ${-scrollTop}px)`;\n });\n const cleanUpOnMouseDown = listenEvent(textarea, \"mousedown\", (e) => {\n selectionStore._updateSeletion();\n const cleanup = listenEvent(document, \"mouseup\", () => {\n selectionStore._updateSeletion();\n cleanup();\n });\n const pointed = getPointedElement(textarea, backdrop, e);\n if (pointed) {\n dispatchClonedMouseEvent(pointed, e);\n }\n });\n const cleanUpOnMouseUp = listenEvent(textarea, \"mouseup\", (e) => {\n const pointed = getPointedElement(textarea, backdrop, e);\n if (pointed) {\n dispatchClonedMouseEvent(pointed, e);\n }\n });\n const cleanUpOnMouseMove = listenEvent(textarea, \"mousemove\", (e) => {\n const p = getPointedElement(textarea, backdrop, e);\n dispatchMouseMoveEvent(p, prevPointed, e);\n prevPointed = p;\n });\n const cleanUpOnMouseLeave = listenEvent(textarea, \"mouseleave\", (e) => {\n dispatchMouseOutEvent(prevPointed, e);\n prevPointed = null;\n });\n const cleanUpOnClick = listenEvent(textarea, \"click\", (e) => {\n const pointed = getPointedElement(textarea, backdrop, e);\n if (pointed) {\n dispatchClonedMouseEvent(pointed, e);\n }\n });\n const cleanUpOnInput = listenEvent(textarea, \"input\", () => {\n selectionStore._updateSeletion();\n });\n const cleanUpOnCompositionStart = listenEvent(\n textarea,\n \"compositionstart\",\n (e) => {\n selectionStore._setComposition(e);\n }\n );\n const cleanUpOnCompositionUpdate = listenEvent(\n textarea,\n \"compositionupdate\",\n (e) => {\n selectionStore._setComposition(e);\n }\n );\n const cleanUpOnCompositionEnd = listenEvent(\n textarea,\n \"compositionend\",\n () => {\n selectionStore._setComposition();\n }\n );\n observer.observe(textarea);\n return () => {\n cleanUpOnFocus();\n cleanUpOnBlur();\n cleanUpOnScroll();\n cleanUpOnMouseDown();\n cleanUpOnMouseUp();\n cleanUpOnMouseMove();\n cleanUpOnMouseLeave();\n cleanUpOnClick();\n cleanUpOnInput();\n cleanUpOnCompositionStart();\n cleanUpOnCompositionUpdate();\n cleanUpOnCompositionEnd();\n observer.disconnect();\n };\n};\n","import {\n useEffect,\n useRef,\n useState,\n useMemo,\n useCallback,\n forwardRef,\n useImperativeHandle,\n memo,\n type RefObject,\n} from \"react\";\nimport type React from \"react\";\nimport rangeAtIndex from \"./vendor/range-at-index.js\";\nimport {\n hasPercentageUnit,\n stopPropagation,\n syncBackdropStyle,\n} from \"./dom.js\";\nimport { type SelectionRange, initSelectionStore } from \"./selection.js\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\nimport type { CaretPosition, Renderer } from \"./types.js\";\nimport { CARET_DETECTOR, refKey } from \"./utils.js\";\nimport { useStatic } from \"./useStatic.js\";\nimport { type TextareaRect, createTextareaObserver } from \"./observer.js\";\n\ntype BackdropHandle = (value: string) => void;\n\nconst Backdrop /*#__PURE__*/ = memo(\n ({\n _ref: backdropRef,\n _handle: handle,\n _render: render,\n _width: width,\n }: {\n _ref: RefObject<HTMLDivElement | null>;\n _handle: RefObject<BackdropHandle | null>;\n _render: Renderer | undefined;\n _width: number;\n }) => {\n const [value, setValue] = useState(\"\");\n\n useImperativeHandle(handle, () => setValue, []);\n\n return (\n <div\n ref={backdropRef}\n aria-hidden\n style={useMemo(\n (): React.CSSProperties => ({\n width,\n transform: \"translate(0px, 0px)\",\n pointerEvents: \"none\",\n userSelect: \"none\",\n msUserSelect: \"none\",\n WebkitUserSelect: \"none\",\n // https://github.com/inokawa/rich-textarea/issues/56\n boxSizing: \"content-box\",\n // https://stackoverflow.com/questions/2545542/font-size-rendering-inconsistencies-on-an-iphone\n textSizeAdjust: \"100%\",\n WebkitTextSizeAdjust: \"100%\",\n MozTextSizeAdjust: \"100%\",\n }),\n [width],\n )}\n // Stop propagation of events dispatched on backdrop\n onClick={stopPropagation}\n onMouseDown={stopPropagation}\n onMouseUp={stopPropagation}\n onMouseOver={stopPropagation}\n onMouseOut={stopPropagation}\n onMouseMove={stopPropagation}\n >\n {useMemo(() => (render ? render(value) : value), [value, render])}\n {CARET_DETECTOR}\n </div>\n );\n },\n);\n\n/**\n * Methods of {@link RichTextarea}.\n *\n * All the others not mentioned are proxied to ref of [textarea](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement).\n *\n * | Name | Type | Description |\n * | ----------------- | -------- | ------------------------------------------------------------------------- |\n * | selectionStart | `number` | Same as original but has handling of composition event |\n * | selectionEnd | `number` | Same as original but has handling of composition event |\n * | setRangeText | | Same as original but has fallback to `document.execCommand(\"insertText\")` |\n */\nexport interface RichTextareaHandle extends HTMLTextAreaElement {}\n\n/**\n * Props of {@link RichTextarea}.\n *\n * For other props not mentioned below will be passed to [textarea](https://developer.mozilla.org/en-US/docs/Web/API/HTMLTextAreaElement).\n */\nexport interface RichTextareaProps\n extends Omit<React.ComponentProps<\"textarea\">, \"children\"> {\n /**\n *\n * Render function to create customized view from value.\n *\n * This function should return ReactNodes which texts are positioned the same as textarea (see examples for detailed usage).\n *\n * On the rendered nodes, currently limited event handlers will work.\n * - `onClick`\n * - `onMouseOver`\n * - `onMouseOut`\n * - `onMouseMove`\n * - `onMouseDown`\n * - `onMouseUp`\n * @defaultValue undefined\n */\n children?: Renderer;\n /**\n * If true, textarea height is automatically resized and height of style prop does not work.\n * Set `maxHeight` to style prop if you need limit.\n * Set `rows` prop to 1 if you need single-line textarea.\n * @defaultValue undefined\n */\n autoHeight?: boolean;\n /**\n * Called when selection in textarea changes. It gives position of caret at the time, which is useful to position menu.\n * @defaultValue undefined\n */\n onSelectionChange?: (pos: CaretPosition) => void;\n}\n\n/**\n * Textarea component with some extra props. See {@link RichTextareaProps} and {@link RichTextareaHandle}.\n */\nexport const RichTextarea = /*#__PURE__*/ forwardRef<\n RichTextareaHandle,\n RichTextareaProps\n>(\n (\n {\n children: render,\n autoHeight,\n style,\n onChange,\n onKeyDown,\n onSelectionChange,\n ...props\n },\n ref,\n ): React.ReactElement => {\n const textAreaRef = useRef<HTMLTextAreaElement>(null);\n const backdropRef = useRef<HTMLDivElement>(null);\n const backdropHandle = useRef<BackdropHandle>(null);\n const [[width, height, hPadding, vPadding], setRect] =\n useState<TextareaRect>([0, 0, 0, 0]);\n const [focused, setFocused] = useState<boolean>(false);\n\n const caretColorRef = useRef(\"\");\n\n const [[selectionStart, selectionEnd], setSelection] =\n useState<SelectionRange>([null, null]);\n const selectionStore = useStatic(() =>\n initSelectionStore(textAreaRef, setSelection),\n );\n\n const totalWidth = width + hPadding;\n const totalHeight = height + vPadding;\n\n const isSizeCalculated = !!(totalWidth + totalHeight);\n\n useImperativeHandle(ref, () => {\n const el = textAreaRef[refKey]!;\n const overrides = {\n get selectionStart() {\n return selectionStore._getSelectionStart();\n },\n get selectionEnd() {\n return selectionStore._getSelectionEnd();\n },\n setRangeText(\n text: string,\n start: number,\n end: number,\n preserve?: SelectionMode,\n ) {\n if (el.setRangeText) {\n el.setRangeText(text, start, end, preserve);\n } else {\n el.focus();\n el.selectionStart = start;\n el.selectionEnd = end;\n document.execCommand(\"insertText\", false, text);\n }\n // Invoke onChange to lift state up\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n },\n };\n\n return new Proxy(el, {\n get(target, prop: keyof HTMLTextAreaElement) {\n if ((overrides as any)[prop]) {\n return (overrides as any)[prop];\n }\n const value = target[prop];\n return typeof value === \"function\" ? value.bind(target) : value;\n },\n set(target, prop, value) {\n (target as any)[prop] = value;\n return true;\n },\n }) as HTMLTextAreaElement;\n }, []);\n\n useIsomorphicLayoutEffect(() => {\n const textarea = textAreaRef[refKey];\n const backdrop = backdropRef[refKey];\n if (!textarea || !backdrop) return;\n\n return createTextareaObserver(\n textarea,\n backdrop,\n selectionStore,\n setRect,\n setFocused,\n );\n }, []);\n\n useIsomorphicLayoutEffect(() => {\n const textarea = textAreaRef[refKey];\n if (!textarea) return;\n // Sync value on init and change\n backdropHandle[refKey]!(textarea.value);\n }, [props.value]);\n\n useIsomorphicLayoutEffect(() => {\n // Sync backdrop style\n const textarea = textAreaRef[refKey];\n const backdrop = backdropRef[refKey];\n if (!backdrop || !textarea) return;\n syncBackdropStyle(textarea, backdrop, caretColorRef, style);\n }, [style]);\n\n useEffect(() => {\n if (selectionStart == null || selectionEnd == null || !onSelectionChange)\n return;\n if (!focused) {\n onSelectionChange({\n focused: false,\n selectionStart: selectionStart,\n selectionEnd: selectionEnd,\n });\n } else {\n const range = rangeAtIndex(\n backdropRef[refKey]!,\n selectionStart,\n selectionStart + 1,\n ) as Range;\n const rect = range.getBoundingClientRect();\n onSelectionChange({\n focused: true,\n top: rect.top,\n left: rect.left,\n height: rect.height,\n selectionStart: selectionStart,\n selectionEnd: selectionEnd,\n });\n }\n }, [focused, selectionStart, selectionEnd]);\n\n useEffect(() => {\n const textarea = textAreaRef[refKey];\n if (!autoHeight || !textarea) return;\n textarea.style.height = \"auto\";\n textarea.style.height = `${textarea.scrollHeight}px`;\n });\n\n return (\n <div\n style={useMemo((): React.CSSProperties => {\n let w: React.CSSProperties[\"width\"] = totalWidth;\n let h: React.CSSProperties[\"height\"] = totalHeight;\n // Prefer prop style to avoid miscalculation https://github.com/inokawa/rich-textarea/issues/39\n if (style) {\n if (hasPercentageUnit(style.width)) {\n w = style.width;\n }\n if (hasPercentageUnit(style.height)) {\n h = style.height;\n }\n }\n return {\n display: \"inline-block\",\n position: \"relative\",\n width: w,\n height: h,\n };\n }, [totalWidth, totalHeight, style])}\n >\n <div\n style={useMemo((): React.CSSProperties => {\n const s: React.CSSProperties = {\n position: \"absolute\",\n overflow: \"hidden\",\n top: 0,\n left: 0,\n width: totalWidth,\n height: totalHeight,\n };\n if (!style) return s;\n ([\"background\", \"backgroundColor\"] as const).forEach((k) => {\n if (style[k]) s[k] = style[k] as any;\n });\n return s;\n }, [totalWidth, totalHeight, style])}\n >\n <Backdrop\n _ref={backdropRef}\n _handle={backdropHandle}\n _render={render}\n _width={width}\n />\n </div>\n <textarea\n {...props}\n ref={textAreaRef}\n style={useMemo(\n () => ({\n ...style,\n background: \"transparent\",\n margin: 0,\n // Set `position: absolute` to fix bug that sometimes texts disappear in Chrome for unknown reason\n position: \"absolute\",\n // And remove it until element size is calculated to avoid layout shift on mount\n // https://github.com/inokawa/rich-textarea/issues/21\n ...(!isSizeCalculated && {\n position: undefined,\n verticalAlign: \"top\",\n }),\n }),\n [style, isSizeCalculated],\n )}\n onChange={useCallback(\n (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n backdropHandle[refKey]?.(e.target.value);\n onChange?.(e);\n },\n [onChange],\n )}\n onKeyDown={useCallback(\n (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n // Ignore keydown events during IME composition.\n // Safari sometimes fires keydown event after compositionend so also ignore it.\n // https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event#ignoring_keydown_during_ime_composition\n if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) {\n return;\n }\n\n onKeyDown?.(e);\n selectionStore._updateSeletion();\n },\n [onKeyDown],\n )}\n />\n </div>\n );\n },\n);\n","import {\n useEffect,\n useRef,\n useState,\n useMemo,\n useCallback,\n forwardRef,\n useImperativeHandle,\n memo,\n type RefObject,\n} from \"react\";\nimport type React from \"react\";\nimport rangeAtIndex from \"./vendor/range-at-index.js\";\nimport {\n hasPercentageUnit,\n isSafari,\n stopPropagation,\n syncBackdropStyle,\n} from \"./dom.js\";\nimport { type SelectionRange, initSelectionStore } from \"./selection.js\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect.js\";\nimport type { CaretPosition, Renderer } from \"./types.js\";\nimport { CARET_DETECTOR, refKey } from \"./utils.js\";\nimport { useStatic } from \"./useStatic.js\";\nimport { type TextareaRect, createTextareaObserver } from \"./observer.js\";\n\ntype BackdropHandle = (value: string) => void;\n\nconst Backdrop = /*#__PURE__*/ memo(\n ({\n _ref: backdropRef,\n _handle: handle,\n _render: render,\n _height: height,\n }: {\n _ref: RefObject<HTMLDivElement | null>;\n _handle: RefObject<BackdropHandle | null>;\n _render: Renderer | undefined;\n _height: number;\n }) => {\n const [value, setValue] = useState(\"\");\n\n useImperativeHandle(handle, () => setValue, []);\n\n return (\n <div\n ref={backdropRef}\n aria-hidden\n // Stop propagation of events dispatched on backdrop\n onClick={stopPropagation}\n onMouseDown={stopPropagation}\n onMouseUp={stopPropagation}\n onMouseOver={stopPropagation}\n onMouseOut={stopPropagation}\n onMouseMove={stopPropagation}\n >\n <div style={{ height }}>\n <div\n style={useMemo(\n (): React.CSSProperties => ({\n transform: \"translate(0px, 0px)\",\n pointerEvents: \"none\",\n userSelect: \"none\",\n msUserSelect: \"none\",\n WebkitUserSelect: \"none\",\n // https://github.com/inokawa/rich-textarea/issues/56\n boxSizing: \"content-box\",\n // https://stackoverflow.com/questions/2545542/font-size-rendering-inconsistencies-on-an-iphone\n textSizeAdjust: \"100%\",\n WebkitTextSizeAdjust: \"100%\",\n MozTextSizeAdjust: \"100%\",\n whiteSpace: \"pre\",\n }),\n [],\n )}\n >\n {useMemo(() => (render ? render(value) : value), [value, render])}\n {CARET_DETECTOR}\n </div>\n </div>\n </div>\n );\n },\n);\n\n/**\n * Methods of {@link RichInput}.\n *\n * All the others not mentioned are proxied to ref of [input](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement).\n *\n * | Name | Type | Description |\n * | ----------------- | -------- | ---------------------------------------------------------------------- |\n * | selectionStart | `number` | Same as original but has handling of composition event |\n * | selectionEnd | `number` | Same as original but has handling of composition event |\n * | setRangeText | | Same as original but has fallback to `document.execCommand(\"insertText\")` |\n */\nexport interface RichInputHandle extends HTMLInputElement {}\n\n/**\n * Props of {@link RichInput}.\n *\n * For other props not mentioned below will be passed to [input](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement).\n * `defaultValue` is omitted for simplicity of logic.\n */\nexport interface RichInputProps\n extends Omit<React.ComponentProps<\"input\">, \"children\"> {\n /**\n * Render function to create customized view from value.\n *\n * This function should return ReactNodes which texts are positioned the same as textarea (see examples for detailed usage).\n *\n * On the rendered nodes, currently limited event handlers will work.\n * - `onClick`\n * - `onMouseOver`\n * - `onMouseOut`\n * - `onMouseMove`\n * - `onMouseDown`\n * - `onMouseUp`\n * @defaultValue undefined\n */\n children?: Renderer;\n /**\n * If true, textarea height is automatically resized and height of style prop does not work. Set `maxHeight` to style prop if you need limit.\n * @defaultValue undefined\n */\n autoHeight?: boolean;\n /**\n * Called when selection in textarea changes. It gives position of caret at the time, which is useful to position menu.\n * @defaultValue undefined\n */\n onSelectionChange?: (pos: CaretPosition) => void;\n}\n\n/**\n * Input component with some extra props. See {@link RichInputProps} and {@link RichInputHandle}.\n *\n * **NOTE: This component is experimental one so it may have some bugs.**\n */\nexport const RichInput = /*#__PURE__*/ forwardRef<\n RichInputHandle,\n RichInputProps\n>(\n (\n {\n children: render,\n autoHeight,\n style,\n onChange,\n onKeyDown,\n onSelectionChange,\n ...props\n },\n ref,\n ): React.ReactElement => {\n const textAreaRef = useRef<HTMLInputElement>(null);\n const backdropRef = useRef<HTMLDivElement>(null);\n const backdropHandle = useRef<BackdropHandle>(null);\n const [[width, height, hPadding, vPadding], setRect] =\n useState<TextareaRect>([0, 0, 0, 0]);\n const [focused, setFocused] = useState<boolean>(false);\n\n const caretColorRef = useRef(\"\");\n\n const [[selectionStart, selectionEnd], setSelection] =\n useState<SelectionRange>([null, null]);\n const selectionStore = useStatic(() => {\n const safari = isSafari();\n\n return initSelectionStore(textAreaRef, (range) => {\n setSelection(range);\n\n if (safari) {\n // FIXME: Safari does not fire scroll event on input so substitute with pseudo selection change event\n if (!textAreaRef[refKey] || !backdropRef[refKey]) return;\n const { scrollTop, scrollLeft } = textAreaRef[refKey];\n backdropRef[refKey].style.transform =\n `translate(${-scrollLeft}px, ${-scrollTop}px)`;\n }\n });\n });\n\n const totalWidth = width + hPadding;\n const totalHeight = height + vPadding;\n\n const isSizeCalculated = !!(totalWidth + totalHeight);\n\n useImperativeHandle(ref, () => {\n const el = textAreaRef[refKey]!;\n const overrides = {\n get selectionStart() {\n return selectionStore._getSelectionStart();\n },\n get selectionEnd() {\n return selectionStore._getSelectionEnd();\n },\n setRangeText(\n text: string,\n start: number,\n end: number,\n preserve?: SelectionMode,\n ) {\n if (el.setRangeText) {\n el.setRangeText(text, start, end, preserve);\n } else {\n el.focus();\n el.selectionStart = start;\n el.selectionEnd = end;\n document.execCommand(\"insertText\", false, text);\n }\n // Invoke onChange to lift state up\n el.dispatchEvent(new Event(\"input\", { bubbles: true }));\n },\n };\n\n return new Proxy(el, {\n get(target, prop: keyof HTMLInputElement) {\n if ((overrides as any)[prop]) {\n return (overrides as any)[prop];\n }\n const value = target[prop];\n return typeof value === \"function\" ? value.bind(target) : value;\n },\n set(target, prop, value) {\n (target as any)[prop] = value;\n return true;\n },\n }) as HTMLInputElement;\n }, []);\n\n useIsomorphicLayoutEffect(() => {\n const textarea = textAreaRef[refKey];\n const backdrop = backdropRef[refKey];\n if (!textarea || !backdrop) return;\n\n return createTextareaObserver(\n textarea,\n backdrop,\n selectionStore,\n setRect,\n setFocused,\n );\n }, []);\n\n useIsomorphicLayoutEffect(() => {\n const textarea = textAreaRef[refKey];\n if (!textarea) return;\n // Sync value on init and change\n backdropHandle[refKey]!(textarea.value);\n }, [props.value]);\n\n useIsomorphicLayoutEffect(() => {\n // Sync backdrop style\n const textarea = textAreaRef[refKey];\n const backdrop = backdropRef[refKey];\n if (!backdrop || !textarea) return;\n syncBackdropStyle(textarea, backdrop, caretColorRef, style);\n }, [style]);\n\n useEffect(() => {\n if (selectionStart == null || selectionEnd == null || !onSelectionChange)\n return;\n if (!focused) {\n onSelectionChange({\n focused: false,\n selectionStart: selectionStart,\n selectionEnd: selectionEnd,\n });\n } else {\n const range = rangeAtIndex(\n backdropRef[refKey]!,\n selectionStart,\n selectionStart + 1,\n ) as Range;\n const rect = range.getBoundingClientRect();\n onSelectionChange({\n focused: true,\n top: rect.top,\n left: rect.left,\n height: rect.height,\n selectionStart: selectionStart,\n selectionEnd: selectionEnd,\n });\n }\n }, [focused, selectionStart, selectionEnd]);\n\n useEffect(() => {\n const textarea = textAreaRef[refKey];\n if (!autoHeight || !textarea) return;\n textarea.style.height = \"auto\";\n textarea.style.height = `${textarea.scrollHeight}px`;\n });\n\n return (\n <div\n style={useMemo((): React.CSSProperties => {\n let w: React.CSSProperties[\"width\"] = totalWidth;\n let h: React.CSSProperties[\"height\"] = totalHeight;\n // Prefer prop style to avoid miscalculation https://github.com/inokawa/rich-textarea/issues/39\n if (style) {\n if (hasPercentageUnit(style.width)) {\n w = style.width;\n }\n if (hasPercentageUnit(style.height)) {\n h = style.height;\n }\n }\n return {\n display: \"inline-block\",\n position: \"relative\",\n width: w,\n height: h,\n };\n }, [totalWidth, totalHeight, style])}\n >\n <div\n style={useMemo((): React.CSSProperties => {\n const s: React.CSSProperties = {\n position: \"absolute\",\n overflow: \"hidden\",\n top: 0,\n left: 0,\n width: totalWidth,\n height: totalHeight,\n };\n if (!style) return s;\n ([\"background\", \"backgroundColor\"] as const).forEach((k) => {\n if (style[k]) s[k] = style[k] as any;\n });\n return s;\n }, [totalWidth, totalHeight, style])}\n >\n <Backdrop\n _ref={backdropRef}\n _handle={backdropHandle}\n _render={render}\n _height={height}\n />\n </div>\n <input\n {...props}\n ref={textAreaRef}\n style={useMemo(\n () => ({\n ...style,\n background: \"transparent\",\n margin: 0,\n // Set `position: absolute` to fix bug that sometimes texts disappear in Chrome for unknown reason\n position: \"absolute\",\n // And remove it until element size is calculated to avoid layout shift on mount\n // https://github.com/inokawa/rich-textarea/issues/21\n ...(!isSizeCalculated && {\n position: undefined,\n verticalAlign: \"top\",\n }),\n }),\n [style, isSizeCalculated],\n )}\n onChange={useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n backdropHandle[refKey]?.(e.target.value);\n onChange?.(e);\n },\n [onChange],\n )}\n onKeyDown={useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n // Ignore keydown events during IME composition.\n // Safari sometimes fires keydown event after compositionend so also ignore it.\n // https://developer.mozilla.org/en-US/docs/Web/API/Document/keydown_event#ignoring_keydown_during_ime_composition\n if (e.nativeEvent.isComposing || e.nativeEvent.keyCode === 229) {\n return;\n }\n\n onKeyDown?.(e);\n selectionStore._updateSeletion();\n },\n [onKeyDown],\n )}\n />\n </div>\n );\n },\n);\n","/**\n * @internal\n */\nexport const execReg = (\n reg: RegExp,\n text: string,\n shouldRender?: (matchedText: string, matchResult: RegExpExecArray) => boolean\n): RegExpExecArray[] => {\n const results: RegExpExecArray[] = [];\n let match: RegExpExecArray | null = null;\n while ((match = reg.exec(text))) {\n if (shouldRender && !shouldRender(text, match)) {\n continue;\n }\n results.push(match);\n }\n return results;\n};\n","import { execReg } from \"./utils.js\";\nimport type { Renderer } from \"../../types.js\";\nimport { useIsomorphicLayoutEffect } from \"../../useIsomorphicLayoutEffect.js\";\nimport { memo, useRef } from \"react\";\n\nexport type StyleOrRender =\n | React.CSSProperties\n | ((props: {\n children: React.ReactNode;\n value: string;\n key?: string | undefined;\n }) => React.ReactNode);\n\n/**\n * An utility to create renderer function with regex.\n *\n * The priority is descending order.\n */\nexport const createRegexRenderer = (\n matchers: (\n | [regex: RegExp, style: StyleOrRender]\n | [\n regex: RegExp,\n style: StyleOrRender,\n shouldRender?: (\n matchedText: string,\n matchResult: RegExpExecArray,\n ) => boolean,\n ]\n )[],\n): Renderer => {\n const allStyles = matchers.map(([, style]) => style);\n\n return (value) => {\n const [indexSet, startToStyleMap, endToStyleMap] = matchers.reduce(\n (acc, [regex, style, shouldRender]) => {\n execReg(regex, value, shouldRender).forEach((m) => {\n const start = m.index;\n const end = m.index + m[0]!.length;\n\n acc[0].add(start).add(end);\n let startStyles = acc[1].get(start);\n let endStyles = acc[2].get(end);\n if (!startStyles) {\n acc[1].set(start, (startStyles = []));\n }\n if (!endStyles) {\n acc[2].set(end, (endStyles = []));\n }\n startStyles.push(style);\n endStyles.push(style);\n });\n return acc;\n },\n [\n new Set<number>(),\n new Map<number, StyleOrRender[]>(),\n new Map<number, StyleOrRender[]>(),\n ] as const,\n );\n const indexes = Array.from(indexSet);\n indexes.sort((a, b) => {\n return a - b;\n });\n\n let prevEnd = 0;\n const activeStyles: StyleOrRender[] = [];\n const res: React.ReactNode[] = [];\n for (let i = 0; i < indexes.length; i++) {\n const start = indexes[i]!;\n const end = indexes[i + 1] ?? value.length;\n if (start === end) continue;\n const headValue = value.slice(prevEnd, start);\n if (headValue) {\n res.push(headValue);\n }\n const startStyles = startToStyleMap.get(start);\n const endStyles = endToStyleMap.get(end);\n if (startStyles) {\n startStyles.forEach((s) => {\n activeStyles.push(s);\n });\n activeStyles.sort((a, b) => {\n return allStyles.indexOf(b) - allStyles.indexOf(a);\n });\n }\n\n const v = value.slice(start, end);\n res.push(\n activeStyles.reduceRight((acc, styleOrRender, j) => {\n const key = j === 0 ? String(start) : undefined;\n if (typeof styleOrRender === \"function\") {\n return styleOrRender({ children: acc, value: v, key });\n } else {\n return (\n <span key={key} style={styleOrRender}>\n {acc}\n </span>\n );\n }\n }, v as React.ReactNode),\n );\n\n if (endStyles) {\n endStyles.forEach((s) => {\n activeStyles.splice(activeStyles.indexOf(s), 1);\n });\n }\n\n prevEnd = end;\n }\n\n const tailValue = value.slice(prevEnd);\n if (tailValue) {\n res.push(tailValue);\n }\n\n return res;\n };\n};\n\nconst createCSSHighlightID = (i: number): string => `rich-textarea-${i}`;\n\nconst CSSHighlighter = /*#__PURE__*/ memo(\n ({\n _value: value,\n _regexes: regexes,\n }: {\n _value: string;\n _regexes: RegExp[];\n }) => {\n const ref = useRef<HTMLDivElement>(null);\n useIsomorphicLayoutEffect(() => {\n const el = ref.current!.firstChild!;\n\n const highlights = regexes.map((r) => {\n return new Highlight(\n ...execReg(r, value).map((m) => {\n const start = m.index;\n const end = m.index + m[0]!.length;\n\n const range = new Range();\n range.setStart(el, start);\n range.setEnd(el, end);\n return range;\n }),\n );\n });\n\n highlights.forEach((h, i) => {\n CSS.highlights.set(createCSSHighlightID(i), h);\n });\n\n return () => {\n highlights.forEach((h, i) => {\n CSS.highlights.delete(createCSSHighlightID(i));\n h.clear();\n });\n };\n }, [value]);\n return <div ref={ref}>{value}</div>;\n },\n);\n\n/**\n * {@link createRegexRenderer} but rendered with [CSS Custom Highlight API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API).\n * @experimental\n */\nexport const createCSSCustomHighlightRenderer = (\n matchers: [regex: RegExp, style: string][],\n): Renderer => {\n const styleStr = matchers.reduce((acc, [, style], i) => {\n return acc + `::highlight(${createCSSHighlightID(i)}){${style}}`;\n }, \"\");\n const regexes = matchers.map(([r]) => r);\n\n return (value) => {\n return (\n <>\n <CSSHighlighter _value={value} _regexes={regexes} />\n <style>{styleStr}</style>\n </>\n );\n };\n};\n"],"mappings":"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