@team-monolith/cds 1.3.2 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +2 -2
  2. package/dist/CodleDesignSystemProvider.d.ts +3 -0
  3. package/dist/index.d.ts +1 -0
  4. package/dist/index.js +1 -0
  5. package/dist/patterns/LexicalEditor/LexicalEditor.d.ts +14 -0
  6. package/dist/patterns/LexicalEditor/LexicalEditor.js +39 -0
  7. package/dist/patterns/LexicalEditor/Plugins.d.ts +10 -0
  8. package/dist/patterns/LexicalEditor/Plugins.js +81 -0
  9. package/dist/patterns/LexicalEditor/hr.svg +3 -0
  10. package/dist/patterns/LexicalEditor/index.d.ts +2 -0
  11. package/dist/patterns/LexicalEditor/index.js +2 -0
  12. package/dist/patterns/LexicalEditor/nodes/ImageComponent.d.ts +18 -0
  13. package/dist/patterns/LexicalEditor/nodes/ImageComponent.js +143 -0
  14. package/dist/patterns/LexicalEditor/nodes/ImageNode.d.ts +56 -0
  15. package/dist/patterns/LexicalEditor/nodes/ImageNode.js +117 -0
  16. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdder.d.ts +10 -0
  17. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdder.js +68 -0
  18. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.d.ts +11 -0
  19. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/ComponentAdderPlugin.js +214 -0
  20. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/getContextMenuOptions.d.ts +6 -0
  21. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/getContextMenuOptions.js +90 -0
  22. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/index.d.ts +1 -0
  23. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/index.js +1 -0
  24. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/menu.svg +8 -0
  25. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/plus.svg +3 -0
  26. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useDraggableBlockMenu.d.ts +16 -0
  27. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useDraggableBlockMenu.js +234 -0
  28. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useFloatingMenu.d.ts +20 -0
  29. package/dist/patterns/LexicalEditor/plugins/ComponentAdderPlugin/useFloatingMenu.js +158 -0
  30. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuItem.d.ts +10 -0
  31. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuItem.js +40 -0
  32. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.d.ts +9 -0
  33. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuList.js +46 -0
  34. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.d.ts +26 -0
  35. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/ComponentPickerMenuPlugin.js +174 -0
  36. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/index.d.ts +3 -0
  37. package/dist/patterns/LexicalEditor/plugins/ComponentPickerMenuPlugin/index.js +3 -0
  38. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/FloatingLinkEditor.d.ts +11 -0
  39. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/FloatingLinkEditor.js +201 -0
  40. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/index.d.ts +6 -0
  41. package/dist/patterns/LexicalEditor/plugins/FloatingLinkEditorPlugin/index.js +62 -0
  42. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/FloatingTextFormatPopup.d.ts +13 -0
  43. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/FloatingTextFormatPopup.js +60 -0
  44. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/index.d.ts +4 -0
  45. package/dist/patterns/LexicalEditor/plugins/FloatingTextFormatToolbarPlugin/index.js +190 -0
  46. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageDialog.d.ts +7 -0
  47. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageDialog.js +39 -0
  48. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUploadedDialogBody.d.ts +5 -0
  49. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUploadedDialogBody.js +49 -0
  50. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUriDialogBody.d.ts +5 -0
  51. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/InsertImageUriDialogBody.js +24 -0
  52. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/index.d.ts +14 -0
  53. package/dist/patterns/LexicalEditor/plugins/ImagesPlugin/index.js +151 -0
  54. package/dist/patterns/LexicalEditor/plugins/ListMaxIndentLevelPlugin/index.d.ts +12 -0
  55. package/dist/patterns/LexicalEditor/plugins/ListMaxIndentLevelPlugin/index.js +49 -0
  56. package/dist/patterns/LexicalEditor/plugins/MarkdownTransformers/index.d.ts +12 -0
  57. package/dist/patterns/LexicalEditor/plugins/MarkdownTransformers/index.js +184 -0
  58. package/dist/patterns/LexicalEditor/theme.d.ts +24 -0
  59. package/dist/patterns/LexicalEditor/theme.js +178 -0
  60. package/dist/patterns/LexicalEditor/utils/getDOMRangeRect.d.ts +8 -0
  61. package/dist/patterns/LexicalEditor/utils/getDOMRangeRect.js +22 -0
  62. package/dist/patterns/LexicalEditor/utils/getSelectedNode.d.ts +2 -0
  63. package/dist/patterns/LexicalEditor/utils/getSelectedNode.js +24 -0
  64. package/dist/patterns/LexicalEditor/utils/guard.d.ts +8 -0
  65. package/dist/patterns/LexicalEditor/utils/guard.js +10 -0
  66. package/dist/patterns/LexicalEditor/utils/point.d.ts +21 -0
  67. package/dist/patterns/LexicalEditor/utils/point.js +41 -0
  68. package/dist/patterns/LexicalEditor/utils/rect.d.ts +45 -0
  69. package/dist/patterns/LexicalEditor/utils/rect.js +99 -0
  70. package/dist/patterns/LexicalEditor/utils/setFloatingElemPosition.d.ts +1 -0
  71. package/dist/patterns/LexicalEditor/utils/setFloatingElemPosition.js +36 -0
  72. package/dist/patterns/LexicalEditor/utils/setFloatingElemPositionForLinkEditor.d.ts +1 -0
  73. package/dist/patterns/LexicalEditor/utils/setFloatingElemPositionForLinkEditor.js +32 -0
  74. package/dist/patterns/LexicalEditor/utils/url.d.ts +9 -0
  75. package/dist/patterns/LexicalEditor/utils/url.js +34 -0
  76. package/dist/patterns/SegmentedControl/SegmentedControlButton.js +19 -6
  77. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.d.ts +1 -1
  78. package/dist/patterns/SegmentedControl/SegmentedControlSquareButton.js +35 -17
  79. package/package.json +6 -3
