@block-kit/react 1.0.4 → 1.0.6

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 (39) hide show
  1. package/dist/es/hooks/use-editor.js +1 -0
  2. package/dist/es/hooks/use-readonly.js +1 -0
  3. package/dist/es/index.d.ts +1 -0
  4. package/dist/es/index.js +1 -0
  5. package/dist/es/model/block.js +8 -0
  6. package/dist/es/model/leaf.js +1 -1
  7. package/dist/es/model/line.js +10 -1
  8. package/dist/es/model/portal.d.ts +5 -0
  9. package/dist/es/model/portal.js +9 -0
  10. package/dist/es/preset/block-kit.js +3 -2
  11. package/dist/es/preset/editable.d.ts +6 -0
  12. package/dist/es/preset/editable.js +4 -3
  13. package/dist/es/preset/text.d.ts +3 -2
  14. package/dist/es/preset/text.js +4 -10
  15. package/dist/es/preset/void.js +2 -1
  16. package/dist/es/preset/zero.d.ts +4 -3
  17. package/dist/es/preset/zero.js +3 -5
  18. package/dist/es/utils/dom.d.ts +36 -0
  19. package/dist/es/utils/dom.js +57 -0
  20. package/dist/lib/hooks/use-editor.js +1 -0
  21. package/dist/lib/hooks/use-readonly.js +1 -0
  22. package/dist/lib/index.d.ts +1 -0
  23. package/dist/lib/index.js +3 -1
  24. package/dist/lib/model/block.js +8 -0
  25. package/dist/lib/model/leaf.js +1 -1
  26. package/dist/lib/model/line.js +10 -1
  27. package/dist/lib/model/portal.d.ts +5 -0
  28. package/dist/lib/model/portal.js +35 -0
  29. package/dist/lib/preset/block-kit.js +2 -1
  30. package/dist/lib/preset/editable.d.ts +6 -0
  31. package/dist/lib/preset/editable.js +4 -3
  32. package/dist/lib/preset/text.d.ts +3 -2
  33. package/dist/lib/preset/text.js +4 -9
  34. package/dist/lib/preset/void.js +2 -1
  35. package/dist/lib/preset/zero.d.ts +4 -3
  36. package/dist/lib/preset/zero.js +4 -5
  37. package/dist/lib/utils/dom.d.ts +36 -0
  38. package/dist/lib/utils/dom.js +60 -0
  39. package/package.json +18 -6
@@ -1,5 +1,6 @@
1
1
  import React, { createContext } from "react";
2
2
  export const BlockKitContext = createContext(null);
