slate-angular 20.2.0-next.9 → 20.2.1

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.
@@ -353,6 +353,18 @@ const CustomDOMEditor = {
353
353
  }
354
354
  };
355
355
 
356
+ /**
357
+ * Symbols.
358
+ */
359
+ const PLACEHOLDER_SYMBOL = Symbol('placeholder');
360
+ /**
361
+ * Weak map for associating the html element with the component.
362
+ */
363
+ const ELEMENT_TO_COMPONENT = new WeakMap();
364
+ const IS_ENABLED_VIRTUAL_SCROLL = new WeakMap();
365
+ const EDITOR_TO_VIRTUAL_SCROLL_SELECTION = new WeakMap();
366
+ const EDITOR_TO_AFTER_VIEW_INIT_QUEUE = new WeakMap();
367
+
356
368
  const AngularEditor = {
357
369
  ...CustomDOMEditor,
358
370
  /**
@@ -424,19 +436,12 @@ const AngularEditor = {
424
436
  // FocusedContext is updated to the correct value
425
437
  el.focus({ preventScroll: true });
426
438
  }
439
+ },
440
+ isEnabledVirtualScroll(editor) {
441
+ return IS_ENABLED_VIRTUAL_SCROLL.get(editor);
427
442
  }
428
443
  };
429
444
 
430
- /**
431
- * Symbols.
432
- */
433
- const PLACEHOLDER_SYMBOL = Symbol('placeholder');
434
- /**
435
- * Weak map for associating the html element with the component.
436
- */
437
- const ELEMENT_TO_COMPONENT = new WeakMap();
438
- const EDITOR_TO_AFTER_VIEW_INIT_QUEUE = new WeakMap();
439
-
440
445
  const IS_IOS = typeof navigator !== 'undefined' &&
441
446
  typeof window !== 'undefined' &&
442
447
  /iPad|iPhone|iPod/.test(navigator.userAgent) &&
@@ -470,9 +475,9 @@ const HAS_BEFORE_INPUT_SUPPORT = !IS_CHROME_LEGACY &&
470
475
  globalThis.InputEvent &&
471
476
  // @ts-ignore The `getTargetRanges` property isn't recognized.
472
477
  typeof globalThis.InputEvent.prototype.getTargetRanges === 'function';
473
- const VIRTUAL_SCROLL_DEFAULT_BUFFER_COUNT = 3;
474
- const VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT = 40;
478
+ const VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT = 30;
475
479
  const SLATE_DEBUG_KEY = '__SLATE_DEBUG__';
480
+ const SLATE_DEBUG_KEY_SCROLL_TOP = '__SLATE_DEBUG_SCROLL_TOP__';
476
481
 
477
482
  /**
478
483
  * Hotkey mappings for each platform.
@@ -950,815 +955,1409 @@ const fallbackCopyText = async (text) => {
950
955
  });
951
956
  };
952
957
 
953
- const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
954
- let e = editor;
955
- let { apply } = e;
956
- e = withDOM(e, clipboardFormatKey);
957
- e.setFragmentData = (dataTransfer, originEvent) => {
958
- const { selection } = e;
959
- if (!selection) {
960
- return;
961
- }
962
- const [start, end] = Range.edges(selection);
963
- const startVoid = Editor.void(e, { at: start.path });
964
- const endVoid = Editor.void(e, { at: end.path });
965
- if (Range.isCollapsed(selection) && !startVoid) {
966
- return;
967
- }
968
- // Create a fake selection so that we can add a Base64-encoded copy of the
969
- // fragment to the HTML, to decode on future pastes.
970
- const domRange = AngularEditor.toDOMRange(e, selection);
971
- let contents = domRange.cloneContents();
972
- let attach = contents.childNodes[0];
973
- // Make sure attach is non-empty, since empty nodes will not get copied.
974
- const contentsArray = Array.from(contents.children);
975
- contentsArray.forEach(node => {
976
- if (node.textContent && node.textContent.trim() !== '') {
977
- attach = node;
958
+ class VirtualScrollDebugOverlay {
959
+ static { this.storageKey = 'slate_virtual_scroll_debug_overlay_state'; }
960
+ static { this.minWidth = 320; }
961
+ static { this.minHeight = 240; }
962
+ static { this.defaultWidth = 410; }
963
+ static { this.defaultHeight = 480; }
964
+ static getInstance(doc) {
965
+ if (!this.instance) {
966
+ this.instance = new VirtualScrollDebugOverlay(doc);
967
+ }
968
+ this.instance.init();
969
+ return this.instance;
970
+ }
971
+ static log(doc, type, ...args) {
972
+ this.getInstance(doc).log(type, ...args);
973
+ }
974
+ // will trigger selectionchange and clear editor's selection
975
+ static syncScrollTop(doc, value) {
976
+ const instance = this.getInstance(doc);
977
+ instance.setScrollTopValue(value);
978
+ }
979
+ constructor(doc) {
980
+ this.doc = doc;
981
+ this.state = {
982
+ left: 16,
983
+ top: 16,
984
+ collapsed: false,
985
+ width: VirtualScrollDebugOverlay.defaultWidth,
986
+ height: VirtualScrollDebugOverlay.defaultHeight
987
+ };
988
+ this.originalConsoleLog = console.log.bind(console);
989
+ this.originalConsoleWarn = console.warn.bind(console);
990
+ this.dragOffsetX = 0;
991
+ this.dragOffsetY = 0;
992
+ this.isDragging = false;
993
+ this.isResizing = false;
994
+ this.resizeStartX = 0;
995
+ this.resizeStartY = 0;
996
+ this.resizeStartWidth = 0;
997
+ this.resizeStartHeight = 0;
998
+ this.dragMoved = false;
999
+ this.wasDragged = false;
1000
+ this.onDragging = (event) => {
1001
+ if (!this.isDragging || !this.container) {
1002
+ return;
978
1003
  }
979
- });
980
- // COMPAT: If the end node is a void node, we need to move the end of the
981
- // range from the void node's spacer span, to the end of the void node's
982
- // content, since the spacer is before void's content in the DOM.
983
- if (endVoid) {
984
- const [voidNode] = endVoid;
985
- const r = domRange.cloneRange();
986
- const domNode = AngularEditor.toDOMNode(e, voidNode);
987
- r.setEndAfter(domNode);
988
- contents = r.cloneContents();
989
- }
990
- // COMPAT: If the start node is a void node, we need to attach the encoded
991
- // fragment to the void node's content node instead of the spacer, because
992
- // attaching it to empty `<div>/<span>` nodes will end up having it erased by
993
- // most browsers. (2018/04/27)
994
- if (startVoid) {
995
- attach = contents.querySelector('[data-slate-spacer]');
996
- }
997
- // Remove any zero-width space spans from the cloned DOM so that they don't
998
- // show up elsewhere when pasted.
999
- Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
1000
- const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
1001
- zw.textContent = isNewline ? '\n' : '';
1002
- });
1003
- // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
1004
- // in the HTML, and can be used for intra-Slate pasting. If it's a text
1005
- // node, wrap it in a `<span>` so we have something to set an attribute on.
1006
- if (isDOMText(attach)) {
1007
- const span = attach.ownerDocument.createElement('span');
1008
- // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
1009
- // then leading and trailing spaces will be ignored. (2017/09/21)
1010
- span.style.whiteSpace = 'pre';
1011
- span.appendChild(attach);
1012
- contents.appendChild(span);
1013
- attach = span;
1014
- }
1015
- const fragment = e.getFragment();
1016
- // Add the content to a <div> so that we can get its inner HTML.
1017
- const div = contents.ownerDocument.createElement('div');
1018
- const attachWrapper = document.createElement('div');
1019
- const elements = Array.from(contents.children);
1020
- if (isInvalidTable(elements)) {
1021
- contents = completeTable(contents.cloneNode(true));
1022
- }
1023
- attachWrapper.appendChild(contents);
1024
- div.appendChild(attachWrapper);
1025
- div.setAttribute('hidden', 'true');
1026
- contents.ownerDocument.body.appendChild(div);
1027
- setClipboardData({ text: getPlainText(div), elements: fragment }, div, attachWrapper, dataTransfer);
1028
- contents.ownerDocument.body.removeChild(div);
1029
- };
1030
- e.deleteCutData = () => {
1031
- const { selection } = editor;
1032
- if (selection) {
1033
- if (Range.isExpanded(selection)) {
1034
- Editor.deleteFragment(editor);
1004
+ this.dragMoved = true;
1005
+ const nextLeft = event.clientX - this.dragOffsetX;
1006
+ const nextTop = event.clientY - this.dragOffsetY;
1007
+ this.container.style.left = `${nextLeft}px`;
1008
+ this.container.style.top = `${nextTop}px`;
1009
+ this.container.style.right = 'auto';
1010
+ this.container.style.bottom = 'auto';
1011
+ };
1012
+ this.onDragEnd = () => {
1013
+ if (!this.isDragging) {
1014
+ return;
1035
1015
  }
1036
- else {
1037
- const node = Node.parent(editor, selection.anchor.path);
1038
- if (Element.isElement(node) && Editor.isVoid(editor, node)) {
1039
- Transforms.delete(editor);
1040
- }
1016
+ this.isDragging = false;
1017
+ this.wasDragged = this.dragMoved;
1018
+ this.dragMoved = false;
1019
+ this.doc.removeEventListener('mousemove', this.onDragging);
1020
+ this.doc.removeEventListener('mouseup', this.onDragEnd);
1021
+ if (this.container) {
1022
+ const rect = this.container.getBoundingClientRect();
1023
+ this.state.left = rect.left;
1024
+ this.state.top = rect.top;
1025
+ this.persistState();
1026
+ this.container.style.transition = '';
1041
1027
  }
1042
- }
1043
- };
1044
- e.insertData = async (data) => {
1045
- if (!(await e.customInsertFragmentData(data, null))) {
1046
- e.insertTextData(data);
1047
- }
1048
- };
1049
- e.customInsertFragmentData = async (data, contextClipboardData) => {
1050
- /**
1051
- * Checking copied fragment from application/x-slate-fragment or data-slate-fragment
1052
- */
1053
- const clipboardData = contextClipboardData || (await getClipboardData(data));
1054
- if (clipboardData && clipboardData.elements) {
1055
- e.insertFragment(clipboardData.elements);
1056
- return true;
1057
- }
1058
- return false;
1059
- };
1060
- e.customInsertTextData = async (data) => {
1061
- const clipboardData = await getClipboardData(data);
1062
- if (clipboardData && clipboardData.text) {
1063
- const lines = clipboardData.text.split(/\r\n|\r|\n/);
1064
- let split = false;
1065
- for (const line of lines) {
1066
- if (split) {
1067
- Transforms.splitNodes(e, { always: true });
1068
- }
1069
- e.insertText(line);
1070
- split = true;
1028
+ };
1029
+ this.onResizing = (event) => {
1030
+ if (!this.isResizing || !this.container) {
1031
+ return;
1071
1032
  }
1072
- return true;
1033
+ const deltaX = event.clientX - this.resizeStartX;
1034
+ const deltaY = event.clientY - this.resizeStartY;
1035
+ const nextWidth = Math.max(VirtualScrollDebugOverlay.minWidth, this.resizeStartWidth + deltaX);
1036
+ const nextHeight = Math.max(VirtualScrollDebugOverlay.minHeight, this.resizeStartHeight + deltaY);
1037
+ this.state.width = nextWidth;
1038
+ this.state.height = nextHeight;
1039
+ this.applySize();
1040
+ };
1041
+ this.onResizeEnd = () => {
1042
+ if (!this.isResizing) {
1043
+ return;
1044
+ }
1045
+ this.isResizing = false;
1046
+ this.doc.removeEventListener('mousemove', this.onResizing);
1047
+ this.doc.removeEventListener('mouseup', this.onResizeEnd);
1048
+ this.persistState();
1049
+ };
1050
+ }
1051
+ init() {
1052
+ if (!this.container) {
1053
+ this.createContainer();
1073
1054
  }
1074
- return false;
1075
- };
1076
- e.onKeydown = () => { };
1077
- e.onClick = () => { };
1078
- e.isBlockCard = element => false;
1079
- e.isExpanded = element => true;
1080
- e.onError = (errorData) => {
1081
- if (errorData.nativeError) {
1082
- console.error(errorData.nativeError);
1055
+ }
1056
+ log(type, ...args) {
1057
+ this.init();
1058
+ if (type === 'warn') {
1059
+ this.originalConsoleWarn(...args);
1083
1060
  }
1084
1061
  else {
1085
- console.error(errorData);
1062
+ this.originalConsoleLog(...args);
1086
1063
  }
1087
- };
1088
- // exist issue for move operation in withDOM
1089
- e.apply = (op) => {
1090
- const matches = [];
1091
- switch (op.type) {
1092
- case 'insert_text':
1093
- case 'remove_text':
1094
- case 'set_node': {
1095
- for (const [node, path] of Editor.levels(e, { at: op.path })) {
1096
- const key = AngularEditor.findKey(e, node);
1097
- matches.push([path, key]);
1098
- }
1099
- break;
1100
- }
1101
- case 'insert_node':
1102
- case 'remove_node':
1103
- case 'merge_node':
1104
- case 'split_node': {
1105
- for (const [node, path] of Editor.levels(e, {
1106
- at: Path.parent(op.path)
1107
- })) {
1108
- const key = AngularEditor.findKey(e, node);
1109
- matches.push([path, key]);
1110
- }
1111
- break;
1064
+ this.appendLog(type, ...args);
1065
+ }
1066
+ dispose() {
1067
+ this.container?.remove();
1068
+ this.container = undefined;
1069
+ this.contentWrapper = undefined;
1070
+ this.bubble = undefined;
1071
+ this.logList = undefined;
1072
+ this.selectorInput = undefined;
1073
+ this.distanceInput = undefined;
1074
+ this.collapseToggle = undefined;
1075
+ this.resizeHandle = undefined;
1076
+ this.doc.removeEventListener('mousemove', this.onDragging);
1077
+ this.doc.removeEventListener('mouseup', this.onDragEnd);
1078
+ this.doc.removeEventListener('mousemove', this.onResizing);
1079
+ this.doc.removeEventListener('mouseup', this.onResizeEnd);
1080
+ this.isDragging = false;
1081
+ this.isResizing = false;
1082
+ }
1083
+ createContainer() {
1084
+ this.loadState();
1085
+ const doc = this.doc;
1086
+ const container = doc.createElement('div');
1087
+ container.style.position = 'fixed';
1088
+ container.style.right = 'auto';
1089
+ container.style.bottom = 'auto';
1090
+ container.style.boxSizing = 'border-box';
1091
+ container.style.background = 'rgba(17, 24, 39, 0.95)';
1092
+ container.style.color = '#e5e7eb';
1093
+ container.style.fontSize = '12px';
1094
+ container.style.border = '1px solid #1f2937';
1095
+ container.style.borderRadius = '10px';
1096
+ container.style.fontFamily = 'Menlo, Consolas, monospace';
1097
+ container.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.35)';
1098
+ container.style.zIndex = '9999';
1099
+ container.style.display = 'flex';
1100
+ container.style.flexDirection = 'column';
1101
+ container.style.gap = '10px';
1102
+ container.style.minWidth = `${VirtualScrollDebugOverlay.minWidth}px`;
1103
+ container.style.minHeight = `${VirtualScrollDebugOverlay.minHeight}px`;
1104
+ container.style.maxHeight = '80vh';
1105
+ container.style.cursor = 'default';
1106
+ container.addEventListener('mousedown', event => {
1107
+ if (this.state.collapsed) {
1108
+ this.startDrag(event);
1112
1109
  }
1113
- case 'move_node': {
1114
- const commonPath = Path.common(Path.parent(op.path), Path.parent(op.newPath));
1115
- for (const [node, path] of Editor.levels(e, {
1116
- at: Path.parent(op.path)
1117
- })) {
1118
- const key = AngularEditor.findKey(e, node);
1119
- matches.push([Editor.pathRef(editor, path), key]);
1120
- }
1121
- for (const [node, path] of Editor.levels(e, {
1122
- at: Path.parent(op.newPath)
1123
- })) {
1124
- if (path.length > commonPath.length) {
1125
- const key = AngularEditor.findKey(e, node);
1126
- matches.push([Editor.pathRef(editor, path), key]);
1127
- }
1110
+ });
1111
+ const header = doc.createElement('div');
1112
+ header.style.display = 'flex';
1113
+ header.style.alignItems = 'center';
1114
+ header.style.justifyContent = 'space-between';
1115
+ header.style.cursor = 'move';
1116
+ header.addEventListener('mousedown', event => {
1117
+ this.startDrag(event);
1118
+ });
1119
+ const title = doc.createElement('div');
1120
+ title.textContent = 'Virtual Scroll Debug';
1121
+ title.style.fontWeight = '600';
1122
+ title.style.letterSpacing = '0.3px';
1123
+ const actions = doc.createElement('div');
1124
+ actions.style.display = 'flex';
1125
+ actions.style.gap = '6px';
1126
+ const collapseButton = doc.createElement('button');
1127
+ collapseButton.type = 'button';
1128
+ collapseButton.textContent = '折叠';
1129
+ collapseButton.style.background = '#1f2937';
1130
+ collapseButton.style.color = '#e5e7eb';
1131
+ collapseButton.style.border = '1px solid #374151';
1132
+ collapseButton.style.borderRadius = '6px';
1133
+ collapseButton.style.padding = '4px 8px';
1134
+ collapseButton.style.cursor = 'pointer';
1135
+ collapseButton.addEventListener('click', () => {
1136
+ this.setCollapsed(!this.state.collapsed);
1137
+ });
1138
+ const clearButton = doc.createElement('button');
1139
+ clearButton.type = 'button';
1140
+ clearButton.textContent = '清除日志';
1141
+ clearButton.style.background = '#374151';
1142
+ clearButton.style.color = '#e5e7eb';
1143
+ clearButton.style.border = '1px solid #4b5563';
1144
+ clearButton.style.borderRadius = '6px';
1145
+ clearButton.style.padding = '4px 10px';
1146
+ clearButton.style.cursor = 'pointer';
1147
+ clearButton.addEventListener('click', () => {
1148
+ if (this.logList) {
1149
+ this.logList.innerHTML = '';
1150
+ }
1151
+ });
1152
+ actions.appendChild(collapseButton);
1153
+ actions.appendChild(clearButton);
1154
+ header.appendChild(title);
1155
+ header.appendChild(actions);
1156
+ const scrollForm = doc.createElement('div');
1157
+ scrollForm.style.display = 'grid';
1158
+ scrollForm.style.gridTemplateColumns = '1fr 110px 50px';
1159
+ scrollForm.style.gap = '6px';
1160
+ scrollForm.style.alignItems = 'center';
1161
+ const selectorInput = doc.createElement('input');
1162
+ selectorInput.placeholder = '滚动容器 selector';
1163
+ selectorInput.style.padding = '6px 8px';
1164
+ selectorInput.style.borderRadius = '6px';
1165
+ selectorInput.style.border = '1px solid #4b5563';
1166
+ selectorInput.style.background = '#111827';
1167
+ selectorInput.style.color = '#e5e7eb';
1168
+ selectorInput.autocomplete = 'off';
1169
+ const distanceInput = doc.createElement('input');
1170
+ distanceInput.placeholder = '滚动距离(px)';
1171
+ distanceInput.type = 'number';
1172
+ distanceInput.style.padding = '6px 8px';
1173
+ distanceInput.style.borderRadius = '6px';
1174
+ distanceInput.style.border = '1px solid #4b5563';
1175
+ distanceInput.style.background = '#111827';
1176
+ distanceInput.style.color = '#e5e7eb';
1177
+ const scrollButton = doc.createElement('button');
1178
+ scrollButton.type = 'button';
1179
+ scrollButton.textContent = '滚动';
1180
+ scrollButton.style.background = '#10b981';
1181
+ scrollButton.style.color = '#0b1c15';
1182
+ scrollButton.style.border = 'none';
1183
+ scrollButton.style.borderRadius = '6px';
1184
+ scrollButton.style.padding = '6px 10px';
1185
+ scrollButton.style.cursor = 'pointer';
1186
+ scrollButton.addEventListener('click', () => {
1187
+ const selector = selectorInput.value.trim();
1188
+ const distanceValue = Number(distanceInput.value ?? 0);
1189
+ const distance = Number.isFinite(distanceValue) ? distanceValue : 0;
1190
+ if (!selector) {
1191
+ this.log('warn', '请先填写滚动容器 selector');
1192
+ return;
1193
+ }
1194
+ const target = doc.querySelector(selector);
1195
+ if (!target) {
1196
+ this.log('warn', `未找到滚动容器: ${selector}`);
1197
+ return;
1198
+ }
1199
+ if (typeof target.scrollTo === 'function') {
1200
+ target.scrollTo({ top: distance, behavior: 'auto' });
1201
+ }
1202
+ else if (Object.prototype.hasOwnProperty.call(target, 'scrollTop')) {
1203
+ target.scrollTop = distance;
1204
+ }
1205
+ else {
1206
+ this.log('warn', '目标元素不支持滚动:', selector);
1207
+ return;
1208
+ }
1209
+ this.log('log', `已将 ${selector} 滚动到`, distance);
1210
+ });
1211
+ scrollForm.appendChild(selectorInput);
1212
+ scrollForm.appendChild(distanceInput);
1213
+ scrollForm.appendChild(scrollButton);
1214
+ const logList = doc.createElement('div');
1215
+ logList.style.height = '260px';
1216
+ logList.style.overflowY = 'auto';
1217
+ logList.style.background = '#0b1220';
1218
+ logList.style.border = '1px solid #1f2937';
1219
+ logList.style.borderRadius = '8px';
1220
+ logList.style.padding = '8px';
1221
+ logList.style.display = 'flex';
1222
+ logList.style.flexDirection = 'column';
1223
+ logList.style.gap = '6px';
1224
+ logList.style.flex = '1';
1225
+ logList.style.minHeight = '160px';
1226
+ const bubble = doc.createElement('div');
1227
+ bubble.textContent = 'VS';
1228
+ bubble.style.display = 'none';
1229
+ bubble.style.alignItems = 'center';
1230
+ bubble.style.justifyContent = 'center';
1231
+ bubble.style.fontWeight = '700';
1232
+ bubble.style.fontSize = '14px';
1233
+ bubble.style.letterSpacing = '0.5px';
1234
+ bubble.style.width = '100%';
1235
+ bubble.style.height = '100%';
1236
+ bubble.style.userSelect = 'none';
1237
+ bubble.addEventListener('click', () => {
1238
+ if (this.wasDragged) {
1239
+ this.wasDragged = false;
1240
+ return;
1241
+ }
1242
+ this.setCollapsed(false);
1243
+ });
1244
+ const contentWrapper = doc.createElement('div');
1245
+ contentWrapper.style.display = 'flex';
1246
+ contentWrapper.style.flexDirection = 'column';
1247
+ contentWrapper.style.gap = '10px';
1248
+ contentWrapper.style.width = '100%';
1249
+ contentWrapper.style.height = '100%';
1250
+ contentWrapper.appendChild(header);
1251
+ contentWrapper.appendChild(scrollForm);
1252
+ contentWrapper.appendChild(logList);
1253
+ container.appendChild(contentWrapper);
1254
+ container.appendChild(bubble);
1255
+ const resizeHandle = doc.createElement('div');
1256
+ resizeHandle.style.position = 'absolute';
1257
+ resizeHandle.style.right = '6px';
1258
+ resizeHandle.style.bottom = '6px';
1259
+ resizeHandle.style.width = '14px';
1260
+ resizeHandle.style.height = '14px';
1261
+ resizeHandle.style.cursor = 'nwse-resize';
1262
+ resizeHandle.style.borderRight = '2px solid #4b5563';
1263
+ resizeHandle.style.borderBottom = '2px solid #4b5563';
1264
+ resizeHandle.addEventListener('mousedown', event => {
1265
+ this.startResize(event);
1266
+ });
1267
+ container.appendChild(resizeHandle);
1268
+ doc.body.appendChild(container);
1269
+ this.container = container;
1270
+ this.contentWrapper = contentWrapper;
1271
+ this.bubble = bubble;
1272
+ this.logList = logList;
1273
+ this.selectorInput = selectorInput;
1274
+ this.distanceInput = distanceInput;
1275
+ this.collapseToggle = collapseButton;
1276
+ this.resizeHandle = resizeHandle;
1277
+ this.applyState();
1278
+ }
1279
+ startDrag(event) {
1280
+ if (event.button !== 0) {
1281
+ return;
1282
+ }
1283
+ if (!this.container) {
1284
+ return;
1285
+ }
1286
+ const rect = this.container.getBoundingClientRect();
1287
+ this.isDragging = true;
1288
+ this.wasDragged = false;
1289
+ this.dragMoved = false;
1290
+ this.dragOffsetX = event.clientX - rect.left;
1291
+ this.dragOffsetY = event.clientY - rect.top;
1292
+ this.container.style.transition = 'none';
1293
+ this.doc.addEventListener('mousemove', this.onDragging);
1294
+ this.doc.addEventListener('mouseup', this.onDragEnd);
1295
+ if (!this.state.collapsed) {
1296
+ event.preventDefault();
1297
+ }
1298
+ }
1299
+ startResize(event) {
1300
+ if (event.button !== 0 || this.state.collapsed) {
1301
+ return;
1302
+ }
1303
+ if (!this.container) {
1304
+ return;
1305
+ }
1306
+ const rect = this.container.getBoundingClientRect();
1307
+ this.isResizing = true;
1308
+ this.resizeStartX = event.clientX;
1309
+ this.resizeStartY = event.clientY;
1310
+ this.resizeStartWidth = rect.width;
1311
+ this.resizeStartHeight = rect.height;
1312
+ this.doc.addEventListener('mousemove', this.onResizing);
1313
+ this.doc.addEventListener('mouseup', this.onResizeEnd);
1314
+ event.preventDefault();
1315
+ event.stopPropagation();
1316
+ }
1317
+ applyPosition() {
1318
+ if (!this.container) {
1319
+ return;
1320
+ }
1321
+ this.container.style.left = `${this.state.left}px`;
1322
+ this.container.style.top = `${this.state.top}px`;
1323
+ }
1324
+ applySize() {
1325
+ if (!this.container) {
1326
+ return;
1327
+ }
1328
+ const width = Math.max(VirtualScrollDebugOverlay.minWidth, this.state.width || VirtualScrollDebugOverlay.defaultWidth);
1329
+ const height = Math.max(VirtualScrollDebugOverlay.minHeight, this.state.height || VirtualScrollDebugOverlay.defaultHeight);
1330
+ this.state.width = width;
1331
+ this.state.height = height;
1332
+ this.container.style.width = `${width}px`;
1333
+ this.container.style.height = `${height}px`;
1334
+ }
1335
+ applyCollapsedState() {
1336
+ if (!this.container || !this.contentWrapper || !this.bubble || !this.collapseToggle) {
1337
+ return;
1338
+ }
1339
+ if (this.state.collapsed) {
1340
+ this.container.style.width = '36px';
1341
+ this.container.style.height = '36px';
1342
+ this.container.style.minWidth = '';
1343
+ this.container.style.minHeight = '';
1344
+ this.container.style.padding = '0';
1345
+ this.container.style.borderRadius = '50%';
1346
+ this.container.style.display = 'flex';
1347
+ this.container.style.flexDirection = 'row';
1348
+ this.container.style.alignItems = 'center';
1349
+ this.container.style.justifyContent = 'center';
1350
+ this.container.style.cursor = 'move';
1351
+ this.contentWrapper.style.display = 'none';
1352
+ this.bubble.style.display = 'flex';
1353
+ this.collapseToggle.textContent = '展开';
1354
+ this.resizeHandle.style.display = 'none';
1355
+ }
1356
+ else {
1357
+ this.applySize();
1358
+ this.container.style.padding = '12px';
1359
+ this.container.style.borderRadius = '10px';
1360
+ this.container.style.display = 'flex';
1361
+ this.container.style.flexDirection = 'column';
1362
+ this.container.style.gap = '10px';
1363
+ this.container.style.cursor = 'default';
1364
+ this.contentWrapper.style.display = 'flex';
1365
+ this.bubble.style.display = 'none';
1366
+ this.collapseToggle.textContent = '折叠';
1367
+ this.resizeHandle.style.display = 'block';
1368
+ }
1369
+ }
1370
+ setCollapsed(collapsed) {
1371
+ this.state.collapsed = collapsed;
1372
+ this.applyCollapsedState();
1373
+ this.persistState();
1374
+ }
1375
+ applyState() {
1376
+ this.applyPosition();
1377
+ this.applyCollapsedState();
1378
+ }
1379
+ loadState() {
1380
+ try {
1381
+ const raw = this.doc.defaultView?.localStorage?.getItem(VirtualScrollDebugOverlay.storageKey);
1382
+ if (raw) {
1383
+ const parsed = JSON.parse(raw);
1384
+ if (typeof parsed.left === 'number') {
1385
+ this.state.left = parsed.left;
1386
+ }
1387
+ if (typeof parsed.top === 'number') {
1388
+ this.state.top = parsed.top;
1389
+ }
1390
+ if (typeof parsed.collapsed === 'boolean') {
1391
+ this.state.collapsed = parsed.collapsed;
1392
+ }
1393
+ if (typeof parsed.width === 'number') {
1394
+ this.state.width = parsed.width;
1395
+ }
1396
+ if (typeof parsed.height === 'number') {
1397
+ this.state.height = parsed.height;
1128
1398
  }
1129
- break;
1130
1399
  }
1131
1400
  }
1132
- apply(op);
1133
- for (const [source, key] of matches) {
1134
- const [node] = Editor.node(e, Path.isPath(source) ? source : source.current);
1135
- NODE_TO_KEY.set(node, key);
1401
+ catch {
1402
+ // ignore storage errors
1136
1403
  }
1137
- };
1138
- e.selectAll = () => {
1139
- Transforms.select(e, []);
1140
- };
1141
- return e;
1142
- };
1143
-
1144
- const TOP_BLUR = 'blur';
1145
- const TOP_COMPOSITION_END = 'compositionend';
1146
- const TOP_COMPOSITION_START = 'compositionstart';
1147
- const TOP_COMPOSITION_UPDATE = 'compositionupdate';
1148
- const TOP_KEY_DOWN = 'keydown';
1149
- const TOP_KEY_PRESS = 'keypress';
1150
- const TOP_KEY_UP = 'keyup';
1151
- const TOP_MOUSE_DOWN = 'mousedown';
1152
- const TOP_MOUSE_MOVE = 'mousemove';
1153
- const TOP_MOUSE_OUT = 'mouseout';
1154
- const TOP_TEXT_INPUT = 'textInput';
1155
- const TOP_PASTE = 'paste';
1156
-
1157
- /**
1158
- * Copyright (c) Facebook, Inc. and its affiliates.
1159
- *
1160
- * This source code is licensed under the MIT license found in the
1161
- * LICENSE file in the root directory of this source tree.
1162
- */
1163
- /**
1164
- * These variables store information about text content of a target node,
1165
- * allowing comparison of content before and after a given event.
1166
- *
1167
- * Identify the node where selection currently begins, then observe
1168
- * both its text content and its current position in the DOM. Since the
1169
- * browser may natively replace the target node during composition, we can
1170
- * use its position to find its replacement.
1171
- *
1172
- *
1173
- */
1174
- let root = null;
1175
- let startText = null;
1176
- let fallbackText = null;
1177
- function initialize(nativeEventTarget) {
1178
- root = nativeEventTarget;
1179
- startText = getText();
1180
- return true;
1181
- }
1182
- function reset() {
1183
- root = null;
1184
- startText = null;
1185
- fallbackText = null;
1186
- }
1187
- function getData() {
1188
- if (fallbackText) {
1189
- return fallbackText;
1190
1404
  }
1191
- let start;
1192
- const startValue = startText;
1193
- const startLength = startValue.length;
1194
- let end;
1195
- const endValue = getText();
1196
- const endLength = endValue.length;
1197
- for (start = 0; start < startLength; start++) {
1198
- if (startValue[start] !== endValue[start]) {
1199
- break;
1405
+ persistState() {
1406
+ try {
1407
+ this.doc.defaultView?.localStorage?.setItem(VirtualScrollDebugOverlay.storageKey, JSON.stringify(this.state));
1408
+ }
1409
+ catch {
1410
+ // ignore storage errors
1200
1411
  }
1201
1412
  }
1202
- const minEnd = startLength - start;
1203
- for (end = 1; end <= minEnd; end++) {
1204
- if (startValue[startLength - end] !== endValue[endLength - end]) {
1205
- break;
1413
+ appendLog(type, ...args) {
1414
+ if (!this.logList) {
1415
+ return;
1416
+ }
1417
+ const item = this.doc.createElement('div');
1418
+ item.style.display = 'flex';
1419
+ item.style.gap = '6px';
1420
+ item.style.alignItems = 'flex-start';
1421
+ item.style.wordBreak = 'break-all';
1422
+ item.style.color = type === 'warn' ? '#fbbf24' : '#9ca3af';
1423
+ const time = this.doc.createElement('span');
1424
+ time.textContent = new Date().toLocaleTimeString();
1425
+ time.style.color = '#6b7280';
1426
+ time.style.flexShrink = '0';
1427
+ time.style.width = '72px';
1428
+ const text = this.doc.createElement('span');
1429
+ text.textContent = `[${type}] ${args.map(arg => this.formatValue(arg)).join(' ')}`;
1430
+ item.appendChild(time);
1431
+ item.appendChild(text);
1432
+ this.logList.appendChild(item);
1433
+ }
1434
+ formatValue(value) {
1435
+ if (typeof value === 'string') {
1436
+ return value;
1437
+ }
1438
+ try {
1439
+ return JSON.stringify(value);
1440
+ }
1441
+ catch (error) {
1442
+ return String(value);
1206
1443
  }
1207
1444
  }
1208
- const sliceTail = end > 1 ? 1 - end : undefined;
1209
- fallbackText = endValue.slice(start, sliceTail);
1210
- return fallbackText;
1211
- }
1212
- function getText() {
1213
- if ('value' in root) {
1214
- return root.value;
1445
+ setScrollTopValue(value) {
1446
+ if (this.distanceInput) {
1447
+ this.distanceInput.value = String(value ?? 0);
1448
+ }
1215
1449
  }
1216
- return root.textContent;
1217
1450
  }
1218
1451
 
1219
- /**
1220
- * Copyright (c) Facebook, Inc. and its affiliates.
1221
- *
1222
- * This source code is licensed under the MIT license found in the
1223
- * LICENSE file in the root directory of this source tree.
1224
- */
1225
- const canUseDOM = !!(typeof window !== 'undefined' &&
1226
- typeof window.document !== 'undefined' &&
1227
- typeof window.document.createElement !== 'undefined');
1228
- const END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
1229
- const START_KEYCODE = 229;
1230
- const canUseCompositionEvent = canUseDOM && 'CompositionEvent' in window;
1231
- let documentMode = null;
1232
- if (canUseDOM && 'documentMode' in document) {
1233
- documentMode = document.documentMode;
1234
- }
1235
- // Webkit offers a very useful `textInput` event that can be used to
1236
- // directly represent `beforeInput`. The IE `textinput` event is not as
1237
- // useful, so we don't use it.
1238
- const canUseTextInputEvent = canUseDOM && 'TextEvent' in window && !documentMode;
1239
- // In IE9+, we have access to composition events, but the data supplied
1240
- // by the native compositionend event may be incorrect. Japanese ideographic
1241
- // spaces, for instance (\u3000) are not recorded correctly.
1242
- const useFallbackCompositionData = canUseDOM && (!canUseCompositionEvent || (documentMode && documentMode > 8 && documentMode <= 11));
1243
- const SPACEBAR_CODE = 32;
1244
- const SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE);
1245
- // Events and their corresponding property names.
1246
- const eventTypes = {
1247
- beforeInput: {
1248
- phasedRegistrationNames: {
1249
- bubbled: 'onBeforeInput',
1250
- captured: 'onBeforeInputCapture'
1251
- },
1252
- dependencies: [TOP_COMPOSITION_END, TOP_KEY_PRESS, TOP_TEXT_INPUT, TOP_PASTE]
1253
- }
1452
+ const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
1453
+ const isDebugScrollTop = localStorage.getItem(SLATE_DEBUG_KEY_SCROLL_TOP) === 'true';
1454
+ const ELEMENT_KEY_TO_HEIGHTS = new WeakMap();
1455
+ const EDITOR_TO_BUSINESS_TOP = new WeakMap();
1456
+ const EDITOR_TO_ROOT_NODE_WIDTH = new WeakMap();
1457
+ const debugLog = (type, ...args) => {
1458
+ const doc = document;
1459
+ VirtualScrollDebugOverlay.log(doc, type, ...args);
1254
1460
  };
1255
- // Track whether we've ever handled a keypress on the space key.
1256
- let hasSpaceKeypress = false;
1257
- /**
1258
- * Return whether a native keypress event is assumed to be a command.
1259
- * This is required because Firefox fires `keypress` events for key commands
1260
- * (cut, copy, select-all, etc.) even though no character is inserted.
1261
- */
1262
- function isKeypressCommand(nativeEvent) {
1263
- return ((nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) &&
1264
- // ctrlKey && altKey is equivalent to AltGr, and is not a command.
1265
- !(nativeEvent.ctrlKey && nativeEvent.altKey));
1266
- }
1267
- /**
1268
- * Does our fallback mode think that this event is the end of composition?
1269
- *
1270
- */
1271
- function isFallbackCompositionEnd(topLevelType, nativeEvent) {
1272
- switch (topLevelType) {
1273
- case TOP_KEY_UP:
1274
- // Command keys insert or clear IME input.
1275
- return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
1276
- case TOP_KEY_DOWN:
1277
- // Expect IME keyCode on each keydown. If we get any other
1278
- // code we must have exited earlier.
1279
- return nativeEvent.keyCode !== START_KEYCODE;
1280
- case TOP_KEY_PRESS:
1281
- case TOP_MOUSE_DOWN:
1282
- case TOP_BLUR:
1283
- // Events are not possible without cancelling IME.
1284
- return true;
1285
- default:
1286
- return false;
1287
- }
1288
- }
1289
- /**
1290
- * Google Input Tools provides composition data via a CustomEvent,
1291
- * with the `data` property populated in the `detail` object. If this
1292
- * is available on the event object, use it. If not, this is a plain
1293
- * composition event and we have nothing special to extract.
1294
- *
1295
- */
1296
- function getDataFromCustomEvent(nativeEvent) {
1297
- const detail = nativeEvent.detail;
1298
- if (typeof detail === 'object' && 'data' in detail) {
1299
- return detail.data;
1300
- }
1301
- return null;
1302
- }
1303
- /**
1304
- * Check if a composition event was triggered by Korean IME.
1305
- * Our fallback mode does not work well with IE's Korean IME,
1306
- * so just use native composition events when Korean IME is used.
1307
- * Although CompositionEvent.locale property is deprecated,
1308
- * it is available in IE, where our fallback mode is enabled.
1309
- *
1310
- */
1311
- function isUsingKoreanIME(nativeEvent) {
1312
- return nativeEvent.locale === 'ko';
1313
- }
1314
- // Track the current IME composition status, if any.
1315
- let isComposing = false;
1316
- function getNativeBeforeInputChars(topLevelType, nativeEvent) {
1317
- switch (topLevelType) {
1318
- case TOP_COMPOSITION_END:
1319
- return getDataFromCustomEvent(nativeEvent);
1320
- case TOP_KEY_PRESS:
1321
- /**
1322
- * If native `textInput` events are available, our goal is to make
1323
- * use of them. However, there is a special case: the spacebar key.
1324
- * In Webkit, preventing default on a spacebar `textInput` event
1325
- * cancels character insertion, but it *also* causes the browser
1326
- * to fall back to its default spacebar behavior of scrolling the
1327
- * page.
1328
- *
1329
- * Tracking at:
1330
- * https://code.google.com/p/chromium/issues/detail?id=355103
1331
- *
1332
- * To avoid this issue, use the keypress event as if no `textInput`
1333
- * event is available.
1334
- */
1335
- const which = nativeEvent.which;
1336
- if (which !== SPACEBAR_CODE) {
1337
- return null;
1338
- }
1339
- hasSpaceKeypress = true;
1340
- return SPACEBAR_CHAR;
1341
- case TOP_TEXT_INPUT:
1342
- // Record the characters to be added to the DOM.
1343
- const chars = nativeEvent.data;
1344
- // If it's a spacebar character, assume that we have already handled
1345
- // it at the keypress level and bail immediately. Android Chrome
1346
- // doesn't give us keycodes, so we need to ignore it.
1347
- if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
1348
- return null;
1349
- }
1350
- return chars;
1351
- default:
1352
- // For other native event types, do nothing.
1353
- return null;
1354
- }
1355
- }
1356
- /**
1357
- * For browsers that do not provide the `textInput` event, extract the
1358
- * appropriate string to use for SyntheticInputEvent.
1359
- *
1360
- */
1361
- function getFallbackBeforeInputChars(topLevelType, nativeEvent) {
1362
- // If we are currently composing (IME) and using a fallback to do so,
1363
- // try to extract the composed characters from the fallback object.
1364
- // If composition event is available, we extract a string only at
1365
- // compositionevent, otherwise extract it at fallback events.
1366
- if (isComposing) {
1367
- if (topLevelType === TOP_COMPOSITION_END || (!canUseCompositionEvent && isFallbackCompositionEnd(topLevelType, nativeEvent))) {
1368
- const chars = getData();
1369
- reset();
1370
- isComposing = false;
1371
- return chars;
1372
- }
1373
- return null;
1461
+ const measureHeightByElement = (editor, element) => {
1462
+ const key = AngularEditor.findKey(editor, element);
1463
+ const view = ELEMENT_TO_COMPONENT.get(element);
1464
+ if (!view) {
1465
+ return;
1374
1466
  }
1375
- switch (topLevelType) {
1376
- case TOP_PASTE:
1377
- // If a paste event occurs after a keypress, throw out the input
1378
- // chars. Paste events should not lead to BeforeInput events.
1379
- return null;
1380
- case TOP_KEY_PRESS:
1381
- /**
1382
- * As of v27, Firefox may fire keypress events even when no character
1383
- * will be inserted. A few possibilities:
1384
- *
1385
- * - `which` is `0`. Arrow keys, Esc key, etc.
1386
- *
1387
- * - `which` is the pressed key code, but no char is available.
1388
- * Ex: 'AltGr + d` in Polish. There is no modified character for
1389
- * this key combination and no character is inserted into the
1390
- * document, but FF fires the keypress for char code `100` anyway.
1391
- * No `input` event will occur.
1392
- *
1393
- * - `which` is the pressed key code, but a command combination is
1394
- * being used. Ex: `Cmd+C`. No character is inserted, and no
1395
- * `input` event will occur.
1396
- */
1397
- if (!isKeypressCommand(nativeEvent)) {
1398
- // IE fires the `keypress` event when a user types an emoji via
1399
- // Touch keyboard of Windows. In such a case, the `char` property
1400
- // holds an emoji character like `\uD83D\uDE0A`. Because its length
1401
- // is 2, the property `which` does not represent an emoji correctly.
1402
- // In such a case, we directly return the `char` property instead of
1403
- // using `which`.
1404
- if (nativeEvent.char && nativeEvent.char.length > 1) {
1405
- return nativeEvent.char;
1406
- }
1407
- else if (nativeEvent.which) {
1408
- return String.fromCharCode(nativeEvent.which);
1467
+ const ret = view.getRealHeight();
1468
+ const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1469
+ heights.set(key.id, ret);
1470
+ return ret;
1471
+ };
1472
+ const measureHeightByIndics = (editor, indics, force = false) => {
1473
+ let hasChanged = false;
1474
+ indics.forEach((index, i) => {
1475
+ const element = editor.children[index];
1476
+ const preHeight = getRealHeightByElement(editor, element, 0);
1477
+ if (preHeight && !force) {
1478
+ if (isDebug) {
1479
+ const height = measureHeightByElement(editor, element);
1480
+ if (height !== preHeight) {
1481
+ debugLog('warn', 'measureHeightByElement: height not equal, index: ', index, 'preHeight: ', preHeight, 'height: ', height);
1409
1482
  }
1410
1483
  }
1411
- return null;
1412
- case TOP_COMPOSITION_END:
1413
- return useFallbackCompositionData && !isUsingKoreanIME(nativeEvent) ? null : nativeEvent.data;
1414
- default:
1415
- return null;
1484
+ return;
1485
+ }
1486
+ hasChanged = true;
1487
+ measureHeightByElement(editor, element);
1488
+ });
1489
+ return hasChanged;
1490
+ };
1491
+ const getBusinessTop = (editor) => {
1492
+ return EDITOR_TO_BUSINESS_TOP.get(editor) ?? 0;
1493
+ };
1494
+ const getRealHeightByElement = (editor, element, defaultHeight = VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT, isVisible) => {
1495
+ const visible = isVisible ?? editor.isVisible(element);
1496
+ if (!visible) {
1497
+ return 0;
1416
1498
  }
1417
- }
1418
- /**
1419
- * Extract a SyntheticInputEvent for `beforeInput`, based on either native
1420
- * `textInput` or fallback behavior.
1421
- *
1422
- */
1423
- function extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
1424
- let chars;
1425
- if (canUseTextInputEvent) {
1426
- chars = getNativeBeforeInputChars(topLevelType, nativeEvent);
1499
+ const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1500
+ const key = AngularEditor.findKey(editor, element);
1501
+ const height = heights?.get(key.id);
1502
+ if (typeof height === 'number') {
1503
+ return height;
1427
1504
  }
1428
- else {
1429
- chars = getFallbackBeforeInputChars(topLevelType, nativeEvent);
1505
+ if (heights?.has(key.id)) {
1506
+ console.error('getBlockHeight: invalid height value', key.id, height);
1430
1507
  }
1431
- // If no characters are being inserted, no BeforeInput event should
1432
- // be fired.
1433
- if (!chars) {
1434
- return null;
1508
+ return defaultHeight;
1509
+ };
1510
+ const buildHeightsAndAccumulatedHeights = (editor) => {
1511
+ const children = (editor.children || []);
1512
+ const heights = new Array(children.length);
1513
+ const visibles = new Array(children.length);
1514
+ const accumulatedHeights = new Array(children.length + 1);
1515
+ accumulatedHeights[0] = 0;
1516
+ for (let i = 0; i < children.length; i++) {
1517
+ const isVisible = editor.isVisible(children[i]);
1518
+ visibles[i] = isVisible;
1519
+ const height = getRealHeightByElement(editor, children[i], VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT, isVisible);
1520
+ heights[i] = height;
1521
+ accumulatedHeights[i + 1] = accumulatedHeights[i] + height;
1522
+ }
1523
+ return { heights, accumulatedHeights, visibles };
1524
+ };
1525
+ const calculateVirtualTopHeight = (editor, startIndex) => {
1526
+ const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor);
1527
+ return accumulatedHeights[startIndex] ?? 0;
1528
+ };
1529
+ const scrollToElement = (editor, element, scrollTo) => {
1530
+ const children = editor.children;
1531
+ if (!children.length) {
1532
+ return;
1435
1533
  }
1436
- const beforeInputEvent = new BeforeInputEvent();
1437
- beforeInputEvent.data = chars;
1438
- beforeInputEvent.nativeEvent = nativeEvent;
1439
- return beforeInputEvent;
1440
- }
1441
- /**
1442
- * Create an `onBeforeInput` event to match
1443
- * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.
1444
- *
1445
- * This event plugin is based on the native `textInput` event
1446
- * available in Chrome, Safari, Opera, and IE. This event fires after
1447
- * `onKeyPress` and `onCompositionEnd`, but before `onInput`.
1448
- *
1449
- * `beforeInput` is spec'd but not implemented in any browsers, and
1450
- * the `input` event does not provide any useful information about what has
1451
- * actually been added, contrary to the spec. Thus, `textInput` is the best
1452
- * available event to identify the characters that have actually been inserted
1453
- * into the target node.
1454
- *
1455
- * This plugin is also responsible for emitting `composition` events, thus
1456
- * allowing us to share composition fallback code for both `beforeInput` and
1457
- * `composition` event types.
1458
- */
1459
- const BeforeInputEventPlugin = {
1460
- extractEvents: (topLevelType, targetInst, nativeEvent, nativeEventTarget) => {
1461
- return extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget);
1534
+ const anchorIndex = children.findIndex(item => item === element);
1535
+ if (anchorIndex < 0) {
1536
+ return;
1462
1537
  }
1538
+ const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor);
1539
+ scrollTo((accumulatedHeights[anchorIndex] ?? 0) + getBusinessTop(editor));
1463
1540
  };
1464
- class BeforeInputEvent {
1465
- }
1466
1541
 
1467
- const BEFORE_INPUT_EVENTS = [
1468
- // { name: 'blur', handler: 'onBlur', isTriggerBeforeInput: true },
1469
- // { name: 'compositionstart', handler: 'onCompositionStart', isTriggerBeforeInput: true },
1470
- { name: 'compositionupdate', handler: null, isTriggerBeforeInput: true },
1471
- // { name: 'compositionend', handler: 'onCompositionEnd', isTriggerBeforeInput: false },
1472
- // { name: 'keydown', handler: 'onKeyDown', isTriggerBeforeInput: true },
1473
- { name: 'keypress', handler: null, isTriggerBeforeInput: true },
1474
- { name: 'keyup', handler: 'onKeyUp', isTriggerBeforeInput: true },
1475
- { name: 'mousedown', handler: 'onMouseDown', isTriggerBeforeInput: true },
1476
- { name: 'textInput', handler: null, isTriggerBeforeInput: true }
1477
- // { name: 'paste', handler: 'onPaste', isTriggerBeforeInput: true }
1478
- ];
1479
-
1480
- var SlateErrorCode;
1481
- (function (SlateErrorCode) {
1482
- SlateErrorCode[SlateErrorCode["ToNativeSelectionError"] = 2100] = "ToNativeSelectionError";
1483
- SlateErrorCode[SlateErrorCode["ToSlateSelectionError"] = 2101] = "ToSlateSelectionError";
1484
- SlateErrorCode[SlateErrorCode["OnDOMBeforeInputError"] = 2102] = "OnDOMBeforeInputError";
1485
- SlateErrorCode[SlateErrorCode["OnSyntheticBeforeInputError"] = 2103] = "OnSyntheticBeforeInputError";
1486
- SlateErrorCode[SlateErrorCode["OnDOMKeydownError"] = 2104] = "OnDOMKeydownError";
1487
- SlateErrorCode[SlateErrorCode["GetStartPointError"] = 2105] = "GetStartPointError";
1488
- SlateErrorCode[SlateErrorCode["NotFoundPreviousRootNodeError"] = 3100] = "NotFoundPreviousRootNodeError";
1489
- SlateErrorCode[SlateErrorCode["InvalidValueError"] = 4100] = "InvalidValueError";
1490
- })(SlateErrorCode || (SlateErrorCode = {}));
1491
-
1492
- function restoreDom(editor, execute) {
1493
- const editable = EDITOR_TO_ELEMENT.get(editor);
1494
- let observer = new MutationObserver(mutations => {
1495
- mutations.reverse().forEach(mutation => {
1496
- if (mutation.type === 'characterData') {
1497
- // We don't want to restore the DOM for characterData mutations
1498
- // because this interrupts the composition.
1499
- return;
1542
+ const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
1543
+ let e = editor;
1544
+ let { apply } = e;
1545
+ e = withDOM(e, clipboardFormatKey);
1546
+ e.setFragmentData = (dataTransfer, originEvent) => {
1547
+ const { selection } = e;
1548
+ if (!selection) {
1549
+ return;
1550
+ }
1551
+ const [start, end] = Range.edges(selection);
1552
+ const startVoid = Editor.void(e, { at: start.path });
1553
+ const endVoid = Editor.void(e, { at: end.path });
1554
+ if (Range.isCollapsed(selection) && !startVoid) {
1555
+ return;
1556
+ }
1557
+ // Create a fake selection so that we can add a Base64-encoded copy of the
1558
+ // fragment to the HTML, to decode on future pastes.
1559
+ let domRange;
1560
+ if (AngularEditor.isEnabledVirtualScroll(e)) {
1561
+ const virtualScrollSelection = EDITOR_TO_VIRTUAL_SCROLL_SELECTION.get(e);
1562
+ if (virtualScrollSelection) {
1563
+ domRange = AngularEditor.toDOMRange(e, virtualScrollSelection);
1500
1564
  }
1501
- mutation.removedNodes.forEach(node => {
1502
- mutation.target.insertBefore(node, mutation.nextSibling);
1503
- });
1504
- mutation.addedNodes.forEach(node => {
1505
- mutation.target.removeChild(node);
1506
- });
1507
- });
1508
- disconnect();
1509
- execute();
1510
- });
1511
- const disconnect = () => {
1512
- observer.disconnect();
1513
- observer = null;
1514
- };
1515
- observer.observe(editable, { subtree: true, childList: true, characterData: true, characterDataOldValue: true });
1516
- setTimeout(() => {
1517
- if (observer) {
1518
- disconnect();
1519
- execute();
1520
1565
  }
1521
- }, 0);
1522
- }
1523
-
1524
- class FlavourRef {
1525
- destroy() {
1526
- this.instance.onDestroy();
1527
- }
1528
- }
1529
- class BlockCardRef {
1530
- destroy() {
1531
- this.instance.onDestroy();
1532
- }
1533
- }
1534
-
1535
- function createEmbeddedViewOrComponentOrFlavour(viewType, context, viewContext, viewContainerRef) {
1536
- if (isFlavourType(viewType)) {
1537
- const flavourRef = new FlavourRef();
1538
- flavourRef.instance = new viewType();
1539
- flavourRef.instance.context = context;
1540
- flavourRef.instance.viewContext = viewContext;
1541
- flavourRef.instance.viewContainerRef = viewContainerRef;
1542
- flavourRef.instance.onInit();
1543
- return flavourRef;
1544
- }
1545
- if (isTemplateRef(viewType)) {
1546
- const embeddedViewContext = {
1547
- context,
1548
- viewContext
1549
- };
1550
- const embeddedViewRef = viewContainerRef.createEmbeddedView(viewType, embeddedViewContext);
1551
- embeddedViewRef.detectChanges();
1552
- return embeddedViewRef;
1553
- }
1554
- if (isComponentType(viewType)) {
1555
- const componentRef = viewContainerRef.createComponent(viewType, {
1556
- injector: viewContainerRef.injector
1557
- });
1558
- componentRef.instance.viewContext = viewContext;
1559
- componentRef.instance.context = context;
1560
- componentRef.changeDetectorRef.detectChanges();
1561
- return componentRef;
1562
- }
1563
- }
1564
- function updateContext(view, newContext, viewContext) {
1565
- if (view instanceof FlavourRef) {
1566
- view.instance.context = newContext;
1567
- return;
1568
- }
1569
- if (view instanceof ComponentRef) {
1570
- view.instance.context = newContext;
1571
- }
1572
- else {
1573
- view.context.context = newContext;
1574
- view.context.viewContext = viewContext;
1575
- view.detectChanges();
1576
- }
1577
- }
1578
- function mount(views, blockCards, outletParent, outletElement) {
1579
- if (views.length > 0) {
1580
- const fragment = document.createDocumentFragment();
1581
- views.forEach((view, index) => {
1582
- const blockCard = blockCards ? blockCards[index] : undefined;
1583
- fragment.append(...getRootNodes(view, blockCard));
1566
+ domRange = domRange ?? AngularEditor.toDOMRange(e, selection);
1567
+ let contents = domRange.cloneContents();
1568
+ let attach = contents.childNodes[0];
1569
+ // Make sure attach is non-empty, since empty nodes will not get copied.
1570
+ const contentsArray = Array.from(contents.children);
1571
+ contentsArray.forEach(node => {
1572
+ if (node.textContent && node.textContent.trim() !== '') {
1573
+ attach = node;
1574
+ }
1584
1575
  });
1585
- if (outletElement) {
1586
- outletElement.parentElement.insertBefore(fragment, outletElement);
1587
- outletElement.remove();
1576
+ // COMPAT: If the end node is a void node, we need to move the end of the
1577
+ // range from the void node's spacer span, to the end of the void node's
1578
+ // content, since the spacer is before void's content in the DOM.
1579
+ if (endVoid) {
1580
+ const [voidNode] = endVoid;
1581
+ const r = domRange.cloneRange();
1582
+ const domNode = AngularEditor.toDOMNode(e, voidNode);
1583
+ r.setEndAfter(domNode);
1584
+ contents = r.cloneContents();
1588
1585
  }
1589
- else {
1590
- outletParent.prepend(fragment);
1586
+ // COMPAT: If the start node is a void node, we need to attach the encoded
1587
+ // fragment to the void node's content node instead of the spacer, because
1588
+ // attaching it to empty `<div>/<span>` nodes will end up having it erased by
1589
+ // most browsers. (2018/04/27)
1590
+ if (startVoid) {
1591
+ attach = contents.querySelector('[data-slate-spacer]');
1591
1592
  }
1592
- }
1593
- }
1594
- function getRootNodes(ref, blockCard) {
1595
- if (blockCard) {
1596
- return [blockCard.instance.nativeElement];
1597
- }
1598
- if (ref instanceof FlavourRef) {
1599
- return [ref.instance.nativeElement];
1600
- }
1601
- if (ref instanceof ComponentRef) {
1602
- ref.hostView.rootNodes.forEach(ele => {
1603
- if (!(ele instanceof HTMLElement)) {
1604
- ele.remove();
1605
- }
1593
+ // Remove any zero-width space spans from the cloned DOM so that they don't
1594
+ // show up elsewhere when pasted.
1595
+ Array.from(contents.querySelectorAll('[data-slate-zero-width]')).forEach(zw => {
1596
+ const isNewline = zw.getAttribute('data-slate-zero-width') === 'n';
1597
+ zw.textContent = isNewline ? '\n' : '';
1606
1598
  });
1607
- return [ref.instance.nativeElement];
1608
- }
1609
- else {
1610
- const result = [];
1611
- ref.rootNodes.forEach(rootNode => {
1612
- const isHTMLElement = rootNode instanceof HTMLElement;
1613
- const isSlateNodeOfLeaf = isHTMLElement && (rootNode.hasAttribute('data-slate-node') || rootNode.hasAttribute('data-slate-leaf'));
1614
- if (isSlateNodeOfLeaf && result.every(item => !item.contains(rootNode))) {
1615
- result.push(rootNode);
1599
+ // Set a `data-slate-fragment` attribute on a non-empty node, so it shows up
1600
+ // in the HTML, and can be used for intra-Slate pasting. If it's a text
1601
+ // node, wrap it in a `<span>` so we have something to set an attribute on.
1602
+ if (isDOMText(attach)) {
1603
+ const span = attach.ownerDocument.createElement('span');
1604
+ // COMPAT: In Chrome and Safari, if we don't add the `white-space` style
1605
+ // then leading and trailing spaces will be ignored. (2017/09/21)
1606
+ span.style.whiteSpace = 'pre';
1607
+ span.appendChild(attach);
1608
+ contents.appendChild(span);
1609
+ attach = span;
1610
+ }
1611
+ const fragment = e.getFragment();
1612
+ // Add the content to a <div> so that we can get its inner HTML.
1613
+ const div = contents.ownerDocument.createElement('div');
1614
+ const attachWrapper = document.createElement('div');
1615
+ const elements = Array.from(contents.children);
1616
+ if (isInvalidTable(elements)) {
1617
+ contents = completeTable(contents.cloneNode(true));
1618
+ }
1619
+ attachWrapper.appendChild(contents);
1620
+ div.appendChild(attachWrapper);
1621
+ div.setAttribute('hidden', 'true');
1622
+ contents.ownerDocument.body.appendChild(div);
1623
+ setClipboardData({ text: getPlainText(div), elements: fragment }, div, attachWrapper, dataTransfer);
1624
+ contents.ownerDocument.body.removeChild(div);
1625
+ };
1626
+ e.deleteCutData = () => {
1627
+ const { selection } = editor;
1628
+ if (selection) {
1629
+ if (Range.isExpanded(selection)) {
1630
+ Editor.deleteFragment(editor);
1616
1631
  }
1617
- if (!isHTMLElement) {
1618
- rootNode.remove();
1632
+ else {
1633
+ const node = Node.parent(editor, selection.anchor.path);
1634
+ if (Element.isElement(node) && Editor.isVoid(editor, node)) {
1635
+ Transforms.delete(editor);
1636
+ }
1619
1637
  }
1620
- });
1621
- return result;
1622
- }
1623
- }
1624
- function mountOnItemChange(index, item, views, blockCards, outletParent, firstRootNode, viewContext) {
1625
- const view = views[index];
1626
- let rootNodes = getRootNodes(view);
1627
- if (blockCards) {
1628
- const isBlockCard = viewContext.editor.isBlockCard(item);
1629
- if (isBlockCard) {
1630
- const blockCard = blockCards[index];
1631
- rootNodes = [blockCard.instance.nativeElement];
1632
1638
  }
1633
- }
1634
- if (index === 0) {
1635
- if (firstRootNode) {
1636
- rootNodes.forEach(rootNode => {
1637
- firstRootNode.insertAdjacentElement('beforebegin', rootNode);
1638
- });
1639
+ };
1640
+ e.insertData = async (data) => {
1641
+ if (!(await e.customInsertFragmentData(data, null))) {
1642
+ e.insertTextData(data);
1639
1643
  }
1640
- else {
1641
- outletParent.prepend(...rootNodes);
1644
+ };
1645
+ e.customInsertFragmentData = async (data, contextClipboardData) => {
1646
+ /**
1647
+ * Checking copied fragment from application/x-slate-fragment or data-slate-fragment
1648
+ */
1649
+ const clipboardData = contextClipboardData || (await getClipboardData(data));
1650
+ if (clipboardData && clipboardData.elements) {
1651
+ e.insertFragment(clipboardData.elements);
1652
+ return true;
1642
1653
  }
1643
- }
1644
- else {
1645
- const previousView = views[index - 1];
1646
- const blockCard = blockCards ? blockCards[index - 1] : null;
1647
- const previousRootNodes = getRootNodes(previousView, blockCard);
1648
- let previousRootNode = previousRootNodes[previousRootNodes.length - 1];
1649
- rootNodes.forEach(rootNode => {
1650
- previousRootNode.insertAdjacentElement('afterend', rootNode);
1651
- previousRootNode = rootNode;
1652
- });
1653
- }
1654
- }
1655
-
1656
- function hasBeforeContextChange(value) {
1657
- if (value.beforeContextChange) {
1654
+ return false;
1655
+ };
1656
+ e.customInsertTextData = async (data) => {
1657
+ const clipboardData = await getClipboardData(data);
1658
+ if (clipboardData && clipboardData.text) {
1659
+ const lines = clipboardData.text.split(/\r\n|\r|\n/);
1660
+ let split = false;
1661
+ for (const line of lines) {
1662
+ if (split) {
1663
+ Transforms.splitNodes(e, { always: true });
1664
+ }
1665
+ e.insertText(line);
1666
+ split = true;
1667
+ }
1668
+ return true;
1669
+ }
1670
+ return false;
1671
+ };
1672
+ e.onKeydown = () => { };
1673
+ e.onClick = () => { };
1674
+ e.isBlockCard = element => false;
1675
+ e.isExpanded = element => true;
1676
+ e.onError = (errorData) => {
1677
+ if (errorData.nativeError) {
1678
+ console.error(errorData.nativeError);
1679
+ }
1680
+ else {
1681
+ console.error(errorData);
1682
+ }
1683
+ };
1684
+ // exist issue for move operation in withDOM
1685
+ e.apply = (op) => {
1686
+ const matches = [];
1687
+ switch (op.type) {
1688
+ case 'insert_text':
1689
+ case 'remove_text':
1690
+ case 'set_node': {
1691
+ for (const [node, path] of Editor.levels(e, { at: op.path })) {
1692
+ const key = AngularEditor.findKey(e, node);
1693
+ matches.push([path, key]);
1694
+ }
1695
+ break;
1696
+ }
1697
+ case 'insert_node':
1698
+ case 'remove_node':
1699
+ case 'merge_node':
1700
+ case 'split_node': {
1701
+ for (const [node, path] of Editor.levels(e, {
1702
+ at: Path.parent(op.path)
1703
+ })) {
1704
+ const key = AngularEditor.findKey(e, node);
1705
+ matches.push([path, key]);
1706
+ }
1707
+ break;
1708
+ }
1709
+ case 'move_node': {
1710
+ const commonPath = Path.common(Path.parent(op.path), Path.parent(op.newPath));
1711
+ for (const [node, path] of Editor.levels(e, {
1712
+ at: Path.parent(op.path)
1713
+ })) {
1714
+ const key = AngularEditor.findKey(e, node);
1715
+ matches.push([Editor.pathRef(editor, path), key]);
1716
+ }
1717
+ for (const [node, path] of Editor.levels(e, {
1718
+ at: Path.parent(op.newPath)
1719
+ })) {
1720
+ if (path.length > commonPath.length) {
1721
+ const key = AngularEditor.findKey(e, node);
1722
+ matches.push([Editor.pathRef(editor, path), key]);
1723
+ }
1724
+ }
1725
+ break;
1726
+ }
1727
+ }
1728
+ apply(op);
1729
+ for (const [source, key] of matches) {
1730
+ const [node] = Editor.node(e, Path.isPath(source) ? source : source.current);
1731
+ NODE_TO_KEY.set(node, key);
1732
+ }
1733
+ };
1734
+ e.selectAll = () => {
1735
+ Transforms.select(e, []);
1736
+ };
1737
+ e.isVisible = element => {
1658
1738
  return true;
1659
- }
1660
- return false;
1739
+ };
1740
+ return e;
1741
+ };
1742
+
1743
+ const TOP_BLUR = 'blur';
1744
+ const TOP_COMPOSITION_END = 'compositionend';
1745
+ const TOP_COMPOSITION_START = 'compositionstart';
1746
+ const TOP_COMPOSITION_UPDATE = 'compositionupdate';
1747
+ const TOP_KEY_DOWN = 'keydown';
1748
+ const TOP_KEY_PRESS = 'keypress';
1749
+ const TOP_KEY_UP = 'keyup';
1750
+ const TOP_MOUSE_DOWN = 'mousedown';
1751
+ const TOP_MOUSE_MOVE = 'mousemove';
1752
+ const TOP_MOUSE_OUT = 'mouseout';
1753
+ const TOP_TEXT_INPUT = 'textInput';
1754
+ const TOP_PASTE = 'paste';
1755
+
1756
+ /**
1757
+ * Copyright (c) Facebook, Inc. and its affiliates.
1758
+ *
1759
+ * This source code is licensed under the MIT license found in the
1760
+ * LICENSE file in the root directory of this source tree.
1761
+ */
1762
+ /**
1763
+ * These variables store information about text content of a target node,
1764
+ * allowing comparison of content before and after a given event.
1765
+ *
1766
+ * Identify the node where selection currently begins, then observe
1767
+ * both its text content and its current position in the DOM. Since the
1768
+ * browser may natively replace the target node during composition, we can
1769
+ * use its position to find its replacement.
1770
+ *
1771
+ *
1772
+ */
1773
+ let root = null;
1774
+ let startText = null;
1775
+ let fallbackText = null;
1776
+ function initialize(nativeEventTarget) {
1777
+ root = nativeEventTarget;
1778
+ startText = getText();
1779
+ return true;
1661
1780
  }
1662
- function hasAfterContextChange(value) {
1663
- if (value.afterContextChange) {
1664
- return true;
1665
- }
1666
- return false;
1781
+ function reset() {
1782
+ root = null;
1783
+ startText = null;
1784
+ fallbackText = null;
1667
1785
  }
1668
-
1669
- class BaseFlavour {
1670
- constructor() {
1671
- this.initialized = false;
1786
+ function getData() {
1787
+ if (fallbackText) {
1788
+ return fallbackText;
1672
1789
  }
1673
- static { this.isFlavour = true; }
1674
- set context(value) {
1675
- if (hasBeforeContextChange(this)) {
1676
- this.beforeContextChange(value);
1677
- }
1678
- this._context = value;
1679
- this.onContextChange();
1680
- if (hasAfterContextChange(this)) {
1681
- this.afterContextChange();
1790
+ let start;
1791
+ const startValue = startText;
1792
+ const startLength = startValue.length;
1793
+ let end;
1794
+ const endValue = getText();
1795
+ const endLength = endValue.length;
1796
+ for (start = 0; start < startLength; start++) {
1797
+ if (startValue[start] !== endValue[start]) {
1798
+ break;
1682
1799
  }
1683
1800
  }
1684
- get context() {
1685
- return this._context;
1686
- }
1687
- get editor() {
1688
- return this.viewContext && this.viewContext.editor;
1801
+ const minEnd = startLength - start;
1802
+ for (end = 1; end <= minEnd; end++) {
1803
+ if (startValue[startLength - end] !== endValue[endLength - end]) {
1804
+ break;
1805
+ }
1689
1806
  }
1807
+ const sliceTail = end > 1 ? 1 - end : undefined;
1808
+ fallbackText = endValue.slice(start, sliceTail);
1809
+ return fallbackText;
1690
1810
  }
1691
-
1692
- const SLATE_BLOCK_CARD_CLASS_NAME = 'slate-block-card';
1693
- class SlateBlockCard {
1694
- onInit() {
1695
- const nativeElement = document.createElement('div');
1696
- nativeElement.classList.add(SLATE_BLOCK_CARD_CLASS_NAME);
1697
- this.nativeElement = nativeElement;
1698
- this.createContent();
1699
- }
1700
- createContent() {
1701
- const leftCaret = document.createElement('span');
1702
- leftCaret.setAttribute(`card-target`, 'card-left');
1703
- leftCaret.classList.add('card-left');
1704
- leftCaret.appendChild(getZeroTextNode());
1705
- const rightCaret = document.createElement('span');
1706
- rightCaret.setAttribute(`card-target`, 'card-right');
1707
- rightCaret.classList.add('card-right');
1708
- rightCaret.appendChild(getZeroTextNode());
1709
- const center = document.createElement('div');
1710
- center.setAttribute(`card-target`, 'card-center');
1711
- this.nativeElement.appendChild(leftCaret);
1712
- this.nativeElement.appendChild(center);
1713
- this.nativeElement.appendChild(rightCaret);
1714
- this.centerContainer = center;
1715
- }
1716
- append() {
1717
- this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
1718
- }
1719
- initializeCenter(rootNodes) {
1720
- this.centerRootNodes = rootNodes;
1721
- this.append();
1722
- }
1723
- onDestroy() {
1724
- this.nativeElement.remove();
1811
+ function getText() {
1812
+ if ('value' in root) {
1813
+ return root.value;
1725
1814
  }
1815
+ return root.textContent;
1726
1816
  }
1727
- const getBlockCardByNativeElement = (nativeElement) => {
1728
- const blockCardElement = nativeElement?.parentElement?.parentElement;
1729
- if (blockCardElement && blockCardElement.classList.contains(SLATE_BLOCK_CARD_CLASS_NAME)) {
1730
- return blockCardElement;
1731
- }
1732
- return null;
1733
- };
1734
1817
 
1735
- const DEFAULT_ELEMENT_HEIGHT = 24;
1736
- class BaseElementFlavour extends BaseFlavour {
1737
- constructor() {
1738
- super(...arguments);
1739
- this.getOutletParent = () => {
1740
- return this.nativeElement;
1741
- };
1742
- this.getOutletElement = () => {
1743
- return this.nativeElement.querySelector('.children-outlet');
1744
- };
1745
- this.stableHeight = null;
1746
- }
1747
- get element() {
1748
- return this._context && this._context.element;
1749
- }
1750
- get selection() {
1751
- return this._context && this._context.selection;
1752
- }
1753
- get decorations() {
1754
- return this._context && this._context.decorations;
1755
- }
1756
- get children() {
1757
- return this._context && this._context.element.children;
1758
- }
1759
- get isCollapsed() {
1760
- return this.selection && Range.isCollapsed(this.selection);
1761
- }
1818
+ /**
1819
+ * Copyright (c) Facebook, Inc. and its affiliates.
1820
+ *
1821
+ * This source code is licensed under the MIT license found in the
1822
+ * LICENSE file in the root directory of this source tree.
1823
+ */
1824
+ const canUseDOM = !!(typeof window !== 'undefined' &&
1825
+ typeof window.document !== 'undefined' &&
1826
+ typeof window.document.createElement !== 'undefined');
1827
+ const END_KEYCODES = [9, 13, 27, 32]; // Tab, Return, Esc, Space
1828
+ const START_KEYCODE = 229;
1829
+ const canUseCompositionEvent = canUseDOM && 'CompositionEvent' in window;
1830
+ let documentMode = null;
1831
+ if (canUseDOM && 'documentMode' in document) {
1832
+ documentMode = document.documentMode;
1833
+ }
1834
+ // Webkit offers a very useful `textInput` event that can be used to
1835
+ // directly represent `beforeInput`. The IE `textinput` event is not as
1836
+ // useful, so we don't use it.
1837
+ const canUseTextInputEvent = canUseDOM && 'TextEvent' in window && !documentMode;
1838
+ // In IE9+, we have access to composition events, but the data supplied
1839
+ // by the native compositionend event may be incorrect. Japanese ideographic
1840
+ // spaces, for instance (\u3000) are not recorded correctly.
1841
+ const useFallbackCompositionData = canUseDOM && (!canUseCompositionEvent || (documentMode && documentMode > 8 && documentMode <= 11));
1842
+ const SPACEBAR_CODE = 32;
1843
+ const SPACEBAR_CHAR = String.fromCharCode(SPACEBAR_CODE);
1844
+ // Events and their corresponding property names.
1845
+ const eventTypes = {
1846
+ beforeInput: {
1847
+ phasedRegistrationNames: {
1848
+ bubbled: 'onBeforeInput',
1849
+ captured: 'onBeforeInputCapture'
1850
+ },
1851
+ dependencies: [TOP_COMPOSITION_END, TOP_KEY_PRESS, TOP_TEXT_INPUT, TOP_PASTE]
1852
+ }
1853
+ };
1854
+ // Track whether we've ever handled a keypress on the space key.
1855
+ let hasSpaceKeypress = false;
1856
+ /**
1857
+ * Return whether a native keypress event is assumed to be a command.
1858
+ * This is required because Firefox fires `keypress` events for key commands
1859
+ * (cut, copy, select-all, etc.) even though no character is inserted.
1860
+ */
1861
+ function isKeypressCommand(nativeEvent) {
1862
+ return ((nativeEvent.ctrlKey || nativeEvent.altKey || nativeEvent.metaKey) &&
1863
+ // ctrlKey && altKey is equivalent to AltGr, and is not a command.
1864
+ !(nativeEvent.ctrlKey && nativeEvent.altKey));
1865
+ }
1866
+ /**
1867
+ * Does our fallback mode think that this event is the end of composition?
1868
+ *
1869
+ */
1870
+ function isFallbackCompositionEnd(topLevelType, nativeEvent) {
1871
+ switch (topLevelType) {
1872
+ case TOP_KEY_UP:
1873
+ // Command keys insert or clear IME input.
1874
+ return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
1875
+ case TOP_KEY_DOWN:
1876
+ // Expect IME keyCode on each keydown. If we get any other
1877
+ // code we must have exited earlier.
1878
+ return nativeEvent.keyCode !== START_KEYCODE;
1879
+ case TOP_KEY_PRESS:
1880
+ case TOP_MOUSE_DOWN:
1881
+ case TOP_BLUR:
1882
+ // Events are not possible without cancelling IME.
1883
+ return true;
1884
+ default:
1885
+ return false;
1886
+ }
1887
+ }
1888
+ /**
1889
+ * Google Input Tools provides composition data via a CustomEvent,
1890
+ * with the `data` property populated in the `detail` object. If this
1891
+ * is available on the event object, use it. If not, this is a plain
1892
+ * composition event and we have nothing special to extract.
1893
+ *
1894
+ */
1895
+ function getDataFromCustomEvent(nativeEvent) {
1896
+ const detail = nativeEvent.detail;
1897
+ if (typeof detail === 'object' && 'data' in detail) {
1898
+ return detail.data;
1899
+ }
1900
+ return null;
1901
+ }
1902
+ /**
1903
+ * Check if a composition event was triggered by Korean IME.
1904
+ * Our fallback mode does not work well with IE's Korean IME,
1905
+ * so just use native composition events when Korean IME is used.
1906
+ * Although CompositionEvent.locale property is deprecated,
1907
+ * it is available in IE, where our fallback mode is enabled.
1908
+ *
1909
+ */
1910
+ function isUsingKoreanIME(nativeEvent) {
1911
+ return nativeEvent.locale === 'ko';
1912
+ }
1913
+ // Track the current IME composition status, if any.
1914
+ let isComposing = false;
1915
+ function getNativeBeforeInputChars(topLevelType, nativeEvent) {
1916
+ switch (topLevelType) {
1917
+ case TOP_COMPOSITION_END:
1918
+ return getDataFromCustomEvent(nativeEvent);
1919
+ case TOP_KEY_PRESS:
1920
+ /**
1921
+ * If native `textInput` events are available, our goal is to make
1922
+ * use of them. However, there is a special case: the spacebar key.
1923
+ * In Webkit, preventing default on a spacebar `textInput` event
1924
+ * cancels character insertion, but it *also* causes the browser
1925
+ * to fall back to its default spacebar behavior of scrolling the
1926
+ * page.
1927
+ *
1928
+ * Tracking at:
1929
+ * https://code.google.com/p/chromium/issues/detail?id=355103
1930
+ *
1931
+ * To avoid this issue, use the keypress event as if no `textInput`
1932
+ * event is available.
1933
+ */
1934
+ const which = nativeEvent.which;
1935
+ if (which !== SPACEBAR_CODE) {
1936
+ return null;
1937
+ }
1938
+ hasSpaceKeypress = true;
1939
+ return SPACEBAR_CHAR;
1940
+ case TOP_TEXT_INPUT:
1941
+ // Record the characters to be added to the DOM.
1942
+ const chars = nativeEvent.data;
1943
+ // If it's a spacebar character, assume that we have already handled
1944
+ // it at the keypress level and bail immediately. Android Chrome
1945
+ // doesn't give us keycodes, so we need to ignore it.
1946
+ if (chars === SPACEBAR_CHAR && hasSpaceKeypress) {
1947
+ return null;
1948
+ }
1949
+ return chars;
1950
+ default:
1951
+ // For other native event types, do nothing.
1952
+ return null;
1953
+ }
1954
+ }
1955
+ /**
1956
+ * For browsers that do not provide the `textInput` event, extract the
1957
+ * appropriate string to use for SyntheticInputEvent.
1958
+ *
1959
+ */
1960
+ function getFallbackBeforeInputChars(topLevelType, nativeEvent) {
1961
+ // If we are currently composing (IME) and using a fallback to do so,
1962
+ // try to extract the composed characters from the fallback object.
1963
+ // If composition event is available, we extract a string only at
1964
+ // compositionevent, otherwise extract it at fallback events.
1965
+ if (isComposing) {
1966
+ if (topLevelType === TOP_COMPOSITION_END || (!canUseCompositionEvent && isFallbackCompositionEnd(topLevelType, nativeEvent))) {
1967
+ const chars = getData();
1968
+ reset();
1969
+ isComposing = false;
1970
+ return chars;
1971
+ }
1972
+ return null;
1973
+ }
1974
+ switch (topLevelType) {
1975
+ case TOP_PASTE:
1976
+ // If a paste event occurs after a keypress, throw out the input
1977
+ // chars. Paste events should not lead to BeforeInput events.
1978
+ return null;
1979
+ case TOP_KEY_PRESS:
1980
+ /**
1981
+ * As of v27, Firefox may fire keypress events even when no character
1982
+ * will be inserted. A few possibilities:
1983
+ *
1984
+ * - `which` is `0`. Arrow keys, Esc key, etc.
1985
+ *
1986
+ * - `which` is the pressed key code, but no char is available.
1987
+ * Ex: 'AltGr + d` in Polish. There is no modified character for
1988
+ * this key combination and no character is inserted into the
1989
+ * document, but FF fires the keypress for char code `100` anyway.
1990
+ * No `input` event will occur.
1991
+ *
1992
+ * - `which` is the pressed key code, but a command combination is
1993
+ * being used. Ex: `Cmd+C`. No character is inserted, and no
1994
+ * `input` event will occur.
1995
+ */
1996
+ if (!isKeypressCommand(nativeEvent)) {
1997
+ // IE fires the `keypress` event when a user types an emoji via
1998
+ // Touch keyboard of Windows. In such a case, the `char` property
1999
+ // holds an emoji character like `\uD83D\uDE0A`. Because its length
2000
+ // is 2, the property `which` does not represent an emoji correctly.
2001
+ // In such a case, we directly return the `char` property instead of
2002
+ // using `which`.
2003
+ if (nativeEvent.char && nativeEvent.char.length > 1) {
2004
+ return nativeEvent.char;
2005
+ }
2006
+ else if (nativeEvent.which) {
2007
+ return String.fromCharCode(nativeEvent.which);
2008
+ }
2009
+ }
2010
+ return null;
2011
+ case TOP_COMPOSITION_END:
2012
+ return useFallbackCompositionData && !isUsingKoreanIME(nativeEvent) ? null : nativeEvent.data;
2013
+ default:
2014
+ return null;
2015
+ }
2016
+ }
2017
+ /**
2018
+ * Extract a SyntheticInputEvent for `beforeInput`, based on either native
2019
+ * `textInput` or fallback behavior.
2020
+ *
2021
+ */
2022
+ function extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget) {
2023
+ let chars;
2024
+ if (canUseTextInputEvent) {
2025
+ chars = getNativeBeforeInputChars(topLevelType, nativeEvent);
2026
+ }
2027
+ else {
2028
+ chars = getFallbackBeforeInputChars(topLevelType, nativeEvent);
2029
+ }
2030
+ // If no characters are being inserted, no BeforeInput event should
2031
+ // be fired.
2032
+ if (!chars) {
2033
+ return null;
2034
+ }
2035
+ const beforeInputEvent = new BeforeInputEvent();
2036
+ beforeInputEvent.data = chars;
2037
+ beforeInputEvent.nativeEvent = nativeEvent;
2038
+ return beforeInputEvent;
2039
+ }
2040
+ /**
2041
+ * Create an `onBeforeInput` event to match
2042
+ * http://www.w3.org/TR/2013/WD-DOM-Level-3-Events-20131105/#events-inputevents.
2043
+ *
2044
+ * This event plugin is based on the native `textInput` event
2045
+ * available in Chrome, Safari, Opera, and IE. This event fires after
2046
+ * `onKeyPress` and `onCompositionEnd`, but before `onInput`.
2047
+ *
2048
+ * `beforeInput` is spec'd but not implemented in any browsers, and
2049
+ * the `input` event does not provide any useful information about what has
2050
+ * actually been added, contrary to the spec. Thus, `textInput` is the best
2051
+ * available event to identify the characters that have actually been inserted
2052
+ * into the target node.
2053
+ *
2054
+ * This plugin is also responsible for emitting `composition` events, thus
2055
+ * allowing us to share composition fallback code for both `beforeInput` and
2056
+ * `composition` event types.
2057
+ */
2058
+ const BeforeInputEventPlugin = {
2059
+ extractEvents: (topLevelType, targetInst, nativeEvent, nativeEventTarget) => {
2060
+ return extractBeforeInputEvent(topLevelType, targetInst, nativeEvent, nativeEventTarget);
2061
+ }
2062
+ };
2063
+ class BeforeInputEvent {
2064
+ }
2065
+
2066
+ const BEFORE_INPUT_EVENTS = [
2067
+ // { name: 'blur', handler: 'onBlur', isTriggerBeforeInput: true },
2068
+ // { name: 'compositionstart', handler: 'onCompositionStart', isTriggerBeforeInput: true },
2069
+ { name: 'compositionupdate', handler: null, isTriggerBeforeInput: true },
2070
+ // { name: 'compositionend', handler: 'onCompositionEnd', isTriggerBeforeInput: false },
2071
+ // { name: 'keydown', handler: 'onKeyDown', isTriggerBeforeInput: true },
2072
+ { name: 'keypress', handler: null, isTriggerBeforeInput: true },
2073
+ { name: 'keyup', handler: 'onKeyUp', isTriggerBeforeInput: true },
2074
+ { name: 'mousedown', handler: 'onMouseDown', isTriggerBeforeInput: true },
2075
+ { name: 'textInput', handler: null, isTriggerBeforeInput: true }
2076
+ // { name: 'paste', handler: 'onPaste', isTriggerBeforeInput: true }
2077
+ ];
2078
+
2079
+ var SlateErrorCode;
2080
+ (function (SlateErrorCode) {
2081
+ SlateErrorCode[SlateErrorCode["ToNativeSelectionError"] = 2100] = "ToNativeSelectionError";
2082
+ SlateErrorCode[SlateErrorCode["ToSlateSelectionError"] = 2101] = "ToSlateSelectionError";
2083
+ SlateErrorCode[SlateErrorCode["OnDOMBeforeInputError"] = 2102] = "OnDOMBeforeInputError";
2084
+ SlateErrorCode[SlateErrorCode["OnSyntheticBeforeInputError"] = 2103] = "OnSyntheticBeforeInputError";
2085
+ SlateErrorCode[SlateErrorCode["OnDOMKeydownError"] = 2104] = "OnDOMKeydownError";
2086
+ SlateErrorCode[SlateErrorCode["GetStartPointError"] = 2105] = "GetStartPointError";
2087
+ SlateErrorCode[SlateErrorCode["NotFoundPreviousRootNodeError"] = 3100] = "NotFoundPreviousRootNodeError";
2088
+ SlateErrorCode[SlateErrorCode["InvalidValueError"] = 4100] = "InvalidValueError";
2089
+ })(SlateErrorCode || (SlateErrorCode = {}));
2090
+
2091
+ function restoreDom(editor, execute) {
2092
+ const editable = EDITOR_TO_ELEMENT.get(editor);
2093
+ let observer = new MutationObserver(mutations => {
2094
+ mutations.reverse().forEach(mutation => {
2095
+ if (mutation.type === 'characterData') {
2096
+ // We don't want to restore the DOM for characterData mutations
2097
+ // because this interrupts the composition.
2098
+ return;
2099
+ }
2100
+ mutation.removedNodes.forEach(node => {
2101
+ mutation.target.insertBefore(node, mutation.nextSibling);
2102
+ });
2103
+ mutation.addedNodes.forEach(node => {
2104
+ mutation.target.removeChild(node);
2105
+ });
2106
+ });
2107
+ disconnect();
2108
+ execute();
2109
+ });
2110
+ const disconnect = () => {
2111
+ observer.disconnect();
2112
+ observer = null;
2113
+ };
2114
+ observer.observe(editable, { subtree: true, childList: true, characterData: true, characterDataOldValue: true });
2115
+ setTimeout(() => {
2116
+ if (observer) {
2117
+ disconnect();
2118
+ execute();
2119
+ }
2120
+ }, 0);
2121
+ }
2122
+
2123
+ class FlavourRef {
2124
+ destroy() {
2125
+ this.instance.onDestroy();
2126
+ }
2127
+ }
2128
+ class BlockCardRef {
2129
+ destroy() {
2130
+ this.instance.onDestroy();
2131
+ }
2132
+ }
2133
+
2134
+ function createEmbeddedViewOrComponentOrFlavour(viewType, context, viewContext, viewContainerRef) {
2135
+ if (isFlavourType(viewType)) {
2136
+ const flavourRef = new FlavourRef();
2137
+ flavourRef.instance = new viewType();
2138
+ flavourRef.instance.context = context;
2139
+ flavourRef.instance.viewContext = viewContext;
2140
+ flavourRef.instance.viewContainerRef = viewContainerRef;
2141
+ flavourRef.instance.onInit();
2142
+ return flavourRef;
2143
+ }
2144
+ if (isTemplateRef(viewType)) {
2145
+ const embeddedViewContext = {
2146
+ context,
2147
+ viewContext
2148
+ };
2149
+ const embeddedViewRef = viewContainerRef.createEmbeddedView(viewType, embeddedViewContext);
2150
+ embeddedViewRef.detectChanges();
2151
+ return embeddedViewRef;
2152
+ }
2153
+ if (isComponentType(viewType)) {
2154
+ const componentRef = viewContainerRef.createComponent(viewType, {
2155
+ injector: viewContainerRef.injector
2156
+ });
2157
+ componentRef.instance.viewContext = viewContext;
2158
+ componentRef.instance.context = context;
2159
+ componentRef.changeDetectorRef.detectChanges();
2160
+ return componentRef;
2161
+ }
2162
+ }
2163
+ function updateContext(view, newContext, viewContext) {
2164
+ if (view instanceof FlavourRef) {
2165
+ view.instance.context = newContext;
2166
+ return;
2167
+ }
2168
+ if (view instanceof ComponentRef) {
2169
+ view.instance.context = newContext;
2170
+ }
2171
+ else {
2172
+ view.context.context = newContext;
2173
+ view.context.viewContext = viewContext;
2174
+ view.detectChanges();
2175
+ }
2176
+ }
2177
+ function mount(views, blockCards, outletParent, outletElement) {
2178
+ if (views.length > 0) {
2179
+ const fragment = document.createDocumentFragment();
2180
+ views.forEach((view, index) => {
2181
+ const blockCard = blockCards ? blockCards[index] : undefined;
2182
+ fragment.append(...getRootNodes(view, blockCard));
2183
+ });
2184
+ if (outletElement) {
2185
+ outletElement.parentElement.insertBefore(fragment, outletElement);
2186
+ outletElement.remove();
2187
+ }
2188
+ else {
2189
+ outletParent.prepend(fragment);
2190
+ }
2191
+ }
2192
+ }
2193
+ function getRootNodes(ref, blockCard) {
2194
+ if (blockCard) {
2195
+ return [blockCard.instance.nativeElement];
2196
+ }
2197
+ if (ref instanceof FlavourRef) {
2198
+ return [ref.instance.nativeElement];
2199
+ }
2200
+ if (ref instanceof ComponentRef) {
2201
+ ref.hostView.rootNodes.forEach(ele => {
2202
+ if (!(ele instanceof HTMLElement)) {
2203
+ ele.remove();
2204
+ }
2205
+ });
2206
+ return [ref.instance.nativeElement];
2207
+ }
2208
+ else {
2209
+ const result = [];
2210
+ ref.rootNodes.forEach(rootNode => {
2211
+ const isHTMLElement = rootNode instanceof HTMLElement;
2212
+ const isSlateNodeOfLeaf = isHTMLElement && (rootNode.hasAttribute('data-slate-node') || rootNode.hasAttribute('data-slate-leaf'));
2213
+ if (isSlateNodeOfLeaf && result.every(item => !item.contains(rootNode))) {
2214
+ result.push(rootNode);
2215
+ }
2216
+ if (!isHTMLElement) {
2217
+ rootNode.remove();
2218
+ }
2219
+ });
2220
+ return result;
2221
+ }
2222
+ }
2223
+ function mountOnItemChange(index, item, views, blockCards, outletParent, firstRootNode, viewContext) {
2224
+ const view = views[index];
2225
+ let rootNodes = getRootNodes(view);
2226
+ if (blockCards) {
2227
+ const isBlockCard = viewContext.editor.isBlockCard(item);
2228
+ if (isBlockCard) {
2229
+ const blockCard = blockCards[index];
2230
+ rootNodes = [blockCard.instance.nativeElement];
2231
+ }
2232
+ }
2233
+ if (index === 0) {
2234
+ if (firstRootNode) {
2235
+ rootNodes.forEach(rootNode => {
2236
+ firstRootNode.insertAdjacentElement('beforebegin', rootNode);
2237
+ });
2238
+ }
2239
+ else {
2240
+ outletParent.prepend(...rootNodes);
2241
+ }
2242
+ }
2243
+ else {
2244
+ const previousView = views[index - 1];
2245
+ const blockCard = blockCards ? blockCards[index - 1] : null;
2246
+ const previousRootNodes = getRootNodes(previousView, blockCard);
2247
+ let previousRootNode = previousRootNodes[previousRootNodes.length - 1];
2248
+ rootNodes.forEach(rootNode => {
2249
+ previousRootNode.insertAdjacentElement('afterend', rootNode);
2250
+ previousRootNode = rootNode;
2251
+ });
2252
+ }
2253
+ }
2254
+
2255
+ function hasBeforeContextChange(value) {
2256
+ if (value.beforeContextChange) {
2257
+ return true;
2258
+ }
2259
+ return false;
2260
+ }
2261
+ function hasAfterContextChange(value) {
2262
+ if (value.afterContextChange) {
2263
+ return true;
2264
+ }
2265
+ return false;
2266
+ }
2267
+
2268
+ class BaseFlavour {
2269
+ constructor() {
2270
+ this.initialized = false;
2271
+ }
2272
+ static { this.isFlavour = true; }
2273
+ set context(value) {
2274
+ if (hasBeforeContextChange(this)) {
2275
+ this.beforeContextChange(value);
2276
+ }
2277
+ this._context = value;
2278
+ this.onContextChange();
2279
+ if (hasAfterContextChange(this)) {
2280
+ this.afterContextChange();
2281
+ }
2282
+ }
2283
+ get context() {
2284
+ return this._context;
2285
+ }
2286
+ get editor() {
2287
+ return this.viewContext && this.viewContext.editor;
2288
+ }
2289
+ }
2290
+
2291
+ const SLATE_BLOCK_CARD_CLASS_NAME = 'slate-block-card';
2292
+ class SlateBlockCard {
2293
+ onInit() {
2294
+ const nativeElement = document.createElement('div');
2295
+ nativeElement.classList.add(SLATE_BLOCK_CARD_CLASS_NAME);
2296
+ this.nativeElement = nativeElement;
2297
+ this.createContent();
2298
+ }
2299
+ createContent() {
2300
+ const leftCaret = document.createElement('span');
2301
+ leftCaret.setAttribute(`card-target`, 'card-left');
2302
+ leftCaret.classList.add('card-left');
2303
+ leftCaret.appendChild(getZeroTextNode());
2304
+ const rightCaret = document.createElement('span');
2305
+ rightCaret.setAttribute(`card-target`, 'card-right');
2306
+ rightCaret.classList.add('card-right');
2307
+ rightCaret.appendChild(getZeroTextNode());
2308
+ const center = document.createElement('div');
2309
+ center.setAttribute(`card-target`, 'card-center');
2310
+ this.nativeElement.appendChild(leftCaret);
2311
+ this.nativeElement.appendChild(center);
2312
+ this.nativeElement.appendChild(rightCaret);
2313
+ this.centerContainer = center;
2314
+ }
2315
+ append() {
2316
+ this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
2317
+ }
2318
+ initializeCenter(rootNodes) {
2319
+ this.centerRootNodes = rootNodes;
2320
+ this.append();
2321
+ }
2322
+ onDestroy() {
2323
+ this.nativeElement.remove();
2324
+ }
2325
+ }
2326
+ const getBlockCardByNativeElement = (nativeElement) => {
2327
+ const blockCardElement = nativeElement?.parentElement?.parentElement;
2328
+ if (blockCardElement && blockCardElement.classList.contains(SLATE_BLOCK_CARD_CLASS_NAME)) {
2329
+ return blockCardElement;
2330
+ }
2331
+ return null;
2332
+ };
2333
+
2334
+ const DEFAULT_ELEMENT_HEIGHT = 24;
2335
+ class BaseElementFlavour extends BaseFlavour {
2336
+ constructor() {
2337
+ super(...arguments);
2338
+ this.getOutletParent = () => {
2339
+ return this.nativeElement;
2340
+ };
2341
+ this.getOutletElement = () => {
2342
+ return this.nativeElement.querySelector('.children-outlet');
2343
+ };
2344
+ this.stableHeight = null;
2345
+ }
2346
+ get element() {
2347
+ return this._context && this._context.element;
2348
+ }
2349
+ get selection() {
2350
+ return this._context && this._context.selection;
2351
+ }
2352
+ get decorations() {
2353
+ return this._context && this._context.decorations;
2354
+ }
2355
+ get children() {
2356
+ return this._context && this._context.element.children;
2357
+ }
2358
+ get isCollapsed() {
2359
+ return this.selection && Range.isCollapsed(this.selection);
2360
+ }
1762
2361
  get isCollapsedAndNonReadonly() {
1763
2362
  return this.selection && Range.isCollapsed(this.selection) && !this.readonly;
1764
2363
  }
@@ -1798,6 +2397,7 @@ class BaseElementFlavour extends BaseFlavour {
1798
2397
  if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
1799
2398
  ELEMENT_TO_COMPONENT.delete(this.element);
1800
2399
  }
2400
+ this.listRender.destroy();
1801
2401
  this.nativeElement?.remove();
1802
2402
  }
1803
2403
  onContextChange() {
@@ -2194,6 +2794,9 @@ class LeavesRender {
2194
2794
  });
2195
2795
  return { decoratedLeaves, contexts };
2196
2796
  }
2797
+ destroy() {
2798
+ this.views.forEach(view => view.destroy());
2799
+ }
2197
2800
  }
2198
2801
  function getContext$1(index, leafContexts) {
2199
2802
  return leafContexts[index];
@@ -2236,6 +2839,7 @@ class BaseTextFlavour extends BaseFlavour {
2236
2839
  NODE_TO_ELEMENT.delete(this.text);
2237
2840
  }
2238
2841
  ELEMENT_TO_NODE.delete(this.nativeElement);
2842
+ this.leavesRender.destroy();
2239
2843
  this.nativeElement?.remove();
2240
2844
  }
2241
2845
  onContextChange() {
@@ -2270,29 +2874,55 @@ const createText = (text) => {
2270
2874
  return { nativeElement };
2271
2875
  };
2272
2876
 
2877
+ const setPreRenderingElementStyle = (editor, rootNode, isClear = false) => {
2878
+ if (isClear) {
2879
+ rootNode.style.top = '';
2880
+ rootNode.style.width = '';
2881
+ rootNode.style.position = '';
2882
+ return;
2883
+ }
2884
+ const preRenderingWidth = EDITOR_TO_ROOT_NODE_WIDTH.get(editor) ?? 0;
2885
+ rootNode.style.top = '-100%';
2886
+ if (preRenderingWidth) {
2887
+ rootNode.style.width = `${preRenderingWidth}px`;
2888
+ }
2889
+ else {
2890
+ rootNode.style.width = '100%';
2891
+ }
2892
+ rootNode.style.position = 'absolute';
2893
+ };
2273
2894
  class ListRender {
2274
2895
  constructor(viewContext, viewContainerRef, getOutletParent, getOutletElement) {
2275
2896
  this.viewContext = viewContext;
2276
2897
  this.viewContainerRef = viewContainerRef;
2277
2898
  this.getOutletParent = getOutletParent;
2278
2899
  this.getOutletElement = getOutletElement;
2900
+ this.children = [];
2279
2901
  this.views = [];
2280
2902
  this.blockCards = [];
2281
2903
  this.contexts = [];
2282
2904
  this.viewTypes = [];
2283
2905
  this.differ = null;
2284
2906
  this.initialized = false;
2907
+ this.preRenderingHTMLElement = [];
2285
2908
  }
2286
- initialize(children, parent, childrenContext) {
2909
+ initialize(children, parent, childrenContext, preRenderingCount = 0, childrenIndics) {
2287
2910
  this.initialized = true;
2288
2911
  this.children = children;
2289
2912
  const isRoot = parent === this.viewContext.editor;
2290
2913
  const firstIndex = isRoot ? this.viewContext.editor.children.indexOf(children[0]) : 0;
2291
2914
  const parentPath = AngularEditor.findPath(this.viewContext.editor, parent);
2915
+ const getBlockIndex = (index) => {
2916
+ if (childrenIndics && childrenIndics[index] !== undefined) {
2917
+ return childrenIndics[index];
2918
+ }
2919
+ return isRoot ? firstIndex + index : index;
2920
+ };
2292
2921
  children.forEach((descendant, _index) => {
2293
- NODE_TO_INDEX.set(descendant, firstIndex + _index);
2922
+ const currentIndex = getBlockIndex(_index);
2923
+ NODE_TO_INDEX.set(descendant, currentIndex);
2294
2924
  NODE_TO_PARENT.set(descendant, parent);
2295
- const context = getContext(firstIndex + _index, descendant, parentPath, childrenContext, this.viewContext);
2925
+ const context = getContext(currentIndex, descendant, parentPath, childrenContext, this.viewContext);
2296
2926
  const viewType = getViewType(descendant, parent, this.viewContext);
2297
2927
  const view = createEmbeddedViewOrComponentOrFlavour(viewType, context, this.viewContext, this.viewContainerRef);
2298
2928
  const blockCard = createBlockCard(descendant, view, this.viewContext);
@@ -2309,19 +2939,45 @@ class ListRender {
2309
2939
  executeAfterViewInit(this.viewContext.editor);
2310
2940
  }
2311
2941
  }
2312
- update(children, parent, childrenContext) {
2942
+ update(children, parent, childrenContext, preRenderingCount = 0, childrenIndics) {
2313
2943
  if (!this.initialized || this.children.length === 0) {
2314
- this.initialize(children, parent, childrenContext);
2944
+ this.initialize(children, parent, childrenContext, preRenderingCount, childrenIndics);
2315
2945
  return;
2316
2946
  }
2317
2947
  if (!this.differ) {
2318
2948
  throw new Error('Exception: Can not find differ ');
2319
2949
  }
2320
- const outletParent = this.getOutletParent();
2950
+ const outletParent = this.getOutletParent();
2951
+ if (this.preRenderingHTMLElement.length > 0) {
2952
+ const preRenderingElement = [...this.preRenderingHTMLElement];
2953
+ preRenderingElement.forEach((rootNodes, index) => {
2954
+ const slateElement = this.children[index];
2955
+ if (slateElement && children.indexOf(slateElement) >= 0) {
2956
+ rootNodes.forEach(rootNode => {
2957
+ setPreRenderingElementStyle(this.viewContext.editor, rootNode, true);
2958
+ });
2959
+ if (isDebug) {
2960
+ debugLog('log', 'preRenderingHTMLElement index: ', this.viewContext.editor.children.indexOf(this.children[index]), 'is clear true');
2961
+ }
2962
+ }
2963
+ else {
2964
+ if (isDebug) {
2965
+ debugLog('log', 'preRenderingHTMLElement index: ', this.viewContext.editor.children.indexOf(this.children[index]), 'do not clear since it would be removed soon');
2966
+ }
2967
+ }
2968
+ });
2969
+ this.preRenderingHTMLElement = [];
2970
+ }
2321
2971
  const diffResult = this.differ.diff(children);
2322
2972
  const parentPath = AngularEditor.findPath(this.viewContext.editor, parent);
2323
2973
  const isRoot = parent === this.viewContext.editor;
2324
- const firstIndex = isRoot ? this.viewContext.editor.children.indexOf(children[0]) : 0;
2974
+ const firstIndex = isRoot && children.length ? this.viewContext.editor.children.indexOf(children[0]) : 0;
2975
+ const getBlockIndex = (index) => {
2976
+ if (childrenIndics && childrenIndics[index] !== undefined) {
2977
+ return childrenIndics[index];
2978
+ }
2979
+ return isRoot ? firstIndex + index : index;
2980
+ };
2325
2981
  if (diffResult) {
2326
2982
  let firstRootNode = getRootNodes(this.views[0], this.blockCards[0])[0];
2327
2983
  const newContexts = [];
@@ -2329,7 +2985,7 @@ class ListRender {
2329
2985
  const newViews = [];
2330
2986
  const newBlockCards = [];
2331
2987
  diffResult.forEachItem(record => {
2332
- const currentIndex = firstIndex + record.currentIndex;
2988
+ const currentIndex = getBlockIndex(record.currentIndex);
2333
2989
  NODE_TO_INDEX.set(record.item, currentIndex);
2334
2990
  NODE_TO_PARENT.set(record.item, parent);
2335
2991
  let context = getContext(currentIndex, record.item, parentPath, childrenContext, this.viewContext);
@@ -2348,462 +3004,262 @@ class ListRender {
2348
3004
  else {
2349
3005
  const previousView = this.views[record.previousIndex];
2350
3006
  const previousViewType = this.viewTypes[record.previousIndex];
2351
- const previousContext = this.contexts[record.previousIndex];
2352
- const previousBlockCard = this.blockCards[record.previousIndex];
2353
- if (previousViewType !== viewType) {
2354
- view = createEmbeddedViewOrComponentOrFlavour(viewType, context, this.viewContext, this.viewContainerRef);
2355
- blockCard = createBlockCard(record.item, view, this.viewContext);
2356
- const firstRootNode = getRootNodes(previousView, previousBlockCard)[0];
2357
- const newRootNodes = getRootNodes(view, blockCard);
2358
- firstRootNode.replaceWith(...newRootNodes);
2359
- previousView.destroy();
2360
- previousBlockCard?.destroy();
2361
- }
2362
- else {
2363
- view = previousView;
2364
- blockCard = previousBlockCard;
2365
- if (memoizedContext(this.viewContext, record.item, previousContext, context)) {
2366
- context = previousContext;
2367
- }
2368
- else {
2369
- updateContext(previousView, context, this.viewContext);
2370
- }
2371
- }
2372
- newContexts.push(context);
2373
- newViews.push(view);
2374
- newBlockCards.push(blockCard);
2375
- }
2376
- });
2377
- diffResult.forEachOperation(record => {
2378
- // removed
2379
- if (record.currentIndex === null) {
2380
- const view = this.views[record.previousIndex];
2381
- const blockCard = this.blockCards[record.previousIndex];
2382
- view.destroy();
2383
- blockCard?.destroy();
2384
- }
2385
- // moved
2386
- if (record.previousIndex !== null && record.currentIndex !== null) {
2387
- mountOnItemChange(record.currentIndex, record.item, newViews, newBlockCards, outletParent, firstRootNode, this.viewContext);
2388
- // Solve the block-card DOMElement loss when moving nodes
2389
- newBlockCards[record.currentIndex]?.instance.append();
2390
- }
2391
- });
2392
- this.viewTypes = newViewTypes;
2393
- this.views = newViews;
2394
- this.contexts = newContexts;
2395
- this.children = children;
2396
- this.blockCards = newBlockCards;
2397
- if (parent === this.viewContext.editor) {
2398
- executeAfterViewInit(this.viewContext.editor);
2399
- }
2400
- }
2401
- else {
2402
- const newContexts = [];
2403
- this.children.forEach((child, _index) => {
2404
- NODE_TO_INDEX.set(child, firstIndex + _index);
2405
- NODE_TO_PARENT.set(child, parent);
2406
- let context = getContext(firstIndex + _index, child, parentPath, childrenContext, this.viewContext);
2407
- const previousContext = this.contexts[_index];
2408
- if (memoizedContext(this.viewContext, child, previousContext, context)) {
2409
- context = previousContext;
2410
- }
2411
- else {
2412
- updateContext(this.views[_index], context, this.viewContext);
2413
- }
2414
- newContexts.push(context);
2415
- });
2416
- this.contexts = newContexts;
2417
- }
2418
- }
2419
- destroy() {
2420
- this.children.forEach((element, index) => {
2421
- if (this.views[index]) {
2422
- this.views[index].destroy();
2423
- }
2424
- if (this.blockCards[index]) {
2425
- this.blockCards[index].destroy();
2426
- }
2427
- });
2428
- this.views = [];
2429
- this.blockCards = [];
2430
- this.contexts = [];
2431
- this.viewTypes = [];
2432
- this.initialized = false;
2433
- this.differ = null;
2434
- }
2435
- }
2436
- function getContext(index, item, parentPath, childrenContext, viewContext) {
2437
- if (Element.isElement(item)) {
2438
- const computedContext = getCommonContext(index, item, parentPath, viewContext, childrenContext);
2439
- const key = AngularEditor.findKey(viewContext.editor, item);
2440
- const isInline = viewContext.editor.isInline(item);
2441
- const isVoid = viewContext.editor.isVoid(item);
2442
- const elementContext = {
2443
- element: item,
2444
- ...computedContext,
2445
- attributes: {
2446
- 'data-slate-node': 'element',
2447
- 'data-slate-key': key.id
2448
- },
2449
- decorate: childrenContext.decorate,
2450
- readonly: childrenContext.readonly
2451
- };
2452
- if (isInline) {
2453
- elementContext.attributes['data-slate-inline'] = true;
2454
- }
2455
- if (isVoid) {
2456
- elementContext.attributes['data-slate-void'] = true;
2457
- }
2458
- // add contentEditable for block element only to avoid chinese input be broken
2459
- if (isVoid && !isInline) {
2460
- elementContext.contentEditable = false;
2461
- }
2462
- return elementContext;
2463
- }
2464
- else {
2465
- const computedContext = getCommonContext(index, item, parentPath, viewContext, childrenContext);
2466
- const isLeafBlock = AngularEditor.isLeafBlock(viewContext.editor, childrenContext.parent);
2467
- const textContext = {
2468
- decorations: computedContext.decorations,
2469
- isLast: isLeafBlock && index === childrenContext.parent.children.length - 1,
2470
- parent: childrenContext.parent,
2471
- text: item
2472
- };
2473
- return textContext;
2474
- }
2475
- }
2476
- function getCommonContext(index, item, parentPath, viewContext, childrenContext) {
2477
- const p = parentPath.concat(index);
2478
- try {
2479
- const ds = childrenContext.decorate([item, p]);
2480
- // [list-render] performance optimization: reduce the number of calls to the `Editor.range(viewContext.editor, p)` method
2481
- if (childrenContext.selection || childrenContext.decorations.length > 0) {
2482
- const range = Editor.range(viewContext.editor, p);
2483
- const sel = childrenContext.selection && Range.intersection(range, childrenContext.selection);
2484
- for (const dec of childrenContext.decorations) {
2485
- const d = Range.intersection(dec, range);
2486
- if (d) {
2487
- ds.push(d);
2488
- }
2489
- }
2490
- return { selection: sel, decorations: ds };
2491
- }
2492
- else {
2493
- return { selection: null, decorations: ds };
2494
- }
2495
- }
2496
- catch (error) {
2497
- viewContext.editor.onError({
2498
- code: SlateErrorCode.GetStartPointError,
2499
- nativeError: error
2500
- });
2501
- return { selection: null, decorations: [] };
2502
- }
2503
- }
2504
- function getViewType(item, parent, viewContext) {
2505
- if (Element.isElement(item)) {
2506
- return (viewContext.renderElement && viewContext.renderElement(item)) || DefaultElementFlavour;
2507
- }
2508
- else {
2509
- const isVoid = viewContext.editor.isVoid(parent);
2510
- return isVoid ? VoidTextFlavour : (viewContext.renderText && viewContext.renderText(item)) || DefaultTextFlavour;
2511
- }
2512
- }
2513
- function createBlockCard(item, view, viewContext) {
2514
- const isBlockCard = viewContext.editor.isBlockCard(item);
2515
- if (isBlockCard) {
2516
- const rootNodes = getRootNodes(view);
2517
- const blockCardRef = new BlockCardRef();
2518
- blockCardRef.instance = new SlateBlockCard();
2519
- blockCardRef.instance.onInit();
2520
- blockCardRef.instance.initializeCenter(rootNodes);
2521
- return blockCardRef;
2522
- }
2523
- else {
2524
- return null;
2525
- }
2526
- }
2527
- function trackBy(viewContext) {
2528
- return (index, node) => {
2529
- return viewContext.trackBy(node) || AngularEditor.findKey(viewContext.editor, node);
2530
- };
2531
- }
2532
- function memoizedContext(viewContext, descendant, prev, next) {
2533
- if (Element.isElement(descendant)) {
2534
- return memoizedElementContext(viewContext, prev, next);
2535
- }
2536
- else {
2537
- return memoizedTextContext(prev, next);
2538
- }
2539
- }
2540
- function memoizedElementContext(viewContext, prev, next) {
2541
- return (prev.element === next.element &&
2542
- (!viewContext.isStrictDecorate || prev.decorate === next.decorate) &&
2543
- prev.readonly === next.readonly &&
2544
- isDecoratorRangeListEqual(prev.decorations, next.decorations) &&
2545
- (prev.selection === next.selection || (!!prev.selection && !!next.selection && Range.equals(prev.selection, next.selection))));
2546
- }
2547
- function memoizedTextContext(prev, next) {
2548
- return (next.parent === prev.parent &&
2549
- next.isLast === prev.isLast &&
2550
- next.text === prev.text &&
2551
- isDecoratorRangeListEqual(next.decorations, prev.decorations));
2552
- }
2553
- function addAfterViewInitQueue(editor, afterViewInitCallback) {
2554
- const queue = getAfterViewInitQueue(editor);
2555
- queue.push(afterViewInitCallback);
2556
- EDITOR_TO_AFTER_VIEW_INIT_QUEUE.set(editor, queue);
2557
- }
2558
- function getAfterViewInitQueue(editor) {
2559
- return EDITOR_TO_AFTER_VIEW_INIT_QUEUE.get(editor) || [];
2560
- }
2561
- function clearAfterViewInitQueue(editor) {
2562
- EDITOR_TO_AFTER_VIEW_INIT_QUEUE.set(editor, []);
2563
- }
2564
- function executeAfterViewInit(editor) {
2565
- const queue = getAfterViewInitQueue(editor);
2566
- queue.forEach(callback => callback());
2567
- clearAfterViewInitQueue(editor);
2568
- }
2569
-
2570
- class VirtualScrollDebugOverlay {
2571
- constructor(doc) {
2572
- this.doc = doc;
2573
- this.originalConsoleLog = console.log.bind(console);
2574
- this.originalConsoleWarn = console.warn.bind(console);
2575
- this.dragOffsetX = 0;
2576
- this.dragOffsetY = 0;
2577
- this.isDragging = false;
2578
- this.onDragging = (event) => {
2579
- if (!this.isDragging || !this.container) {
2580
- return;
2581
- }
2582
- const nextLeft = event.clientX - this.dragOffsetX;
2583
- const nextTop = event.clientY - this.dragOffsetY;
2584
- this.container.style.left = `${nextLeft}px`;
2585
- this.container.style.top = `${nextTop}px`;
2586
- this.container.style.right = 'auto';
2587
- this.container.style.bottom = 'auto';
2588
- };
2589
- this.onDragEnd = () => {
2590
- if (!this.isDragging) {
2591
- return;
2592
- }
2593
- this.isDragging = false;
2594
- this.doc.removeEventListener('mousemove', this.onDragging);
2595
- this.doc.removeEventListener('mouseup', this.onDragEnd);
2596
- if (this.container) {
2597
- this.container.style.transition = '';
3007
+ const previousContext = this.contexts[record.previousIndex];
3008
+ const previousBlockCard = this.blockCards[record.previousIndex];
3009
+ if (previousViewType !== viewType) {
3010
+ view = createEmbeddedViewOrComponentOrFlavour(viewType, context, this.viewContext, this.viewContainerRef);
3011
+ blockCard = createBlockCard(record.item, view, this.viewContext);
3012
+ const firstRootNode = getRootNodes(previousView, previousBlockCard)[0];
3013
+ const newRootNodes = getRootNodes(view, blockCard);
3014
+ firstRootNode.replaceWith(...newRootNodes);
3015
+ previousView.destroy();
3016
+ previousBlockCard?.destroy();
3017
+ }
3018
+ else {
3019
+ view = previousView;
3020
+ blockCard = previousBlockCard;
3021
+ if (memoizedContext(this.viewContext, record.item, previousContext, context)) {
3022
+ context = previousContext;
3023
+ }
3024
+ else {
3025
+ updateContext(previousView, context, this.viewContext);
3026
+ }
3027
+ }
3028
+ newContexts.push(context);
3029
+ newViews.push(view);
3030
+ newBlockCards.push(blockCard);
3031
+ }
3032
+ });
3033
+ diffResult.forEachOperation(record => {
3034
+ // removed
3035
+ if (record.currentIndex === null) {
3036
+ const view = this.views[record.previousIndex];
3037
+ const blockCard = this.blockCards[record.previousIndex];
3038
+ view.destroy();
3039
+ blockCard?.destroy();
3040
+ }
3041
+ // moved
3042
+ if (record.previousIndex !== null && record.currentIndex !== null) {
3043
+ mountOnItemChange(record.currentIndex, record.item, newViews, newBlockCards, outletParent, firstRootNode, this.viewContext);
3044
+ // Solve the block-card DOMElement loss when moving nodes
3045
+ newBlockCards[record.currentIndex]?.instance.append();
3046
+ }
3047
+ });
3048
+ this.viewTypes = newViewTypes;
3049
+ this.views = newViews;
3050
+ this.contexts = newContexts;
3051
+ this.children = children;
3052
+ this.blockCards = newBlockCards;
3053
+ if (parent === this.viewContext.editor) {
3054
+ executeAfterViewInit(this.viewContext.editor);
2598
3055
  }
2599
- };
2600
- }
2601
- init() {
2602
- if (!this.container) {
2603
- this.createContainer();
2604
- }
2605
- }
2606
- log(type, ...args) {
2607
- this.init();
2608
- if (type === 'warn') {
2609
- this.originalConsoleWarn(...args);
2610
3056
  }
2611
3057
  else {
2612
- this.originalConsoleLog(...args);
3058
+ const newContexts = [];
3059
+ this.children.forEach((child, _index) => {
3060
+ const currentIndex = getBlockIndex(_index);
3061
+ NODE_TO_INDEX.set(child, currentIndex);
3062
+ NODE_TO_PARENT.set(child, parent);
3063
+ let context = getContext(currentIndex, child, parentPath, childrenContext, this.viewContext);
3064
+ const previousContext = this.contexts[_index];
3065
+ if (memoizedContext(this.viewContext, child, previousContext, context)) {
3066
+ context = previousContext;
3067
+ }
3068
+ else {
3069
+ updateContext(this.views[_index], context, this.viewContext);
3070
+ }
3071
+ newContexts.push(context);
3072
+ });
3073
+ this.contexts = newContexts;
2613
3074
  }
2614
- this.appendLog(type, ...args);
2615
- }
2616
- dispose() {
2617
- this.container?.remove();
2618
- this.container = undefined;
2619
- this.logList = undefined;
2620
- this.selectorInput = undefined;
2621
- this.distanceInput = undefined;
2622
- this.doc.removeEventListener('mousemove', this.onDragging);
2623
- this.doc.removeEventListener('mouseup', this.onDragEnd);
2624
- this.isDragging = false;
2625
- }
2626
- createContainer() {
2627
- const doc = this.doc;
2628
- const container = doc.createElement('div');
2629
- container.style.position = 'fixed';
2630
- container.style.left = '16px';
2631
- container.style.top = '16px';
2632
- container.style.right = 'auto';
2633
- container.style.bottom = 'auto';
2634
- container.style.width = '360px';
2635
- container.style.maxHeight = '70vh';
2636
- container.style.padding = '12px';
2637
- container.style.boxSizing = 'border-box';
2638
- container.style.background = 'rgba(17, 24, 39, 0.95)';
2639
- container.style.color = '#e5e7eb';
2640
- container.style.fontSize = '12px';
2641
- container.style.fontFamily = 'Menlo, Consolas, monospace';
2642
- container.style.border = '1px solid #1f2937';
2643
- container.style.borderRadius = '10px';
2644
- container.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.35)';
2645
- container.style.zIndex = '9999';
2646
- container.style.display = 'flex';
2647
- container.style.flexDirection = 'column';
2648
- container.style.gap = '10px';
2649
- const header = doc.createElement('div');
2650
- header.style.display = 'flex';
2651
- header.style.alignItems = 'center';
2652
- header.style.justifyContent = 'space-between';
2653
- header.style.cursor = 'move';
2654
- header.addEventListener('mousedown', event => {
2655
- if (!this.container) {
2656
- return;
2657
- }
2658
- const rect = this.container.getBoundingClientRect();
2659
- this.isDragging = true;
2660
- this.dragOffsetX = event.clientX - rect.left;
2661
- this.dragOffsetY = event.clientY - rect.top;
2662
- this.container.style.transition = 'none';
2663
- this.doc.addEventListener('mousemove', this.onDragging);
2664
- this.doc.addEventListener('mouseup', this.onDragEnd);
2665
- event.preventDefault();
2666
- });
2667
- const title = doc.createElement('div');
2668
- title.textContent = 'Virtual Scroll Debug';
2669
- title.style.fontWeight = '600';
2670
- title.style.letterSpacing = '0.3px';
2671
- const clearButton = doc.createElement('button');
2672
- clearButton.type = 'button';
2673
- clearButton.textContent = '清除日志';
2674
- clearButton.style.background = '#374151';
2675
- clearButton.style.color = '#e5e7eb';
2676
- clearButton.style.border = '1px solid #4b5563';
2677
- clearButton.style.borderRadius = '6px';
2678
- clearButton.style.padding = '4px 10px';
2679
- clearButton.style.cursor = 'pointer';
2680
- clearButton.addEventListener('click', () => {
2681
- if (this.logList) {
2682
- this.logList.innerHTML = '';
2683
- }
2684
- });
2685
- header.appendChild(title);
2686
- header.appendChild(clearButton);
2687
- const scrollForm = doc.createElement('div');
2688
- scrollForm.style.display = 'grid';
2689
- scrollForm.style.gridTemplateColumns = '1fr 90px 70px';
2690
- scrollForm.style.gap = '6px';
2691
- scrollForm.style.alignItems = 'center';
2692
- const selectorInput = doc.createElement('input');
2693
- selectorInput.placeholder = '滚动容器 selector';
2694
- selectorInput.style.padding = '6px 8px';
2695
- selectorInput.style.borderRadius = '6px';
2696
- selectorInput.style.border = '1px solid #4b5563';
2697
- selectorInput.style.background = '#111827';
2698
- selectorInput.style.color = '#e5e7eb';
2699
- selectorInput.autocomplete = 'off';
2700
- const distanceInput = doc.createElement('input');
2701
- distanceInput.placeholder = '滚动距离(px)';
2702
- distanceInput.type = 'number';
2703
- distanceInput.style.padding = '6px 8px';
2704
- distanceInput.style.borderRadius = '6px';
2705
- distanceInput.style.border = '1px solid #4b5563';
2706
- distanceInput.style.background = '#111827';
2707
- distanceInput.style.color = '#e5e7eb';
2708
- const scrollButton = doc.createElement('button');
2709
- scrollButton.type = 'button';
2710
- scrollButton.textContent = '滚动';
2711
- scrollButton.style.background = '#10b981';
2712
- scrollButton.style.color = '#0b1c15';
2713
- scrollButton.style.border = 'none';
2714
- scrollButton.style.borderRadius = '6px';
2715
- scrollButton.style.padding = '6px 10px';
2716
- scrollButton.style.cursor = 'pointer';
2717
- scrollButton.addEventListener('click', () => {
2718
- const selector = selectorInput.value.trim();
2719
- const distanceValue = Number(distanceInput.value ?? 0);
2720
- const distance = Number.isFinite(distanceValue) ? distanceValue : 0;
2721
- if (!selector) {
2722
- this.log('warn', '请先填写滚动容器 selector');
2723
- return;
2724
- }
2725
- const target = doc.querySelector(selector);
2726
- if (!target) {
2727
- this.log('warn', `未找到滚动容器: ${selector}`);
2728
- return;
3075
+ if (preRenderingCount > 0) {
3076
+ for (let i = 0; i < preRenderingCount; i++) {
3077
+ const rootNodes = [...getRootNodes(this.views[i], this.blockCards[i])];
3078
+ rootNodes.forEach(rootNode => {
3079
+ setPreRenderingElementStyle(this.viewContext.editor, rootNode);
3080
+ });
3081
+ this.preRenderingHTMLElement.push(rootNodes);
3082
+ if (isDebug) {
3083
+ debugLog('log', 'preRenderingHTMLElement index: ', this.viewContext.editor.children.indexOf(children[i]));
3084
+ }
2729
3085
  }
2730
- if (typeof target.scrollTo === 'function') {
2731
- target.scrollTo({ top: distance, behavior: 'auto' });
3086
+ }
3087
+ if (isDebug) {
3088
+ for (let i = 0; i < children.length; i++) {
3089
+ const rootNodes = [...getRootNodes(this.views[i], this.blockCards[i])];
3090
+ const index = this.viewContext.editor.children.indexOf(children[i]);
3091
+ const height = getRealHeightByElement(this.viewContext.editor, children[i]);
3092
+ rootNodes.forEach(rootNode => {
3093
+ rootNode.setAttribute('debug-index', index.toString());
3094
+ rootNode.setAttribute('debug-height', height.toString());
3095
+ });
2732
3096
  }
2733
- else if (Object.prototype.hasOwnProperty.call(target, 'scrollTop')) {
2734
- target.scrollTop = distance;
3097
+ }
3098
+ }
3099
+ destroy() {
3100
+ this.children.forEach((element, index) => {
3101
+ if (this.views[index]) {
3102
+ this.views[index].destroy();
2735
3103
  }
2736
- else {
2737
- this.log('warn', '目标元素不支持滚动:', selector);
2738
- return;
3104
+ if (this.blockCards[index]) {
3105
+ this.blockCards[index].destroy();
2739
3106
  }
2740
- this.log('log', `已将 ${selector} 滚动到`, distance);
2741
3107
  });
2742
- scrollForm.appendChild(selectorInput);
2743
- scrollForm.appendChild(distanceInput);
2744
- scrollForm.appendChild(scrollButton);
2745
- const logList = doc.createElement('div');
2746
- logList.style.height = '260px';
2747
- logList.style.overflowY = 'auto';
2748
- logList.style.background = '#0b1220';
2749
- logList.style.border = '1px solid #1f2937';
2750
- logList.style.borderRadius = '8px';
2751
- logList.style.padding = '8px';
2752
- logList.style.display = 'flex';
2753
- logList.style.flexDirection = 'column';
2754
- logList.style.gap = '6px';
2755
- container.appendChild(header);
2756
- container.appendChild(scrollForm);
2757
- container.appendChild(logList);
2758
- doc.body.appendChild(container);
2759
- this.container = container;
2760
- this.logList = logList;
2761
- this.selectorInput = selectorInput;
2762
- this.distanceInput = distanceInput;
2763
- }
2764
- appendLog(type, ...args) {
2765
- if (!this.logList) {
2766
- return;
2767
- }
2768
- const item = this.doc.createElement('div');
2769
- item.style.display = 'flex';
2770
- item.style.gap = '6px';
2771
- item.style.alignItems = 'flex-start';
2772
- item.style.wordBreak = 'break-all';
2773
- item.style.color = type === 'warn' ? '#fbbf24' : '#9ca3af';
2774
- const time = this.doc.createElement('span');
2775
- time.textContent = new Date().toLocaleTimeString();
2776
- time.style.color = '#6b7280';
2777
- time.style.flexShrink = '0';
2778
- time.style.width = '72px';
2779
- const text = this.doc.createElement('span');
2780
- text.textContent = `[${type}] ${args.map(arg => this.formatValue(arg)).join(' ')}`;
2781
- item.appendChild(time);
2782
- item.appendChild(text);
2783
- this.logList.appendChild(item);
2784
- this.logList.scrollTop = this.logList.scrollHeight;
3108
+ this.children = [];
3109
+ this.views = [];
3110
+ this.blockCards = [];
3111
+ this.contexts = [];
3112
+ this.viewTypes = [];
3113
+ this.initialized = false;
3114
+ this.differ = null;
2785
3115
  }
2786
- formatValue(value) {
2787
- if (typeof value === 'string') {
2788
- return value;
3116
+ }
3117
+ function getContext(index, item, parentPath, childrenContext, viewContext) {
3118
+ if (Element.isElement(item)) {
3119
+ const computedContext = getCommonContext(index, item, parentPath, viewContext, childrenContext);
3120
+ const key = AngularEditor.findKey(viewContext.editor, item);
3121
+ const isInline = viewContext.editor.isInline(item);
3122
+ const isVoid = viewContext.editor.isVoid(item);
3123
+ const elementContext = {
3124
+ element: item,
3125
+ ...computedContext,
3126
+ attributes: {
3127
+ 'data-slate-node': 'element',
3128
+ 'data-slate-key': key.id
3129
+ },
3130
+ decorate: childrenContext.decorate,
3131
+ readonly: childrenContext.readonly
3132
+ };
3133
+ if (isInline) {
3134
+ elementContext.attributes['data-slate-inline'] = true;
2789
3135
  }
2790
- try {
2791
- return JSON.stringify(value);
3136
+ if (isVoid) {
3137
+ elementContext.attributes['data-slate-void'] = true;
2792
3138
  }
2793
- catch (error) {
2794
- return String(value);
3139
+ // add contentEditable for block element only to avoid chinese input be broken
3140
+ if (isVoid && !isInline) {
3141
+ elementContext.contentEditable = false;
3142
+ }
3143
+ return elementContext;
3144
+ }
3145
+ else {
3146
+ const computedContext = getCommonContext(index, item, parentPath, viewContext, childrenContext);
3147
+ const isLeafBlock = AngularEditor.isLeafBlock(viewContext.editor, childrenContext.parent);
3148
+ const textContext = {
3149
+ decorations: computedContext.decorations,
3150
+ isLast: isLeafBlock && index === childrenContext.parent.children.length - 1,
3151
+ parent: childrenContext.parent,
3152
+ text: item
3153
+ };
3154
+ return textContext;
3155
+ }
3156
+ }
3157
+ function getCommonContext(index, item, parentPath, viewContext, childrenContext) {
3158
+ const p = parentPath.concat(index);
3159
+ try {
3160
+ const ds = childrenContext.decorate([item, p]);
3161
+ // [list-render] performance optimization: reduce the number of calls to the `Editor.range(viewContext.editor, p)` method
3162
+ if (childrenContext.selection || childrenContext.decorations.length > 0) {
3163
+ const range = Editor.range(viewContext.editor, p);
3164
+ const sel = childrenContext.selection && Range.intersection(range, childrenContext.selection);
3165
+ for (const dec of childrenContext.decorations) {
3166
+ const d = Range.intersection(dec, range);
3167
+ if (d) {
3168
+ ds.push(d);
3169
+ }
3170
+ }
3171
+ return { selection: sel, decorations: ds };
3172
+ }
3173
+ else {
3174
+ return { selection: null, decorations: ds };
2795
3175
  }
2796
3176
  }
3177
+ catch (error) {
3178
+ viewContext.editor.onError({
3179
+ code: SlateErrorCode.GetStartPointError,
3180
+ nativeError: error
3181
+ });
3182
+ return { selection: null, decorations: [] };
3183
+ }
3184
+ }
3185
+ function getViewType(item, parent, viewContext) {
3186
+ if (Element.isElement(item)) {
3187
+ return (viewContext.renderElement && viewContext.renderElement(item)) || DefaultElementFlavour;
3188
+ }
3189
+ else {
3190
+ const isVoid = viewContext.editor.isVoid(parent);
3191
+ return isVoid ? VoidTextFlavour : (viewContext.renderText && viewContext.renderText(item)) || DefaultTextFlavour;
3192
+ }
3193
+ }
3194
+ function createBlockCard(item, view, viewContext) {
3195
+ const isBlockCard = viewContext.editor.isBlockCard(item);
3196
+ if (isBlockCard) {
3197
+ const rootNodes = getRootNodes(view);
3198
+ const blockCardRef = new BlockCardRef();
3199
+ blockCardRef.instance = new SlateBlockCard();
3200
+ blockCardRef.instance.onInit();
3201
+ blockCardRef.instance.initializeCenter(rootNodes);
3202
+ return blockCardRef;
3203
+ }
3204
+ else {
3205
+ return null;
3206
+ }
3207
+ }
3208
+ function trackBy(viewContext) {
3209
+ return (index, node) => {
3210
+ return viewContext.trackBy(node) || AngularEditor.findKey(viewContext.editor, node);
3211
+ };
3212
+ }
3213
+ function memoizedContext(viewContext, descendant, prev, next) {
3214
+ if (Element.isElement(descendant)) {
3215
+ return memoizedElementContext(viewContext, prev, next);
3216
+ }
3217
+ else {
3218
+ return memoizedTextContext(prev, next);
3219
+ }
3220
+ }
3221
+ function memoizedElementContext(viewContext, prev, next) {
3222
+ return (prev.element === next.element &&
3223
+ (!viewContext.isStrictDecorate || prev.decorate === next.decorate) &&
3224
+ prev.readonly === next.readonly &&
3225
+ isDecoratorRangeListEqual(prev.decorations, next.decorations) &&
3226
+ (prev.selection === next.selection || (!!prev.selection && !!next.selection && Range.equals(prev.selection, next.selection))));
3227
+ }
3228
+ function memoizedTextContext(prev, next) {
3229
+ return (next.parent === prev.parent &&
3230
+ next.isLast === prev.isLast &&
3231
+ next.text === prev.text &&
3232
+ isDecoratorRangeListEqual(next.decorations, prev.decorations));
3233
+ }
3234
+ function addAfterViewInitQueue(editor, afterViewInitCallback) {
3235
+ const queue = getAfterViewInitQueue(editor);
3236
+ queue.push(afterViewInitCallback);
3237
+ EDITOR_TO_AFTER_VIEW_INIT_QUEUE.set(editor, queue);
3238
+ }
3239
+ function getAfterViewInitQueue(editor) {
3240
+ return EDITOR_TO_AFTER_VIEW_INIT_QUEUE.get(editor) || [];
3241
+ }
3242
+ function clearAfterViewInitQueue(editor) {
3243
+ EDITOR_TO_AFTER_VIEW_INIT_QUEUE.set(editor, []);
3244
+ }
3245
+ function executeAfterViewInit(editor) {
3246
+ const queue = getAfterViewInitQueue(editor);
3247
+ queue.forEach(callback => callback());
3248
+ clearAfterViewInitQueue(editor);
2797
3249
  }
2798
3250
 
2799
- const JUST_NOW_UPDATED_VIRTUAL_VIEW = new WeakMap();
2800
3251
  // not correctly clipboardData on beforeinput
2801
3252
  const forceOnDOMPaste = IS_SAFARI;
2802
- const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
2803
3253
  class SlateEditable {
2804
3254
  set virtualScroll(config) {
2805
- this.virtualConfig = config;
2806
- this.doVirtualScroll();
3255
+ this.virtualScrollConfig = config;
3256
+ if (isDebugScrollTop) {
3257
+ debugLog('log', 'virtualScrollConfig scrollTop:', config.scrollTop);
3258
+ }
3259
+ IS_ENABLED_VIRTUAL_SCROLL.set(this.editor, config.enabled);
3260
+ if (this.isEnabledVirtualScroll()) {
3261
+ this.tryUpdateVirtualViewport();
3262
+ }
2807
3263
  }
2808
3264
  get hasBeforeInputSupport() {
2809
3265
  return HAS_BEFORE_INPUT_SUPPORT;
@@ -2848,16 +3304,16 @@ class SlateEditable {
2848
3304
  return null;
2849
3305
  }
2850
3306
  };
2851
- this.virtualConfig = {
3307
+ this.virtualScrollConfig = {
2852
3308
  enabled: false,
2853
3309
  scrollTop: 0,
2854
- viewportHeight: 0
3310
+ viewportHeight: 0,
3311
+ viewportBoundingTop: 0,
3312
+ scrollContainer: null
2855
3313
  };
2856
- this.renderedChildren = [];
2857
- this.virtualVisibleIndexes = new Set();
2858
- this.measuredHeights = new Map();
2859
- // the height from scroll container top to editor top height element
2860
- this.businessHeight = 0;
3314
+ this.inViewportChildren = [];
3315
+ this.inViewportIndics = [];
3316
+ this.keyHeightMap = new Map();
2861
3317
  this.virtualScrollInitialized = false;
2862
3318
  }
2863
3319
  ngOnInit() {
@@ -2869,6 +3325,7 @@ class SlateEditable {
2869
3325
  NODE_TO_ELEMENT.set(this.editor, this.elementRef.nativeElement);
2870
3326
  ELEMENT_TO_NODE.set(this.elementRef.nativeElement, this.editor);
2871
3327
  IS_READ_ONLY.set(this.editor, this.readonly);
3328
+ ELEMENT_KEY_TO_HEIGHTS.set(this.editor, this.keyHeightMap);
2872
3329
  EDITOR_TO_ON_CHANGE.set(this.editor, () => {
2873
3330
  this.ngZone.run(() => {
2874
3331
  this.onChange();
@@ -2882,7 +3339,7 @@ class SlateEditable {
2882
3339
  // add browser class
2883
3340
  let browserClass = IS_FIREFOX ? 'firefox' : IS_SAFARI ? 'safari' : '';
2884
3341
  browserClass && this.elementRef.nativeElement.classList.add(browserClass);
2885
- this.initializeVirtualScrolling();
3342
+ this.initializeVirtualScroll();
2886
3343
  this.listRender = new ListRender(this.viewContext, this.viewContainerRef, this.getOutletParent, this.getOutletElement);
2887
3344
  }
2888
3345
  ngOnChanges(simpleChanges) {
@@ -2914,16 +3371,29 @@ class SlateEditable {
2914
3371
  if (value && value.length) {
2915
3372
  this.editor.children = value;
2916
3373
  this.initializeContext();
2917
- const virtualView = this.refreshVirtualView();
2918
- this.applyVirtualView(virtualView);
2919
- const childrenForRender = virtualView.renderedChildren;
2920
- if (!this.listRender.initialized) {
2921
- this.listRender.initialize(childrenForRender, this.editor, this.context);
3374
+ if (this.isEnabledVirtualScroll()) {
3375
+ const virtualView = this.calculateVirtualViewport();
3376
+ this.applyVirtualView(virtualView);
3377
+ const childrenForRender = virtualView.inViewportChildren;
3378
+ if (isDebug) {
3379
+ debugLog('log', 'writeValue calculate: ', virtualView.inViewportIndics, 'initialized: ', this.listRender.initialized);
3380
+ }
3381
+ if (!this.listRender.initialized) {
3382
+ this.listRender.initialize(childrenForRender, this.editor, this.context, 0, virtualView.inViewportIndics);
3383
+ }
3384
+ else {
3385
+ const { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics } = this.handlePreRendering();
3386
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount, childrenWithPreRenderingIndics);
3387
+ }
2922
3388
  }
2923
3389
  else {
2924
- this.listRender.update(childrenForRender, this.editor, this.context);
3390
+ if (!this.listRender.initialized) {
3391
+ this.listRender.initialize(this.editor.children, this.editor, this.context);
3392
+ }
3393
+ else {
3394
+ this.listRender.update(this.editor.children, this.editor, this.context);
3395
+ }
2925
3396
  }
2926
- this.scheduleMeasureVisibleHeights();
2927
3397
  this.cdr.markForCheck();
2928
3398
  }
2929
3399
  }
@@ -2954,26 +3424,52 @@ class SlateEditable {
2954
3424
  this.addEventListener(event.name, () => { });
2955
3425
  });
2956
3426
  }
2957
- toNativeSelection() {
2958
- try {
2959
- let { selection } = this.editor;
2960
- if (this.virtualConfig?.enabled && selection) {
2961
- const indics = Array.from(this.virtualVisibleIndexes.values());
2962
- if (indics.length > 0) {
2963
- const currentVisibleRange = {
2964
- anchor: Editor.start(this.editor, [indics[0]]),
2965
- focus: Editor.end(this.editor, [indics[indics.length - 1]])
2966
- };
2967
- const [start, end] = Range.edges(selection);
2968
- const forwardSelection = { anchor: start, focus: end };
2969
- const intersectedSelection = Range.intersection(forwardSelection, currentVisibleRange);
2970
- if (!intersectedSelection || !Range.equals(intersectedSelection, forwardSelection)) {
2971
- selection = intersectedSelection;
2972
- if (isDebug) {
2973
- this.debugLog('log', `selection is not in visible range, selection: ${JSON.stringify(selection)}, intersectedSelection: ${JSON.stringify(intersectedSelection)}`);
2974
- }
3427
+ calculateVirtualScrollSelection(selection) {
3428
+ if (selection) {
3429
+ const isBlockCardCursor = AngularEditor.isBlockCardLeftCursor(this.editor) || AngularEditor.isBlockCardRightCursor(this.editor);
3430
+ const indics = this.inViewportIndics;
3431
+ if (indics.length > 0) {
3432
+ const currentVisibleRange = {
3433
+ anchor: Editor.start(this.editor, [indics[0]]),
3434
+ focus: Editor.end(this.editor, [indics[indics.length - 1]])
3435
+ };
3436
+ const [start, end] = Range.edges(selection);
3437
+ let forwardSelection = { anchor: start, focus: end };
3438
+ if (!isBlockCardCursor) {
3439
+ forwardSelection = { anchor: start, focus: end };
3440
+ }
3441
+ else {
3442
+ forwardSelection = { anchor: { path: start.path, offset: 0 }, focus: { path: end.path, offset: 0 } };
3443
+ }
3444
+ const intersectedSelection = Range.intersection(forwardSelection, currentVisibleRange);
3445
+ if (intersectedSelection && isBlockCardCursor) {
3446
+ return selection;
3447
+ }
3448
+ EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, intersectedSelection);
3449
+ if (!intersectedSelection || !Range.equals(intersectedSelection, forwardSelection)) {
3450
+ if (isDebug) {
3451
+ debugLog('log', `selection is not in visible range, selection: ${JSON.stringify(selection)}, currentVisibleRange: ${JSON.stringify(currentVisibleRange)}, intersectedSelection: ${JSON.stringify(intersectedSelection)}`);
2975
3452
  }
3453
+ return intersectedSelection;
2976
3454
  }
3455
+ return selection;
3456
+ }
3457
+ }
3458
+ EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, null);
3459
+ return selection;
3460
+ }
3461
+ isSelectionInvisible(selection) {
3462
+ const anchorIndex = selection.anchor.path[0];
3463
+ const focusIndex = selection.focus.path[0];
3464
+ const anchorElement = this.editor.children[anchorIndex];
3465
+ const focusElement = this.editor.children[focusIndex];
3466
+ return !anchorElement || !focusElement || !this.editor.isVisible(anchorElement) || !this.editor.isVisible(focusElement);
3467
+ }
3468
+ toNativeSelection(autoScroll = true) {
3469
+ try {
3470
+ let { selection } = this.editor;
3471
+ if (this.isEnabledVirtualScroll()) {
3472
+ selection = this.calculateVirtualScrollSelection(selection);
2977
3473
  }
2978
3474
  const root = AngularEditor.findDocumentOrShadowRoot(this.editor);
2979
3475
  const { activeElement } = root;
@@ -3035,13 +3531,24 @@ class SlateEditable {
3035
3531
  domSelection.removeAllRanges();
3036
3532
  }
3037
3533
  setTimeout(() => {
3038
- // handle scrolling in setTimeout because of
3039
- // dom should not have updated immediately after listRender's updating
3040
- newDomRange && this.scrollSelectionIntoView(this.editor, newDomRange);
3041
- // COMPAT: In Firefox, it's not enough to create a range, you also need
3042
- // to focus the contenteditable element too. (2016/11/16)
3043
- if (newDomRange && IS_FIREFOX) {
3044
- el.focus();
3534
+ if (this.isEnabledVirtualScroll() &&
3535
+ !selection &&
3536
+ this.editor.selection &&
3537
+ autoScroll &&
3538
+ this.virtualScrollConfig.scrollContainer) {
3539
+ this.virtualScrollConfig.scrollContainer.scrollTop = this.virtualScrollConfig.scrollContainer.scrollTop + 100;
3540
+ this.isUpdatingSelection = false;
3541
+ return;
3542
+ }
3543
+ else {
3544
+ // handle scrolling in setTimeout because of
3545
+ // dom should not have updated immediately after listRender's updating
3546
+ newDomRange && autoScroll && this.scrollSelectionIntoView(this.editor, newDomRange);
3547
+ // COMPAT: In Firefox, it's not enough to create a range, you also need
3548
+ // to focus the contenteditable element too. (2016/11/16)
3549
+ if (newDomRange && IS_FIREFOX) {
3550
+ el.focus();
3551
+ }
3045
3552
  }
3046
3553
  this.isUpdatingSelection = false;
3047
3554
  });
@@ -3062,10 +3569,12 @@ class SlateEditable {
3062
3569
  ngDoCheck() { }
3063
3570
  forceRender() {
3064
3571
  this.updateContext();
3065
- const virtualView = this.refreshVirtualView();
3066
- this.applyVirtualView(virtualView);
3067
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
3068
- this.scheduleMeasureVisibleHeights();
3572
+ if (this.isEnabledVirtualScroll()) {
3573
+ this.updateListRenderAndRemeasureHeights();
3574
+ }
3575
+ else {
3576
+ this.listRender.update(this.editor.children, this.editor, this.context);
3577
+ }
3069
3578
  // repair collaborative editing when Chinese input is interrupted by other users' cursors
3070
3579
  // when the DOMElement where the selection is located is removed
3071
3580
  // the compositionupdate and compositionend events will no longer be fired
@@ -3099,15 +3608,40 @@ class SlateEditable {
3099
3608
  }
3100
3609
  }, 0);
3101
3610
  }
3102
- this.toNativeSelection();
3611
+ if (this.editor.selection && this.isSelectionInvisible(this.editor.selection)) {
3612
+ Transforms.deselect(this.editor);
3613
+ return;
3614
+ }
3615
+ else {
3616
+ this.toNativeSelection();
3617
+ }
3103
3618
  }
3104
3619
  render() {
3105
3620
  const changed = this.updateContext();
3106
3621
  if (changed) {
3107
- const virtualView = this.refreshVirtualView();
3108
- this.applyVirtualView(virtualView);
3109
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
3110
- this.scheduleMeasureVisibleHeights();
3622
+ if (this.isEnabledVirtualScroll()) {
3623
+ this.updateListRenderAndRemeasureHeights();
3624
+ }
3625
+ else {
3626
+ this.listRender.update(this.editor.children, this.editor, this.context);
3627
+ }
3628
+ }
3629
+ }
3630
+ updateListRenderAndRemeasureHeights() {
3631
+ const virtualView = this.calculateVirtualViewport();
3632
+ const oldInViewportChildren = this.inViewportChildren;
3633
+ this.applyVirtualView(virtualView);
3634
+ const { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics } = this.handlePreRendering();
3635
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount, childrenWithPreRenderingIndics);
3636
+ // 新增或者修改的才需要重算,计算出这个结果
3637
+ const remeasureIndics = [];
3638
+ this.inViewportChildren.forEach((child, index) => {
3639
+ if (oldInViewportChildren.indexOf(child) === -1) {
3640
+ remeasureIndics.push(this.inViewportIndics[index]);
3641
+ }
3642
+ });
3643
+ if (isDebug && remeasureIndics.length > 0) {
3644
+ console.log('remeasure height by indics: ', remeasureIndics);
3111
3645
  }
3112
3646
  }
3113
3647
  updateContext() {
@@ -3170,14 +3704,14 @@ class SlateEditable {
3170
3704
  decorations.push(...placeholderDecorations);
3171
3705
  return decorations;
3172
3706
  }
3173
- shouldUseVirtual() {
3174
- return !!(this.virtualConfig && this.virtualConfig.enabled);
3707
+ isEnabledVirtualScroll() {
3708
+ return !!(this.virtualScrollConfig && this.virtualScrollConfig.enabled);
3175
3709
  }
3176
- initializeVirtualScrolling() {
3710
+ initializeVirtualScroll() {
3177
3711
  if (this.virtualScrollInitialized) {
3178
3712
  return;
3179
3713
  }
3180
- if (this.virtualConfig && this.virtualConfig.enabled) {
3714
+ if (this.isEnabledVirtualScroll()) {
3181
3715
  this.virtualScrollInitialized = true;
3182
3716
  this.virtualTopHeightElement = document.createElement('div');
3183
3717
  this.virtualTopHeightElement.classList.add('virtual-top-height');
@@ -3190,188 +3724,274 @@ class SlateEditable {
3190
3724
  this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
3191
3725
  this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
3192
3726
  this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
3193
- this.businessHeight = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3194
- let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect()?.width ?? 0;
3727
+ let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect().width;
3728
+ EDITOR_TO_ROOT_NODE_WIDTH.set(this.editor, this.virtualTopHeightElement.getBoundingClientRect().width);
3195
3729
  this.editorResizeObserver = new ResizeObserver(entries => {
3196
3730
  if (entries.length > 0 && entries[0].contentRect.width !== editorResizeObserverRectWidth) {
3197
3731
  editorResizeObserverRectWidth = entries[0].contentRect.width;
3198
- this.remeasureHeightByIndics(Array.from(this.virtualVisibleIndexes));
3732
+ this.keyHeightMap.clear();
3733
+ const remeasureIndics = this.inViewportIndics;
3734
+ measureHeightByIndics(this.editor, remeasureIndics, true);
3735
+ EDITOR_TO_ROOT_NODE_WIDTH.set(this.editor, this.virtualTopHeightElement.getBoundingClientRect().width);
3736
+ if (isDebug) {
3737
+ debugLog('log', 'editorResizeObserverRectWidth: ', editorResizeObserverRectWidth, 'EDITOR_TO_ROOT_NODE_WIDTH: ', EDITOR_TO_ROOT_NODE_WIDTH.get(this.editor));
3738
+ }
3199
3739
  }
3200
3740
  });
3201
3741
  this.editorResizeObserver.observe(this.elementRef.nativeElement);
3202
- if (isDebug) {
3203
- const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3204
- this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3205
- this.debugOverlay.init();
3206
- }
3207
3742
  }
3208
3743
  }
3209
- changeVirtualHeight(topHeight, bottomHeight) {
3744
+ setVirtualSpaceHeight(topHeight, bottomHeight) {
3210
3745
  if (!this.virtualScrollInitialized) {
3211
3746
  return;
3212
3747
  }
3213
3748
  this.virtualTopHeightElement.style.height = `${topHeight}px`;
3214
- this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
3749
+ if (bottomHeight !== undefined) {
3750
+ this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
3751
+ }
3215
3752
  }
3216
- debugLog(type, ...args) {
3217
- if (!this.debugOverlay) {
3218
- const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3219
- this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3753
+ getActualVirtualTopHeight() {
3754
+ if (!this.virtualScrollInitialized) {
3755
+ return 0;
3756
+ }
3757
+ return parseFloat(this.virtualTopHeightElement.style.height.replace('px', ''));
3758
+ }
3759
+ handlePreRendering() {
3760
+ let preRenderingCount = 0;
3761
+ const childrenWithPreRendering = [...this.inViewportChildren];
3762
+ const childrenWithPreRenderingIndics = [...this.inViewportIndics];
3763
+ const firstIndex = this.inViewportIndics[0];
3764
+ for (let index = firstIndex - 1; index >= 0; index--) {
3765
+ const element = this.editor.children[index];
3766
+ if (this.editor.isVisible(element)) {
3767
+ childrenWithPreRendering.unshift(element);
3768
+ childrenWithPreRenderingIndics.unshift(index);
3769
+ preRenderingCount = 1;
3770
+ break;
3771
+ }
3772
+ }
3773
+ const lastIndex = this.inViewportIndics[this.inViewportIndics.length - 1];
3774
+ for (let index = lastIndex + 1; index < this.editor.children.length; index++) {
3775
+ const element = this.editor.children[index];
3776
+ if (this.editor.isVisible(element)) {
3777
+ childrenWithPreRendering.push(element);
3778
+ childrenWithPreRenderingIndics.push(index);
3779
+ break;
3780
+ }
3220
3781
  }
3221
- this.debugOverlay.log(type, ...args);
3782
+ return { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics };
3222
3783
  }
3223
- doVirtualScroll() {
3224
- this.refreshVirtualViewAnimId && cancelAnimationFrame(this.refreshVirtualViewAnimId);
3225
- this.refreshVirtualViewAnimId = requestAnimationFrame(() => {
3226
- let virtualView = this.refreshVirtualView();
3227
- let diff = this.diffVirtualView(virtualView);
3228
- if (!diff.isDiff) {
3784
+ tryUpdateVirtualViewport() {
3785
+ if (isDebug) {
3786
+ debugLog('log', 'tryUpdateVirtualViewport');
3787
+ }
3788
+ if (this.inViewportIndics.length > 0) {
3789
+ const topHeight = this.getActualVirtualTopHeight();
3790
+ const refreshVirtualTopHeight = calculateVirtualTopHeight(this.editor, this.inViewportIndics[0]);
3791
+ if (topHeight !== refreshVirtualTopHeight) {
3792
+ if (isDebug) {
3793
+ debugLog('log', 'update top height since dirty state(正数减去高度,负数代表增加高度): ', topHeight - refreshVirtualTopHeight);
3794
+ }
3795
+ this.setVirtualSpaceHeight(refreshVirtualTopHeight);
3229
3796
  return;
3230
3797
  }
3231
- if (diff.isMissingTop) {
3232
- const result = this.remeasureHeightByIndics(diff.diffTopRenderedIndexes);
3233
- if (result) {
3234
- virtualView = this.refreshVirtualView();
3235
- diff = this.diffVirtualView(virtualView, 'second');
3236
- if (!diff.isDiff) {
3237
- return;
3238
- }
3798
+ }
3799
+ this.tryUpdateVirtualViewportAnimId && cancelAnimationFrame(this.tryUpdateVirtualViewportAnimId);
3800
+ this.tryUpdateVirtualViewportAnimId = requestAnimationFrame(() => {
3801
+ if (isDebug) {
3802
+ debugLog('log', 'tryUpdateVirtualViewport Anim start');
3803
+ }
3804
+ let virtualView = this.calculateVirtualViewport();
3805
+ let diff = this.diffVirtualViewport(virtualView);
3806
+ if (diff.isDifferent && diff.needRemoveOnTop) {
3807
+ const remeasureIndics = diff.changedIndexesOfTop;
3808
+ const changed = measureHeightByIndics(this.editor, remeasureIndics);
3809
+ if (changed) {
3810
+ virtualView = this.calculateVirtualViewport();
3811
+ diff = this.diffVirtualViewport(virtualView, 'second');
3239
3812
  }
3240
3813
  }
3241
- this.applyVirtualView(virtualView);
3242
- if (this.listRender.initialized) {
3243
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
3244
- if (!AngularEditor.isReadOnly(this.editor) && this.editor.selection) {
3245
- this.toNativeSelection();
3814
+ if (diff.isDifferent) {
3815
+ this.applyVirtualView(virtualView);
3816
+ if (this.listRender.initialized) {
3817
+ const { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics } = this.handlePreRendering();
3818
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount, childrenWithPreRenderingIndics);
3819
+ if (diff.needAddOnTop) {
3820
+ const remeasureAddedIndics = diff.changedIndexesOfTop;
3821
+ if (isDebug) {
3822
+ debugLog('log', 'needAddOnTop to remeasure heights: ', remeasureAddedIndics);
3823
+ }
3824
+ const startIndexBeforeAdd = diff.changedIndexesOfTop[diff.changedIndexesOfTop.length - 1] + 1;
3825
+ const topHeightBeforeAdd = virtualView.accumulatedHeights[startIndexBeforeAdd];
3826
+ const changed = measureHeightByIndics(this.editor, remeasureAddedIndics);
3827
+ if (changed) {
3828
+ const newHeights = buildHeightsAndAccumulatedHeights(this.editor);
3829
+ const actualTopHeightAfterAdd = newHeights.accumulatedHeights[startIndexBeforeAdd];
3830
+ const newTopHeight = virtualView.top - (actualTopHeightAfterAdd - topHeightBeforeAdd);
3831
+ this.setVirtualSpaceHeight(newTopHeight);
3832
+ if (isDebug) {
3833
+ debugLog('log', `update top height since will add element in top(正数减去高度,负数代表增加高度): ${actualTopHeightAfterAdd - topHeightBeforeAdd}`);
3834
+ }
3835
+ }
3836
+ }
3837
+ if (!AngularEditor.isReadOnly(this.editor) && this.editor.selection) {
3838
+ this.toNativeSelection(false);
3839
+ }
3246
3840
  }
3247
3841
  }
3248
- this.scheduleMeasureVisibleHeights();
3842
+ if (isDebug) {
3843
+ debugLog('log', 'tryUpdateVirtualViewport Anim end');
3844
+ }
3249
3845
  });
3250
3846
  }
3251
- refreshVirtualView() {
3847
+ calculateVirtualViewport() {
3252
3848
  const children = (this.editor.children || []);
3253
- if (!children.length || !this.shouldUseVirtual()) {
3849
+ if (!children.length || !this.isEnabledVirtualScroll()) {
3254
3850
  return {
3255
- renderedChildren: children,
3256
- visibleIndexes: new Set(),
3851
+ inViewportChildren: children,
3852
+ inViewportIndics: [],
3257
3853
  top: 0,
3258
3854
  bottom: 0,
3259
3855
  heights: []
3260
3856
  };
3261
3857
  }
3262
- const scrollTop = this.virtualConfig.scrollTop;
3263
- const viewportHeight = this.virtualConfig.viewportHeight ?? 0;
3858
+ const scrollTop = this.virtualScrollConfig.scrollTop;
3859
+ const viewportHeight = this.virtualScrollConfig.viewportHeight ?? 0;
3264
3860
  if (!viewportHeight) {
3265
3861
  return {
3266
- renderedChildren: [],
3267
- visibleIndexes: new Set(),
3862
+ inViewportChildren: [],
3863
+ inViewportIndics: [],
3268
3864
  top: 0,
3269
3865
  bottom: 0,
3270
3866
  heights: []
3271
3867
  };
3272
3868
  }
3273
3869
  const elementLength = children.length;
3274
- const adjustedScrollTop = Math.max(0, scrollTop - this.businessHeight);
3275
- const heights = children.map((_, idx) => this.getBlockHeight(idx));
3276
- const accumulatedHeights = this.buildAccumulatedHeight(heights);
3870
+ if (!EDITOR_TO_BUSINESS_TOP.has(this.editor)) {
3871
+ EDITOR_TO_BUSINESS_TOP.set(this.editor, 0);
3872
+ setTimeout(() => {
3873
+ const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3874
+ const businessTop = Math.ceil(virtualTopBoundingTop) +
3875
+ Math.ceil(this.virtualScrollConfig.scrollTop) -
3876
+ Math.floor(this.virtualScrollConfig.viewportBoundingTop);
3877
+ EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
3878
+ if (isDebug) {
3879
+ debugLog('log', 'businessTop', businessTop);
3880
+ }
3881
+ }, 100);
3882
+ }
3883
+ const adjustedScrollTop = Math.max(0, scrollTop - getBusinessTop(this.editor));
3884
+ const { heights, accumulatedHeights, visibles } = buildHeightsAndAccumulatedHeights(this.editor);
3277
3885
  const totalHeight = accumulatedHeights[elementLength];
3278
3886
  const maxScrollTop = Math.max(0, totalHeight - viewportHeight);
3279
3887
  const limitedScrollTop = Math.min(adjustedScrollTop, maxScrollTop);
3280
- const viewBottom = limitedScrollTop + viewportHeight + this.businessHeight;
3888
+ const viewBottom = limitedScrollTop + viewportHeight;
3281
3889
  let accumulatedOffset = 0;
3282
- let visibleStartIndex = -1;
3890
+ let inViewportStartIndex = -1;
3283
3891
  const visible = [];
3284
- const visibleIndexes = [];
3892
+ const inViewportIndics = [];
3285
3893
  for (let i = 0; i < elementLength && accumulatedOffset < viewBottom; i++) {
3286
3894
  const currentHeight = heights[i];
3287
3895
  const nextOffset = accumulatedOffset + currentHeight;
3896
+ if (!visibles[i]) {
3897
+ accumulatedOffset = nextOffset;
3898
+ continue;
3899
+ }
3288
3900
  // 可视区域有交集,加入渲染
3289
3901
  if (nextOffset > limitedScrollTop && accumulatedOffset < viewBottom) {
3290
- if (visibleStartIndex === -1)
3291
- visibleStartIndex = i; // 第一个相交起始位置
3902
+ if (inViewportStartIndex === -1)
3903
+ inViewportStartIndex = i; // 第一个相交起始位置
3292
3904
  visible.push(children[i]);
3293
- visibleIndexes.push(i);
3905
+ inViewportIndics.push(i);
3294
3906
  }
3295
3907
  accumulatedOffset = nextOffset;
3296
3908
  }
3297
- if (visibleStartIndex === -1 && elementLength) {
3298
- visibleStartIndex = elementLength - 1;
3299
- visible.push(children[visibleStartIndex]);
3300
- visibleIndexes.push(visibleStartIndex);
3301
- }
3302
- const visibleEndIndex = visibleStartIndex === -1 ? elementLength - 1 : (visibleIndexes[visibleIndexes.length - 1] ?? visibleStartIndex);
3303
- const top = visibleStartIndex === -1 ? 0 : accumulatedHeights[visibleStartIndex];
3304
- const bottom = totalHeight - accumulatedHeights[visibleEndIndex + 1];
3909
+ const inViewportEndIndex = inViewportStartIndex === -1 ? elementLength - 1 : (inViewportIndics[inViewportIndics.length - 1] ?? inViewportStartIndex);
3910
+ const top = inViewportStartIndex === -1 ? 0 : accumulatedHeights[inViewportStartIndex];
3911
+ const bottom = totalHeight - accumulatedHeights[inViewportEndIndex + 1];
3305
3912
  return {
3306
- renderedChildren: visible.length ? visible : children,
3307
- visibleIndexes: new Set(visibleIndexes),
3913
+ inViewportChildren: visible.length ? visible : children,
3914
+ inViewportIndics,
3308
3915
  top,
3309
3916
  bottom,
3310
- heights
3917
+ heights,
3918
+ accumulatedHeights
3311
3919
  };
3312
3920
  }
3313
3921
  applyVirtualView(virtualView) {
3314
- this.renderedChildren = virtualView.renderedChildren;
3315
- this.changeVirtualHeight(virtualView.top, virtualView.bottom);
3316
- this.virtualVisibleIndexes = virtualView.visibleIndexes;
3922
+ this.inViewportChildren = virtualView.inViewportChildren;
3923
+ this.setVirtualSpaceHeight(virtualView.top, virtualView.bottom);
3924
+ this.inViewportIndics = virtualView.inViewportIndics;
3317
3925
  }
3318
- diffVirtualView(virtualView, stage = 'first') {
3319
- if (!this.renderedChildren.length) {
3926
+ diffVirtualViewport(virtualView, stage = 'first') {
3927
+ if (!this.inViewportChildren.length) {
3928
+ if (isDebug) {
3929
+ debugLog('log', 'diffVirtualViewport', stage, 'empty inViewportChildren', virtualView.inViewportIndics);
3930
+ }
3931
+ return {
3932
+ isDifferent: true,
3933
+ changedIndexesOfTop: [],
3934
+ changedIndexesOfBottom: []
3935
+ };
3936
+ }
3937
+ const oldIndexesInViewport = [...this.inViewportIndics];
3938
+ const newIndexesInViewport = [...virtualView.inViewportIndics];
3939
+ const firstNewIndex = newIndexesInViewport[0];
3940
+ const lastNewIndex = newIndexesInViewport[newIndexesInViewport.length - 1];
3941
+ const firstOldIndex = oldIndexesInViewport[0];
3942
+ const lastOldIndex = oldIndexesInViewport[oldIndexesInViewport.length - 1];
3943
+ const isSameViewport = oldIndexesInViewport.length === newIndexesInViewport.length &&
3944
+ oldIndexesInViewport.every((index, i) => index === newIndexesInViewport[i]);
3945
+ if (firstNewIndex === firstOldIndex && lastNewIndex === lastOldIndex) {
3320
3946
  return {
3321
- isDiff: true,
3322
- diffTopRenderedIndexes: [],
3323
- diffBottomRenderedIndexes: []
3947
+ isDifferent: !isSameViewport,
3948
+ changedIndexesOfTop: [],
3949
+ changedIndexesOfBottom: []
3324
3950
  };
3325
3951
  }
3326
- const oldVisibleIndexes = [...this.virtualVisibleIndexes];
3327
- const newVisibleIndexes = [...virtualView.visibleIndexes];
3328
- const firstNewIndex = newVisibleIndexes[0];
3329
- const lastNewIndex = newVisibleIndexes[newVisibleIndexes.length - 1];
3330
- const firstOldIndex = oldVisibleIndexes[0];
3331
- const lastOldIndex = oldVisibleIndexes[oldVisibleIndexes.length - 1];
3332
3952
  if (firstNewIndex !== firstOldIndex || lastNewIndex !== lastOldIndex) {
3333
- const diffTopRenderedIndexes = [];
3334
- const diffBottomRenderedIndexes = [];
3335
- const isMissingTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
3336
- const isAddedTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
3337
- const isMissingBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
3338
- const isAddedBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
3339
- if (isMissingTop || isAddedBottom) {
3953
+ const changedIndexesOfTop = [];
3954
+ const changedIndexesOfBottom = [];
3955
+ const needRemoveOnTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
3956
+ const needAddOnTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
3957
+ const needRemoveOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
3958
+ const needAddOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
3959
+ if (needRemoveOnTop || needAddOnBottom) {
3340
3960
  // 向下
3341
- for (let index = 0; index < oldVisibleIndexes.length; index++) {
3342
- const element = oldVisibleIndexes[index];
3343
- if (!newVisibleIndexes.includes(element)) {
3344
- diffTopRenderedIndexes.push(element);
3961
+ for (let index = 0; index < oldIndexesInViewport.length; index++) {
3962
+ const element = oldIndexesInViewport[index];
3963
+ if (!newIndexesInViewport.includes(element)) {
3964
+ changedIndexesOfTop.push(element);
3345
3965
  }
3346
3966
  else {
3347
3967
  break;
3348
3968
  }
3349
3969
  }
3350
- for (let index = newVisibleIndexes.length - 1; index >= 0; index--) {
3351
- const element = newVisibleIndexes[index];
3352
- if (!oldVisibleIndexes.includes(element)) {
3353
- diffBottomRenderedIndexes.push(element);
3970
+ for (let index = newIndexesInViewport.length - 1; index >= 0; index--) {
3971
+ const element = newIndexesInViewport[index];
3972
+ if (!oldIndexesInViewport.includes(element)) {
3973
+ changedIndexesOfBottom.push(element);
3354
3974
  }
3355
3975
  else {
3356
3976
  break;
3357
3977
  }
3358
3978
  }
3359
3979
  }
3360
- else if (isAddedTop || isMissingBottom) {
3980
+ else if (needAddOnTop || needRemoveOnBottom) {
3361
3981
  // 向上
3362
- for (let index = 0; index < newVisibleIndexes.length; index++) {
3363
- const element = newVisibleIndexes[index];
3364
- if (!oldVisibleIndexes.includes(element)) {
3365
- diffTopRenderedIndexes.push(element);
3982
+ for (let index = 0; index < newIndexesInViewport.length; index++) {
3983
+ const element = newIndexesInViewport[index];
3984
+ if (!oldIndexesInViewport.includes(element)) {
3985
+ changedIndexesOfTop.push(element);
3366
3986
  }
3367
3987
  else {
3368
3988
  break;
3369
3989
  }
3370
3990
  }
3371
- for (let index = oldVisibleIndexes.length - 1; index >= 0; index--) {
3372
- const element = oldVisibleIndexes[index];
3373
- if (!newVisibleIndexes.includes(element)) {
3374
- diffBottomRenderedIndexes.push(element);
3991
+ for (let index = oldIndexesInViewport.length - 1; index >= 0; index--) {
3992
+ const element = oldIndexesInViewport[index];
3993
+ if (!newIndexesInViewport.includes(element)) {
3994
+ changedIndexesOfBottom.push(element);
3375
3995
  }
3376
3996
  else {
3377
3997
  break;
@@ -3379,125 +3999,35 @@ class SlateEditable {
3379
3999
  }
3380
4000
  }
3381
4001
  if (isDebug) {
3382
- this.debugLog('log', `====== diffVirtualView stage: ${stage} ======`);
3383
- this.debugLog('log', 'oldVisibleIndexes:', oldVisibleIndexes);
3384
- this.debugLog('log', 'newVisibleIndexes:', newVisibleIndexes);
3385
- this.debugLog('log', 'diffTopRenderedIndexes:', isMissingTop ? '-' : isAddedTop ? '+' : '-', diffTopRenderedIndexes, diffTopRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3386
- this.debugLog('log', 'diffBottomRenderedIndexes:', isAddedBottom ? '+' : isMissingBottom ? '-' : '+', diffBottomRenderedIndexes, diffBottomRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3387
- const needTop = virtualView.heights.slice(0, newVisibleIndexes[0]).reduce((acc, height) => acc + height, 0);
4002
+ debugLog('log', `====== diffVirtualViewport stage: ${stage} ======`);
4003
+ debugLog('log', 'oldIndexesInViewport:', oldIndexesInViewport);
4004
+ debugLog('log', 'newIndexesInViewport:', newIndexesInViewport);
4005
+ debugLog('log', 'changedIndexesOfTop:', needRemoveOnTop ? '-' : needAddOnTop ? '+' : '-', changedIndexesOfTop, changedIndexesOfTop.map(index => getRealHeightByElement(this.editor, this.editor.children[index], 0)));
4006
+ debugLog('log', 'changedIndexesOfBottom:', needAddOnBottom ? '+' : needRemoveOnBottom ? '-' : '+', changedIndexesOfBottom, changedIndexesOfBottom.map(index => getRealHeightByElement(this.editor, this.editor.children[index], 0)));
4007
+ const needTop = virtualView.heights.slice(0, newIndexesInViewport[0]).reduce((acc, height) => acc + height, 0);
3388
4008
  const needBottom = virtualView.heights
3389
- .slice(newVisibleIndexes[newVisibleIndexes.length - 1] + 1)
4009
+ .slice(newIndexesInViewport[newIndexesInViewport.length - 1] + 1)
3390
4010
  .reduce((acc, height) => acc + height, 0);
3391
- this.debugLog('log', 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3392
- this.debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3393
- this.debugLog('warn', '=========== Dividing line ===========');
4011
+ debugLog('log', needTop - parseFloat(this.virtualTopHeightElement.style.height), 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
4012
+ debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
4013
+ debugLog('warn', '=========== Dividing line ===========');
3394
4014
  }
3395
4015
  return {
3396
- isDiff: true,
3397
- isMissingTop,
3398
- isAddedTop,
3399
- isMissingBottom,
3400
- isAddedBottom,
3401
- diffTopRenderedIndexes,
3402
- diffBottomRenderedIndexes
4016
+ isDifferent: true,
4017
+ needRemoveOnTop,
4018
+ needAddOnTop,
4019
+ needRemoveOnBottom,
4020
+ needAddOnBottom,
4021
+ changedIndexesOfTop,
4022
+ changedIndexesOfBottom
3403
4023
  };
3404
4024
  }
3405
4025
  return {
3406
- isDiff: false,
3407
- diffTopRenderedIndexes: [],
3408
- diffBottomRenderedIndexes: []
4026
+ isDifferent: false,
4027
+ changedIndexesOfTop: [],
4028
+ changedIndexesOfBottom: []
3409
4029
  };
3410
4030
  }
3411
- getBlockHeight(index, defaultHeight = VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT) {
3412
- const node = this.editor.children[index];
3413
- if (!node) {
3414
- return defaultHeight;
3415
- }
3416
- const key = AngularEditor.findKey(this.editor, node);
3417
- return this.measuredHeights.get(key.id) ?? defaultHeight;
3418
- }
3419
- buildAccumulatedHeight(heights) {
3420
- const accumulatedHeights = new Array(heights.length + 1).fill(0);
3421
- for (let i = 0; i < heights.length; i++) {
3422
- // 存储前 i 个的累计高度
3423
- accumulatedHeights[i + 1] = accumulatedHeights[i] + heights[i];
3424
- }
3425
- return accumulatedHeights;
3426
- }
3427
- scheduleMeasureVisibleHeights() {
3428
- if (!this.shouldUseVirtual()) {
3429
- return;
3430
- }
3431
- this.measureVisibleHeightsAnimId && cancelAnimationFrame(this.measureVisibleHeightsAnimId);
3432
- this.measureVisibleHeightsAnimId = requestAnimationFrame(() => {
3433
- this.measureVisibleHeights();
3434
- });
3435
- }
3436
- measureVisibleHeights() {
3437
- const children = (this.editor.children || []);
3438
- this.virtualVisibleIndexes.forEach(index => {
3439
- const node = children[index];
3440
- if (!node) {
3441
- return;
3442
- }
3443
- const key = AngularEditor.findKey(this.editor, node);
3444
- // 跳过已测过的块
3445
- if (this.measuredHeights.has(key.id)) {
3446
- return;
3447
- }
3448
- const view = ELEMENT_TO_COMPONENT.get(node);
3449
- if (!view) {
3450
- return;
3451
- }
3452
- const ret = view.getRealHeight();
3453
- if (ret instanceof Promise) {
3454
- ret.then(height => {
3455
- this.measuredHeights.set(key.id, height);
3456
- });
3457
- }
3458
- else {
3459
- this.measuredHeights.set(key.id, ret);
3460
- }
3461
- });
3462
- }
3463
- remeasureHeightByIndics(indics) {
3464
- const children = (this.editor.children || []);
3465
- let isHeightChanged = false;
3466
- indics.forEach(index => {
3467
- const node = children[index];
3468
- if (!node) {
3469
- return;
3470
- }
3471
- const key = AngularEditor.findKey(this.editor, node);
3472
- const view = ELEMENT_TO_COMPONENT.get(node);
3473
- if (!view) {
3474
- return;
3475
- }
3476
- const prevHeight = this.measuredHeights.get(key.id);
3477
- const ret = view.getRealHeight();
3478
- if (ret instanceof Promise) {
3479
- ret.then(height => {
3480
- if (height !== prevHeight) {
3481
- this.measuredHeights.set(key.id, height);
3482
- isHeightChanged = true;
3483
- if (isDebug) {
3484
- this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${height}`);
3485
- }
3486
- }
3487
- });
3488
- }
3489
- else {
3490
- if (ret !== prevHeight) {
3491
- this.measuredHeights.set(key.id, ret);
3492
- isHeightChanged = true;
3493
- if (isDebug) {
3494
- this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${ret}`);
3495
- }
3496
- }
3497
- }
3498
- });
3499
- return isHeightChanged;
3500
- }
3501
4031
  //#region event proxy
3502
4032
  addEventListener(eventName, listener, target = this.elementRef.nativeElement) {
3503
4033
  this.manualListeners.push(this.renderer2.listen(target, eventName, (event) => {
@@ -3538,7 +4068,7 @@ class SlateEditable {
3538
4068
  if (this.editor.selection && Range.equals(range, this.editor.selection) && !hasStringTarget(domSelection)) {
3539
4069
  if (!isTargetInsideVoid(this.editor, activeElement)) {
3540
4070
  // force adjust DOMSelection
3541
- this.toNativeSelection();
4071
+ this.toNativeSelection(false);
3542
4072
  }
3543
4073
  }
3544
4074
  else {
@@ -4195,8 +4725,6 @@ class SlateEditable {
4195
4725
  //#endregion
4196
4726
  ngOnDestroy() {
4197
4727
  this.editorResizeObserver?.disconnect();
4198
- this.debugOverlay?.dispose();
4199
- this.debugOverlay = undefined;
4200
4728
  NODE_TO_ELEMENT.delete(this.editor);
4201
4729
  this.manualListeners.forEach(manualListener => {
4202
4730
  manualListener();
@@ -4385,16 +4913,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
4385
4913
  }]
4386
4914
  }], ctorParameters: () => [{ type: i0.ElementRef }] });
4387
4915
 
4916
+ class SlateString {
4917
+ ngOnInit() { }
4918
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateString, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4919
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.12", type: SlateString, isStandalone: true, selector: "span[slateString]", inputs: { context: "context", viewContext: "viewContext" }, ngImport: i0, template: '', isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
4920
+ }
4921
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateString, decorators: [{
4922
+ type: Component,
4923
+ args: [{
4924
+ selector: 'span[slateString]',
4925
+ template: '',
4926
+ changeDetection: ChangeDetectionStrategy.OnPush,
4927
+ standalone: true
4928
+ }]
4929
+ }], propDecorators: { context: [{
4930
+ type: Input
4931
+ }], viewContext: [{
4932
+ type: Input
4933
+ }] } });
4934
+
4388
4935
  class SlateModule {
4389
4936
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
4390
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, imports: [CommonModule, SlateEditable, SlateChildrenOutlet], exports: [SlateEditable, SlateChildrenOutlet] }); }
4937
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, imports: [CommonModule, SlateEditable, SlateChildrenOutlet, SlateString], exports: [SlateEditable, SlateChildrenOutlet, SlateString] }); }
4391
4938
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, imports: [CommonModule] }); }
4392
4939
  }
4393
4940
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, decorators: [{
4394
4941
  type: NgModule,
4395
4942
  args: [{
4396
- imports: [CommonModule, SlateEditable, SlateChildrenOutlet],
4397
- exports: [SlateEditable, SlateChildrenOutlet],
4943
+ imports: [CommonModule, SlateEditable, SlateChildrenOutlet, SlateString],
4944
+ exports: [SlateEditable, SlateChildrenOutlet, SlateString],
4398
4945
  providers: []
4399
4946
  }]
4400
4947
  }] });
@@ -4510,6 +5057,7 @@ class BaseElementComponent extends BaseComponent {
4510
5057
  if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
4511
5058
  ELEMENT_TO_COMPONENT.delete(this.element);
4512
5059
  }
5060
+ this.listRender.destroy();
4513
5061
  }
4514
5062
  onContextChange() {
4515
5063
  this.childrenContext = this.getChildrenContext();
@@ -4597,6 +5145,7 @@ class BaseTextComponent extends BaseComponent {
4597
5145
  NODE_TO_ELEMENT.delete(this.text);
4598
5146
  }
4599
5147
  ELEMENT_TO_NODE.delete(this.nativeElement);
5148
+ this.leavesRender.destroy();
4600
5149
  }
4601
5150
  onContextChange() {
4602
5151
  this.updateWeakMap();
@@ -4622,6 +5171,9 @@ class BaseLeafComponent extends BaseComponent {
4622
5171
  super(...arguments);
4623
5172
  this.stringRender = null;
4624
5173
  this.isSlateLeaf = true;
5174
+ this.getOutletParent = () => {
5175
+ return this.elementRef.nativeElement;
5176
+ };
4625
5177
  }
4626
5178
  get text() {
4627
5179
  return this.context && this.context.text;
@@ -4636,7 +5188,7 @@ class BaseLeafComponent extends BaseComponent {
4636
5188
  if (!this.initialized) {
4637
5189
  this.stringRender = new SlateStringRender(this.context, this.viewContext);
4638
5190
  const stringNode = this.stringRender.render();
4639
- this.nativeElement.appendChild(stringNode);
5191
+ this.getOutletParent().appendChild(stringNode);
4640
5192
  }
4641
5193
  else {
4642
5194
  this.stringRender?.update(this.context, this.viewContext);
@@ -4708,5 +5260,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
4708
5260
  * Generated bundle index. Do not edit.
4709
5261
  */
4710
5262
 
4711
- export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, DEFAULT_ELEMENT_HEIGHT, DefaultTextFlavour, EDITOR_TO_AFTER_VIEW_INIT_QUEUE, ELEMENT_TO_COMPONENT, FAKE_LEFT_BLOCK_CARD_OFFSET, FAKE_RIGHT_BLOCK_CARD_OFFSET, FlavourRef, HAS_BEFORE_INPUT_SUPPORT, IS_ANDROID, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_FIREFOX, IS_FIREFOX_LEGACY, IS_IOS, IS_QQBROWSER, IS_SAFARI, IS_UC_MOBILE, IS_WECHATBROWSER, JUST_NOW_UPDATED_VIRTUAL_VIEW, PLACEHOLDER_SYMBOL, SLATE_BLOCK_CARD_CLASS_NAME, SLATE_DEBUG_KEY, SlateBlockCard, SlateChildrenOutlet, SlateEditable, SlateErrorCode, SlateFragmentAttributeKey, SlateModule, VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT, VIRTUAL_SCROLL_DEFAULT_BUFFER_COUNT, VoidTextFlavour, blobAsString, buildHTMLText, check, completeTable, createClipboardData, createText, createThrottleRAF, defaultScrollSelectionIntoView, fallbackCopyText, getBlockCardByNativeElement, getCardTargetAttribute, getClipboardData, getClipboardFromHTMLText, getContentHeight, getDataTransferClipboard, getDataTransferClipboardText, getNavigatorClipboard, getPlainText, getSelection, getSlateFragmentAttribute, getZeroTextNode, hasAfterContextChange, hasBeforeContextChange, hasBlockCard, hasBlockCardWithNode, hotkeys, isCardCenterByTargetAttr, isCardLeft, isCardLeftByTargetAttr, isCardRightByTargetAttr, isClipboardFile, isClipboardReadSupported, isClipboardWriteSupported, isClipboardWriteTextSupported, isComponentType, isDOMText, isDecoratorRangeListEqual, isFlavourType, isInvalidTable, isTemplateRef, isValid, normalize, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setNavigatorClipboard, shallowCompare, stripHtml, withAngular };
5263
+ export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, DEFAULT_ELEMENT_HEIGHT, DefaultTextFlavour, EDITOR_TO_AFTER_VIEW_INIT_QUEUE, EDITOR_TO_BUSINESS_TOP, EDITOR_TO_ROOT_NODE_WIDTH, EDITOR_TO_VIRTUAL_SCROLL_SELECTION, ELEMENT_KEY_TO_HEIGHTS, ELEMENT_TO_COMPONENT, FAKE_LEFT_BLOCK_CARD_OFFSET, FAKE_RIGHT_BLOCK_CARD_OFFSET, FlavourRef, HAS_BEFORE_INPUT_SUPPORT, IS_ANDROID, IS_APPLE, IS_CHROME, IS_CHROME_LEGACY, IS_EDGE_LEGACY, IS_ENABLED_VIRTUAL_SCROLL, IS_FIREFOX, IS_FIREFOX_LEGACY, IS_IOS, IS_QQBROWSER, IS_SAFARI, IS_UC_MOBILE, IS_WECHATBROWSER, PLACEHOLDER_SYMBOL, SLATE_BLOCK_CARD_CLASS_NAME, SLATE_DEBUG_KEY, SLATE_DEBUG_KEY_SCROLL_TOP, SlateBlockCard, SlateChildrenOutlet, SlateEditable, SlateErrorCode, SlateFragmentAttributeKey, SlateModule, SlateString, VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT, VoidTextFlavour, blobAsString, buildHTMLText, buildHeightsAndAccumulatedHeights, calculateVirtualTopHeight, check, completeTable, createClipboardData, createText, createThrottleRAF, debugLog, defaultScrollSelectionIntoView, fallbackCopyText, getBlockCardByNativeElement, getBusinessTop, getCardTargetAttribute, getClipboardData, getClipboardFromHTMLText, getContentHeight, getDataTransferClipboard, getDataTransferClipboardText, getNavigatorClipboard, getPlainText, getRealHeightByElement, getSelection, getSlateFragmentAttribute, getZeroTextNode, hasAfterContextChange, hasBeforeContextChange, hasBlockCard, hasBlockCardWithNode, hotkeys, isCardCenterByTargetAttr, isCardLeft, isCardLeftByTargetAttr, isCardRightByTargetAttr, isClipboardFile, isClipboardReadSupported, isClipboardWriteSupported, isClipboardWriteTextSupported, isComponentType, isDOMText, isDebug, isDebugScrollTop, isDecoratorRangeListEqual, isFlavourType, isInvalidTable, isTemplateRef, isValid, measureHeightByElement, measureHeightByIndics, normalize, scrollToElement, setClipboardData, setDataTransferClipboard, setDataTransferClipboardText, setNavigatorClipboard, shallowCompare, stripHtml, withAngular };
4712
5264
  //# sourceMappingURL=slate-angular.mjs.map