@@ -0,0 +1,214 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "@emotion/react/jsx-runtime";
11
+ /**
12
+ * + 버튼을 눌러 컴포넌트를 추가하는 플러그인
13
+ * ComponentPickerPlugin과 동일한 메뉴가 노출됩니다.
14
+ */
15
+ /** @jsxImportSource @emotion/react */
16
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
17
+ import { useCallback, useEffect, useRef, useState } from "react";
18
+ import { $getNearestNodeFromDOMNode, $createParagraphNode, $isParagraphNode, $isElementNode, $isTextNode, $getSelection, $isRangeSelection, } from "lexical";
19
+ import ReactDOM, { createPortal } from "react-dom";
20
+ // LexicalNodeMenuPlugin는 특정 Node 옆에 메뉴를 노출시키는 플러그인
21
+ // 공식 문서에는 아무 내용도 없음...
22
+ import { LexicalNodeMenuPlugin } from "@lexical/react/LexicalNodeMenuPlugin";
23
+ import { useDraggableBlockMenu } from "./useDraggableBlockMenu";
24
+ import { css as cssToClassName } from "@emotion/css";
25
+ import { ComponentPickerMenuList, getBaseOptions, } from "../ComponentPickerMenuPlugin";
26
+ import { useFloatingMenu } from "./useFloatingMenu";
27
+ import ComponentAdder from "./ComponentAdder";
28
+ import styled from "@emotion/styled";
29
+ import { getContextMenuOptions } from "./getContextMenuOptions";
30
+ import { InsertImageDialog } from "../ImagesPlugin/InsertImageDialog";
31
+ export const COMPONENT_ADDER_MENU_CLASSNAME = "component-adder-menu";
32
+ function isOnMenu(element) {
33
+ return !!element.closest(`.${COMPONENT_ADDER_MENU_CLASSNAME}`);
34
+ }
35
+ // https://github.com/facebook/lexical/blob/4d2e3102b90d09662cbd70b014464be0ff80c7a0/packages/lexical-react/src/LexicalTypeaheadMenuPlugin.tsx#L37
36
+ function getTextUpToAnchor(selection) {
37
+ const anchor = selection.anchor;
38
+ if (anchor.type !== "text") {
39
+ return null;
40
+ }
41
+ const anchorNode = anchor.getNode();
42
+ if (!anchorNode.isSimpleText()) {
43
+ return null;
44
+ }
45
+ const anchorOffset = anchor.offset;
46
+ return anchorNode.getTextContent().slice(0, anchorOffset);
47
+ }
48
+ export function ComponentAdderPlugin(props) {
49
+ const { anchorElem } = props;
50
+ const [editor] = useLexicalComposerContext();
51
+ const [nodeKey, setNodeKey] = useState(null);
52
+ const menuRef = useRef(null);
53
+ const { blockElem, setBlockElem } = useFloatingMenu({
54
+ anchorElem,
55
+ menuRef,
56
+ isOnMenu,
57
+ });
58
+ const [plusOrMenu, setPlusOrMenu] = useState("plus");
59
+ const [options, setOptions] = useState([]);
60
+ const [open, setOpen] = useState(false);
61
+ const [query, setQuery] = useState("");
62
+ // EditorState의 Selection 을 추적하여
63
+ // Plus 및 Menu 버튼을 누르고 다른 곳을 클릭하면 메뉴를 닫습니다.
64
+ // 텍스트를 입력했다면 옵션 검색에 활용합니다.
65
+ useEffect(() => {
66
+ const updateListener = () => {
67
+ editor.getEditorState().read(() => {
68
+ var _a;
69
+ const selection = $getSelection();
70
+ if ($isRangeSelection(selection)) {
71
+ // 다른 노드를 선택하면 메뉴를 닫습니다. (nodeKey를 null로 변경)
72
+ // selection 에 포함된 모든 node가 nodeKey와 일치하지 않을 때
73
+ // 비교할 때는 selection이 Nested 하게 있을 수 있어 Root 바로 아래의 Node를 찾아 비교합니다.
74
+ const nodes = selection.getNodes();
75
+ const isAllNodesNotMatched = nodes.every((node) => {
76
+ const nodeAndParents = [node, ...node.getParents()];
77
+ return (nodeAndParents[nodeAndParents.length - 2].getKey() !== nodeKey);
78
+ });
79
+ if (isAllNodesNotMatched) {
80
+ setQuery("");
81
+ setNodeKey(null);
82
+ return;
83
+ }
84
+ // Plus 버튼을 누르고 있고
85
+ // 같은 노드를 선택하고 있다면 텍스트를 검색합니다.
86
+ if (plusOrMenu === "plus") {
87
+ setQuery((_a = getTextUpToAnchor(selection)) !== null && _a !== void 0 ? _a : "");
88
+ }
89
+ }
90
+ else {
91
+ setQuery("");
92
+ setNodeKey(null);
93
+ }
94
+ });
95
+ };
96
+ const removeUpdateListener = editor.registerUpdateListener(updateListener);
97
+ return () => {
98
+ removeUpdateListener();
99
+ };
100
+ }, [editor, nodeKey, plusOrMenu]);
101
+ const onSelectOption = useCallback((selectedOption, textNodeContainingQuery, closeMenu, matchingString) => {
102
+ editor.update(() => {
103
+ selectedOption.onSelect(matchingString);
104
+ setNodeKey(null);
105
+ closeMenu();
106
+ });
107
+ }, [editor]);
108
+ const [resolves, setResolves] = useState([]);
109
+ const onClose = () => __awaiter(this, void 0, void 0, function* () {
110
+ resolves.forEach((resolve) => resolve());
111
+ setResolves((newResolves) => {
112
+ return newResolves.filter((newResolve) => resolves.indexOf(newResolve) === -1);
113
+ });
114
+ });
115
+ const filteredOptions = options.filter((option) => {
116
+ const regex = new RegExp(query, "i");
117
+ return (regex.test(option.title) ||
118
+ option.keywords.some((keyword) => regex.test(keyword)));
119
+ });
120
+ const { onDragStart, onDragEnd, targetLineRef } = useDraggableBlockMenu(editor, anchorElem, blockElem, setBlockElem);
121
+ return (_jsxs(_Fragment, { children: [_jsx(InsertImageDialog, { open: open, activeEditor: editor, onClose: () => setOpen(false) }), _jsx(LexicalNodeMenuPlugin, { nodeKey: nodeKey, anchorClassName: cssToClassName `
122
+ // TODO 어드민에서 기본 z-index가 1이라 추가함. 없앨 수도 있음
123
+ z-index: 1;
124
+ `, options: filteredOptions, menuRenderFn: (anchorElementRef, { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex }) => anchorElementRef.current && filteredOptions.length
125
+ ? ReactDOM.createPortal(_jsx(ComponentPickerMenuList, { options: filteredOptions, selectedIndex: selectedIndex, selectOptionAndCleanUp: selectOptionAndCleanUp, setHighlightedIndex: setHighlightedIndex }), anchorElementRef.current)
126
+ : null, onSelectOption: onSelectOption, onClose: () => {
127
+ setNodeKey(null);
128
+ onClose();
129
+ } }), createPortal(_jsxs(_Fragment, { children: [_jsx(ComponentAdder, { ref: menuRef, className: COMPONENT_ADDER_MENU_CLASSNAME, onPlusClick: () => __awaiter(this, void 0, void 0, function* () {
130
+ if (!blockElem) {
131
+ return;
132
+ }
133
+ let newNodeKey = null;
134
+ editor.update(() => {
135
+ const selectedNode = $getNearestNodeFromDOMNode(blockElem);
136
+ if (selectedNode) {
137
+ if ($isParagraphNode(selectedNode) &&
138
+ selectedNode.isEmpty()) {
139
+ newNodeKey = selectedNode.getKey();
140
+ selectedNode.selectStart();
141
+ }
142
+ else {
143
+ const insertedNode = $createParagraphNode();
144
+ selectedNode.insertAfter(insertedNode);
145
+ insertedNode.selectStart();
146
+ newNodeKey = insertedNode.getKey();
147
+ insertedNode.selectStart();
148
+ }
149
+ }
150
+ });
151
+ if (!newNodeKey) {
152
+ return;
153
+ }
154
+ if (nodeKey === null) {
155
+ setNodeKey(newNodeKey);
156
+ }
157
+ else {
158
+ const promise = new Promise((resolve) => {
159
+ setResolves((newResolves) => [...newResolves, resolve]);
160
+ });
161
+ // TODO: LexicalNodeMenuPlugin에 기여
162
+ // setNodeKey(newNodeKey) 만으로 새로운 위치에 모달이 열리지 않는 문제.
163
+ setNodeKey(null);
164
+ yield promise;
165
+ setNodeKey(newNodeKey);
166
+ }
167
+ const baseOptions = getBaseOptions(editor, setOpen);
168
+ setOptions(baseOptions);
169
+ setPlusOrMenu("plus");
170
+ }), onMenuClick: () => __awaiter(this, void 0, void 0, function* () {
171
+ if (!blockElem) {
172
+ return;
173
+ }
174
+ let newNode = null;
175
+ editor.update(() => __awaiter(this, void 0, void 0, function* () {
176
+ newNode = $getNearestNodeFromDOMNode(blockElem);
177
+ if (!newNode) {
178
+ return;
179
+ }
180
+ if ($isElementNode(newNode)) {
181
+ newNode.selectStart();
182
+ }
183
+ else if ($isTextNode(newNode)) {
184
+ newNode.select();
185
+ }
186
+ const contextMenuOptions = getContextMenuOptions(editor, newNode, setOpen);
187
+ setOptions(contextMenuOptions);
188
+ if (nodeKey === null) {
189
+ setNodeKey(newNode.getKey());
190
+ }
191
+ else {
192
+ const promise = new Promise((resolve) => {
193
+ setResolves((newResolves) => [...newResolves, resolve]);
194
+ });
195
+ // TODO: LexicalNodeMenuPlugin에 기여
196
+ // setNodeKey(newNodeKey) 만으로 새로운 위치에 모달이 열리지 않는 문제.
197
+ setNodeKey(null);
198
+ yield promise;
199
+ setNodeKey(newNode.getKey());
200
+ }
201
+ setPlusOrMenu("menu");
202
+ }));
203
+ }), onMenuDragStart: onDragStart, onMenuDragEnd: onDragEnd }), _jsx(TargetLine, { ref: targetLineRef })] }), anchorElem)] }));
204
+ }
205
+ const TargetLine = styled.div `
206
+ pointer-events: none;
207
+ background: deepskyblue;
208
+ height: 4px;
209
+ position: absolute;
210
+ left: 0;
211
+ top: 0;
212
+ opacity: 0;
213
+ will-change: transform;
214
+ `;
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Context Menu (:: 버튼)
3
+ */
4
+ import { LexicalEditor, LexicalNode } from "lexical";
5
+ import { ComponentPickerOption } from "../ComponentPickerMenuPlugin";
6
+ export declare function getContextMenuOptions(editor: LexicalEditor, node: LexicalNode, setOpen: (open: boolean) => void): ComponentPickerOption[];
@@ -0,0 +1,90 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * Context Menu (:: 버튼)
4
+ */
5
+ import { $getSelection, $isRangeSelection, } from "lexical";
6
+ import { ComponentPickerOption, getBaseOptions, } from "../ComponentPickerMenuPlugin";
7
+ import { $createHeadingNode, HeadingNode } from "@lexical/rich-text";
8
+ import { $setBlocksType } from "@lexical/selection";
9
+ import { ListItemNode, ListNode, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, } from "@lexical/list";
10
+ import { H1Icon, H2Icon, H3Icon, CloseFillIcon, ListUnorderedIcon, ListOrderedIcon, } from "../../../../icons";
11
+ function getHeadingContextMenuOptions(editor, node, setOpen) {
12
+ return [
13
+ ...[1, 2, 3].map((n) => {
14
+ const titleMap = {
15
+ 1: "큰 제목",
16
+ 2: "중간 제목",
17
+ 3: "작은 제목",
18
+ };
19
+ const iconMap = {
20
+ 1: _jsx(H1Icon, {}),
21
+ 2: _jsx(H2Icon, {}),
22
+ 3: _jsx(H3Icon, {}),
23
+ };
24
+ return new ComponentPickerOption(titleMap[n], {
25
+ icon: iconMap[n],
26
+ keywords: ["heading", "header", `h${n}`, titleMap[n]],
27
+ onSelect: () => editor.update(() => {
28
+ const selection = $getSelection();
29
+ if ($isRangeSelection(selection)) {
30
+ $setBlocksType(selection, () => $createHeadingNode(`h${n}`));
31
+ }
32
+ }),
33
+ });
34
+ }),
35
+ new ComponentPickerOption("블록 삭제", {
36
+ icon: _jsx(CloseFillIcon, {}),
37
+ keywords: [],
38
+ onSelect: () => {
39
+ editor.update(() => {
40
+ node.remove();
41
+ });
42
+ },
43
+ }),
44
+ ];
45
+ }
46
+ function getListContextMenuOptions(editor, node, setOpen) {
47
+ return [
48
+ new ComponentPickerOption("일반 목록", {
49
+ icon: _jsx(ListUnorderedIcon, {}),
50
+ keywords: ["bulleted list", "unordered list", "ul"],
51
+ onSelect: () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),
52
+ }),
53
+ new ComponentPickerOption("숫자 목록", {
54
+ icon: _jsx(ListOrderedIcon, {}),
55
+ keywords: ["numbered list", "ordered list", "ol"],
56
+ onSelect: () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),
57
+ }),
58
+ new ComponentPickerOption("블록 삭제", {
59
+ icon: _jsx(CloseFillIcon, {}),
60
+ keywords: [],
61
+ onSelect: () => {
62
+ editor.update(() => {
63
+ node.remove();
64
+ });
65
+ },
66
+ }),
67
+ ];
68
+ }
69
+ export function getContextMenuOptions(editor, node, setOpen) {
70
+ if (node instanceof HeadingNode) {
71
+ return getHeadingContextMenuOptions(editor, node, setOpen);
72
+ }
73
+ else if (node instanceof ListNode || node instanceof ListItemNode) {
74
+ return getListContextMenuOptions(editor, node, setOpen);
75
+ }
76
+ else {
77
+ return [
78
+ ...getBaseOptions(editor, setOpen),
79
+ new ComponentPickerOption("블록 삭제", {
80
+ icon: _jsx(CloseFillIcon, {}),
81
+ keywords: [],
82
+ onSelect: () => {
83
+ editor.update(() => {
84
+ node.remove();
85
+ });
86
+ },
87
+ }),
88
+ ];
89
+ }
90
+ }
@@ -0,0 +1 @@
1
+ export * from "./ComponentAdderPlugin";
@@ -0,0 +1 @@
1
+ export * from "./ComponentAdderPlugin";
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
2
+ <path d="M9.41002 7.29999H9.40002" stroke="#363636" stroke-width="2.6" stroke-linecap="round"/>
3
+ <path d="M14.6 7.29999H14.59" stroke="#363636" stroke-width="2.6" stroke-linecap="round"/>
4
+ <path d="M9.31004 12H9.30005" stroke="#363636" stroke-width="2.6" stroke-linecap="round"/>
5
+ <path d="M14.6 12H14.59" stroke="#363636" stroke-width="2.6" stroke-linecap="round"/>
6
+ <path d="M9.41002 16.7H9.40002" stroke="#363636" stroke-width="2.6" stroke-linecap="round"/>
7
+ <path d="M14.6 16.7H14.59" stroke="#363636" stroke-width="2.6" stroke-linecap="round"/>
8
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
2
+ <path d="M12 7V12M12 12V17M12 12H17M12 12H7" stroke="#363636" stroke-width="2" stroke-linecap="round"/>
3
+ </svg>
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ /** @jsxImportSource @emotion/react */
9
+ import { LexicalEditor } from "lexical";
10
+ import { DragEvent as ReactDragEvent } from "react";
11
+ export declare function getBlockElement(anchorElem: HTMLElement, editor: LexicalEditor, event: MouseEvent, useEdgeAsDefault?: boolean): HTMLElement | null;
12
+ export declare function useDraggableBlockMenu(editor: LexicalEditor, anchorElem: HTMLElement, blockElem: HTMLElement | null, setBlockElem: (blockElem: HTMLElement | null) => void): {
13
+ onDragStart: (event: ReactDragEvent<HTMLDivElement>) => void;
14
+ onDragEnd: () => void;
15
+ targetLineRef: import("react").RefObject<HTMLDivElement>;
16
+ };
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ /** @jsxImportSource @emotion/react */
9
+ import { eventFiles } from "@lexical/rich-text";
10
+ import { mergeRegister } from "@lexical/utils";
11
+ import { $getNearestNodeFromDOMNode, $getNodeByKey, $getRoot, COMMAND_PRIORITY_HIGH, COMMAND_PRIORITY_LOW, DRAGOVER_COMMAND, DROP_COMMAND, } from "lexical";
12
+ import { useEffect, useRef } from "react";
13
+ import { isHTMLElement } from "../../utils/guard";
14
+ import { Point } from "../../utils/point";
15
+ import { Rect } from "../../utils/rect";
16
+ const SPACE = 4;
17
+ const TARGET_LINE_HALF_HEIGHT = 2;
18
+ const DRAG_DATA_FORMAT = "application/x-lexical-drag-block";
19
+ const TEXT_BOX_HORIZONTAL_PADDING = 28;
20
+ const Downward = 1;
21
+ const Upward = -1;
22
+ const Indeterminate = 0;
23
+ let prevIndex = Infinity;
24
+ function getCurrentIndex(keysLength) {
25
+ if (keysLength === 0) {
26
+ return Infinity;
27
+ }
28
+ if (prevIndex >= 0 && prevIndex < keysLength) {
29
+ return prevIndex;
30
+ }
31
+ return Math.floor(keysLength / 2);
32
+ }
33
+ function getTopLevelNodeKeys(editor) {
34
+ return editor.getEditorState().read(() => $getRoot().getChildrenKeys());
35
+ }
36
+ function getCollapsedMargins(elem) {
37
+ const getMargin = (element, margin) => element ? parseFloat(window.getComputedStyle(element)[margin]) : 0;
38
+ const { marginTop, marginBottom } = window.getComputedStyle(elem);
39
+ const prevElemSiblingMarginBottom = getMargin(elem.previousElementSibling, "marginBottom");
40
+ const nextElemSiblingMarginTop = getMargin(elem.nextElementSibling, "marginTop");
41
+ const collapsedTopMargin = Math.max(parseFloat(marginTop), prevElemSiblingMarginBottom);
42
+ const collapsedBottomMargin = Math.max(parseFloat(marginBottom), nextElemSiblingMarginTop);
43
+ return { marginBottom: collapsedBottomMargin, marginTop: collapsedTopMargin };
44
+ }
45
+ export function getBlockElement(anchorElem, editor, event, useEdgeAsDefault = false) {
46
+ const anchorElementRect = anchorElem.getBoundingClientRect();
47
+ const topLevelNodeKeys = getTopLevelNodeKeys(editor);
48
+ let blockElem = null;
49
+ editor.getEditorState().read(() => {
50
+ if (useEdgeAsDefault) {
51
+ const [firstNode, lastNode] = [
52
+ editor.getElementByKey(topLevelNodeKeys[0]),
53
+ editor.getElementByKey(topLevelNodeKeys[topLevelNodeKeys.length - 1]),
54
+ ];
55
+ const [firstNodeRect, lastNodeRect] = [
56
+ firstNode === null || firstNode === void 0 ? void 0 : firstNode.getBoundingClientRect(),
57
+ lastNode === null || lastNode === void 0 ? void 0 : lastNode.getBoundingClientRect(),
58
+ ];
59
+ if (firstNodeRect && lastNodeRect) {
60
+ if (event.y < firstNodeRect.top) {
61
+ blockElem = firstNode;
62
+ }
63
+ else if (event.y > lastNodeRect.bottom) {
64
+ blockElem = lastNode;
65
+ }
66
+ if (blockElem) {
67
+ return;
68
+ }
69
+ }
70
+ }
71
+ let index = getCurrentIndex(topLevelNodeKeys.length);
72
+ let direction = Indeterminate;
73
+ while (index >= 0 && index < topLevelNodeKeys.length) {
74
+ const key = topLevelNodeKeys[index];
75
+ const elem = editor.getElementByKey(key);
76
+ if (elem === null) {
77
+ break;
78
+ }
79
+ const point = new Point(event.x, event.y);
80
+ const domRect = Rect.fromDOM(elem);
81
+ const { marginTop, marginBottom } = getCollapsedMargins(elem);
82
+ const rect = domRect.generateNewRect({
83
+ bottom: domRect.bottom + marginBottom,
84
+ left: anchorElementRect.left,
85
+ right: anchorElementRect.right,
86
+ top: domRect.top - marginTop,
87
+ });
88
+ const { result, reason: { isOnTopSide, isOnBottomSide }, } = rect.contains(point);
89
+ if (result) {
90
+ blockElem = elem;
91
+ prevIndex = index;
92
+ break;
93
+ }
94
+ if (direction === Indeterminate) {
95
+ if (isOnTopSide) {
96
+ direction = Upward;
97
+ }
98
+ else if (isOnBottomSide) {
99
+ direction = Downward;
100
+ }
101
+ else {
102
+ // stop search block element
103
+ direction = Infinity;
104
+ }
105
+ }
106
+ index += direction;
107
+ }
108
+ });
109
+ return blockElem;
110
+ }
111
+ function setDragImage(dataTransfer, draggableBlockElem) {
112
+ const { transform } = draggableBlockElem.style;
113
+ // Remove dragImage borders
114
+ draggableBlockElem.style.transform = "translateZ(0)";
115
+ dataTransfer.setDragImage(draggableBlockElem, 0, 0);
116
+ setTimeout(() => {
117
+ draggableBlockElem.style.transform = transform;
118
+ });
119
+ }
120
+ function setTargetLine(targetLineElem, targetBlockElem, mouseY, anchorElem) {
121
+ const { top: targetBlockElemTop, height: targetBlockElemHeight } = targetBlockElem.getBoundingClientRect();
122
+ const { top: anchorTop, width: anchorWidth } = anchorElem.getBoundingClientRect();
123
+ const { marginTop, marginBottom } = getCollapsedMargins(targetBlockElem);
124
+ let lineTop = targetBlockElemTop;
125
+ if (mouseY >= targetBlockElemTop) {
126
+ lineTop += targetBlockElemHeight + marginBottom / 2;
127
+ }
128
+ else {
129
+ lineTop -= marginTop / 2;
130
+ }
131
+ const top = lineTop - anchorTop - TARGET_LINE_HALF_HEIGHT;
132
+ const left = TEXT_BOX_HORIZONTAL_PADDING - SPACE;
133
+ targetLineElem.style.transform = `translate(${left}px, ${top}px)`;
134
+ targetLineElem.style.width = `${anchorWidth - (TEXT_BOX_HORIZONTAL_PADDING - SPACE) * 2}px`;
135
+ targetLineElem.style.opacity = ".4";
136
+ }
137
+ function hideTargetLine(targetLineElem) {
138
+ if (targetLineElem) {
139
+ targetLineElem.style.opacity = "0";
140
+ targetLineElem.style.transform = "translate(-10000px, -10000px)";
141
+ }
142
+ }
143
+ export function useDraggableBlockMenu(editor, anchorElem, blockElem, setBlockElem) {
144
+ const targetLineRef = useRef(null);
145
+ const isDraggingBlockRef = useRef(false);
146
+ useEffect(() => {
147
+ function onDragover(event) {
148
+ if (!isDraggingBlockRef.current) {
149
+ return false;
150
+ }
151
+ const [isFileTransfer] = eventFiles(event);
152
+ if (isFileTransfer) {
153
+ return false;
154
+ }
155
+ const { pageY, target } = event;
156
+ if (!isHTMLElement(target)) {
157
+ return false;
158
+ }
159
+ const targetBlockElem = getBlockElement(anchorElem, editor, event, true);
160
+ const targetLineElem = targetLineRef.current;
161
+ if (targetBlockElem === null || targetLineElem === null) {
162
+ return false;
163
+ }
164
+ setTargetLine(targetLineElem, targetBlockElem, pageY, anchorElem);
165
+ // Prevent default event to be able to trigger onDrop events
166
+ event.preventDefault();
167
+ return true;
168
+ }
169
+ function onDrop(event) {
170
+ if (!isDraggingBlockRef.current) {
171
+ return false;
172
+ }
173
+ const [isFileTransfer] = eventFiles(event);
174
+ if (isFileTransfer) {
175
+ return false;
176
+ }
177
+ const { target, dataTransfer, pageY } = event;
178
+ const dragData = (dataTransfer === null || dataTransfer === void 0 ? void 0 : dataTransfer.getData(DRAG_DATA_FORMAT)) || "";
179
+ const draggedNode = $getNodeByKey(dragData);
180
+ if (!draggedNode) {
181
+ return false;
182
+ }
183
+ if (!isHTMLElement(target)) {
184
+ return false;
185
+ }
186
+ const targetBlockElem = getBlockElement(anchorElem, editor, event, true);
187
+ if (!targetBlockElem) {
188
+ return false;
189
+ }
190
+ const targetNode = $getNearestNodeFromDOMNode(targetBlockElem);
191
+ if (!targetNode) {
192
+ return false;
193
+ }
194
+ if (targetNode === draggedNode) {
195
+ return true;
196
+ }
197
+ const targetBlockElemTop = targetBlockElem.getBoundingClientRect().top;
198
+ if (pageY >= targetBlockElemTop) {
199
+ targetNode.insertAfter(draggedNode);
200
+ }
201
+ else {
202
+ targetNode.insertBefore(draggedNode);
203
+ }
204
+ setBlockElem(null);
205
+ return true;
206
+ }
207
+ return mergeRegister(editor.registerCommand(DRAGOVER_COMMAND, (event) => {
208
+ return onDragover(event);
209
+ }, COMMAND_PRIORITY_LOW), editor.registerCommand(DROP_COMMAND, (event) => {
210
+ return onDrop(event);
211
+ }, COMMAND_PRIORITY_HIGH));
212
+ }, [anchorElem, editor, setBlockElem]);
213
+ function onDragStart(event) {
214
+ const dataTransfer = event.dataTransfer;
215
+ if (!dataTransfer || !blockElem) {
216
+ return;
217
+ }
218
+ setDragImage(dataTransfer, blockElem);
219
+ let nodeKey = "";
220
+ editor.update(() => {
221
+ const node = $getNearestNodeFromDOMNode(blockElem);
222
+ if (node) {
223
+ nodeKey = node.getKey();
224
+ }
225
+ });
226
+ isDraggingBlockRef.current = true;
227
+ dataTransfer.setData(DRAG_DATA_FORMAT, nodeKey);
228
+ }
229
+ function onDragEnd() {
230
+ isDraggingBlockRef.current = false;
231
+ hideTargetLine(targetLineRef.current);
232
+ }
233
+ return { onDragStart, onDragEnd, targetLineRef };
234
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ /** @jsxImportSource @emotion/react */
9
+ /// <reference types="react" />
10
+ import { LexicalEditor } from "lexical";
11
+ export declare function getBlockElement(anchorElem: HTMLElement, editor: LexicalEditor, event: MouseEvent, useEdgeAsDefault?: boolean): HTMLElement | null;
12
+ export interface UseFloatingMenuProps {
13
+ anchorElem: HTMLElement;
14
+ menuRef: React.RefObject<HTMLDivElement>;
15
+ isOnMenu: (element: HTMLElement) => boolean;
16
+ }
17
+ export declare function useFloatingMenu(props: UseFloatingMenuProps): {
18
+ blockElem: HTMLElement | null;
19
+ setBlockElem: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
20
+ };