3
+ BlockKitContext.displayName = "BlockKit";
3
4
  export const useEditorStatic = () => {
4
5
  const editor = React.useContext(BlockKitContext);
5
6
  if (!editor) {
@@ -1,5 +1,6 @@
1
1
  import React, { createContext } from "react";
2
2
  export const ReadonlyContext = createContext(false);
3
+ ReadonlyContext.displayName = "Readonly";
3
4
  export const useReadonly = () => {
4
5
  const readonly = React.useContext(ReadonlyContext);
5
6
  return { readonly };
@@ -15,4 +15,5 @@ export { Isolate } from "./preset/isolate";
15
15
  export { Text } from "./preset/text";
16
16
  export { Void } from "./preset/void";
17
17
  export { ZeroSpace } from "./preset/zero";
18
+ export { MountNode } from "./utils/dom";
18
19
  export { preventNativeEvent, preventReactEvent } from "./utils/event";
package/dist/es/index.js CHANGED
@@ -14,4 +14,5 @@ export { Isolate } from "./preset/isolate";
14
14
  export { Text } from "./preset/text";
15
15
  export { Void } from "./preset/void";
16
16
  export { ZeroSpace } from "./preset/zero";
17
+ export { MountNode } from "./utils/dom";
17
18
  export { preventNativeEvent, preventReactEvent } from "./utils/event";
@@ -77,6 +77,14 @@ const BlockView = props => {
77
77
  * 处理行节点
78
78
  */
79
79
  const elements = useMemo(() => {
80
+ // 开发模式严格检查数据准确性
81
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
82
+ for (const line of lines) {
83
+ if (line.length < 0 || line.size < 0 || line.index < 0 || line.start < 0 || !line.key) {
84
+ throw new Error("LineState index, size, start or length is not set");
85
+ }
86
+ }
87
+ }
80
88
  return lines.map((line, index) => {
81
89
  const node = (_jsx(LineModel, { editor: editor, lineState: line, index: index }, line.key));
82
90
  JSX_TO_STATE.set(node, line);
@@ -23,7 +23,7 @@ const LeafView = props => {
23
23
  leafState: leafState,
24
24
  attributes: leafState.op.attributes,
25
25
  style: {},
26
- children: _jsx(Text, { ref: el => LT.set(leafState, el), children: text }),
26
+ children: _jsx(Text, { onRef: el => LT.set(leafState, el), children: text }),
27
27
  };
28
28
  const plugins = editor.plugin.getPriorityPlugins(PLUGIN_TYPE.RENDER_LEAF);
29
29
  for (const plugin of plugins) {
@@ -52,10 +52,19 @@ const LineView = props => {
52
52
  * 处理行内的节点
53
53
  */
54
54
  const elements = useMemo(() => {
55
+ // 开发模式下严格检查数据准确性
56
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
57
+ const leaves = lineState.getLeaves();
58
+ for (const leaf of leaves) {
59
+ if (leaf.index === -1 || leaf.offset === -1 || !leaf.key) {
60
+ throw new Error("LeafState index or offset is not set");
61
+ }
62
+ }
63
+ }
55
64
  const leaves = lineState.getLeaves();
56
65
  const textLeaves = leaves.slice(0, -1);
57
66
  const nodes = textLeaves.map((n, i) => {
58
- const node = _jsx(LeafModel, { editor: editor, index: i, leafState: n }, i);
67
+ const node = _jsx(LeafModel, { editor: editor, index: i, leafState: n }, n.key);
59
68
  JSX_TO_STATE.set(node, n);
60
69
  return node;
61
70
  });
@@ -0,0 +1,5 @@
1
+ import type { Editor } from "@block-kit/core";
2
+ import React from "react";
3
+ export declare const PortalModel: React.NamedExoticComponent<{
4
+ editor: Editor;
5
+ }>;
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { Fragment, useState } from "react";
3
+ import { EDITOR_TO_PORTAL } from "../utils/dom";
4
+ const PortalView = props => {
5
+ const [portals, setPortals] = useState({});
6
+ EDITOR_TO_PORTAL.set(props.editor, setPortals);
7
+ return (_jsx(Fragment, { children: Object.entries(portals).map(([key, node]) => (_jsx(Fragment, { children: node }, key))) }));
8
+ };
9
+ export const PortalModel = React.memo(PortalView);
@@ -1,8 +1,9 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { EDITOR_STATE } from "@block-kit/core";
3
3
  import { useMemo } from "react";
4
4
  import { BlockKitContext } from "../hooks/use-editor";
5
5
  import { ReadonlyContext } from "../hooks/use-readonly";
6
+ import { PortalModel } from "../model/portal";
6
7
  import { initWrapPlugins } from "../plugin/modules/wrap";
7
8
  export const BlockKit = props => {
8
9
  const { editor, readonly, children } = props;
@@ -13,5 +14,5 @@ export const BlockKit = props => {
13
14
  // 希望在 Editor 初始化后立即执行一次
14
15
  initWrapPlugins(editor);
15
16
  }, [editor]);
16
- return (_jsx(BlockKitContext.Provider, { value: editor, children: _jsx(ReadonlyContext.Provider, { value: !!readonly, children: children }) }));
17
+ return (_jsx(BlockKitContext.Provider, { value: editor, children: _jsxs(ReadonlyContext.Provider, { value: !!readonly, children: [_jsx(PortalModel, { editor: editor }), children] }) }));
17
18
  };
@@ -13,4 +13,10 @@ export declare const Editable: React.FC<{
13
13
  * - 节点会脱离文档流, 需要注意 CSS 布局
14
14
  */
15
15
  placeholder?: React.ReactNode;
16
+ /**
17
+ * 阻止编辑器主动销毁
18
+ * - 谨慎使用, 编辑器生命周期结束必须主动销毁
19
+ * - 注意保持值不可变, 否则会导致编辑器多次挂载
20
+ */
21
+ preventDestroy?: boolean;
16
22
  }>;
@@ -10,7 +10,7 @@ import { BlockModel } from "../model/block";
10
10
  * @param props
11
11
  */
12
12
  export const Editable = props => {
13
- const { className, autoFocus } = props;
13
+ const { className, autoFocus, preventDestroy } = props;
14
14
  const { editor } = useEditorStatic();
15
15
  const { readonly } = useReadonly();
16
16
  const ref = useRef(null);
@@ -18,9 +18,10 @@ export const Editable = props => {
18
18
  const el = ref.current;
19
19
  el && editor.onMount(el);
20
20
  return () => {
21
- editor.destroy();
21
+ editor.onUnmount();
22
+ !preventDestroy && editor.destroy();
22
23
  };
23
- }, [editor]);
24
+ }, [editor, preventDestroy]);
24
25
  useEffect(() => {
25
26
  // COMPAT: 这里有个奇怪的表现
26
27
  // 当自动聚焦时, 必须要先更新浏览器选区再聚焦
@@ -1,9 +1,10 @@
1
- /// <reference types="react" />
1
+ import type { FC } from "react";
2
2
  export type TextProps = {
3
3
  children: string;
4
+ onRef?: (ref: HTMLSpanElement | null) => void;
4
5
  };
5
6
  /**
6
7
  * 文本节点
7
8
  * @param props
8
9
  */
9
- export declare const Text: import("react").ForwardRefExoticComponent<TextProps & import("react").RefAttributes<HTMLSpanElement>>;
10
+ export declare const Text: FC<TextProps>;
@@ -1,20 +1,14 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { LEAF_STRING } from "@block-kit/core";
3
- import { isDOMText, isFunction } from "@block-kit/utils";
4
- import { forwardRef } from "react";
3
+ import { isDOMText } from "@block-kit/utils";
5
4
  /**
6
5
  * 文本节点
7
6
  * @param props
8
7
  */
9
- export const Text = /*#__PURE__*/ forwardRef((props, ref) => {
8
+ export const Text = props => {
10
9
  const onRef = (dom) => {
11
10
  // 处理外部引用的 ref
12
- if (isFunction(ref)) {
13
- ref(dom);
14
- }
15
- else if (ref) {
16
- ref.current = dom;
17
- }
11
+ props.onRef && props.onRef(dom);
18
12
  // COMPAT: 避免 React 非受控与 IME 造成的 DOM 内容问题
19
13
  if (!dom || props.children === dom.textContent)
20
14
  return void 0;
@@ -31,4 +25,4 @@ export const Text = /*#__PURE__*/ forwardRef((props, ref) => {
31
25
  }
32
26
  };
33
27
  return (_jsx("span", { ref: onRef, [LEAF_STRING]: true, children: props.children }));
34
- });
28
+ };
@@ -13,11 +13,12 @@ export const Void = props => {
13
13
  const { context, tag: Tag = "span" } = props;
14
14
  const { editor } = useEditorStatic();
15
15
  const leaf = context.leafState;
16
- const onMouseDown = () => {
16
+ const onMouseDown = (event) => {
17
17
  // FIX: 修复选区偏移量, leaf 的长度可能 > 1, 而 DOM 节点的长度仅为 1
18
18
  const point = new Point(leaf.parent.index, leaf.offset + 1);
19
19
  const range = new Range(point, point.clone());
20
20
  editor.selection.set(range, true);
21
+ event.preventDefault();
21
22
  };
22
23
  return (_jsxs(React.Fragment, { children: [_jsx(ZeroSpace, { void: true, hide: true, len: leaf.length > 1 ? leaf.length : void 0 }), _jsx(Tag, { className: props.className, style: Object.assign({ userSelect: "none" }, props.style), contentEditable: false, [VOID_KEY]: true, onMouseDown: onMouseDown, children: props.children })] }));
23
24
  };
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import type { FC } from "react";
2
2
  export type ZeroSpaceProps = {
3
3
  /** 隐藏光标 */
4
4
  hide?: boolean;
@@ -10,6 +10,8 @@ export type ZeroSpaceProps = {
10
10
  enter?: boolean;
11
11
  /** 空节点占位长度 */
12
12
  len?: number;
13
+ /** 获取 DOM 引用 */
14
+ onRef?: React.MutableRefObject<HTMLDivElement | null>;
13
15
  };
14
16
  /**
15
17
  * 零宽字符组件
@@ -18,6 +20,5 @@ export type ZeroSpaceProps = {
18
20
  * - enter => 行末尾占位, 保留于 EOLView
19
21
  * - len => 空节点占位长度, 配合 Void/Embed
20
22
  * @param props
21
- * @param ref
22
23
  */
23
- export declare const ZeroSpace: import("react").ForwardRefExoticComponent<ZeroSpaceProps & import("react").RefAttributes<HTMLSpanElement>>;
24
+ export declare const ZeroSpace: FC<ZeroSpaceProps>;
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { VOID_LEN_KEY, ZERO_EMBED_KEY, ZERO_ENTER_KEY, ZERO_SPACE_KEY, ZERO_SYMBOL, ZERO_VOID_KEY, } from "@block-kit/core";
3
- import { forwardRef } from "react";
4
3
  import { NO_CURSOR } from "../utils/constant";
5
4
  /**
6
5
  * 零宽字符组件
@@ -9,12 +8,11 @@ import { NO_CURSOR } from "../utils/constant";
9
8
  * - enter => 行末尾占位, 保留于 EOLView
10
9
  * - len => 空节点占位长度, 配合 Void/Embed
11
10
  * @param props
12
- * @param ref
13
11
  */
14
- export const ZeroSpace = /*#__PURE__*/ forwardRef((props, ref) => {
15
- return (_jsx("span", { ref: ref, [ZERO_SPACE_KEY]: true,
12
+ export const ZeroSpace = props => {
13
+ return (_jsx("span", { ref: props.onRef, [ZERO_SPACE_KEY]: true,
16
14
  [ZERO_VOID_KEY]: props.void,
17
15
  [ZERO_ENTER_KEY]: props.enter,
18
16
  [ZERO_EMBED_KEY]: props.embed,
19
17
  [VOID_LEN_KEY]: props.len, style: props.hide ? NO_CURSOR : void 0, children: ZERO_SYMBOL }));
20
- });
18
+ };
@@ -0,0 +1,36 @@
1
+ import type { Editor } from "@block-kit/core";
2
+ import type { O } from "@block-kit/utils/dist/es/types";
3
+ import type { ReactPortal } from "react";
4
+ export declare const EDITOR_TO_DOM: WeakMap<Editor, HTMLElement | null>;
5
+ export declare const EDITOR_TO_PORTAL: WeakMap<Editor, import("react").Dispatch<import("react").SetStateAction<O.Map<ReactPortal>>> | null>;
6
+ /**
7
+ * 共享的挂载节点
8
+ */
9
+ export declare const MountNode: {
10
+ /**
11
+ * 设置挂载 DOM
12
+ * @param editor
13
+ * @param dom
14
+ */
15
+ set: (editor: Editor, dom: HTMLElement | null) => void;
16
+ /**
17
+ * 获取挂载 DOM
18
+ * @param editor
19
+ * @param dom
20
+ */
21
+ get: (editor: Editor) => HTMLElement;
22
+ /**
23
+ * 渲染节点
24
+ * @param editor
25
+ * @param key
26
+ * @param node
27
+ * @param dom [?=undef]
28
+ */
29
+ mount: (editor: Editor, key: string, node: JSX.Element, dom?: HTMLElement) => undefined;
30
+ /**
31
+ * 卸载节点
32
+ * @param editor
33
+ * @param key
34
+ */
35
+ unmount: (editor: Editor, key: string) => undefined;
36
+ };
@@ -0,0 +1,57 @@
1
+ import { createPortal } from "react-dom";
2
+ export const EDITOR_TO_DOM = new WeakMap();
3
+ export const EDITOR_TO_PORTAL = new WeakMap();
4
+ /**
5
+ * 共享的挂载节点
6
+ */
7
+ export const MountNode = {
8
+ /**
9
+ * 设置挂载 DOM
10
+ * @param editor
11
+ * @param dom
12
+ */
13
+ set: (editor, dom) => {
14
+ EDITOR_TO_DOM.set(editor, dom);
15
+ },
16
+ /**
17
+ * 获取挂载 DOM
18
+ * @param editor
19
+ * @param dom
20
+ */
21
+ get: (editor) => {
22
+ return EDITOR_TO_DOM.get(editor) || document.body;
23
+ },
24
+ /**
25
+ * 渲染节点
26
+ * @param editor
27
+ * @param key
28
+ * @param node
29
+ * @param dom [?=undef]
30
+ */
31
+ mount: (editor, key, node, dom) => {
32
+ const setPortals = EDITOR_TO_PORTAL.get(editor);
33
+ if (!setPortals)
34
+ return void 0;
35
+ const portal = createPortal(node, dom || MountNode.get(editor));
36
+ setPortals(prev => {
37
+ const newNodes = Object.assign({}, prev);
38
+ newNodes[key] = portal;
39
+ return newNodes;
40
+ });
41
+ },
42
+ /**
43
+ * 卸载节点
44
+ * @param editor
45
+ * @param key
46
+ */
47
+ unmount: (editor, key) => {
48
+ const setPortals = EDITOR_TO_PORTAL.get(editor);
49
+ if (!setPortals)
50
+ return void 0;
51
+ setPortals(prev => {
52
+ const newNodes = Object.assign({}, prev);
53
+ delete newNodes[key];
54
+ return newNodes;
55
+ });
56
+ },
57
+ };
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.useEditorStatic = exports.BlockKitContext = void 0;
27
27
  const react_1 = __importStar(require("react"));
28
28
  exports.BlockKitContext = (0, react_1.createContext)(null);
29
+ exports.BlockKitContext.displayName = "BlockKit";
29
30
  const useEditorStatic = () => {
30
31
  const editor = react_1.default.useContext(exports.BlockKitContext);
31
32
  if (!editor) {
@@ -26,6 +26,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
26
26
  exports.useReadonly = exports.ReadonlyContext = void 0;
27
27
  const react_1 = __importStar(require("react"));
28
28
  exports.ReadonlyContext = (0, react_1.createContext)(false);
29
+ exports.ReadonlyContext.displayName = "Readonly";
29
30
  const useReadonly = () => {
30
31
  const readonly = react_1.default.useContext(exports.ReadonlyContext);
31
32
  return { readonly };
@@ -15,4 +15,5 @@ export { Isolate } from "./preset/isolate";
15
15
  export { Text } from "./preset/text";
16
16
  export { Void } from "./preset/void";
17
17
  export { ZeroSpace } from "./preset/zero";
18
+ export { MountNode } from "./utils/dom";
18
19
  export { preventNativeEvent, preventReactEvent } from "./utils/event";
package/dist/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.preventReactEvent = exports.preventNativeEvent = exports.ZeroSpace = exports.Void = exports.Text = exports.Isolate = exports.Embed = exports.Editable = exports.BlockKit = exports.InjectWrapKeys = exports.Priority = exports.EditorPlugin = exports.LineModel = exports.LeafModel = exports.EOLModel = exports.BlockModel = exports.useReadonly = exports.ReadonlyContext = exports.useEditorStatic = exports.BlockKitContext = void 0;
3
+ exports.preventReactEvent = exports.preventNativeEvent = exports.MountNode = exports.ZeroSpace = exports.Void = exports.Text = exports.Isolate = exports.Embed = exports.Editable = exports.BlockKit = exports.InjectWrapKeys = exports.Priority = exports.EditorPlugin = exports.LineModel = exports.LeafModel = exports.EOLModel = exports.BlockModel = exports.useReadonly = exports.ReadonlyContext = exports.useEditorStatic = exports.BlockKitContext = void 0;
4
4
  var use_editor_1 = require("./hooks/use-editor");
5
5
  Object.defineProperty(exports, "BlockKitContext", { enumerable: true, get: function () { return use_editor_1.BlockKitContext; } });
6
6
  Object.defineProperty(exports, "useEditorStatic", { enumerable: true, get: function () { return use_editor_1.useEditorStatic; } });
@@ -35,6 +35,8 @@ var void_1 = require("./preset/void");
35
35
  Object.defineProperty(exports, "Void", { enumerable: true, get: function () { return void_1.Void; } });
36
36
  var zero_1 = require("./preset/zero");
37
37
  Object.defineProperty(exports, "ZeroSpace", { enumerable: true, get: function () { return zero_1.ZeroSpace; } });
38
+ var dom_1 = require("./utils/dom");
39
+ Object.defineProperty(exports, "MountNode", { enumerable: true, get: function () { return dom_1.MountNode; } });
38
40
  var event_1 = require("./utils/event");
39
41
  Object.defineProperty(exports, "preventNativeEvent", { enumerable: true, get: function () { return event_1.preventNativeEvent; } });
40
42
  Object.defineProperty(exports, "preventReactEvent", { enumerable: true, get: function () { return event_1.preventReactEvent; } });
@@ -103,6 +103,14 @@ const BlockView = props => {
103
103
  * 处理行节点
104
104
  */
105
105
  const elements = (0, react_1.useMemo)(() => {
106
+ // 开发模式严格检查数据准确性
107
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
108
+ for (const line of lines) {
109
+ if (line.length < 0 || line.size < 0 || line.index < 0 || line.start < 0 || !line.key) {
110
+ throw new Error("LineState index, size, start or length is not set");
111
+ }
112
+ }
113
+ }
106
114
  return lines.map((line, index) => {
107
115
  const node = ((0, jsx_runtime_1.jsx)(line_1.LineModel, { editor: editor, lineState: line, index: index }, line.key));
108
116
  weak_map_1.JSX_TO_STATE.set(node, line);
@@ -49,7 +49,7 @@ const LeafView = props => {
49
49
  leafState: leafState,
50
50
  attributes: leafState.op.attributes,
51
51
  style: {},
52
- children: (0, jsx_runtime_1.jsx)(text_1.Text, { ref: el => weak_map_1.LEAF_TO_TEXT.set(leafState, el), children: text }),
52
+ children: (0, jsx_runtime_1.jsx)(text_1.Text, { onRef: el => weak_map_1.LEAF_TO_TEXT.set(leafState, el), children: text }),
53
53
  };
54
54
  const plugins = editor.plugin.getPriorityPlugins(core_1.PLUGIN_TYPE.RENDER_LEAF);
55
55
  for (const plugin of plugins) {
@@ -78,10 +78,19 @@ const LineView = props => {
78
78
  * 处理行内的节点
79
79
  */
80
80
  const elements = (0, react_1.useMemo)(() => {
81
+ // 开发模式下严格检查数据准确性
82
+ if (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") {
83
+ const leaves = lineState.getLeaves();
84
+ for (const leaf of leaves) {
85
+ if (leaf.index === -1 || leaf.offset === -1 || !leaf.key) {
86
+ throw new Error("LeafState index or offset is not set");
87
+ }
88
+ }
89
+ }
81
90
  const leaves = lineState.getLeaves();
82
91
  const textLeaves = leaves.slice(0, -1);
83
92
  const nodes = textLeaves.map((n, i) => {
84
- const node = (0, jsx_runtime_1.jsx)(leaf_1.LeafModel, { editor: editor, index: i, leafState: n }, i);
93
+ const node = (0, jsx_runtime_1.jsx)(leaf_1.LeafModel, { editor: editor, index: i, leafState: n }, n.key);
85
94
  weak_map_1.JSX_TO_STATE.set(node, n);
86
95
  return node;
87
96
  });
@@ -0,0 +1,5 @@
1
+ import type { Editor } from "@block-kit/core";
2
+ import React from "react";
3
+ export declare const PortalModel: React.NamedExoticComponent<{
4
+ editor: Editor;
5
+ }>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.PortalModel = void 0;
27
+ const jsx_runtime_1 = require("react/jsx-runtime");
28
+ const react_1 = __importStar(require("react"));
29
+ const dom_1 = require("../utils/dom");
30
+ const PortalView = props => {
31
+ const [portals, setPortals] = (0, react_1.useState)({});
32
+ dom_1.EDITOR_TO_PORTAL.set(props.editor, setPortals);
33
+ return ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: Object.entries(portals).map(([key, node]) => ((0, jsx_runtime_1.jsx)(react_1.Fragment, { children: node }, key))) }));
34
+ };
35
+ exports.PortalModel = react_1.default.memo(PortalView);
@@ -6,6 +6,7 @@ const core_1 = require("@block-kit/core");
6
6
  const react_1 = require("react");
7
7
  const use_editor_1 = require("../hooks/use-editor");
8
8
  const use_readonly_1 = require("../hooks/use-readonly");
9
+ const portal_1 = require("../model/portal");
9
10
  const wrap_1 = require("../plugin/modules/wrap");
10
11
  const BlockKit = props => {
11
12
  const { editor, readonly, children } = props;
@@ -16,6 +17,6 @@ const BlockKit = props => {
16
17
  // 希望在 Editor 初始化后立即执行一次
17
18
  (0, wrap_1.initWrapPlugins)(editor);
18
19
  }, [editor]);
19
- return ((0, jsx_runtime_1.jsx)(use_editor_1.BlockKitContext.Provider, { value: editor, children: (0, jsx_runtime_1.jsx)(use_readonly_1.ReadonlyContext.Provider, { value: !!readonly, children: children }) }));
20
+ return ((0, jsx_runtime_1.jsx)(use_editor_1.BlockKitContext.Provider, { value: editor, children: (0, jsx_runtime_1.jsxs)(use_readonly_1.ReadonlyContext.Provider, { value: !!readonly, children: [(0, jsx_runtime_1.jsx)(portal_1.PortalModel, { editor: editor }), children] }) }));
20
21
  };
21
22
  exports.BlockKit = BlockKit;
@@ -13,4 +13,10 @@ export declare const Editable: React.FC<{
13
13
  * - 节点会脱离文档流, 需要注意 CSS 布局
14
14
  */
15
15
  placeholder?: React.ReactNode;
16
+ /**
17
+ * 阻止编辑器主动销毁
18
+ * - 谨慎使用, 编辑器生命周期结束必须主动销毁
19
+ * - 注意保持值不可变, 否则会导致编辑器多次挂载
20
+ */
21
+ preventDestroy?: boolean;
16
22
  }>;
@@ -13,7 +13,7 @@ const block_1 = require("../model/block");
13
13
  * @param props
14
14
  */
15
15
  const Editable = props => {
16
- const { className, autoFocus } = props;
16
+ const { className, autoFocus, preventDestroy } = props;
17
17
  const { editor } = (0, use_editor_1.useEditorStatic)();
18
18
  const { readonly } = (0, use_readonly_1.useReadonly)();
19
19
  const ref = (0, react_1.useRef)(null);
@@ -21,9 +21,10 @@ const Editable = props => {
21
21
  const el = ref.current;
22
22
  el && editor.onMount(el);
23
23
  return () => {
24
- editor.destroy();
24
+ editor.onUnmount();
25
+ !preventDestroy && editor.destroy();
25
26
  };
26
- }, [editor]);
27
+ }, [editor, preventDestroy]);
27
28
  (0, react_1.useEffect)(() => {
28
29
  // COMPAT: 这里有个奇怪的表现
29
30
  // 当自动聚焦时, 必须要先更新浏览器选区再聚焦
@@ -1,9 +1,10 @@
1
- /// <reference types="react" />
1
+ import type { FC } from "react";
2
2
  export type TextProps = {
3
3
  children: string;
4
+ onRef?: (ref: HTMLSpanElement | null) => void;
4
5
  };
5
6
  /**
6
7
  * 文本节点
7
8
  * @param props
8
9
  */
9
- export declare const Text: import("react").ForwardRefExoticComponent<TextProps & import("react").RefAttributes<HTMLSpanElement>>;
10
+ export declare const Text: FC<TextProps>;
@@ -4,20 +4,14 @@ exports.Text = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const core_1 = require("@block-kit/core");
6
6
  const utils_1 = require("@block-kit/utils");
7
- const react_1 = require("react");
8
7
  /**
9
8
  * 文本节点
10
9
  * @param props
11
10
  */
12
- exports.Text = (0, react_1.forwardRef)((props, ref) => {
11
+ const Text = props => {
13
12
  const onRef = (dom) => {
14
13
  // 处理外部引用的 ref
15
- if ((0, utils_1.isFunction)(ref)) {
16
- ref(dom);
17
- }
18
- else if (ref) {
19
- ref.current = dom;
20
- }
14
+ props.onRef && props.onRef(dom);
21
15
  // COMPAT: 避免 React 非受控与 IME 造成的 DOM 内容问题
22
16
  if (!dom || props.children === dom.textContent)
23
17
  return void 0;
@@ -34,4 +28,5 @@ exports.Text = (0, react_1.forwardRef)((props, ref) => {
34
28
  }
35
29
  };
36
30
  return ((0, jsx_runtime_1.jsx)("span", { ref: onRef, [core_1.LEAF_STRING]: true, children: props.children }));
37
- });
31
+ };
32
+ exports.Text = Text;
@@ -19,11 +19,12 @@ const Void = props => {
19
19
  const { context, tag: Tag = "span" } = props;
20
20
  const { editor } = (0, use_editor_1.useEditorStatic)();
21
21
  const leaf = context.leafState;
22
- const onMouseDown = () => {
22
+ const onMouseDown = (event) => {
23
23
  // FIX: 修复选区偏移量, leaf 的长度可能 > 1, 而 DOM 节点的长度仅为 1
24
24
  const point = new core_3.Point(leaf.parent.index, leaf.offset + 1);
25
25
  const range = new core_2.Range(point, point.clone());
26
26
  editor.selection.set(range, true);
27
+ event.preventDefault();
27
28
  };
28
29
  return ((0, jsx_runtime_1.jsxs)(react_1.default.Fragment, { children: [(0, jsx_runtime_1.jsx)(zero_1.ZeroSpace, { void: true, hide: true, len: leaf.length > 1 ? leaf.length : void 0 }), (0, jsx_runtime_1.jsx)(Tag, { className: props.className, style: Object.assign({ userSelect: "none" }, props.style), contentEditable: false, [core_1.VOID_KEY]: true, onMouseDown: onMouseDown, children: props.children })] }));
29
30
  };
@@ -1,4 +1,4 @@
1
- /// <reference types="react" />
1
+ import type { FC } from "react";
2
2
  export type ZeroSpaceProps = {
3
3
  /** 隐藏光标 */
4
4
  hide?: boolean;
@@ -10,6 +10,8 @@ export type ZeroSpaceProps = {
10
10
  enter?: boolean;
11
11
  /** 空节点占位长度 */
12
12
  len?: number;
13
+ /** 获取 DOM 引用 */
14
+ onRef?: React.MutableRefObject<HTMLDivElement | null>;
13
15
  };
14
16
  /**
15
17
  * 零宽字符组件
@@ -18,6 +20,5 @@ export type ZeroSpaceProps = {
18
20
  * - enter => 行末尾占位, 保留于 EOLView
19
21
  * - len => 空节点占位长度, 配合 Void/Embed
20
22
  * @param props
21
- * @param ref
22
23
  */
23
- export declare const ZeroSpace: import("react").ForwardRefExoticComponent<ZeroSpaceProps & import("react").RefAttributes<HTMLSpanElement>>;
24
+ export declare const ZeroSpace: FC<ZeroSpaceProps>;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ZeroSpace = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const core_1 = require("@block-kit/core");
6
- const react_1 = require("react");
7
6
  const constant_1 = require("../utils/constant");
8
7
  /**
9
8
  * 零宽字符组件
@@ -12,12 +11,12 @@ const constant_1 = require("../utils/constant");
12
11
  * - enter => 行末尾占位, 保留于 EOLView
13
12
  * - len => 空节点占位长度, 配合 Void/Embed
14
13
  * @param props
15
- * @param ref
16
14
  */
17
- exports.ZeroSpace = (0, react_1.forwardRef)((props, ref) => {
18
- return ((0, jsx_runtime_1.jsx)("span", { ref: ref, [core_1.ZERO_SPACE_KEY]: true,
15
+ const ZeroSpace = props => {
16
+ return ((0, jsx_runtime_1.jsx)("span", { ref: props.onRef, [core_1.ZERO_SPACE_KEY]: true,
19
17
  [core_1.ZERO_VOID_KEY]: props.void,
20
18
  [core_1.ZERO_ENTER_KEY]: props.enter,
21
19
  [core_1.ZERO_EMBED_KEY]: props.embed,
22
20
  [core_1.VOID_LEN_KEY]: props.len, style: props.hide ? constant_1.NO_CURSOR : void 0, children: core_1.ZERO_SYMBOL }));
23
- });
21
+ };
22
+ exports.ZeroSpace = ZeroSpace;
@@ -0,0 +1,36 @@
1
+ import type { Editor } from "@block-kit/core";
2
+ import type { O } from "@block-kit/utils/dist/es/types";
3
+ import type { ReactPortal } from "react";
4
+ export declare const EDITOR_TO_DOM: WeakMap<Editor, HTMLElement | null>;
5
+ export declare const EDITOR_TO_PORTAL: WeakMap<Editor, import("react").Dispatch<import("react").SetStateAction<O.Map<ReactPortal>>> | null>;
6
+ /**
7
+ * 共享的挂载节点
8
+ */
9
+ export declare const MountNode: {
10
+ /**
11
+ * 设置挂载 DOM
12
+ * @param editor
13
+ * @param dom
14
+ */
15
+ set: (editor: Editor, dom: HTMLElement | null) => void;
16
+ /**
17
+ * 获取挂载 DOM
18
+ * @param editor
19
+ * @param dom
20
+ */
21
+ get: (editor: Editor) => HTMLElement;
22
+ /**
23
+ * 渲染节点
24
+ * @param editor
25
+ * @param key
26
+ * @param node
27
+ * @param dom [?=undef]
28
+ */
29
+ mount: (editor: Editor, key: string, node: JSX.Element, dom?: HTMLElement) => undefined;
30
+ /**
31
+ * 卸载节点
32
+ * @param editor
33
+ * @param key
34
+ */
35
+ unmount: (editor: Editor, key: string) => undefined;
36
+ };
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MountNode = exports.EDITOR_TO_PORTAL = exports.EDITOR_TO_DOM = void 0;
4
+ const react_dom_1 = require("react-dom");
5
+ exports.EDITOR_TO_DOM = new WeakMap();
6
+ exports.EDITOR_TO_PORTAL = new WeakMap();
7
+ /**
8
+ * 共享的挂载节点
9
+ */
10
+ exports.MountNode = {
11
+ /**
12
+ * 设置挂载 DOM
13
+ * @param editor
14
+ * @param dom
15
+ */
16
+ set: (editor, dom) => {
17
+ exports.EDITOR_TO_DOM.set(editor, dom);
18
+ },
19
+ /**
20
+ * 获取挂载 DOM
21
+ * @param editor
22
+ * @param dom
23
+ */
24
+ get: (editor) => {
25
+ return exports.EDITOR_TO_DOM.get(editor) || document.body;
26
+ },
27
+ /**
28
+ * 渲染节点
29
+ * @param editor
30
+ * @param key
31
+ * @param node
32
+ * @param dom [?=undef]
33
+ */
34
+ mount: (editor, key, node, dom) => {
35
+ const setPortals = exports.EDITOR_TO_PORTAL.get(editor);
36
+ if (!setPortals)
37
+ return void 0;
38
+ const portal = (0, react_dom_1.createPortal)(node, dom || exports.MountNode.get(editor));
39
+ setPortals(prev => {
40
+ const newNodes = Object.assign({}, prev);
41
+ newNodes[key] = portal;
42
+ return newNodes;
43
+ });
44
+ },
45
+ /**
46
+ * 卸载节点
47
+ * @param editor
48
+ * @param key
49
+ */
50
+ unmount: (editor, key) => {
51
+ const setPortals = exports.EDITOR_TO_PORTAL.get(editor);
52
+ if (!setPortals)
53
+ return void 0;
54
+ setPortals(prev => {
55
+ const newNodes = Object.assign({}, prev);
56
+ delete newNodes[key];
57
+ return newNodes;
58
+ });
59
+ },
60
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@block-kit/react",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "",
5
5
  "main": "./dist/lib/index.js",
6
6
  "types": "./dist/es/index.d.ts",
@@ -19,17 +19,29 @@
19
19
  "lint:ts": "tsc -p tsconfig.build.json --noEmit",
20
20
  "lint:circular": "madge --extensions js --circular ./dist"
21
21
  },
22
- "keywords": [],
23
- "author": "",
22
+ "author": "WindRunnerMax",
24
23
  "license": "MIT",
24
+ "keywords": [
25
+ "editor",
26
+ "wysiwyg",
27
+ "rich-text"
28
+ ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/WindrunnerMax/BlockKit.git"
32
+ },
33
+ "homepage": "https://github.com/WindrunnerMax/BlockKit",
34
+ "bugs": {
35
+ "url": "https://github.com/WindrunnerMax/BlockKit/issues"
36
+ },
25
37
  "peerDependencies": {
26
38
  "react": ">=16",
27
39
  "react-dom": ">=16"
28
40
  },
29
41
  "dependencies": {
30
- "@block-kit/core": "1.0.4",
31
- "@block-kit/delta": "1.0.4",
32
- "@block-kit/utils": "1.0.4"
42
+ "@block-kit/core": "1.0.6",
43
+ "@block-kit/delta": "1.0.6",
44
+ "@block-kit/utils": "1.0.6"
33
45
  },
34
46
  "devDependencies": {
35
47
  "@types/react": "17.0.2",