@block-kit/react 1.0.21 → 1.0.23

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.
@@ -0,0 +1,4 @@
1
+ import type { Func } from "@block-kit/utils";
2
+ import { debounce, throttle } from "@block-kit/utils";
3
+ export declare const useDebounceMemoFn: <T extends Func.Any>(fn: T, options: Func.Parameters<typeof debounce>[1]) => import("@block-kit/utils").DebouncedFn<T>;
4
+ export declare const useThrottleMemoFn: <T extends Func.Any>(fn: T, options: Func.Parameters<typeof throttle>[1]) => import("@block-kit/utils").ThrottledFn<T>;
@@ -0,0 +1,17 @@
1
+ import { debounce, throttle } from "@block-kit/utils";
2
+ import { useMemoFn } from "@block-kit/utils/dist/es/hooks";
3
+ import { useMemo } from "react";
4
+ export const useDebounceMemoFn = (fn, options) => {
5
+ const memorized = useMemoFn(fn);
6
+ const debouncedFn = useMemo(() => {
7
+ return debounce(memorized, options);
8
+ }, [memorized]);
9
+ return debouncedFn;
10
+ };
11
+ export const useThrottleMemoFn = (fn, options) => {
12
+ const memorized = useMemoFn(fn);
13
+ const throttledFn = useMemo(() => {
14
+ return throttle(memorized, options);
15
+ }, [memorized]);
16
+ return throttledFn;
17
+ };
@@ -1,5 +1,6 @@
1
1
  export { useComposing } from "./hooks/use-composing";
2
2
  export { BlockKitContext, useEditorStatic } from "./hooks/use-editor";
3
+ export { useDebounceMemoFn, useThrottleMemoFn } from "./hooks/use-limit";
3
4
  export { ReadonlyContext, useReadonly } from "./hooks/use-readonly";
4
5
  export { BlockModel } from "./model/block";
5
6
  export { EOLModel } from "./model/eol";
package/dist/es/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { useComposing } from "./hooks/use-composing";
2
2
  export { BlockKitContext, useEditorStatic } from "./hooks/use-editor";
3
+ export { useDebounceMemoFn, useThrottleMemoFn } from "./hooks/use-limit";
3
4
  export { ReadonlyContext, useReadonly } from "./hooks/use-readonly";
4
5
  export { BlockModel } from "./model/block";
5
6
  export { EOLModel } from "./model/eol";
@@ -1,11 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { BLOCK_ID_KEY, BLOCK_KEY, EDITOR_EVENT, EDITOR_STATE } from "@block-kit/core";
3
3
  import { useMemoFn } from "@block-kit/utils/dist/es/hooks";
4
- import React, { useLayoutEffect, useMemo, useRef, useState } from "react";
4
+ import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
5
5
  import { withWrapLineNodes } from "../plugin/modules/wrap";
6
6
  import { rewriteRemoveChild } from "../utils/dirty-dom";
7
7
  import { JSX_TO_STATE } from "../utils/weak-map";
8
- import { PaintEffectModel } from "./effect";
9
8
  import { LineModel } from "./line";
10
9
  import { Placeholder } from "./ph";
11
10
  const BlockView = props => {
@@ -15,14 +14,13 @@ const BlockView = props => {
15
14
  /**
16
15
  * 设置 Block DOM 节点
17
16
  */
18
- const setModel = useMemoFn((ref) => {
17
+ const setModel = (ref) => {
19
18
  if (ref) {
20
19
  // ref 触发时机最早 ref -> layout effect -> effect
21
- // 需要保证 editor 稳定, 重渲染并不会触发执行, 导致 DOM 映射关系失效
22
20
  editor.model.setBlockModel(ref, state);
23
21
  rewriteRemoveChild(ref);
24
22
  }
25
- });
23
+ };
26
24
  /**
27
25
  * 数据同步变更, 异步批量绘制变更
28
26
  */
@@ -48,6 +46,29 @@ const BlockView = props => {
48
46
  editor.event.off(EDITOR_EVENT.CONTENT_CHANGE, onContentChange);
49
47
  };
50
48
  }, [editor.event, onContentChange]);
