UNPKG

text-editor-studio-ts

Version:

A powerful mobile-responsive rich text editor built with Lexical and React

335 lines (334 loc) 10.7 kB
import { jsxs, Fragment, jsx } from "react/jsx-runtime"; import { useRef, useState, useCallback, useEffect, Suspense } from "react"; import { g as useEditorModal, u, o, a, i as $isInlineImageNode, B as Button, d as o$1, L as LinkPlugin, h, f as a$1, C as ContentEditable, j as Label, I as Input, S as Select, k as SelectTrigger, l as SelectValue, m as SelectContent, n as SelectItem, p as Checkbox, D as DialogFooter } from "./index-BwW17RmP.js"; import { f } from "./LexicalNestedComposer.prod-DcHyIDfI.js"; import { mergeRegister } from "@lexical/utils"; import { $getSelection, $isNodeSelection, $setSelection, SELECTION_CHANGE_COMMAND, COMMAND_PRIORITY_LOW, CLICK_COMMAND, DRAGSTART_COMMAND, KEY_DELETE_COMMAND, KEY_BACKSPACE_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, $getNodeByKey } from "lexical"; const imageCache = /* @__PURE__ */ new Set(); function useSuspenseImage(src) { if (!imageCache.has(src)) { throw new Promise((resolve) => { const img = new Image(); img.src = src; img.onload = () => { imageCache.add(src); resolve(null); }; }); } } function LazyImage({ altText, className, imageRef, src, width, height, position }) { useSuspenseImage(src); return /* @__PURE__ */ jsx( "img", { className: className || void 0, src, alt: altText, ref: imageRef, "data-position": position, style: { display: "block", height, width }, draggable: "false" } ); } function UpdateInlineImageDialog({ activeEditor, nodeKey, onClose }) { const editorState = activeEditor.getEditorState(); const node = editorState.read( () => $getNodeByKey(nodeKey) ); const [altText, setAltText] = useState(node.getAltText()); const [showCaption, setShowCaption] = useState(node.getShowCaption()); const [position, setPosition] = useState(node.getPosition()); const handleOnConfirm = () => { const payload = { altText, position, showCaption }; if (node) { activeEditor.update(() => { node.update(payload); }); } onClose(); }; return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [ /* @__PURE__ */ jsx(Label, { htmlFor: "alt-text", children: "Alt Text" }), /* @__PURE__ */ jsx( Input, { id: "alt-text", placeholder: "Descriptive alternative text", onChange: (e) => setAltText(e.target.value), value: altText, "data-test-id": "image-modal-alt-text-input" } ) ] }), /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [ /* @__PURE__ */ jsx(Label, { htmlFor: "position-select", children: "Position" }), /* @__PURE__ */ jsxs( Select, { value: position, onValueChange: (value) => setPosition(value), children: [ /* @__PURE__ */ jsx(SelectTrigger, { id: "position-select", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "Select position" }) }), /* @__PURE__ */ jsxs(SelectContent, { children: [ /* @__PURE__ */ jsx(SelectItem, { value: "left", children: "Left" }), /* @__PURE__ */ jsx(SelectItem, { value: "right", children: "Right" }), /* @__PURE__ */ jsx(SelectItem, { value: "full", children: "Full Width" }) ] }) ] } ) ] }), /* @__PURE__ */ jsxs("div", { className: "flex items-center space-x-2", children: [ /* @__PURE__ */ jsx( Checkbox, { id: "caption", checked: showCaption, onCheckedChange: (checked) => setShowCaption(checked) } ), /* @__PURE__ */ jsx(Label, { htmlFor: "caption", children: "Show Caption" }) ] }), /* @__PURE__ */ jsx(DialogFooter, { children: /* @__PURE__ */ jsx( Button, { "data-test-id": "image-modal-file-upload-btn", onClick: handleOnConfirm, children: "Confirm" } ) }) ] }); } function InlineImageComponent({ src, altText, nodeKey, width, height, showCaption, caption, position }) { const [modal, showModal] = useEditorModal(); const imageRef = useRef(null); const buttonRef = useRef(null); const [isSelected, setSelected, clearSelection] = u(nodeKey); const [editor] = o(); const [selection, setSelection] = useState(null); const activeEditorRef = useRef(null); const isEditable = a(); const $onDelete = useCallback( (payload) => { const deleteSelection = $getSelection(); if (isSelected && $isNodeSelection(deleteSelection)) { const event = payload; event.preventDefault(); if (isSelected && $isNodeSelection(deleteSelection)) { editor.update(() => { deleteSelection.getNodes().forEach((node) => { if ($isInlineImageNode(node)) { node.remove(); } }); }); } } return false; }, [editor, isSelected] ); const $onEnter = useCallback( (event) => { const latestSelection = $getSelection(); const buttonElem = buttonRef.current; if (isSelected && $isNodeSelection(latestSelection) && latestSelection.getNodes().length === 1) { if (showCaption) { $setSelection(null); event.preventDefault(); caption.focus(); return true; } else if (buttonElem !== null && buttonElem !== document.activeElement) { event.preventDefault(); buttonElem.focus(); return true; } } return false; }, [caption, isSelected, showCaption] ); const $onEscape = useCallback( (event) => { if (activeEditorRef.current === caption || buttonRef.current === event.target) { $setSelection(null); editor.update(() => { setSelected(true); const parentRootElement = editor.getRootElement(); if (parentRootElement !== null) { parentRootElement.focus(); } }); return true; } return false; }, [caption, editor, setSelected] ); useEffect(() => { let isMounted = true; const unregister = mergeRegister( editor.registerUpdateListener(({ editorState }) => { if (isMounted) { setSelection(editorState.read(() => $getSelection())); } }), editor.registerCommand( SELECTION_CHANGE_COMMAND, (_, activeEditor) => { activeEditorRef.current = activeEditor; return false; }, COMMAND_PRIORITY_LOW ), editor.registerCommand( CLICK_COMMAND, (payload) => { const event = payload; if (event.target === imageRef.current) { if (event.shiftKey) { setSelected(!isSelected); } else { clearSelection(); setSelected(true); } return true; } return false; }, COMMAND_PRIORITY_LOW ), editor.registerCommand( DRAGSTART_COMMAND, (event) => { if (event.target === imageRef.current) { event.preventDefault(); return true; } return false; }, COMMAND_PRIORITY_LOW ), editor.registerCommand( KEY_DELETE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW ), editor.registerCommand( KEY_BACKSPACE_COMMAND, $onDelete, COMMAND_PRIORITY_LOW ), editor.registerCommand(KEY_ENTER_COMMAND, $onEnter, COMMAND_PRIORITY_LOW), editor.registerCommand( KEY_ESCAPE_COMMAND, $onEscape, COMMAND_PRIORITY_LOW ) ); return () => { isMounted = false; unregister(); }; }, [ clearSelection, editor, isSelected, nodeKey, $onDelete, $onEnter, $onEscape, setSelected ]); const draggable = isSelected && $isNodeSelection(selection); const isFocused = isSelected && isEditable; return /* @__PURE__ */ jsxs(Suspense, { fallback: null, children: [ /* @__PURE__ */ jsxs(Fragment, { children: [ /* @__PURE__ */ jsxs("span", { draggable, children: [ isEditable && /* @__PURE__ */ jsx( Button, { className: "image-edit-button absolute right-1 top-1", variant: "BorderStyle", ref: buttonRef, onClick: () => { showModal("Update Inline Image", (onClose) => /* @__PURE__ */ jsx( UpdateInlineImageDialog, { activeEditor: editor, nodeKey, onClose } )); }, children: "Edit" } ), /* @__PURE__ */ jsx( LazyImage, { className: `max-w-full cursor-default ${isFocused ? `${$isNodeSelection(selection) ? "draggable cursor-grab active:cursor-grabbing" : ""} focused ring-2 ring-primary ring-offset-2` : null}`, src, altText, imageRef, width, height, position } ) ] }), showCaption && /* @__PURE__ */ jsx("div", { className: "image-caption-container absolute bottom-1 left-0 right-0 m-0 block min-w-[100px] overflow-hidden border-t bg-background-system-body-primary/90 p-0", children: /* @__PURE__ */ jsxs(f, { initialEditor: caption, children: [ /* @__PURE__ */ jsx(o$1, {}), /* @__PURE__ */ jsx(LinkPlugin, {}), /* @__PURE__ */ jsx( h, { contentEditable: /* @__PURE__ */ jsx( ContentEditable, { placeholder: "Enter a caption...", className: "ImageNode__contentEditable user-select-text word-break-break-word relative block min-h-5 w-[calc(100%-20px)] cursor-text resize-none whitespace-pre-wrap border-0 p-2.5 text-sm caret-primary outline-none", placeholderClassName: "ImageNode__placeholder text-sm text-content-system-global-secondary overflow-hidden absolute top-2.5 left-2.5 pointer-events-none text-ellipsis user-select-none whitespace-nowrap inline-block" } ), ErrorBoundary: a$1 } ) ] }) }) ] }), modal ] }); } export { UpdateInlineImageDialog, InlineImageComponent as default }; //# sourceMappingURL=inline-image-component-D2wlU4Ob.js.map