UNPKG

@portabletext/editor

Version:

Portable Text Editor made in React

1,288 lines 62.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: !0 }); var reactCompilerRuntime = require("react-compiler-runtime"), React = require("react"), useEffectEvent = require("use-effect-event"), editorProvider = require("./_chunks-cjs/editor-provider.cjs"), jsxRuntime = require("react/jsx-runtime"), react = require("@xstate/react"), noop = require("lodash/noop.js"), slate = require("slate"), slateReact = require("slate-react"), selector_isOverlappingSelection = require("./_chunks-cjs/selector.is-overlapping-selection.cjs"), util_sliceBlocks = require("./_chunks-cjs/util.slice-blocks.cjs"), selector_getFocusInlineObject = require("./_chunks-cjs/selector.get-focus-inline-object.cjs"), slateDom = require("slate-dom"), util_isSelectionCollapsed = require("./_chunks-cjs/util.is-selection-collapsed.cjs"), parseBlocks = require("./_chunks-cjs/parse-blocks.cjs"), isEqual = require("lodash/isEqual.js"), selector_isSelectingEntireBlocks = require("./_chunks-cjs/selector.is-selecting-entire-blocks.cjs"), behavior_core = require("./_chunks-cjs/behavior.core.cjs"), uniq = require("lodash/uniq.js"), xstate = require("xstate"); function _interopDefaultCompat(e) { return e && typeof e == "object" && "default" in e ? e : { default: e }; } var noop__default = /* @__PURE__ */ _interopDefaultCompat(noop), isEqual__default = /* @__PURE__ */ _interopDefaultCompat(isEqual), uniq__default = /* @__PURE__ */ _interopDefaultCompat(uniq); function EditorEventListener(props) { const $ = reactCompilerRuntime.c(5), editor = editorProvider.useEditor(), on = useEffectEvent.useEffectEvent(props.on); let t0; $[0] !== editor || $[1] !== on ? (t0 = () => { const subscription = editor.on("*", on); return () => { subscription.unsubscribe(); }; }, $[0] = editor, $[1] = on, $[2] = t0) : t0 = $[2]; let t1; return $[3] !== editor ? (t1 = [editor], $[3] = editor, $[4] = t1) : t1 = $[4], React.useEffect(t0, t1), null; } function getCompoundClientRect(nodes) { if (nodes.length === 0) return new DOMRect(0, 0, 0, 0); const elements = nodes.filter((node) => node instanceof Element), firstRect = elements.at(0)?.getBoundingClientRect(); if (!firstRect) return new DOMRect(0, 0, 0, 0); let left = firstRect.left, top = firstRect.top, right = firstRect.right, bottom = firstRect.bottom; for (let i = 1; i < elements.length; i++) { const rect = elements[i].getBoundingClientRect(); left = Math.min(left, rect.left), top = Math.min(top, rect.top), right = Math.max(right, rect.right), bottom = Math.max(bottom, rect.bottom); } return new DOMRect(left, top, right - left, bottom - top); } function getDragSelection({ eventSelection, snapshot }) { let dragSelection = eventSelection; if (selector_getFocusInlineObject.getFocusInlineObject({ context: { ...snapshot.context, selection: eventSelection } })) return dragSelection; const draggingCollapsedSelection = selector_isOverlappingSelection.isSelectionCollapsed({ context: { ...snapshot.context, selection: eventSelection } }), draggedTextBlock = selector_isOverlappingSelection.getFocusTextBlock({ context: { ...snapshot.context, selection: eventSelection } }), draggedSpan = selector_isOverlappingSelection.getFocusSpan({ context: { ...snapshot.context, selection: eventSelection } }); draggingCollapsedSelection && draggedTextBlock && draggedSpan && (dragSelection = { anchor: util_sliceBlocks.getBlockStartPoint(draggedTextBlock), focus: util_sliceBlocks.getBlockEndPoint(draggedTextBlock) }); const selectedBlocks = selector_isOverlappingSelection.getSelectedBlocks(snapshot); if (snapshot.context.selection && selector_isOverlappingSelection.isSelectionExpanded(snapshot) && selectedBlocks.length > 1) { const selectionStartBlock = selector_isOverlappingSelection.getSelectionStartBlock(snapshot), selectionEndBlock = selector_isOverlappingSelection.getSelectionEndBlock(snapshot); if (!selectionStartBlock || !selectionEndBlock) return dragSelection; const selectionStartPoint = util_sliceBlocks.getBlockStartPoint(selectionStartBlock), selectionEndPoint = util_sliceBlocks.getBlockEndPoint(selectionEndBlock); selector_isOverlappingSelection.isOverlappingSelection(eventSelection)({ ...snapshot, context: { ...snapshot.context, selection: { anchor: selectionStartPoint, focus: selectionEndPoint } } }) && (dragSelection = { anchor: selectionStartPoint, focus: selectionEndPoint }); } return dragSelection; } function getEventPosition({ editorActor, slateEditor, event }) { if (editorActor.getSnapshot().matches({ setup: "setting up" })) return; const node = getEventNode({ slateEditor, event }); if (!node) return; const block = editorProvider.getNodeBlock({ editor: slateEditor, schema: editorActor.getSnapshot().context.schema, node }), positionBlock = getEventPositionBlock({ node, slateEditor, event }), selection = getEventSelection({ schema: editorActor.getSnapshot().context.schema, slateEditor, event }); if (block && positionBlock && !selection && !slate.Editor.isEditor(node)) return { block: positionBlock, isEditor: !1, selection: { anchor: util_sliceBlocks.getBlockStartPoint({ node: block, path: [{ _key: block._key }] }), focus: util_sliceBlocks.getBlockEndPoint({ node: block, path: [{ _key: block._key }] }) } }; if (!positionBlock || !selection) return; const focusBlockPath = selection.focus.path.at(0), focusBlockKey = util_sliceBlocks.isKeyedSegment(focusBlockPath) ? focusBlockPath._key : void 0; if (focusBlockKey) return util_isSelectionCollapsed.isSelectionCollapsed(selection) && block && focusBlockKey !== block._key ? { block: positionBlock, isEditor: !1, selection: { anchor: util_sliceBlocks.getBlockStartPoint({ node: block, path: [{ _key: block._key }] }), focus: util_sliceBlocks.getBlockEndPoint({ node: block, path: [{ _key: block._key }] }) } } : { block: positionBlock, isEditor: slate.Editor.isEditor(node), selection }; } function getEventNode({ slateEditor, event }) { return slateDom.DOMEditor.hasTarget(slateEditor, event.target) ? slateDom.DOMEditor.toSlateNode(slateEditor, event.target) : void 0; } function getEventPositionBlock({ node, slateEditor, event }) { const [firstBlock] = editorProvider.getFirstBlock({ editor: slateEditor }); if (!firstBlock) return; const firstBlockRect = slateDom.DOMEditor.toDOMNode(slateEditor, firstBlock).getBoundingClientRect(); if (event.pageY < firstBlockRect.top) return "start"; const [lastBlock] = editorProvider.getLastBlock({ editor: slateEditor }); if (!lastBlock) return; const lastBlockRef = slateDom.DOMEditor.toDOMNode(slateEditor, lastBlock).getBoundingClientRect(); if (event.pageY > lastBlockRef.bottom) return "end"; const elementRect = slateDom.DOMEditor.toDOMNode(slateEditor, node).getBoundingClientRect(), top = elementRect.top, height = elementRect.height; return Math.abs(top - event.pageY) < height / 2 ? "start" : "end"; } function getEventSelection({ schema, slateEditor, event }) { const range = getSlateRangeFromEvent(slateEditor, event); return range ? editorProvider.slateRangeToSelection({ schema, editor: slateEditor, range }) : null; } function getSlateRangeFromEvent(editor, event) { if (!event.target || !slateDom.isDOMNode(event.target)) return; const window2 = slateDom.DOMEditor.getWindow(editor); let domRange; if (window2.document.caretPositionFromPoint !== void 0) { const position = window2.document.caretPositionFromPoint(event.clientX, event.clientY); if (position) try { domRange = window2.document.createRange(), domRange.setStart(position.offsetNode, position.offset), domRange.setEnd(position.offsetNode, position.offset); } catch { } } else if (window2.document.caretRangeFromPoint !== void 0) domRange = window2.document.caretRangeFromPoint(event.clientX, event.clientY) ?? void 0; else { console.warn("Neither caretPositionFromPoint nor caretRangeFromPoint is supported"); return; } if (!domRange) return; let range; try { range = slateDom.DOMEditor.toSlateRange(editor, domRange, { exactMatch: !1, // It can still throw even with this option set to true suppressThrow: !1 }); } catch { } return range; } function normalizePoint(point, value) { if (!point || !value) return null; const newPath = []; let newOffset = point.offset || 0; const blockKey = typeof point.path[0] == "object" && "_key" in point.path[0] && point.path[0]._key, childKey = typeof point.path[2] == "object" && "_key" in point.path[2] && point.path[2]._key, block = value.find((blk) => blk._key === blockKey); if (block) newPath.push({ _key: block._key }); else return null; if (block && point.path[1] === "children") { if (!block.children || Array.isArray(block.children) && block.children.length === 0) return null; const child = Array.isArray(block.children) && block.children.find((cld) => cld._key === childKey); if (child) newPath.push("children"), newPath.push({ _key: child._key }), newOffset = child.text && child.text.length >= point.offset ? point.offset : child.text && child.text.length || 0; else return null; } return { path: newPath, offset: newOffset }; } function normalizeSelection(selection, value) { if (!selection || !value || value.length === 0) return null; let newAnchor = null, newFocus = null; const { anchor, focus } = selection; return anchor && value.find((blk) => isEqual__default.default({ _key: blk._key }, anchor.path[0])) && (newAnchor = normalizePoint(anchor, value)), focus && value.find((blk) => isEqual__default.default({ _key: blk._key }, focus.path[0])) && (newFocus = normalizePoint(focus, value)), newAnchor && newFocus ? { anchor: newAnchor, focus: newFocus, backward: selection.backward } : null; } function getSelectionDomNodes({ slateEditor, snapshot }) { if (!snapshot.context.selection) return { blockNodes: [], childNodes: [] }; const range = editorProvider.toSlateRange(snapshot.context.selection, slateEditor); if (!range) return { blockNodes: [], childNodes: [] }; const blockEntries = Array.from(slate.Editor.nodes(slateEditor, { at: range, mode: "highest", match: (n) => !slate.Editor.isEditor(n) })), childEntries = Array.from(slate.Editor.nodes(slateEditor, { at: range, mode: "lowest", match: (n) => !slate.Editor.isEditor(n) && slateEditor.isTextSpan(n) || !slateEditor.isBlock(n) })); return { blockNodes: blockEntries.map(([blockNode]) => slateDom.DOMEditor.toDOMNode(slateEditor, blockNode)), childNodes: childEntries.map(([childNode]) => slateDom.DOMEditor.toDOMNode(slateEditor, childNode)) }; } function DefaultBlockObject(props) { const $ = reactCompilerRuntime.c(4); let t0; $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = { userSelect: "none" }, $[0] = t0) : t0 = $[0]; let t1; return $[1] !== props.value._key || $[2] !== props.value._type ? (t1 = /* @__PURE__ */ jsxRuntime.jsxs("div", { style: t0, children: [ "[", props.value._type, ": ", props.value._key, "]" ] }), $[1] = props.value._key, $[2] = props.value._type, $[3] = t1) : t1 = $[3], t1; } function DefaultInlineObject(props) { const $ = reactCompilerRuntime.c(4); let t0; $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = { userSelect: "none" }, $[0] = t0) : t0 = $[0]; let t1; return $[1] !== props.value._key || $[2] !== props.value._type ? (t1 = /* @__PURE__ */ jsxRuntime.jsxs("span", { style: t0, children: [ "[", props.value._type, ": ", props.value._key, "]" ] }), $[1] = props.value._key, $[2] = props.value._type, $[3] = t1) : t1 = $[3], t1; } function DropIndicator() { const $ = reactCompilerRuntime.c(1); let t0; return $[0] === Symbol.for("react.memo_cache_sentinel") ? (t0 = /* @__PURE__ */ jsxRuntime.jsx("div", { contentEditable: !1, className: "pt-drop-indicator", style: { position: "absolute", width: "100%", height: 1, borderBottom: "1px solid currentColor", zIndex: 5 }, children: /* @__PURE__ */ jsxRuntime.jsx("span", {}) }), $[0] = t0) : t0 = $[0], t0; } editorProvider.debugWithName("components:Element"); const EMPTY_ANNOTATIONS = [], inlineBlockStyle = { display: "inline-block" }, Element$1 = ({ attributes, children, element, schemaTypes, readOnly, renderBlock, renderChild, renderListItem, renderStyle, spellCheck }) => { const editorActor = React.useContext(editorProvider.EditorActorContext), slateEditor = slateReact.useSlateStatic(), selected = slateReact.useSelected(), blockRef = React.useRef(null), inlineBlockObjectRef = React.useRef(null), focused = selected && slateEditor.selection && slate.Range.isCollapsed(slateEditor.selection) || !1, [dragPositionBlock, setDragPositionBlock] = React.useState(); React.useEffect(() => { const behavior = behavior_core.defineBehavior({ on: "drag.dragover", guard: ({ snapshot, event }) => { const dropFocusBlock = selector_isOverlappingSelection.getFocusBlock({ context: { ...snapshot.context, selection: event.position.selection } }); if (!dropFocusBlock || dropFocusBlock.node._key !== element._key) return !1; const dragOrigin = snapshot.beta.internalDrag?.origin; return !dragOrigin || selector_isOverlappingSelection.getSelectedBlocks({ context: { ...snapshot.context, selection: dragOrigin.selection } }).some((draggedBlock) => draggedBlock.node._key === element._key) ? !1 : selector_isSelectingEntireBlocks.isSelectingEntireBlocks({ context: { ...snapshot.context, selection: dragOrigin.selection } }); }, actions: [({ event: event_0 }) => [{ type: "effect", effect: () => { setDragPositionBlock(event_0.position.block); } }, { type: "noop" }]] }); return editorActor.send({ type: "add behavior", behavior }), () => { editorActor.send({ type: "remove behavior", behavior }); }; }, [editorActor, element._key]), React.useEffect(() => { const behavior_0 = behavior_core.defineBehavior({ on: "drag.*", guard: ({ event: event_1 }) => event_1.type !== "drag.dragover", actions: [() => [{ type: "effect", effect: () => { setDragPositionBlock(void 0); } }]] }); return editorActor.send({ type: "add behavior", behavior: behavior_0 }), () => { editorActor.send({ type: "remove behavior", behavior: behavior_0 }); }; }, [editorActor]); const value = React.useMemo(() => editorProvider.fromSlateValue([element], schemaTypes.block.name, editorProvider.KEY_TO_VALUE_ELEMENT.get(slateEditor))[0], [slateEditor, element, schemaTypes.block.name]); let renderedBlock = children, className; const blockPath = React.useMemo(() => [{ _key: element._key }], [element]); if (typeof element._type != "string") throw new Error("Expected element to have a _type property"); if (typeof element._key != "string") throw new Error("Expected element to have a _key property"); if (slateEditor.isInline(element)) { const path = slateReact.ReactEditor.findPath(slateEditor, element), [block] = slate.Editor.node(slateEditor, path, { depth: 1 }), schemaType = schemaTypes.inlineObjects.find((_type) => _type.name === element._type); if (!schemaType) throw new Error("Could not find type for inline block element"); if (slate.Element.isElement(block)) { const elmPath = [{ _key: block._key }, "children", { _key: element._key }]; return /* @__PURE__ */ jsxRuntime.jsxs("span", { ...attributes, children: [ children, /* @__PURE__ */ jsxRuntime.jsxs("span", { draggable: !readOnly, className: "pt-inline-object", "data-testid": "pt-inline-object", ref: inlineBlockObjectRef, style: inlineBlockStyle, contentEditable: !1, children: [ renderChild && renderChild({ annotations: EMPTY_ANNOTATIONS, // These inline objects currently doesn't support annotations. This is a limitation of the current PT spec/model. children: /* @__PURE__ */ jsxRuntime.jsx(DefaultInlineObject, { value }), editorElementRef: inlineBlockObjectRef, focused, path: elmPath, schemaType, selected, type: schemaType, value }), !renderChild && /* @__PURE__ */ jsxRuntime.jsx(DefaultInlineObject, { value }) ] }, element._key) ] }); } throw new Error("Block not found!"); } if (element._type === schemaTypes.block.name) { className = "pt-block pt-text-block"; const isListItem = "listItem" in element, style = "style" in element && element.style || "normal"; className = `pt-block pt-text-block pt-text-block-style-${style}`; const blockStyleType = schemaTypes.styles.find((item) => item.value === style); renderStyle && blockStyleType && (renderedBlock = renderStyle({ block: element, children, focused, selected, value: style, path: blockPath, schemaType: blockStyleType, editorElementRef: blockRef })); let level; if (isListItem && (typeof element.level == "number" && (level = element.level), className += ` pt-list-item pt-list-item-${element.listItem} pt-list-item-level-${level || 1}`), slateEditor.isListBlock(value) && isListItem && element.listItem) { const listType = schemaTypes.lists.find((item_0) => item_0.value === element.listItem); renderListItem && listType && (renderedBlock = renderListItem({ block: value, children: renderedBlock, focused, selected, value: element.listItem, path: blockPath, schemaType: listType, level: value.level || 1, editorElementRef: blockRef })); } const renderProps = Object.defineProperty({ children: renderedBlock, editorElementRef: blockRef, focused, level, listItem: isListItem ? element.listItem : void 0, path: blockPath, selected, style, schemaType: schemaTypes.block, value }, "type", { enumerable: !1, get() { return console.warn("Property 'type' is deprecated, use 'schemaType' instead."), schemaTypes.block; } }), propsOrDefaultRendered = renderBlock ? renderBlock(renderProps) : children; return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...attributes, className, spellCheck, children: [ dragPositionBlock === "start" ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, {}) : null, /* @__PURE__ */ jsxRuntime.jsx("div", { ref: blockRef, children: propsOrDefaultRendered }), dragPositionBlock === "end" ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, {}) : null ] }, element._key); } const schemaType_0 = schemaTypes.blockObjects.find((_type_0) => _type_0.name === element._type); if (!schemaType_0) throw new Error(`Could not find schema type for block element of _type ${element._type}`); className = "pt-block pt-object-block"; const block_0 = editorProvider.fromSlateValue([element], schemaTypes.block.name, editorProvider.KEY_TO_VALUE_ELEMENT.get(slateEditor))[0]; let renderedBlockFromProps; if (renderBlock) { const _props = Object.defineProperty({ children: /* @__PURE__ */ jsxRuntime.jsx(DefaultBlockObject, { value }), editorElementRef: blockRef, focused, path: blockPath, schemaType: schemaType_0, selected, value: block_0 }, "type", { enumerable: !1, get() { return console.warn("Property 'type' is deprecated, use 'schemaType' instead."), schemaType_0; } }); renderedBlockFromProps = renderBlock(_props); } return /* @__PURE__ */ jsxRuntime.jsxs("div", { ...attributes, className, children: [ dragPositionBlock === "start" ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, {}) : null, children, /* @__PURE__ */ jsxRuntime.jsx("div", { ref: blockRef, contentEditable: !1, draggable: !readOnly, children: renderedBlockFromProps || /* @__PURE__ */ jsxRuntime.jsx(DefaultBlockObject, { value }) }), dragPositionBlock === "end" ? /* @__PURE__ */ jsxRuntime.jsx(DropIndicator, {}) : null ] }, element._key); }; Element$1.displayName = "Element"; const debug$2 = editorProvider.debugWithName("components:Leaf"), EMPTY_MARKS = [], Leaf = (props) => { const { editorActor, attributes, children, leaf, schemaTypes, renderChild, renderDecorator, renderAnnotation } = props, spanRef = React.useRef(null), portableTextEditor = editorProvider.usePortableTextEditor(), blockSelected = slateReact.useSelected(), [focused, setFocused] = React.useState(!1), [selected, setSelected] = React.useState(!1), block = children.props.parent, path = React.useMemo(() => block ? [{ _key: block?._key }, "children", { _key: leaf._key }] : [], [block, leaf._key]), decoratorValues = React.useMemo(() => schemaTypes.decorators.map((dec) => dec.value), [schemaTypes.decorators]), marks = React.useMemo(() => uniq__default.default((leaf.marks || EMPTY_MARKS).filter((mark) => decoratorValues.includes(mark))), [decoratorValues, leaf.marks]), annotationMarks = Array.isArray(leaf.marks) ? leaf.marks : EMPTY_MARKS, annotations = React.useMemo(() => annotationMarks.map((mark_0) => !decoratorValues.includes(mark_0) && block?.markDefs?.find((def) => def._key === mark_0)).filter(Boolean), [annotationMarks, block, decoratorValues]), shouldTrackSelectionAndFocus = annotations.length > 0 && blockSelected; React.useEffect(() => { if (!shouldTrackSelectionAndFocus) { setFocused(!1); return; } const sel = editorProvider.PortableTextEditor.getSelection(portableTextEditor); sel && isEqual__default.default(sel.focus.path, path) && editorProvider.PortableTextEditor.isCollapsedSelection(portableTextEditor) && React.startTransition(() => { setFocused(!0); }); }, [shouldTrackSelectionAndFocus, path, portableTextEditor]); const setSelectedFromRange = React.useCallback(() => { if (!shouldTrackSelectionAndFocus) return; debug$2("Setting selection and focus from range"); const winSelection = window.getSelection(); if (!winSelection) { setSelected(!1); return; } if (winSelection && winSelection.rangeCount > 0) { const range = winSelection.getRangeAt(0); spanRef.current && range.intersectsNode(spanRef.current) ? setSelected(!0) : setSelected(!1); } else setSelected(!1); }, [shouldTrackSelectionAndFocus]); React.useEffect(() => { if (!shouldTrackSelectionAndFocus) return; const onBlur = editorActor.on("blurred", () => { setFocused(!1), setSelected(!1); }), onFocus = editorActor.on("focused", () => { const sel_0 = editorProvider.PortableTextEditor.getSelection(portableTextEditor); sel_0 && isEqual__default.default(sel_0.focus.path, path) && editorProvider.PortableTextEditor.isCollapsedSelection(portableTextEditor) && setFocused(!0), setSelectedFromRange(); }), onSelection = editorActor.on("selection", (event) => { event.selection && isEqual__default.default(event.selection.focus.path, path) && editorProvider.PortableTextEditor.isCollapsedSelection(portableTextEditor) ? setFocused(!0) : setFocused(!1), setSelectedFromRange(); }); return () => { onBlur.unsubscribe(), onFocus.unsubscribe(), onSelection.unsubscribe(); }; }, [editorActor, path, portableTextEditor, setSelectedFromRange, shouldTrackSelectionAndFocus]), React.useEffect(() => setSelectedFromRange(), [setSelectedFromRange]); const content = React.useMemo(() => { let returnedChildren = children; if (slate.Text.isText(leaf) && leaf._type === schemaTypes.span.name && (marks.forEach((mark_1) => { const schemaType = schemaTypes.decorators.find((dec_0) => dec_0.value === mark_1); if (schemaType && renderDecorator) { const _props = Object.defineProperty({ children: returnedChildren, editorElementRef: spanRef, focused, path, selected, schemaType, value: mark_1 }, "type", { enumerable: !1, get() { return console.warn("Property 'type' is deprecated, use 'schemaType' instead."), schemaType; } }); returnedChildren = renderDecorator(_props); } }), block && annotations.length > 0 && annotations.forEach((annotation) => { const schemaType_0 = schemaTypes.annotations.find((t) => t.name === annotation._type); if (schemaType_0) if (renderAnnotation) { const _props_0 = Object.defineProperty({ block, children: returnedChildren, editorElementRef: spanRef, focused, path, selected, schemaType: schemaType_0, value: annotation }, "type", { enumerable: !1, get() { return console.warn("Property 'type' is deprecated, use 'schemaType' instead."), schemaType_0; } }); returnedChildren = /* @__PURE__ */ jsxRuntime.jsx("span", { ref: spanRef, children: renderAnnotation(_props_0) }); } else returnedChildren = /* @__PURE__ */ jsxRuntime.jsx("span", { ref: spanRef, children: returnedChildren }); }), block && renderChild)) { const child = block.children.find((_child) => _child._key === leaf._key); if (child) { const _props_1 = Object.defineProperty({ annotations, children: /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: returnedChildren }), editorElementRef: spanRef, focused, path, schemaType: schemaTypes.span, selected, value: child }, "type", { enumerable: !1, get() { return console.warn("Property 'type' is deprecated, use 'schemaType' instead."), schemaTypes.span; } }); returnedChildren = renderChild(_props_1); } } return returnedChildren; }, [annotations, block, children, focused, leaf, marks, path, renderAnnotation, renderChild, renderDecorator, schemaTypes.annotations, schemaTypes.decorators, schemaTypes.span, selected]); return React.useMemo(() => /* @__PURE__ */ jsxRuntime.jsx("span", { ...attributes, ref: spanRef, children: content }, leaf._key), [leaf, attributes, content]); }; Leaf.displayName = "Leaf"; const debug$1 = editorProvider.debugWithName("plugin:withHotKeys"); function createWithHotkeys(editorActor, portableTextEditor, hotkeysFromOptions) { const reservedHotkeys = ["enter", "tab", "shift", "delete", "end"], activeHotkeys = hotkeysFromOptions ?? {}; return function(editor) { return editor.pteWithHotKeys = (event) => { Object.keys(activeHotkeys).forEach((cat) => { if (cat === "marks") for (const hotkey in activeHotkeys[cat]) { if (reservedHotkeys.includes(hotkey)) throw new Error(`The hotkey ${hotkey} is reserved!`); if (behavior_core.isHotkey(hotkey, event.nativeEvent)) { event.preventDefault(); const possibleMark = activeHotkeys[cat]; if (possibleMark) { const mark = possibleMark[hotkey]; debug$1(`HotKey ${hotkey} to toggle ${mark}`), editorActor.send({ type: "behavior event", behaviorEvent: { type: "decorator.toggle", decorator: mark }, editor }); } } } if (cat === "custom") for (const hotkey in activeHotkeys[cat]) { if (reservedHotkeys.includes(hotkey)) throw new Error(`The hotkey ${hotkey} is reserved!`); if (behavior_core.isHotkey(hotkey, event.nativeEvent)) { const possibleCommand = activeHotkeys[cat]; if (possibleCommand) { const command = possibleCommand[hotkey]; command(event, portableTextEditor); } } } }); }, editor; }; } const slateOperationCallback = ({ input, sendBack }) => { const originalApply = input.slateEditor.apply; return input.slateEditor.apply = (op) => { op.type !== "set_selection" && sendBack({ type: "slate operation", operation: op }), originalApply(op); }, () => { input.slateEditor.apply = originalApply; }; }, rangeDecorationsMachine = xstate.setup({ types: { context: {}, input: {}, events: {} }, actions: { "update pending range decorations": xstate.assign({ pendingRangeDecorations: ({ event }) => (xstate.assertEvent(event, "range decorations updated"), event.rangeDecorations) }), "set up initial range decorations": xstate.assign({ decoratedRanges: ({ context, event }) => { xstate.assertEvent(event, "ready"); const rangeDecorationState = []; for (const rangeDecoration of context.pendingRangeDecorations) { const slateRange = editorProvider.toSlateRange(rangeDecoration.selection, context.slateEditor); if (!slate.Range.isRange(slateRange)) { rangeDecoration.onMoved?.({ newSelection: null, rangeDecoration, origin: "local" }); continue; } rangeDecorationState.push({ rangeDecoration, ...slateRange }); } return rangeDecorationState; } }), "update range decorations": xstate.assign({ decoratedRanges: ({ context, event }) => { xstate.assertEvent(event, "range decorations updated"); const rangeDecorationState = []; for (const rangeDecoration of event.rangeDecorations) { const slateRange = editorProvider.toSlateRange(rangeDecoration.selection, context.slateEditor); if (!slate.Range.isRange(slateRange)) { rangeDecoration.onMoved?.({ newSelection: null, rangeDecoration, origin: "local" }); continue; } rangeDecorationState.push({ rangeDecoration, ...slateRange }); } return rangeDecorationState; } }), "move range decorations": xstate.assign({ decoratedRanges: ({ context, event }) => { xstate.assertEvent(event, "slate operation"); const rangeDecorationState = []; for (const decoratedRange of context.decoratedRanges) { const slateRange = editorProvider.toSlateRange(decoratedRange.rangeDecoration.selection, context.slateEditor); if (!slate.Range.isRange(slateRange)) { decoratedRange.rangeDecoration.onMoved?.({ newSelection: null, rangeDecoration: decoratedRange.rangeDecoration, origin: "local" }); continue; } let newRange; if (newRange = editorProvider.moveRangeByOperation(slateRange, event.operation), newRange && newRange !== slateRange || newRange === null && slateRange) { const newRangeSelection = newRange ? editorProvider.slateRangeToSelection({ schema: context.schema, editor: context.slateEditor, range: newRange }) : null; decoratedRange.rangeDecoration.onMoved?.({ newSelection: newRangeSelection, rangeDecoration: decoratedRange.rangeDecoration, origin: "local" }); } newRange !== null && rangeDecorationState.push({ ...newRange || slateRange, rangeDecoration: { ...decoratedRange.rangeDecoration, selection: editorProvider.slateRangeToSelection({ schema: context.schema, editor: context.slateEditor, range: newRange }) } }); } return rangeDecorationState; } }), "assign readOnly": xstate.assign({ readOnly: ({ event }) => (xstate.assertEvent(event, "update read only"), event.readOnly) }), "increment update count": xstate.assign({ updateCount: ({ context }) => context.updateCount + 1 }) }, actors: { "slate operation listener": xstate.fromCallback(slateOperationCallback) }, guards: { "has pending range decorations": ({ context }) => context.pendingRangeDecorations.length > 0, "has range decorations": ({ context }) => context.decoratedRanges.length > 0, "has different decorations": ({ context, event }) => { xstate.assertEvent(event, "range decorations updated"); const existingRangeDecorations = context.decoratedRanges.map((decoratedRange) => ({ anchor: decoratedRange.rangeDecoration.selection?.anchor, focus: decoratedRange.rangeDecoration.selection?.focus })), newRangeDecorations = event.rangeDecorations.map((rangeDecoration) => ({ anchor: rangeDecoration.selection?.anchor, focus: rangeDecoration.selection?.focus })); return !isEqual__default.default(existingRangeDecorations, newRangeDecorations); }, "not read only": ({ context }) => !context.readOnly, "should skip setup": ({ context }) => context.skipSetup } }).createMachine({ id: "range decorations", context: ({ input }) => ({ readOnly: input.readOnly, pendingRangeDecorations: input.rangeDecorations, decoratedRanges: [], skipSetup: input.skipSetup, schema: input.schema, slateEditor: input.slateEditor, updateCount: 0 }), invoke: { src: "slate operation listener", input: ({ context }) => ({ slateEditor: context.slateEditor }) }, on: { "update read only": { actions: ["assign readOnly"] } }, initial: "setting up", states: { "setting up": { always: [{ guard: xstate.and(["should skip setup", "has pending range decorations"]), target: "ready", actions: ["set up initial range decorations", "increment update count"] }, { guard: "should skip setup", target: "ready" }], on: { "range decorations updated": { actions: ["update pending range decorations"] }, ready: [{ target: "ready", guard: "has pending range decorations", actions: ["set up initial range decorations", "increment update count"] }, { target: "ready" }] } }, ready: { initial: "idle", on: { "range decorations updated": { target: ".idle", guard: "has different decorations", actions: ["update range decorations", "increment update count"] } }, states: { idle: { on: { "slate operation": { target: "moving range decorations", guard: xstate.and(["has range decorations", "not read only"]) } } }, "moving range decorations": { entry: ["move range decorations"], always: { target: "idle" } } } } } }); function createDecorate(rangeDecorationActor) { return function([node, path]) { if (editorProvider.isEqualToEmptyEditor(rangeDecorationActor.getSnapshot().context.slateEditor.children, rangeDecorationActor.getSnapshot().context.schema)) return [{ anchor: { path: [0, 0], offset: 0 }, focus: { path: [0, 0], offset: 0 }, placeholder: !0 }]; if (path.length === 0) return []; if (!slate.Element.isElement(node) || node.children.length === 0) return []; const blockIndex = path.at(0); return blockIndex === void 0 ? [] : rangeDecorationActor.getSnapshot().context.decoratedRanges.filter((decoratedRange) => slate.Range.isCollapsed(decoratedRange) ? node.children.some((_, childIndex) => slate.Path.equals(decoratedRange.anchor.path, [blockIndex, childIndex]) && slate.Path.equals(decoratedRange.focus.path, [blockIndex, childIndex])) : slate.Range.intersection(decoratedRange, { anchor: { path, offset: 0 }, focus: { path, offset: 0 } }) || slate.Range.includes(decoratedRange, path)); }; } const debug = editorProvider.debugWithName("component:Editable"), PLACEHOLDER_STYLE = { position: "absolute", userSelect: "none", pointerEvents: "none", left: 0, right: 0 }, PortableTextEditable = React.forwardRef(function(props, forwardedRef) { const { hotkeys, onBlur, onFocus, onBeforeInput, onPaste, onCopy, onCut, onClick, onDragStart, onDrag, onDragEnd, onDragEnter, onDragOver, onDrop, onDragLeave, rangeDecorations, renderAnnotation, renderBlock, renderChild, renderDecorator, renderListItem, renderPlaceholder, renderStyle, selection: propsSelection, scrollSelectionIntoView, spellCheck, ...restProps } = props, portableTextEditor = editorProvider.usePortableTextEditor(), ref = React.useRef(null), [editableElement, setEditableElement] = React.useState(null), [hasInvalidValue, setHasInvalidValue] = React.useState(!1); React.useImperativeHandle(forwardedRef, () => ref.current); const editorActor = React.useContext(editorProvider.EditorActorContext), readOnly = react.useSelector(editorActor, (s) => s.matches({ "edit mode": "read only" })), slateEditor = slateReact.useSlate(), rangeDecorationsActor = react.useActorRef(rangeDecorationsMachine, { input: { rangeDecorations: rangeDecorations ?? [], readOnly, schema: editorActor.getSnapshot().context.schema, slateEditor, skipSetup: !editorActor.getSnapshot().matches({ setup: "setting up" }) } }); react.useSelector(rangeDecorationsActor, (s_0) => s_0.context.updateCount); const decorate = React.useMemo(() => createDecorate(rangeDecorationsActor), [rangeDecorationsActor]); React.useEffect(() => { rangeDecorationsActor.send({ type: "update read only", readOnly }); }, [rangeDecorationsActor, readOnly]), React.useEffect(() => { rangeDecorationsActor.send({ type: "range decorations updated", rangeDecorations: rangeDecorations ?? [] }); }, [rangeDecorationsActor, rangeDecorations]), React.useMemo(() => { if (readOnly) return debug("Editable is in read only mode"), slateEditor; const withHotKeys = createWithHotkeys(editorActor, portableTextEditor, hotkeys); return debug("Editable is in edit mode"), withHotKeys(slateEditor); }, [editorActor, hotkeys, portableTextEditor, readOnly, slateEditor]); const renderElement = React.useCallback((eProps) => /* @__PURE__ */ jsxRuntime.jsx(Element$1, { ...eProps, readOnly, renderBlock, renderChild, renderListItem, renderStyle, schemaTypes: portableTextEditor.schemaTypes, spellCheck }), [portableTextEditor, spellCheck, readOnly, renderBlock, renderChild, renderListItem, renderStyle]), renderLeaf = React.useCallback((lProps) => { if (lProps.leaf._type === "span") { let rendered = /* @__PURE__ */ jsxRuntime.jsx(Leaf, { ...lProps, editorActor, schemaTypes: portableTextEditor.schemaTypes, renderAnnotation, renderChild, renderDecorator, readOnly }); if (renderPlaceholder && lProps.leaf.placeholder && lProps.text.text === "") return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [ /* @__PURE__ */ jsxRuntime.jsx("span", { style: PLACEHOLDER_STYLE, contentEditable: !1, children: renderPlaceholder() }), rendered ] }); const decoration = lProps.leaf.rangeDecoration; return decoration && (rendered = decoration.component({ children: rendered })), rendered; } return lProps.children; }, [editorActor, portableTextEditor, readOnly, renderAnnotation, renderChild, renderDecorator, renderPlaceholder]), restoreSelectionFromProps = React.useCallback(() => { if (propsSelection) { debug(`Selection from props ${JSON.stringify(propsSelection)}`); const normalizedSelection = normalizeSelection(propsSelection, editorProvider.fromSlateValue(slateEditor.children, editorActor.getSnapshot().context.schema.block.name)); if (normalizedSelection !== null) { debug(`Normalized selection from props ${JSON.stringify(normalizedSelection)}`); const slateRange = editorProvider.toSlateRange(normalizedSelection, slateEditor); slateRange && (slate.Transforms.select(slateEditor, slateRange), slateEditor.operations.some((o) => o.type === "set_selection") || editorActor.send({ type: "notify.selection", selection: normalizedSelection }), slateEditor.onChange()); } } }, [editorActor, propsSelection, slateEditor]); React.useEffect(() => { const onReady = editorActor.on("ready", () => { rangeDecorationsActor.send({ type: "ready" }), restoreSelectionFromProps(); }), onInvalidValue = editorActor.on("invalid value", () => { setHasInvalidValue(!0); }), onValueChanged = editorActor.on("value changed", () => { setHasInvalidValue(!1); }); return () => { onReady.unsubscribe(), onInvalidValue.unsubscribe(), onValueChanged.unsubscribe(); }; }, [rangeDecorationsActor, editorActor, restoreSelectionFromProps]), React.useEffect(() => { propsSelection && !hasInvalidValue && restoreSelectionFromProps(); }, [hasInvalidValue, propsSelection, restoreSelectionFromProps]); const handleCopy = React.useCallback((event) => { if (onCopy) onCopy(event) !== void 0 && event.preventDefault(); else if (event.nativeEvent.clipboardData) { event.stopPropagation(), event.preventDefault(); const selection = slateEditor.selection ? editorProvider.slateRangeToSelection({ schema: editorActor.getSnapshot().context.schema, editor: slateEditor, range: slateEditor.selection }) : void 0, position = selection ? { selection } : void 0; if (!position) { console.warn("Could not find position for copy event"); return; } editorActor.send({ type: "behavior event", behaviorEvent: { type: "clipboard.copy", originEvent: { dataTransfer: event.nativeEvent.clipboardData }, position }, editor: slateEditor, nativeEvent: event }); } }, [onCopy, editorActor, slateEditor]), handleCut = React.useCallback((event_0) => { if (onCut) onCut(event_0) !== void 0 && event_0.preventDefault(); else if (event_0.nativeEvent.clipboardData) { event_0.stopPropagation(), event_0.preventDefault(); const selection_0 = editorActor.getSnapshot().context.selection, position_0 = selection_0 ? { selection: selection_0 } : void 0; if (!position_0) { console.warn("Could not find position for cut event"); return; } editorActor.send({ type: "behavior event", behaviorEvent: { type: "clipboard.cut", originEvent: { dataTransfer: event_0.nativeEvent.clipboardData }, position: position_0 }, editor: slateEditor, nativeEvent: event_0 }); } }, [onCut, editorActor, slateEditor]), handlePaste = React.useCallback((event_1) => { const value = editorProvider.fromSlateValue(slateEditor.children, editorActor.getSnapshot().context.schema.block.name, editorProvider.KEY_TO_VALUE_ELEMENT.get(slateEditor)), path = (slateEditor.selection ? editorProvider.slateRangeToSelection({ schema: editorActor.getSnapshot().context.schema, editor: slateEditor, range: slateEditor.selection }) : null)?.focus.path || [], onPasteResult = onPaste?.({ event: event_1, value, path, schemaTypes: portableTextEditor.schemaTypes }); if (onPasteResult || !slateEditor.selection) event_1.preventDefault(), editorActor.send({ type: "notify.loading" }), Promise.resolve(onPasteResult).then((result_1) => { if (debug("Custom paste function from client resolved", result_1), !result_1 || !result_1.insert) { debug("No result from custom paste handler, pasting normally"); const selection_1 = editorActor.getSnapshot().context.selection, position_1 = selection_1 ? { selection: selection_1 } : void 0; if (!position_1) { console.warn("Could not find position for paste event"); return; } editorActor.send({ type: "behavior event", behaviorEvent: { type: "clipboard.paste", originEvent: { dataTransfer: event_1.clipboardData }, position: position_1 }, editor: slateEditor, nativeEvent: event_1 }); } else result_1.insert ? editorActor.send({ type: "behavior event", behaviorEvent: { type: "insert.blocks", blocks: parseBlocks.parseBlocks({ context: { keyGenerator: editorActor.getSnapshot().context.keyGenerator, schema: editorActor.getSnapshot().context.schema }, blocks: result_1.insert, options: { refreshKeys: !0 } }), placement: "auto" }, editor: slateEditor }) : console.warn("Your onPaste function returned something unexpected:", result_1); }).catch((error) => (console.warn(error), error)).finally(() => { editorActor.send({ type: "notify.done loading" }); }); else if (event_1.nativeEvent.clipboardData) { event_1.preventDefault(), event_1.stopPropagation(); const selection_2 = editorActor.getSnapshot().context.selection, position_2 = selection_2 ? { selection: selection_2 } : void 0; if (!position_2) { console.warn("Could not find position for paste event"); return; } editorActor.send({ type: "behavior event", behaviorEvent: { type: "clipboard.paste", originEvent: { dataTransfer: event_1.nativeEvent.clipboardData }, position: position_2 }, editor: slateEditor, nativeEvent: event_1 }); } debug("No result from custom paste handler, pasting normally"); }, [editorActor, onPaste, portableTextEditor, slateEditor]), handleOnFocus = React.useCallback((event_2) => { if (onFocus && onFocus(event_2), !event_2.isDefaultPrevented()) { const selection_3 = editorProvider.PortableTextEditor.getSelection(portableTextEditor); selection_3 === null && (slate.Transforms.select(slateEditor, slate.Editor.start(slateEditor, [])), slateEditor.onChange()), editorActor.send({ type: "notify.focused", event: event_2 }); const newSelection = editorProvider.PortableTextEditor.getSelection(portableTextEditor); selection_3 === newSelection && editorActor.send({ type: "notify.selection", selection: selection_3 }); } }, [editorActor, onFocus, slateEditor, portableTextEditor]), handleClick = React.useCallback((event_3) => { if (onClick && onClick(event_3), event_3.isDefaultPrevented() || event_3.isPropagationStopped()) return; const position_3 = getEventPosition({ editorActor, slateEditor, event: event_3.nativeEvent }); position_3 && editorActor.send({ type: "behavior event", behaviorEvent: { type: "mouse.click", position: position_3 }, editor: slateEditor, nativeEvent: event_3 }); }, [onClick, editorActor, slateEditor]), handleOnBlur = React.useCallback((event_4) => { onBlur && onBlur(event_4), event_4.isPropagationStopped() || editorActor.send({ type: "notify.blurred", event: event_4 }); }, [editorActor, onBlur]), handleOnBeforeInput = React.useCallback((event_5) => { onBeforeInput && onBeforeInput(event_5); }, [onBeforeInput]), validateSelection = React.useCallback(() => { if (!slateEditor.selection) return; c