49
+ /**
50
+ * 视图更新需要重新设置选区, 依赖于行状态变更
51
+ */
52
+ useLayoutEffect(() => {
53
+ const selection = editor.selection.get();
54
+ // 同步计算完成后更新浏览器选区, 等待 Paint
55
+ if (editor.state.isFocused() && selection) {
56
+ editor.logger.debug("UpdateDOMSelection");
57
+ editor.selection.updateDOMSelection(true);
58
+ }
59
+ }, [editor, lines]);
60
+ /**
61
+ * 视图更新需要触发视图绘制完成事件, 依赖于行状态变更
62
+ */
63
+ useEffect(() => {
64
+ // state -> parent -> node -> child ->|
65
+ // effect <- parent <- node <- child <-|
66
+ editor.logger.debug("OnPaint");
67
+ editor.state.set(EDITOR_STATE.PAINTING, false);
68
+ Promise.resolve().then(() => {
69
+ editor.event.trigger(EDITOR_EVENT.PAINT, {});
70
+ });
71
+ }, [editor, lines]);
51
72
  /**
52
73
  * 处理行节点
53
74
  */
@@ -60,8 +81,8 @@ const BlockView = props => {
60
81
  }
61
82
  }
62
83
  }
63
- return lines.map((line, index) => {
64
- const node = (_jsx(LineModel, { editor: editor, lineState: line, index: index }, line.key));
84
+ return lines.map(line => {
85
+ const node = _jsx(LineModel, { editor: editor, lineState: line }, line.key);
65
86
  JSX_TO_STATE.set(node, line);
66
87
  return node;
67
88
  });
@@ -72,7 +93,7 @@ const BlockView = props => {
72
93
  const children = useMemo(() => {
73
94
  return withWrapLineNodes(editor, elements);
74
95
  }, [editor, elements]);
75
- return (_jsxs("div", { [BLOCK_KEY]: true, [BLOCK_ID_KEY]: state.key, ref: setModel, children: [_jsx(PaintEffectModel, { editor: editor, lines: lines }), _jsx(Placeholder, { editor: editor, lines: lines, placeholder: props.placeholder }), children] }));
96
+ return (_jsxs("div", { [BLOCK_KEY]: true, [BLOCK_ID_KEY]: state.key, ref: setModel, children: [_jsx(Placeholder, { editor: editor, lines: lines, placeholder: props.placeholder }), children] }));
76
97
  };
77
98
  /** Block Model */
78
99
  export const BlockModel = React.memo(BlockView);
@@ -3,6 +3,5 @@ import React from "react";
3
3
  /** Leaf Model */
4
4
  export declare const LeafModel: React.NamedExoticComponent<{
5
5
  editor: Editor;
6
- index: number;
7
6
  leafState: LeafState;
8
7
  }>;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { LEAF_KEY, PLUGIN_FUNC } from "@block-kit/core";
3
3
  import { SPACE } from "@block-kit/utils";
4
- import { useForceUpdate, useMemoFn } from "@block-kit/utils/dist/es/hooks";
4
+ import { useForceUpdate } from "@block-kit/utils/dist/es/hooks";
5
5
  import React, { useMemo } from "react";
6
6
  import { Text } from "../preset/text";
7
7
  import { rewriteRemoveChild } from "../utils/dirty-dom";
@@ -12,13 +12,13 @@ const LeafView = props => {
12
12
  /**
13
13
  * 设置叶子 DOM 节点
14
14
  */
15
- const setModel = useMemoFn((ref) => {
15
+ const setModel = (ref) => {
16
16
  if (ref) {
17
17
  rewriteRemoveChild(ref);
18
18
  editor.model.setLeafModel(ref, leafState);
19
19
  }
20
20
  LEAF_TO_REMOUNT.set(leafState, forceUpdate);
21
- });
21
+ };
22
22
  /**
23
23
  * 处理叶子节点的渲染
24
24
  */
@@ -3,6 +3,5 @@ import React from "react";
3
3
  /** Line Model */
4
4
  export declare const LineModel: React.NamedExoticComponent<{
5
5
  editor: Editor;
6
- index: number;
7
6
  lineState: LineState;
8
7
  }>;
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { CALLER_TYPE, NODE_KEY, PLUGIN_FUNC } from "@block-kit/core";
3
3
  import { EOL, EOL_OP } from "@block-kit/delta";
4
4
  import { SPACE } from "@block-kit/utils";
5
- import { useMemoFn, useUpdateEffect, useUpdateLayoutEffect } from "@block-kit/utils/dist/es/hooks";
5
+ import { useUpdateEffect, useUpdateLayoutEffect } from "@block-kit/utils/dist/es/hooks";
6
6
  import React, { useMemo } from "react";
7
7
  import { withWrapLeafNodes } from "../plugin/modules/wrap";
8
8
  import { rewriteRemoveChild, updateDirtyLeaf, updateDirtyText } from "../utils/dirty-dom";
@@ -14,12 +14,12 @@ const LineView = props => {
14
14
  /**
15
15
  * 设置行 DOM 节点
16
16
  */
17
- const setModel = useMemoFn((ref) => {
17
+ const setModel = (ref) => {
18
18
  if (ref) {
19
19
  editor.model.setLineModel(ref, lineState);
20
20
  rewriteRemoveChild(ref);
21
21
  }
22
- });
22
+ };
23
23
  /**
24
24
  * 编辑器行结构布局计算后同步调用
25
25
  * - 首次处理会将所有 DOM 渲染, 不需要执行脏数据检查
@@ -55,13 +55,15 @@ const LineView = props => {
55
55
  }
56
56
  }
57
57
  const leaves = lineState.getLeaves();
58
+ // 首先渲染所有非 EOL 的叶子节点
58
59
  const textLeaves = leaves.slice(0, -1);
59
- const nodes = textLeaves.map((n, i) => {
60
- const node = _jsx(LeafModel, { editor: editor, index: i, leafState: n }, n.key);
60
+ const nodes = textLeaves.map(n => {
61
+ const node = _jsx(LeafModel, { editor: editor, leafState: n }, n.key);
61
62
  JSX_TO_STATE.set(node, n);
62
63
  return node;
63
64
  });
64
- // 空行则仅存在一个 Leaf, 此时需要渲染空的占位节点
65
+ // 存在内容时不渲染 EOL, 避免块级元素出现额外视觉上的换行
66
+ // 若是空行则仅存在一个 Leaf, 此时需要渲染空的占位节点
65
67
  if (!nodes.length && leaves[0]) {
66
68
  const leaf = leaves[0];
67
69
  const node = _jsx(EOLModel, { editor: editor, leafState: leaf }, EOL);
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { EDITOR_KEY, Point, Range } from "@block-kit/core";
3
3
  import { cs } from "@block-kit/utils";
4
+ import { useUpdateEffect } from "@block-kit/utils/dist/es/hooks";
4
5
  import { useEffect, useLayoutEffect, useRef } from "react";
5
6
  import { useEditorStatic } from "../hooks/use-editor";
6
7
  import { useReadonly } from "../hooks/use-readonly";
@@ -22,6 +23,10 @@ export const Editable = props => {
22
23
  !preventDestroy && editor.destroy();
23
24
  };
24
25
  }, [editor, preventDestroy]);
26
+ useUpdateEffect(() => {
27
+ // 需要保证 editor 实例稳定, 重渲染并不会触发 ref 执行, 导致 DOM 映射关系失效
28
+ editor.logger.warning("Confirm the uniqueness of the editor instance.", editor);
29
+ }, [editor]);
25
30
  useEffect(() => {
26
31
  // COMPAT: 这里有个奇怪的表现
27
32
  // 当自动聚焦时, 必须要先更新浏览器选区再聚焦
@@ -3,6 +3,7 @@ import React from "react";
3
3
  export type IsolateProps = PropsWithChildren<{
4
4
  className?: string;
5
5
  style?: React.CSSProperties;
6
+ onRef?: (ref: HTMLSpanElement | null) => void;
6
7
  }>;
7
8
  /**
8
9
  * 独立节点嵌入 HOC
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { EDITOR_EVENT, ISOLATED_KEY } from "@block-kit/core";
3
3
  import { stopNativeEvent } from "@block-kit/utils";
4
+ import { useMemoFn } from "@block-kit/utils/dist/es/hooks";
4
5
  import { useEffect, useState } from "react";
5
6
  /**
6
7
  * 独立节点嵌入 HOC
@@ -9,6 +10,10 @@ import { useEffect, useState } from "react";
9
10
  */
10
11
  export const Isolate = props => {
11
12
  const [ref, setRef] = useState(null);
13
+ const onRef = useMemoFn((dom) => {
14
+ setRef(dom);
15
+ props.onRef && props.onRef(dom);
16
+ });
12
17
  useEffect(() => {
13
18
  const el = ref;
14
19
  if (!el)
@@ -47,5 +52,5 @@ export const Isolate = props => {
47
52
  el.removeEventListener(COMPOSITION_END, stopNativeEvent);
48
53
  };
49
54
  }, [ref]);
50
- return (_jsx("span", { ref: setRef, [ISOLATED_KEY]: true, className: props.className, style: props.style, contentEditable: false, children: props.children }));
55
+ return (_jsx("span", { ref: onRef, [ISOLATED_KEY]: true, className: props.className, style: props.style, contentEditable: false, children: props.children }));
51
56
  };
@@ -1,6 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { LEAF_STRING } from "@block-kit/core";
3
- import { useMemoFn } from "@block-kit/utils/dist/es/hooks";
4
3
  /**
5
4
  * 文本节点
6
5
  * @param props
@@ -9,11 +8,13 @@ export const Text = props => {
9
8
  /**
10
9
  * 处理 ref 回调
11
10
  * - 节点执行时机为 ref -> layout effect -> effect
12
- * - 需要保证引用不变, 否则会导致回调在 rerender 时被多次调用 null/span 状态
11
+ * - 若是不保持引用不变, 会导致回调在 rerender 时被多次调用 null/span 状态
13
12
  * - https://18.react.dev/reference/react-dom/components/common#ref-callback
13
+ * - 而若是保持引用不变, 会导致节点变化渲染时, 即使文本内容变化了, 也不会触发回调
14
+ * - 因为 LEAF 映射 TEXT 依赖于回调函数的执行, 就会导致叶子对象变化时, 不会重新映射
14
15
  */
15
- const onRef = useMemoFn((dom) => {
16
+ const onRef = (dom) => {
16
17
  props.onRef && props.onRef(dom);
17
- });
18
+ };
18
19
  return (_jsx("span", { ref: onRef, [LEAF_STRING]: true, children: props.children }));
19
20
  };
@@ -0,0 +1,4 @@
1
+ import type { Func } from "@block-kit/utils";
2
+ import { debounce, throttle } from "@block-kit/utils";
3
+ export declare const useDebounceMemoFn: <T extends Func.Any>(fn: T, options: Func.Parameters<typeof debounce>[1]) => import("@block-kit/utils").DebouncedFn<T>;
4
+ export declare const useThrottleMemoFn: <T extends Func.Any>(fn: T, options: Func.Parameters<typeof throttle>[1]) => import("@block-kit/utils").ThrottledFn<T>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useThrottleMemoFn = exports.useDebounceMemoFn = void 0;
4
+ const utils_1 = require("@block-kit/utils");
5
+ const hooks_1 = require("@block-kit/utils/dist/es/hooks");
6
+ const react_1 = require("react");
7
+ const useDebounceMemoFn = (fn, options) => {
8
+ const memorized = (0, hooks_1.useMemoFn)(fn);
9
+ const debouncedFn = (0, react_1.useMemo)(() => {
10
+ return (0, utils_1.debounce)(memorized, options);
11
+ }, [memorized]);
12
+ return debouncedFn;
13
+ };
14
+ exports.useDebounceMemoFn = useDebounceMemoFn;
15
+ const useThrottleMemoFn = (fn, options) => {
16
+ const memorized = (0, hooks_1.useMemoFn)(fn);
17
+ const throttledFn = (0, react_1.useMemo)(() => {
18
+ return (0, utils_1.throttle)(memorized, options);
19
+ }, [memorized]);
20
+ return throttledFn;
21
+ };
22
+ exports.useThrottleMemoFn = useThrottleMemoFn;
@@ -1,5 +1,6 @@
1
1
  export { useComposing } from "./hooks/use-composing";
2
2
  export { BlockKitContext, useEditorStatic } from "./hooks/use-editor";
3
+ export { useDebounceMemoFn, useThrottleMemoFn } from "./hooks/use-limit";
3
4
  export { ReadonlyContext, useReadonly } from "./hooks/use-readonly";
4
5
  export { BlockModel } from "./model/block";
5
6
  export { EOLModel } from "./model/eol";
package/dist/lib/index.js CHANGED
@@ -1,11 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.LEAF_TO_ZERO_TEXT = exports.LEAF_TO_TEXT = exports.LEAF_TO_REMOUNT = exports.JSX_TO_STATE = exports.MountNode = exports.preventReactEvent = exports.preventNativeEvent = exports.rewriteRemoveChild = exports.NO_CURSOR = 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 = exports.useComposing = void 0;
3
+ exports.LEAF_TO_ZERO_TEXT = exports.LEAF_TO_TEXT = exports.LEAF_TO_REMOUNT = exports.JSX_TO_STATE = exports.MountNode = exports.preventReactEvent = exports.preventNativeEvent = exports.rewriteRemoveChild = exports.NO_CURSOR = 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.useThrottleMemoFn = exports.useDebounceMemoFn = exports.useEditorStatic = exports.BlockKitContext = exports.useComposing = void 0;
4
4
  var use_composing_1 = require("./hooks/use-composing");
5
5
  Object.defineProperty(exports, "useComposing", { enumerable: true, get: function () { return use_composing_1.useComposing; } });
6
6
  var use_editor_1 = require("./hooks/use-editor");
7
7
  Object.defineProperty(exports, "BlockKitContext", { enumerable: true, get: function () { return use_editor_1.BlockKitContext; } });
8
8
  Object.defineProperty(exports, "useEditorStatic", { enumerable: true, get: function () { return use_editor_1.useEditorStatic; } });
9
+ var use_limit_1 = require("./hooks/use-limit");
10
+ Object.defineProperty(exports, "useDebounceMemoFn", { enumerable: true, get: function () { return use_limit_1.useDebounceMemoFn; } });
11
+ Object.defineProperty(exports, "useThrottleMemoFn", { enumerable: true, get: function () { return use_limit_1.useThrottleMemoFn; } });
9
12
  var use_readonly_1 = require("./hooks/use-readonly");
10
13
  Object.defineProperty(exports, "ReadonlyContext", { enumerable: true, get: function () { return use_readonly_1.ReadonlyContext; } });
11
14
  Object.defineProperty(exports, "useReadonly", { enumerable: true, get: function () { return use_readonly_1.useReadonly; } });
@@ -31,7 +31,6 @@ const react_1 = __importStar(require("react"));
31
31
  const wrap_1 = require("../plugin/modules/wrap");
32
32
  const dirty_dom_1 = require("../utils/dirty-dom");
33
33
  const weak_map_1 = require("../utils/weak-map");
34
- const effect_1 = require("./effect");
35
34
  const line_1 = require("./line");
36
35
  const ph_1 = require("./ph");
37
36
  const BlockView = props => {
@@ -41,14 +40,13 @@ const BlockView = props => {
41
40
  /**
42
41
  * 设置 Block DOM 节点
43
42
  */
44
- const setModel = (0, hooks_1.useMemoFn)((ref) => {
43
+ const setModel = (ref) => {
45
44
  if (ref) {
46
45
  // ref 触发时机最早 ref -> layout effect -> effect
47
- // 需要保证 editor 稳定, 重渲染并不会触发执行, 导致 DOM 映射关系失效
48
46
  editor.model.setBlockModel(ref, state);
49
47
  (0, dirty_dom_1.rewriteRemoveChild)(ref);
50
48
  }
51
- });
49
+ };
52
50
  /**
53
51
  * 数据同步变更, 异步批量绘制变更
54
52
  */
@@ -74,6 +72,29 @@ const BlockView = props => {
74
72
  editor.event.off(core_1.EDITOR_EVENT.CONTENT_CHANGE, onContentChange);
75
73
  };
76
74
  }, [editor.event, onContentChange]);
75
+ /**
76
+ * 视图更新需要重新设置选区, 依赖于行状态变更
77
+ */
78
+ (0, react_1.useLayoutEffect)(() => {
79
+ const selection = editor.selection.get();
80
+ // 同步计算完成后更新浏览器选区, 等待 Paint
81
+ if (editor.state.isFocused() && selection) {
82
+ editor.logger.debug("UpdateDOMSelection");
83
+ editor.selection.updateDOMSelection(true);
84
+ }
85
+ }, [editor, lines]);
86
+ /**
87
+ * 视图更新需要触发视图绘制完成事件, 依赖于行状态变更
88
+ */
89
+ (0, react_1.useEffect)(() => {
90
+ // state -> parent -> node -> child ->|
91
+ // effect <- parent <- node <- child <-|
92
+ editor.logger.debug("OnPaint");
93
+ editor.state.set(core_1.EDITOR_STATE.PAINTING, false);
94
+ Promise.resolve().then(() => {
95
+ editor.event.trigger(core_1.EDITOR_EVENT.PAINT, {});
96
+ });
97
+ }, [editor, lines]);
77
98
  /**
78
99
  * 处理行节点
79
100
  */
@@ -86,8 +107,8 @@ const BlockView = props => {
86
107
  }
87
108
  }
88
109
  }
89
- return lines.map((line, index) => {
90
- const node = ((0, jsx_runtime_1.jsx)(line_1.LineModel, { editor: editor, lineState: line, index: index }, line.key));
110
+ return lines.map(line => {
111
+ const node = (0, jsx_runtime_1.jsx)(line_1.LineModel, { editor: editor, lineState: line }, line.key);
91
112
  weak_map_1.JSX_TO_STATE.set(node, line);
92
113
  return node;
93
114
  });
@@ -98,7 +119,7 @@ const BlockView = props => {
98
119
  const children = (0, react_1.useMemo)(() => {
99
120
  return (0, wrap_1.withWrapLineNodes)(editor, elements);
100
121
  }, [editor, elements]);
101
- return ((0, jsx_runtime_1.jsxs)("div", { [core_1.BLOCK_KEY]: true, [core_1.BLOCK_ID_KEY]: state.key, ref: setModel, children: [(0, jsx_runtime_1.jsx)(effect_1.PaintEffectModel, { editor: editor, lines: lines }), (0, jsx_runtime_1.jsx)(ph_1.Placeholder, { editor: editor, lines: lines, placeholder: props.placeholder }), children] }));
122
+ return ((0, jsx_runtime_1.jsxs)("div", { [core_1.BLOCK_KEY]: true, [core_1.BLOCK_ID_KEY]: state.key, ref: setModel, children: [(0, jsx_runtime_1.jsx)(ph_1.Placeholder, { editor: editor, lines: lines, placeholder: props.placeholder }), children] }));
102
123
  };
103
124
  /** Block Model */
104
125
  exports.BlockModel = react_1.default.memo(BlockView);
@@ -3,6 +3,5 @@ import React from "react";
3
3
  /** Leaf Model */
4
4
  export declare const LeafModel: React.NamedExoticComponent<{
5
5
  editor: Editor;
6
- index: number;
7
6
  leafState: LeafState;
8
7
  }>;
@@ -38,13 +38,13 @@ const LeafView = props => {
38
38
  /**
39
39
  * 设置叶子 DOM 节点
40
40
  */
41
- const setModel = (0, hooks_1.useMemoFn)((ref) => {
41
+ const setModel = (ref) => {
42
42
  if (ref) {
43
43
  (0, dirty_dom_1.rewriteRemoveChild)(ref);
44
44
  editor.model.setLeafModel(ref, leafState);
45
45
  }
46
46
  weak_map_1.LEAF_TO_REMOUNT.set(leafState, forceUpdate);
47
- });
47
+ };
48
48
  /**
49
49
  * 处理叶子节点的渲染
50
50
  */
@@ -3,6 +3,5 @@ import React from "react";
3
3
  /** Line Model */
4
4
  export declare const LineModel: React.NamedExoticComponent<{
5
5
  editor: Editor;
6
- index: number;
7
6
  lineState: LineState;
8
7
  }>;
@@ -40,12 +40,12 @@ const LineView = props => {
40
40
  /**
41
41
  * 设置行 DOM 节点
42
42
  */
43
- const setModel = (0, hooks_1.useMemoFn)((ref) => {
43
+ const setModel = (ref) => {
44
44
  if (ref) {
45
45
  editor.model.setLineModel(ref, lineState);
46
46
  (0, dirty_dom_1.rewriteRemoveChild)(ref);
47
47
  }
48
- });
48
+ };
49
49
  /**
50
50
  * 编辑器行结构布局计算后同步调用
51
51
  * - 首次处理会将所有 DOM 渲染, 不需要执行脏数据检查
@@ -81,13 +81,15 @@ const LineView = props => {
81
81
  }
82
82
  }
83
83
  const leaves = lineState.getLeaves();
84
+ // 首先渲染所有非 EOL 的叶子节点
84
85
  const textLeaves = leaves.slice(0, -1);
85
- const nodes = textLeaves.map((n, i) => {
86
- const node = (0, jsx_runtime_1.jsx)(leaf_1.LeafModel, { editor: editor, index: i, leafState: n }, n.key);
86
+ const nodes = textLeaves.map(n => {
87
+ const node = (0, jsx_runtime_1.jsx)(leaf_1.LeafModel, { editor: editor, leafState: n }, n.key);
87
88
  weak_map_1.JSX_TO_STATE.set(node, n);
88
89
  return node;
89
90
  });
90
- // 空行则仅存在一个 Leaf, 此时需要渲染空的占位节点
91
+ // 存在内容时不渲染 EOL, 避免块级元素出现额外视觉上的换行
92
+ // 若是空行则仅存在一个 Leaf, 此时需要渲染空的占位节点
91
93
  if (!nodes.length && leaves[0]) {
92
94
  const leaf = leaves[0];
93
95
  const node = (0, jsx_runtime_1.jsx)(eol_1.EOLModel, { editor: editor, leafState: leaf }, delta_1.EOL);
@@ -4,6 +4,7 @@ exports.Editable = 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 hooks_1 = require("@block-kit/utils/dist/es/hooks");
7
8
  const react_1 = require("react");
8
9
  const use_editor_1 = require("../hooks/use-editor");
9
10
  const use_readonly_1 = require("../hooks/use-readonly");
@@ -25,6 +26,10 @@ const Editable = props => {
25
26
  !preventDestroy && editor.destroy();
26
27
  };
27
28
  }, [editor, preventDestroy]);
29
+ (0, hooks_1.useUpdateEffect)(() => {
30
+ // 需要保证 editor 实例稳定, 重渲染并不会触发 ref 执行, 导致 DOM 映射关系失效
31
+ editor.logger.warning("Confirm the uniqueness of the editor instance.", editor);
32
+ }, [editor]);
28
33
  (0, react_1.useEffect)(() => {
29
34
  // COMPAT: 这里有个奇怪的表现
30
35
  // 当自动聚焦时, 必须要先更新浏览器选区再聚焦
@@ -3,6 +3,7 @@ import React from "react";
3
3
  export type IsolateProps = PropsWithChildren<{
4
4
  className?: string;
5
5
  style?: React.CSSProperties;
6
+ onRef?: (ref: HTMLSpanElement | null) => void;
6
7
  }>;
7
8
  /**
8
9
  * 独立节点嵌入 HOC
@@ -4,6 +4,7 @@ exports.Isolate = 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 hooks_1 = require("@block-kit/utils/dist/es/hooks");
7
8
  const react_1 = require("react");
8
9
  /**
9
10
  * 独立节点嵌入 HOC
@@ -12,6 +13,10 @@ const react_1 = require("react");
12
13
  */
13
14
  const Isolate = props => {
14
15
  const [ref, setRef] = (0, react_1.useState)(null);
16
+ const onRef = (0, hooks_1.useMemoFn)((dom) => {
17
+ setRef(dom);
18
+ props.onRef && props.onRef(dom);
19
+ });
15
20
  (0, react_1.useEffect)(() => {
16
21
  const el = ref;
17
22
  if (!el)
@@ -50,6 +55,6 @@ const Isolate = props => {
50
55
  el.removeEventListener(COMPOSITION_END, utils_1.stopNativeEvent);
51
56
  };
52
57
  }, [ref]);
53
- return ((0, jsx_runtime_1.jsx)("span", { ref: setRef, [core_1.ISOLATED_KEY]: true, className: props.className, style: props.style, contentEditable: false, children: props.children }));
58
+ return ((0, jsx_runtime_1.jsx)("span", { ref: onRef, [core_1.ISOLATED_KEY]: true, className: props.className, style: props.style, contentEditable: false, children: props.children }));
54
59
  };
55
60
  exports.Isolate = Isolate;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Text = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const core_1 = require("@block-kit/core");
6
- const hooks_1 = require("@block-kit/utils/dist/es/hooks");
7
6
  /**
8
7
  * 文本节点
9
8
  * @param props
@@ -12,12 +11,14 @@ const Text = props => {
12
11
  /**
13
12
  * 处理 ref 回调
14
13
  * - 节点执行时机为 ref -> layout effect -> effect
15
- * - 需要保证引用不变, 否则会导致回调在 rerender 时被多次调用 null/span 状态
14
+ * - 若是不保持引用不变, 会导致回调在 rerender 时被多次调用 null/span 状态
16
15
  * - https://18.react.dev/reference/react-dom/components/common#ref-callback
16
+ * - 而若是保持引用不变, 会导致节点变化渲染时, 即使文本内容变化了, 也不会触发回调
17
+ * - 因为 LEAF 映射 TEXT 依赖于回调函数的执行, 就会导致叶子对象变化时, 不会重新映射
17
18
  */
18
- const onRef = (0, hooks_1.useMemoFn)((dom) => {
19
+ const onRef = (dom) => {
19
20
  props.onRef && props.onRef(dom);
20
- });
21
+ };
21
22
  return ((0, jsx_runtime_1.jsx)("span", { ref: onRef, [core_1.LEAF_STRING]: true, children: props.children }));
22
23
  };
23
24
  exports.Text = Text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@block-kit/react",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "description": "",
5
5
  "main": "./dist/lib/index.js",
6
6
  "types": "./dist/es/index.d.ts",
@@ -39,9 +39,9 @@
39
39
  "react-dom": ">=17"
40
40
  },
41
41
  "dependencies": {
42
- "@block-kit/core": "1.0.21",
43
- "@block-kit/delta": "1.0.21",
44
- "@block-kit/utils": "1.0.21"
42
+ "@block-kit/core": "1.0.23",
43
+ "@block-kit/delta": "1.0.23",
44
+ "@block-kit/utils": "1.0.23"
45
45
  },
46
46
  "devDependencies": {
47
47
  "@babel/core": "7.20.12",
@@ -1,12 +0,0 @@
1
- import type { Editor, LineState } from "@block-kit/core";
2
- import React from "react";
3
- /**
4
- * 副作用依赖视图
5
- * - 父组件渲染会引发子组件渲染问题, memo 并未严格控制对比, 例如 ph 变更会导致重渲染
6
- * - 避免 memo 的渲染穿透问题, 参考 packages/react/test/render/effect.test.tsx
7
- * - 同样也可以直接将依赖即 [lines] 作为以来放置于 effect 中, 但抽离出独立组件能够更清晰
8
- */
9
- export declare const PaintEffectModel: React.NamedExoticComponent<{
10
- editor: Editor;
11
- lines: LineState[];
12
- }>;
@@ -1,36 +0,0 @@
1
- import { EDITOR_EVENT, EDITOR_STATE } from "@block-kit/core";
2
- import React, { useEffect, useLayoutEffect } from "react";
3
- const PaintEffectView = props => {
4
- const editor = props.editor;
5
- /**
6
- * 视图更新需要重新设置选区 无依赖数组
7
- */
8
- useLayoutEffect(() => {
9
- const selection = editor.selection.get();
10
- // 同步计算完成后更新浏览器选区, 等待 Paint
11
- if (editor.state.isFocused() && selection) {
12
- editor.logger.debug("UpdateDOMSelection");
13
- editor.selection.updateDOMSelection(true);
14
- }
15
- });
16
- /**
17
- * 视图更新需要触发视图绘制完成事件 无依赖数组
18
- * state -> parent -> node -> child ->|
19
- * effect <- parent <- node <- child <-|
20
- */
21
- useEffect(() => {
22
- editor.logger.debug("OnPaint");
23
- editor.state.set(EDITOR_STATE.PAINTING, false);
24
- Promise.resolve().then(() => {
25
- editor.event.trigger(EDITOR_EVENT.PAINT, {});
26
- });
27
- });
28
- return null;
29
- };
30
- /**
31
- * 副作用依赖视图
32
- * - 父组件渲染会引发子组件渲染问题, memo 并未严格控制对比, 例如 ph 变更会导致重渲染
33
- * - 避免 memo 的渲染穿透问题, 参考 packages/react/test/render/effect.test.tsx
34
- * - 同样也可以直接将依赖即 [lines] 作为以来放置于 effect 中, 但抽离出独立组件能够更清晰
35
- */
36
- export const PaintEffectModel = React.memo(PaintEffectView);
@@ -1,12 +0,0 @@
1
- import type { Editor, LineState } from "@block-kit/core";
2
- import React from "react";
3
- /**
4
- * 副作用依赖视图
5
- * - 父组件渲染会引发子组件渲染问题, memo 并未严格控制对比, 例如 ph 变更会导致重渲染
6
- * - 避免 memo 的渲染穿透问题, 参考 packages/react/test/render/effect.test.tsx
7
- * - 同样也可以直接将依赖即 [lines] 作为以来放置于 effect 中, 但抽离出独立组件能够更清晰
8
- */
9
- export declare const PaintEffectModel: React.NamedExoticComponent<{
10
- editor: Editor;
11
- lines: LineState[];
12
- }>;
@@ -1,62 +0,0 @@
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.PaintEffectModel = void 0;
27
- const core_1 = require("@block-kit/core");
28
- const react_1 = __importStar(require("react"));
29
- const PaintEffectView = props => {
30
- const editor = props.editor;
31
- /**
32
- * 视图更新需要重新设置选区 无依赖数组
33
- */
34
- (0, react_1.useLayoutEffect)(() => {
35
- const selection = editor.selection.get();
36
- // 同步计算完成后更新浏览器选区, 等待 Paint
37
- if (editor.state.isFocused() && selection) {
38
- editor.logger.debug("UpdateDOMSelection");
39
- editor.selection.updateDOMSelection(true);
40
- }
41
- });
42
- /**
43
- * 视图更新需要触发视图绘制完成事件 无依赖数组
44
- * state -> parent -> node -> child ->|
45
- * effect <- parent <- node <- child <-|
46
- */
47
- (0, react_1.useEffect)(() => {
48
- editor.logger.debug("OnPaint");
49
- editor.state.set(core_1.EDITOR_STATE.PAINTING, false);
50
- Promise.resolve().then(() => {
51
- editor.event.trigger(core_1.EDITOR_EVENT.PAINT, {});
52
- });
53
- });
54
- return null;
55
- };
56
- /**
57
- * 副作用依赖视图
58
- * - 父组件渲染会引发子组件渲染问题, memo 并未严格控制对比, 例如 ph 变更会导致重渲染
59
- * - 避免 memo 的渲染穿透问题, 参考 packages/react/test/render/effect.test.tsx
60
- * - 同样也可以直接将依赖即 [lines] 作为以来放置于 effect 中, 但抽离出独立组件能够更清晰
61
- */
62
- exports.PaintEffectModel = react_1.default.memo(PaintEffectView);