UNPKG

rich-textarea

Version:

A small customizable textarea for React to colorize, highlight, decorate texts, offer autocomplete and much more.

1 lines 102 kB
{"version":3,"file":"index.mjs","sources":["../src/src/utils.tsx","../src/vendor/range-at-index.js","../src/src/dom.ts","../src/src/selection.ts","../src/src/useIsomorphicLayoutEffect.ts","../src/src/useStatic.ts","../src/src/observer.ts","../src/src/textarea.tsx","../src/src/input.tsx","../src/src/renderers/regex/utils.ts","../src/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\";\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\";\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\";\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\";\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\";\nimport { type SelectionStore } from \"./selection\";\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\";\nimport { hasPercentageUnit, stopPropagation, syncBackdropStyle } from \"./dom\";\nimport { type SelectionRange, initSelectionStore } from \"./selection\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\nimport type { CaretPosition, Renderer } from \"./types\";\nimport { CARET_DETECTOR, refKey } from \"./utils\";\nimport { useStatic } from \"./useStatic\";\nimport { type TextareaRect, createTextareaObserver } from \"./observer\";\n\ntype BackdropHandle = (value: string) => void;\n\nconst Backdrop = 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 = forwardRef<RichTextareaHandle, RichTextareaProps>(\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\";\nimport {\n hasPercentageUnit,\n isSafari,\n stopPropagation,\n syncBackdropStyle,\n} from \"./dom\";\nimport { type SelectionRange, initSelectionStore } from \"./selection\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\nimport type { CaretPosition, Renderer } from \"./types\";\nimport { CARET_DETECTOR, refKey } from \"./utils\";\nimport { useStatic } from \"./useStatic\";\nimport { type TextareaRect, createTextareaObserver } from \"./observer\";\n\ntype BackdropHandle = (value: string) => void;\n\nconst Backdrop = 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 = forwardRef<RichInputHandle, RichInputProps>(\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\";\nimport type { Renderer } from \"../../types\";\nimport { useIsomorphicLayoutEffect } from \"../../useIsomorphicLayoutEffect\";\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?: (matchedText: string, matchResult: RegExpExecArray) => 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 = 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"],"names":["min","max","Math","refKey","CARET_DETECTOR","_jsx","style","color","RangeAtIndex","el","index","offset","range","doc","ownerDocument","createRange","node","val","len","iterator","createNodeIterator","NodeFilter","SHOW_TEXT","start","end","nextNode","nodeValue","length","setStart","setEnd","TEXT_STYLE_KEYS","getPropertyValue","key","setProperty","value","getValueFromStyle","parseInt","getStyle","e","getComputedStyle","hasPercentageUnit","widthOrHeight","endsWith","getVerticalPadding","getHorizontalPadding","getPointedElement","textarea","backdrop","POINTER_EVENTS","textareaStyle","backdropStyle","prev","backPrev","pointed","document","elementFromPoint","clientX","clientY","isInsideBackdrop","contains","dispatchMouseEvent","target","type","init","dispatchEvent","MouseEvent","dispatchClonedMouseEvent","dispatchMouseOutEvent","prevPointed","stopPropagation","event","syncBackdropStyle","caretColorRef","computedTextAreaStyle","forEach","k","borderColor","caretColor","listenEvent","cb","addEventListener","removeEventListener","initSelectionStore","ref","onSelectionUpdate","compositionEvent","prevSelection","handle","_updateSeletion","setTimeout","getSelection","selectionStart","_getSelectionStart","selectionEnd","_getSelectionEnd","_setComposition","pos","data","useIsomorphicLayoutEffect","window","useLayoutEffect","useEffect","useStatic","useRef","undefined","createTextareaObserver","selectionStore","onResize","onFocus","observer","ResizeObserver","entry","contentRect","width","height","borderBoxSize","inlineSize","blockSize","cleanUpOnFocus","cleanUpOnBlur","cleanUpOnScroll","scrollTop","scrollLeft","transform","cleanUpOnMouseDown","cleanup","cleanUpOnMouseUp","cleanUpOnMouseMove","p","dispatchMouseMoveEvent","cleanUpOnMouseLeave","cleanUpOnClick","cleanUpOnInput","cleanUpOnCompositionStart","cleanUpOnCompositionUpdate","cleanUpOnCompositionEnd","observe","disconnect","Backdrop","memo","_ref","backdropRef","_handle","_render","render","_width","setValue","useState","useImperativeHandle","_jsxs","useMemo","pointerEvents","userSelect","msUserSelect","WebkitUserSelect","boxSizing","textSizeAdjust","WebkitTextSizeAdjust","MozTextSizeAdjust","onClick","onMouseDown","onMouseUp","onMouseOver","onMouseOut","onMouseMove","children","RichTextarea","forwardRef","autoHeight","onChange","onKeyDown","onSelectionChange","props","textAreaRef","backdropHandle","hPadding","vPadding","setRect","focused","setFocused","setSelection","totalWidth","totalHeight","isSizeCalculated","overrides","setRangeText","text","preserve","focus","execCommand","Event","bubbles","Proxy","get","prop","bind","set","rect","rangeAtIndex","getBoundingClientRect","top","left","scrollHeight","w","h","display","position","s","overflow","background","margin","verticalAlign","useCallback","_a","call","nativeEvent","isComposing","keyCode","_height","whiteSpace","RichInput","safari","isSafari","navigator","ua","userAgent","toLowerCase","indexOf","execReg","reg","shouldRender","results","match","exec","push","createRegexRenderer","matchers","allStyles","map","indexSet","startToStyleMap","endToStyleMap","reduce","acc","regex","m","add","startStyles","endStyles","Set","Map","indexes","Array","from","sort","a","b","prevEnd","activeStyles","res","i","headValue","slice","v","reduceRight","styleOrRender","j","String","splice","tailValue","createCSSHighlightID","CSSHighlighter","_value","_regexes","regexes","current","firstChild","highlights","r","Highlight","Range","CSS","delete","clear","createCSSCustomHighlightRenderer","styleStr","_Fragment"],"mappings":"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