slate-angular 20.2.0-next.3 → 20.2.0-next.30

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,6 +955,586 @@ const fallbackCopyText = async (text) => {
950
955
  });
951
956
  };
952
957
 
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;
1003
+ }
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;
1015
+ }
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 = '';
1027
+ }
1028
+ };
1029
+ this.onResizing = (event) => {
1030
+ if (!this.isResizing || !this.container) {
1031
+ return;
1032
+ }
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();
1054
+ }
1055
+ }
1056
+ log(type, ...args) {
1057
+ this.init();
1058
+ if (type === 'warn') {
1059
+ this.originalConsoleWarn(...args);
1060
+ }
1061
+ else {
1062
+ this.originalConsoleLog(...args);
1063
+ }
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);
1109
+ }
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;
1398
+ }
1399
+ }
1400
+ }
1401
+ catch {
1402
+ // ignore storage errors
1403
+ }
1404
+ }
1405
+ persistState() {
1406
+ try {
1407
+ this.doc.defaultView?.localStorage?.setItem(VirtualScrollDebugOverlay.storageKey, JSON.stringify(this.state));
1408
+ }
1409
+ catch {
1410
+ // ignore storage errors
1411
+ }
1412
+ }
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);
1443
+ }
1444
+ }
1445
+ setScrollTopValue(value) {
1446
+ if (this.distanceInput) {
1447
+ this.distanceInput.value = String(value ?? 0);
1448
+ }
1449
+ }
1450
+ }
1451
+
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 debugLog = (type, ...args) => {
1457
+ const doc = document;
1458
+ VirtualScrollDebugOverlay.log(doc, type, ...args);
1459
+ };
1460
+ const measureHeightByElement = (editor, element) => {
1461
+ const key = AngularEditor.findKey(editor, element);
1462
+ const view = ELEMENT_TO_COMPONENT.get(element);
1463
+ if (!view) {
1464
+ return;
1465
+ }
1466
+ const ret = view.getRealHeight();
1467
+ const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1468
+ heights.set(key.id, ret);
1469
+ return ret;
1470
+ };
1471
+ const measureHeightByIndics = (editor, indics, force = false) => {
1472
+ let hasChanged = false;
1473
+ indics.forEach((index, i) => {
1474
+ const element = editor.children[index];
1475
+ const preHeight = getRealHeightByElement(editor, element, 0);
1476
+ if (preHeight && !force) {
1477
+ if (isDebug) {
1478
+ const height = measureHeightByElement(editor, element);
1479
+ if (height !== preHeight) {
1480
+ debugLog('warn', 'measureHeightByElement: height not equal, index: ', index, 'preHeight: ', preHeight, 'height: ', height);
1481
+ }
1482
+ }
1483
+ return;
1484
+ }
1485
+ hasChanged = true;
1486
+ measureHeightByElement(editor, element);
1487
+ });
1488
+ return hasChanged;
1489
+ };
1490
+ const getBusinessTop = (editor) => {
1491
+ return EDITOR_TO_BUSINESS_TOP.get(editor) ?? 0;
1492
+ };
1493
+ const getRealHeightByElement = (editor, element, defaultHeight = VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT) => {
1494
+ const isVisible = editor.isVisible(element);
1495
+ if (!isVisible) {
1496
+ return 0;
1497
+ }
1498
+ const heights = ELEMENT_KEY_TO_HEIGHTS.get(editor);
1499
+ const key = AngularEditor.findKey(editor, element);
1500
+ const height = heights?.get(key.id);
1501
+ if (typeof height === 'number') {
1502
+ return height;
1503
+ }
1504
+ if (heights?.has(key.id)) {
1505
+ console.error('getBlockHeight: invalid height value', key.id, height);
1506
+ }
1507
+ return defaultHeight;
1508
+ };
1509
+ const buildHeightsAndAccumulatedHeights = (editor) => {
1510
+ const children = (editor.children || []);
1511
+ const heights = new Array(children.length);
1512
+ const accumulatedHeights = new Array(children.length + 1);
1513
+ accumulatedHeights[0] = 0;
1514
+ for (let i = 0; i < children.length; i++) {
1515
+ const height = getRealHeightByElement(editor, children[i]);
1516
+ heights[i] = height;
1517
+ accumulatedHeights[i + 1] = accumulatedHeights[i] + height;
1518
+ }
1519
+ return { heights, accumulatedHeights };
1520
+ };
1521
+ const calculateVirtualTopHeight = (editor, startIndex) => {
1522
+ const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor);
1523
+ return accumulatedHeights[startIndex] ?? 0;
1524
+ };
1525
+ const scrollToElement = (editor, element, scrollTo) => {
1526
+ const children = editor.children;
1527
+ if (!children.length) {
1528
+ return;
1529
+ }
1530
+ const anchorIndex = children.findIndex(item => item === element);
1531
+ if (anchorIndex < 0) {
1532
+ return;
1533
+ }
1534
+ const { accumulatedHeights } = buildHeightsAndAccumulatedHeights(editor);
1535
+ scrollTo((accumulatedHeights[anchorIndex] ?? 0) + getBusinessTop(editor));
1536
+ };
1537
+
953
1538
  const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
954
1539
  let e = editor;
955
1540
  let { apply } = e;
@@ -967,7 +1552,14 @@ const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
967
1552
  }
968
1553
  // Create a fake selection so that we can add a Base64-encoded copy of the
969
1554
  // fragment to the HTML, to decode on future pastes.
970
- const domRange = AngularEditor.toDOMRange(e, selection);
1555
+ let domRange;
1556
+ if (AngularEditor.isEnabledVirtualScroll(e)) {
1557
+ const virtualScrollSelection = EDITOR_TO_VIRTUAL_SCROLL_SELECTION.get(e);
1558
+ if (virtualScrollSelection) {
1559
+ domRange = AngularEditor.toDOMRange(e, virtualScrollSelection);
1560
+ }
1561
+ }
1562
+ domRange = domRange ?? AngularEditor.toDOMRange(e, selection);
971
1563
  let contents = domRange.cloneContents();
972
1564
  let attach = contents.childNodes[0];
973
1565
  // Make sure attach is non-empty, since empty nodes will not get copied.
@@ -1135,6 +1727,12 @@ const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
1135
1727
  NODE_TO_KEY.set(node, key);
1136
1728
  }
1137
1729
  };
1730
+ e.selectAll = () => {
1731
+ Transforms.select(e, []);
1732
+ };
1733
+ e.isVisible = element => {
1734
+ return true;
1735
+ };
1138
1736
  return e;
1139
1737
  };
1140
1738
 
@@ -1795,6 +2393,7 @@ class BaseElementFlavour extends BaseFlavour {
1795
2393
  if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
1796
2394
  ELEMENT_TO_COMPONENT.delete(this.element);
1797
2395
  }
2396
+ this.listRender.destroy();
1798
2397
  this.nativeElement?.remove();
1799
2398
  }
1800
2399
  onContextChange() {
@@ -1835,7 +2434,7 @@ class BaseElementFlavour extends BaseFlavour {
1835
2434
  const blockCard = getBlockCardByNativeElement(this.nativeElement);
1836
2435
  const target = blockCard || this.nativeElement;
1837
2436
  const computedStyle = getComputedStyle(target);
1838
- const height = target.offsetHeight + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom);
2437
+ const height = Math.ceil(target.getBoundingClientRect().height) + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom);
1839
2438
  if (this.isStableHeight()) {
1840
2439
  this.stableHeight = height;
1841
2440
  }
@@ -2191,6 +2790,9 @@ class LeavesRender {
2191
2790
  });
2192
2791
  return { decoratedLeaves, contexts };
2193
2792
  }
2793
+ destroy() {
2794
+ this.views.forEach(view => view.destroy());
2795
+ }
2194
2796
  }
2195
2797
  function getContext$1(index, leafContexts) {
2196
2798
  return leafContexts[index];
@@ -2233,6 +2835,7 @@ class BaseTextFlavour extends BaseFlavour {
2233
2835
  NODE_TO_ELEMENT.delete(this.text);
2234
2836
  }
2235
2837
  ELEMENT_TO_NODE.delete(this.nativeElement);
2838
+ this.leavesRender.destroy();
2236
2839
  this.nativeElement?.remove();
2237
2840
  }
2238
2841
  onContextChange() {
@@ -2273,14 +2876,16 @@ class ListRender {
2273
2876
  this.viewContainerRef = viewContainerRef;
2274
2877
  this.getOutletParent = getOutletParent;
2275
2878
  this.getOutletElement = getOutletElement;
2879
+ this.children = [];
2276
2880
  this.views = [];
2277
2881
  this.blockCards = [];
2278
2882
  this.contexts = [];
2279
2883
  this.viewTypes = [];
2280
2884
  this.differ = null;
2281
2885
  this.initialized = false;
2886
+ this.preRenderingHTMLElement = [];
2282
2887
  }
2283
- initialize(children, parent, childrenContext) {
2888
+ initialize(children, parent, childrenContext, preRenderingCount = 0) {
2284
2889
  this.initialized = true;
2285
2890
  this.children = children;
2286
2891
  const isRoot = parent === this.viewContext.editor;
@@ -2306,15 +2911,25 @@ class ListRender {
2306
2911
  executeAfterViewInit(this.viewContext.editor);
2307
2912
  }
2308
2913
  }
2309
- update(children, parent, childrenContext) {
2914
+ update(children, parent, childrenContext, preRenderingCount = 0) {
2310
2915
  if (!this.initialized || this.children.length === 0) {
2311
- this.initialize(children, parent, childrenContext);
2916
+ this.initialize(children, parent, childrenContext, preRenderingCount);
2312
2917
  return;
2313
2918
  }
2314
2919
  if (!this.differ) {
2315
2920
  throw new Error('Exception: Can not find differ ');
2316
2921
  }
2317
2922
  const outletParent = this.getOutletParent();
2923
+ if (this.preRenderingHTMLElement.length > 0) {
2924
+ const preRenderingElement = [...this.preRenderingHTMLElement];
2925
+ preRenderingElement.forEach((rootNodes, index) => {
2926
+ rootNodes.forEach(rootNode => {
2927
+ rootNode.style.position = '';
2928
+ rootNode.style.top = '';
2929
+ });
2930
+ });
2931
+ this.preRenderingHTMLElement = [];
2932
+ }
2318
2933
  const diffResult = this.differ.diff(children);
2319
2934
  const parentPath = AngularEditor.findPath(this.viewContext.editor, parent);
2320
2935
  const isRoot = parent === this.viewContext.editor;
@@ -2412,6 +3027,16 @@ class ListRender {
2412
3027
  });
2413
3028
  this.contexts = newContexts;
2414
3029
  }
3030
+ if (preRenderingCount > 0) {
3031
+ for (let i = 0; i < preRenderingCount; i++) {
3032
+ const rootNodes = [...getRootNodes(this.views[i], this.blockCards[i])];
3033
+ rootNodes.forEach(rootNode => {
3034
+ rootNode.style.top = '-100%';
3035
+ rootNode.style.position = 'absolute';
3036
+ });
3037
+ this.preRenderingHTMLElement.push(rootNodes);
3038
+ }
3039
+ }
2415
3040
  }
2416
3041
  destroy() {
2417
3042
  this.children.forEach((element, index) => {
@@ -2422,6 +3047,7 @@ class ListRender {
2422
3047
  this.blockCards[index].destroy();
2423
3048
  }
2424
3049
  });
3050
+ this.children = [];
2425
3051
  this.views = [];
2426
3052
  this.blockCards = [];
2427
3053
  this.contexts = [];
@@ -2564,36 +3190,18 @@ function executeAfterViewInit(editor) {
2564
3190
  clearAfterViewInitQueue(editor);
2565
3191
  }
2566
3192
 
2567
- const JUST_NOW_UPDATED_VIRTUAL_VIEW = new WeakMap();
2568
3193
  // not correctly clipboardData on beforeinput
2569
3194
  const forceOnDOMPaste = IS_SAFARI;
2570
- const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
2571
3195
  class SlateEditable {
2572
3196
  set virtualScroll(config) {
2573
- this.virtualConfig = config;
2574
- this.refreshVirtualViewAnimId && cancelAnimationFrame(this.refreshVirtualViewAnimId);
2575
- this.refreshVirtualViewAnimId = requestAnimationFrame(() => {
2576
- let virtualView = this.refreshVirtualView();
2577
- let diff = this.diffVirtualView(virtualView);
2578
- if (!diff.isDiff) {
2579
- return;
2580
- }
2581
- if (diff.isMissingTop) {
2582
- const result = this.remeasureHeightByIndics([...diff.diffTopRenderedIndexes]);
2583
- if (result) {
2584
- virtualView = this.refreshVirtualView();
2585
- diff = this.diffVirtualView(virtualView, 'second');
2586
- if (!diff.isDiff) {
2587
- return;
2588
- }
2589
- }
2590
- }
2591
- this.applyVirtualView(virtualView);
2592
- if (this.listRender.initialized) {
2593
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2594
- }
2595
- this.scheduleMeasureVisibleHeights();
2596
- });
3197
+ this.virtualScrollConfig = config;
3198
+ if (isDebugScrollTop) {
3199
+ debugLog('log', 'virtualScrollConfig scrollTop:', config.scrollTop);
3200
+ }
3201
+ IS_ENABLED_VIRTUAL_SCROLL.set(this.editor, config.enabled);
3202
+ if (this.isEnabledVirtualScroll()) {
3203
+ this.tryUpdateVirtualViewport();
3204
+ }
2597
3205
  }
2598
3206
  get hasBeforeInputSupport() {
2599
3207
  return HAS_BEFORE_INPUT_SUPPORT;
@@ -2638,15 +3246,16 @@ class SlateEditable {
2638
3246
  return null;
2639
3247
  }
2640
3248
  };
2641
- this.virtualConfig = {
3249
+ this.virtualScrollConfig = {
2642
3250
  enabled: false,
2643
3251
  scrollTop: 0,
2644
- viewportHeight: 0
3252
+ viewportHeight: 0,
3253
+ viewportBoundingTop: 0,
3254
+ scrollContainer: null
2645
3255
  };
2646
- this.renderedChildren = [];
2647
- this.virtualVisibleIndexes = new Set();
2648
- this.measuredHeights = new Map();
2649
- this.measurePending = false;
3256
+ this.inViewportChildren = [];
3257
+ this.inViewportIndics = [];
3258
+ this.keyHeightMap = new Map();
2650
3259
  this.virtualScrollInitialized = false;
2651
3260
  }
2652
3261
  ngOnInit() {
@@ -2658,6 +3267,7 @@ class SlateEditable {
2658
3267
  NODE_TO_ELEMENT.set(this.editor, this.elementRef.nativeElement);
2659
3268
  ELEMENT_TO_NODE.set(this.elementRef.nativeElement, this.editor);
2660
3269
  IS_READ_ONLY.set(this.editor, this.readonly);
3270
+ ELEMENT_KEY_TO_HEIGHTS.set(this.editor, this.keyHeightMap);
2661
3271
  EDITOR_TO_ON_CHANGE.set(this.editor, () => {
2662
3272
  this.ngZone.run(() => {
2663
3273
  this.onChange();
@@ -2671,7 +3281,7 @@ class SlateEditable {
2671
3281
  // add browser class
2672
3282
  let browserClass = IS_FIREFOX ? 'firefox' : IS_SAFARI ? 'safari' : '';
2673
3283
  browserClass && this.elementRef.nativeElement.classList.add(browserClass);
2674
- this.initializeVirtualScrolling();
3284
+ this.initializeVirtualScroll();
2675
3285
  this.listRender = new ListRender(this.viewContext, this.viewContainerRef, this.getOutletParent, this.getOutletElement);
2676
3286
  }
2677
3287
  ngOnChanges(simpleChanges) {
@@ -2703,16 +3313,29 @@ class SlateEditable {
2703
3313
  if (value && value.length) {
2704
3314
  this.editor.children = value;
2705
3315
  this.initializeContext();
2706
- const virtualView = this.refreshVirtualView();
2707
- this.applyVirtualView(virtualView);
2708
- const childrenForRender = virtualView.renderedChildren;
2709
- if (!this.listRender.initialized) {
2710
- this.listRender.initialize(childrenForRender, this.editor, this.context);
3316
+ if (this.isEnabledVirtualScroll()) {
3317
+ const virtualView = this.calculateVirtualViewport();
3318
+ this.applyVirtualView(virtualView);
3319
+ const childrenForRender = virtualView.inViewportChildren;
3320
+ if (isDebug) {
3321
+ debugLog('log', 'writeValue calculate: ', virtualView.visibleIndexes, 'initialized: ', this.listRender.initialized);
3322
+ }
3323
+ if (!this.listRender.initialized) {
3324
+ this.listRender.initialize(childrenForRender, this.editor, this.context);
3325
+ }
3326
+ else {
3327
+ const { preRenderingCount, childrenWithPreRendering } = this.handlePreRendering();
3328
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount);
3329
+ }
2711
3330
  }
2712
3331
  else {
2713
- this.listRender.update(childrenForRender, this.editor, this.context);
3332
+ if (!this.listRender.initialized) {
3333
+ this.listRender.initialize(this.editor.children, this.editor, this.context);
3334
+ }
3335
+ else {
3336
+ this.listRender.update(this.editor.children, this.editor, this.context);
3337
+ }
2714
3338
  }
2715
- this.scheduleMeasureVisibleHeights();
2716
3339
  this.cdr.markForCheck();
2717
3340
  }
2718
3341
  }
@@ -2743,9 +3366,46 @@ class SlateEditable {
2743
3366
  this.addEventListener(event.name, () => { });
2744
3367
  });
2745
3368
  }
2746
- toNativeSelection() {
3369
+ calculateVirtualScrollSelection(selection) {
3370
+ if (selection) {
3371
+ const isBlockCardCursor = AngularEditor.isBlockCardLeftCursor(this.editor) || AngularEditor.isBlockCardRightCursor(this.editor);
3372
+ const indics = this.inViewportIndics;
3373
+ if (indics.length > 0) {
3374
+ const currentVisibleRange = {
3375
+ anchor: Editor.start(this.editor, [indics[0]]),
3376
+ focus: Editor.end(this.editor, [indics[indics.length - 1]])
3377
+ };
3378
+ const [start, end] = Range.edges(selection);
3379
+ let forwardSelection = { anchor: start, focus: end };
3380
+ if (!isBlockCardCursor) {
3381
+ forwardSelection = { anchor: start, focus: end };
3382
+ }
3383
+ else {
3384
+ forwardSelection = { anchor: { path: start.path, offset: 0 }, focus: { path: end.path, offset: 0 } };
3385
+ }
3386
+ const intersectedSelection = Range.intersection(forwardSelection, currentVisibleRange);
3387
+ if (intersectedSelection && isBlockCardCursor) {
3388
+ return selection;
3389
+ }
3390
+ EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, intersectedSelection);
3391
+ if (!intersectedSelection || !Range.equals(intersectedSelection, forwardSelection)) {
3392
+ if (isDebug) {
3393
+ debugLog('log', `selection is not in visible range, selection: ${JSON.stringify(selection)}, currentVisibleRange: ${JSON.stringify(currentVisibleRange)}, intersectedSelection: ${JSON.stringify(intersectedSelection)}`);
3394
+ }
3395
+ return intersectedSelection;
3396
+ }
3397
+ return selection;
3398
+ }
3399
+ }
3400
+ EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, null);
3401
+ return selection;
3402
+ }
3403
+ toNativeSelection(autoScroll = true) {
2747
3404
  try {
2748
- const { selection } = this.editor;
3405
+ let { selection } = this.editor;
3406
+ if (this.isEnabledVirtualScroll()) {
3407
+ selection = this.calculateVirtualScrollSelection(selection);
3408
+ }
2749
3409
  const root = AngularEditor.findDocumentOrShadowRoot(this.editor);
2750
3410
  const { activeElement } = root;
2751
3411
  const domSelection = root.getSelection();
@@ -2806,13 +3466,23 @@ class SlateEditable {
2806
3466
  domSelection.removeAllRanges();
2807
3467
  }
2808
3468
  setTimeout(() => {
2809
- // handle scrolling in setTimeout because of
2810
- // dom should not have updated immediately after listRender's updating
2811
- newDomRange && this.scrollSelectionIntoView(this.editor, newDomRange);
2812
- // COMPAT: In Firefox, it's not enough to create a range, you also need
2813
- // to focus the contenteditable element too. (2016/11/16)
2814
- if (newDomRange && IS_FIREFOX) {
2815
- el.focus();
3469
+ if (this.isEnabledVirtualScroll() &&
3470
+ !selection &&
3471
+ this.editor.selection &&
3472
+ autoScroll &&
3473
+ this.virtualScrollConfig.scrollContainer) {
3474
+ this.virtualScrollConfig.scrollContainer.scrollTop = this.virtualScrollConfig.scrollContainer.scrollTop + 100;
3475
+ return;
3476
+ }
3477
+ else {
3478
+ // handle scrolling in setTimeout because of
3479
+ // dom should not have updated immediately after listRender's updating
3480
+ newDomRange && autoScroll && this.scrollSelectionIntoView(this.editor, newDomRange);
3481
+ // COMPAT: In Firefox, it's not enough to create a range, you also need
3482
+ // to focus the contenteditable element too. (2016/11/16)
3483
+ if (newDomRange && IS_FIREFOX) {
3484
+ el.focus();
3485
+ }
2816
3486
  }
2817
3487
  this.isUpdatingSelection = false;
2818
3488
  });
@@ -2833,10 +3503,12 @@ class SlateEditable {
2833
3503
  ngDoCheck() { }
2834
3504
  forceRender() {
2835
3505
  this.updateContext();
2836
- const virtualView = this.refreshVirtualView();
2837
- this.applyVirtualView(virtualView);
2838
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2839
- this.scheduleMeasureVisibleHeights();
3506
+ if (this.isEnabledVirtualScroll()) {
3507
+ this.updateListRenderAndRemeasureHeights();
3508
+ }
3509
+ else {
3510
+ this.listRender.update(this.editor.children, this.editor, this.context);
3511
+ }
2840
3512
  // repair collaborative editing when Chinese input is interrupted by other users' cursors
2841
3513
  // when the DOMElement where the selection is located is removed
2842
3514
  // the compositionupdate and compositionend events will no longer be fired
@@ -2875,12 +3547,32 @@ class SlateEditable {
2875
3547
  render() {
2876
3548
  const changed = this.updateContext();
2877
3549
  if (changed) {
2878
- const virtualView = this.refreshVirtualView();
2879
- this.applyVirtualView(virtualView);
2880
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2881
- this.scheduleMeasureVisibleHeights();
3550
+ if (this.isEnabledVirtualScroll()) {
3551
+ this.updateListRenderAndRemeasureHeights();
3552
+ }
3553
+ else {
3554
+ this.listRender.update(this.editor.children, this.editor, this.context);
3555
+ }
2882
3556
  }
2883
3557
  }
3558
+ updateListRenderAndRemeasureHeights() {
3559
+ const virtualView = this.calculateVirtualViewport();
3560
+ const oldInViewportChildren = this.inViewportChildren;
3561
+ this.applyVirtualView(virtualView);
3562
+ const { preRenderingCount, childrenWithPreRendering } = this.handlePreRendering();
3563
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount);
3564
+ // 新增或者修改的才需要重算,计算出这个结果
3565
+ const remeasureIndics = [];
3566
+ this.inViewportChildren.forEach((child, index) => {
3567
+ if (oldInViewportChildren.indexOf(child) === -1) {
3568
+ remeasureIndics.push(this.inViewportIndics[index]);
3569
+ }
3570
+ });
3571
+ if (isDebug && remeasureIndics.length > 0) {
3572
+ console.log('remeasure height by indics: ', remeasureIndics);
3573
+ }
3574
+ measureHeightByIndics(this.editor, remeasureIndics, true);
3575
+ }
2884
3576
  updateContext() {
2885
3577
  const decorations = this.generateDecorations();
2886
3578
  if (this.context.selection !== this.editor.selection ||
@@ -2941,154 +3633,271 @@ class SlateEditable {
2941
3633
  decorations.push(...placeholderDecorations);
2942
3634
  return decorations;
2943
3635
  }
2944
- shouldUseVirtual() {
2945
- return !!(this.virtualConfig && this.virtualConfig.enabled);
3636
+ isEnabledVirtualScroll() {
3637
+ return !!(this.virtualScrollConfig && this.virtualScrollConfig.enabled);
2946
3638
  }
2947
- initializeVirtualScrolling() {
3639
+ initializeVirtualScroll() {
2948
3640
  if (this.virtualScrollInitialized) {
2949
3641
  return;
2950
3642
  }
2951
- if (this.virtualConfig && this.virtualConfig.enabled) {
3643
+ if (this.isEnabledVirtualScroll()) {
2952
3644
  this.virtualScrollInitialized = true;
2953
3645
  this.virtualTopHeightElement = document.createElement('div');
2954
3646
  this.virtualTopHeightElement.classList.add('virtual-top-height');
3647
+ this.virtualTopHeightElement.contentEditable = 'false';
2955
3648
  this.virtualBottomHeightElement = document.createElement('div');
2956
3649
  this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
3650
+ this.virtualBottomHeightElement.contentEditable = 'false';
2957
3651
  this.virtualCenterOutlet = document.createElement('div');
2958
3652
  this.virtualCenterOutlet.classList.add('virtual-center-outlet');
2959
3653
  this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
2960
3654
  this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
2961
3655
  this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
2962
- // businessHeight
3656
+ let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect()?.width ?? 0;
3657
+ this.editorResizeObserver = new ResizeObserver(entries => {
3658
+ if (entries.length > 0 && entries[0].contentRect.width !== editorResizeObserverRectWidth) {
3659
+ editorResizeObserverRectWidth = entries[0].contentRect.width;
3660
+ this.keyHeightMap.clear();
3661
+ const remeasureIndics = this.inViewportIndics;
3662
+ measureHeightByIndics(this.editor, remeasureIndics, true);
3663
+ }
3664
+ });
3665
+ this.editorResizeObserver.observe(this.elementRef.nativeElement);
2963
3666
  }
2964
3667
  }
2965
- changeVirtualHeight(topHeight, bottomHeight) {
3668
+ setVirtualSpaceHeight(topHeight, bottomHeight) {
2966
3669
  if (!this.virtualScrollInitialized) {
2967
3670
  return;
2968
3671
  }
2969
3672
  this.virtualTopHeightElement.style.height = `${topHeight}px`;
2970
- this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
3673
+ if (bottomHeight !== undefined) {
3674
+ this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
3675
+ }
2971
3676
  }
2972
- refreshVirtualView() {
3677
+ getActualVirtualTopHeight() {
3678
+ if (!this.virtualScrollInitialized) {
3679
+ return 0;
3680
+ }
3681
+ return parseFloat(this.virtualTopHeightElement.style.height.replace('px', ''));
3682
+ }
3683
+ handlePreRendering() {
3684
+ let preRenderingCount = 1;
3685
+ const childrenWithPreRendering = [...this.inViewportChildren];
3686
+ if (this.inViewportIndics[0] !== 0) {
3687
+ childrenWithPreRendering.unshift(this.editor.children[this.inViewportIndics[0] - 1]);
3688
+ }
3689
+ else {
3690
+ preRenderingCount = 0;
3691
+ }
3692
+ const lastIndex = this.inViewportIndics[this.inViewportIndics.length - 1];
3693
+ if (lastIndex !== this.editor.children.length - 1) {
3694
+ childrenWithPreRendering.push(this.editor.children[lastIndex + 1]);
3695
+ }
3696
+ return { preRenderingCount, childrenWithPreRendering };
3697
+ }
3698
+ tryUpdateVirtualViewport() {
3699
+ if (isDebug) {
3700
+ debugLog('log', 'tryUpdateVirtualViewport');
3701
+ }
3702
+ if (this.inViewportIndics.length > 0) {
3703
+ const topHeight = this.getActualVirtualTopHeight();
3704
+ const refreshVirtualTopHeight = calculateVirtualTopHeight(this.editor, this.inViewportIndics[0]);
3705
+ if (topHeight !== refreshVirtualTopHeight) {
3706
+ if (isDebug) {
3707
+ debugLog('log', 'update top height since dirty state(正数减去高度,负数代表增加高度): ', topHeight - refreshVirtualTopHeight);
3708
+ }
3709
+ this.setVirtualSpaceHeight(refreshVirtualTopHeight);
3710
+ return;
3711
+ }
3712
+ }
3713
+ this.tryUpdateVirtualViewportAnimId && cancelAnimationFrame(this.tryUpdateVirtualViewportAnimId);
3714
+ this.tryUpdateVirtualViewportAnimId = requestAnimationFrame(() => {
3715
+ if (isDebug) {
3716
+ debugLog('log', 'tryUpdateVirtualViewport Anim start');
3717
+ }
3718
+ let virtualView = this.calculateVirtualViewport();
3719
+ let diff = this.diffVirtualViewport(virtualView);
3720
+ if (diff.isDifferent && diff.needRemoveOnTop) {
3721
+ const remeasureIndics = diff.changedIndexesOfTop;
3722
+ const changed = measureHeightByIndics(this.editor, remeasureIndics);
3723
+ if (changed) {
3724
+ virtualView = this.calculateVirtualViewport();
3725
+ diff = this.diffVirtualViewport(virtualView, 'second');
3726
+ }
3727
+ }
3728
+ if (diff.isDifferent) {
3729
+ this.applyVirtualView(virtualView);
3730
+ if (this.listRender.initialized) {
3731
+ const { preRenderingCount, childrenWithPreRendering } = this.handlePreRendering();
3732
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount);
3733
+ if (diff.needAddOnTop) {
3734
+ const remeasureAddedIndics = diff.changedIndexesOfTop;
3735
+ if (isDebug) {
3736
+ debugLog('log', 'needAddOnTop to remeasure heights: ', remeasureAddedIndics);
3737
+ }
3738
+ const startIndexBeforeAdd = diff.changedIndexesOfTop[diff.changedIndexesOfTop.length - 1] + 1;
3739
+ const topHeightBeforeAdd = virtualView.accumulatedHeights[startIndexBeforeAdd];
3740
+ const changed = measureHeightByIndics(this.editor, remeasureAddedIndics);
3741
+ if (changed) {
3742
+ const newHeights = buildHeightsAndAccumulatedHeights(this.editor);
3743
+ const actualTopHeightAfterAdd = newHeights.accumulatedHeights[startIndexBeforeAdd];
3744
+ const newTopHeight = virtualView.top - (actualTopHeightAfterAdd - topHeightBeforeAdd);
3745
+ this.setVirtualSpaceHeight(newTopHeight);
3746
+ if (isDebug) {
3747
+ debugLog('log', `update top height since will add element in top(正数减去高度,负数代表增加高度): ${actualTopHeightAfterAdd - topHeightBeforeAdd}`);
3748
+ }
3749
+ }
3750
+ }
3751
+ if (!AngularEditor.isReadOnly(this.editor) && this.editor.selection) {
3752
+ this.toNativeSelection(false);
3753
+ }
3754
+ }
3755
+ }
3756
+ if (isDebug) {
3757
+ debugLog('log', 'tryUpdateVirtualViewport Anim end');
3758
+ }
3759
+ });
3760
+ }
3761
+ calculateVirtualViewport() {
2973
3762
  const children = (this.editor.children || []);
2974
- if (!children.length || !this.shouldUseVirtual()) {
3763
+ if (!children.length || !this.isEnabledVirtualScroll()) {
2975
3764
  return {
2976
- renderedChildren: children,
2977
- visibleIndexes: new Set(),
3765
+ inViewportChildren: children,
3766
+ visibleIndexes: [],
2978
3767
  top: 0,
2979
3768
  bottom: 0,
2980
3769
  heights: []
2981
3770
  };
2982
3771
  }
2983
- const scrollTop = this.virtualConfig.scrollTop ?? 0;
2984
- const viewportHeight = this.virtualConfig.viewportHeight ?? 0;
3772
+ const scrollTop = this.virtualScrollConfig.scrollTop;
3773
+ const viewportHeight = this.virtualScrollConfig.viewportHeight ?? 0;
2985
3774
  if (!viewportHeight) {
2986
- // 已经启用虚拟滚动,但可视区域高度还未获取到,先置空不渲染
2987
3775
  return {
2988
- renderedChildren: [],
2989
- visibleIndexes: new Set(),
3776
+ inViewportChildren: [],
3777
+ visibleIndexes: [],
2990
3778
  top: 0,
2991
3779
  bottom: 0,
2992
3780
  heights: []
2993
3781
  };
2994
3782
  }
2995
- const bufferCount = this.virtualConfig.bufferCount ?? VIRTUAL_SCROLL_DEFAULT_BUFFER_COUNT;
2996
- const heights = children.map((_, idx) => this.getBlockHeight(idx));
2997
- const accumulatedHeights = this.buildAccumulatedHeight(heights);
2998
- let visibleStart = 0;
2999
- // 按真实或估算高度往后累加,找到滚动起点所在块
3000
- while (visibleStart < heights.length && accumulatedHeights[visibleStart + 1] <= scrollTop) {
3001
- visibleStart++;
3002
- }
3003
- // 向上预留 bufferCount 块
3004
- const startIndex = Math.max(0, visibleStart - bufferCount);
3005
- const top = accumulatedHeights[startIndex];
3006
- const bufferBelowHeight = this.getBufferBelowHeight(viewportHeight, visibleStart, bufferCount);
3007
- const targetHeight = accumulatedHeights[visibleStart] - top + viewportHeight + bufferBelowHeight;
3783
+ const elementLength = children.length;
3784
+ if (!EDITOR_TO_BUSINESS_TOP.has(this.editor)) {
3785
+ EDITOR_TO_BUSINESS_TOP.set(this.editor, 0);
3786
+ setTimeout(() => {
3787
+ const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3788
+ const businessTop = Math.ceil(virtualTopBoundingTop) +
3789
+ Math.ceil(this.virtualScrollConfig.scrollTop) -
3790
+ Math.floor(this.virtualScrollConfig.viewportBoundingTop);
3791
+ EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
3792
+ if (isDebug) {
3793
+ debugLog('log', 'businessTop', businessTop);
3794
+ }
3795
+ }, 100);
3796
+ }
3797
+ const adjustedScrollTop = Math.max(0, scrollTop - getBusinessTop(this.editor));
3798
+ const { heights, accumulatedHeights } = buildHeightsAndAccumulatedHeights(this.editor);
3799
+ const totalHeight = accumulatedHeights[elementLength];
3800
+ const maxScrollTop = Math.max(0, totalHeight - viewportHeight);
3801
+ const limitedScrollTop = Math.min(adjustedScrollTop, maxScrollTop);
3802
+ const viewBottom = limitedScrollTop + viewportHeight;
3803
+ let accumulatedOffset = 0;
3804
+ let visibleStartIndex = -1;
3008
3805
  const visible = [];
3009
3806
  const visibleIndexes = [];
3010
- let accumulated = 0;
3011
- let cursor = startIndex;
3012
- // 循环累计高度超出目标高度(可视高度 + 上下 buffer)
3013
- while (cursor < children.length && accumulated < targetHeight) {
3014
- visible.push(children[cursor]);
3015
- visibleIndexes.push(cursor);
3016
- accumulated += this.getBlockHeight(cursor);
3017
- cursor++;
3018
- }
3019
- const bottom = heights.slice(cursor).reduce((acc, height) => acc + height, 0);
3020
- const renderedChildren = visible.length ? visible : children;
3021
- const visibleIndexesSet = new Set(visibleIndexes);
3807
+ for (let i = 0; i < elementLength && accumulatedOffset < viewBottom; i++) {
3808
+ const currentHeight = heights[i];
3809
+ const nextOffset = accumulatedOffset + currentHeight;
3810
+ // 可视区域有交集,加入渲染
3811
+ if (nextOffset > limitedScrollTop && accumulatedOffset < viewBottom) {
3812
+ if (visibleStartIndex === -1)
3813
+ visibleStartIndex = i; // 第一个相交起始位置
3814
+ visible.push(children[i]);
3815
+ visibleIndexes.push(i);
3816
+ }
3817
+ accumulatedOffset = nextOffset;
3818
+ }
3819
+ if (visibleStartIndex === -1 && elementLength) {
3820
+ visibleStartIndex = elementLength - 1;
3821
+ visible.push(children[visibleStartIndex]);
3822
+ visibleIndexes.push(visibleStartIndex);
3823
+ }
3824
+ const visibleEndIndex = visibleStartIndex === -1 ? elementLength - 1 : (visibleIndexes[visibleIndexes.length - 1] ?? visibleStartIndex);
3825
+ const top = visibleStartIndex === -1 ? 0 : accumulatedHeights[visibleStartIndex];
3826
+ const bottom = totalHeight - accumulatedHeights[visibleEndIndex + 1];
3022
3827
  return {
3023
- renderedChildren,
3024
- visibleIndexes: visibleIndexesSet,
3828
+ inViewportChildren: visible.length ? visible : children,
3829
+ visibleIndexes,
3025
3830
  top,
3026
3831
  bottom,
3027
- heights
3832
+ heights,
3833
+ accumulatedHeights
3028
3834
  };
3029
3835
  }
3030
3836
  applyVirtualView(virtualView) {
3031
- this.renderedChildren = virtualView.renderedChildren;
3032
- this.changeVirtualHeight(virtualView.top, virtualView.bottom);
3033
- this.virtualVisibleIndexes = virtualView.visibleIndexes;
3837
+ this.inViewportChildren = virtualView.inViewportChildren;
3838
+ this.setVirtualSpaceHeight(virtualView.top, virtualView.bottom);
3839
+ this.inViewportIndics = virtualView.visibleIndexes;
3034
3840
  }
3035
- diffVirtualView(virtualView, stage = 'first') {
3036
- if (!this.renderedChildren.length) {
3841
+ diffVirtualViewport(virtualView, stage = 'first') {
3842
+ if (!this.inViewportChildren.length) {
3843
+ if (isDebug) {
3844
+ debugLog('log', 'diffVirtualViewport', stage, 'empty inViewportChildren', virtualView.visibleIndexes);
3845
+ }
3037
3846
  return {
3038
- isDiff: true,
3039
- diffTopRenderedIndexes: [],
3040
- diffBottomRenderedIndexes: []
3847
+ isDifferent: true,
3848
+ changedIndexesOfTop: [],
3849
+ changedIndexesOfBottom: []
3041
3850
  };
3042
3851
  }
3043
- const oldVisibleIndexes = [...this.virtualVisibleIndexes];
3044
- const newVisibleIndexes = [...virtualView.visibleIndexes];
3045
- const firstNewIndex = newVisibleIndexes[0];
3046
- const lastNewIndex = newVisibleIndexes[newVisibleIndexes.length - 1];
3047
- const firstOldIndex = oldVisibleIndexes[0];
3048
- const lastOldIndex = oldVisibleIndexes[oldVisibleIndexes.length - 1];
3852
+ const oldIndexesInViewport = [...this.inViewportIndics];
3853
+ const newIndexesInViewport = [...virtualView.visibleIndexes];
3854
+ const firstNewIndex = newIndexesInViewport[0];
3855
+ const lastNewIndex = newIndexesInViewport[newIndexesInViewport.length - 1];
3856
+ const firstOldIndex = oldIndexesInViewport[0];
3857
+ const lastOldIndex = oldIndexesInViewport[oldIndexesInViewport.length - 1];
3049
3858
  if (firstNewIndex !== firstOldIndex || lastNewIndex !== lastOldIndex) {
3050
- const diffTopRenderedIndexes = [];
3051
- const diffBottomRenderedIndexes = [];
3052
- const isMissingTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
3053
- const isAddedTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
3054
- const isMissingBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
3055
- const isAddedBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
3056
- if (isMissingTop || isAddedBottom) {
3859
+ const changedIndexesOfTop = [];
3860
+ const changedIndexesOfBottom = [];
3861
+ const needRemoveOnTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
3862
+ const needAddOnTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
3863
+ const needRemoveOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
3864
+ const needAddOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
3865
+ if (needRemoveOnTop || needAddOnBottom) {
3057
3866
  // 向下
3058
- for (let index = 0; index < oldVisibleIndexes.length; index++) {
3059
- const element = oldVisibleIndexes[index];
3060
- if (!newVisibleIndexes.includes(element)) {
3061
- diffTopRenderedIndexes.push(element);
3867
+ for (let index = 0; index < oldIndexesInViewport.length; index++) {
3868
+ const element = oldIndexesInViewport[index];
3869
+ if (!newIndexesInViewport.includes(element)) {
3870
+ changedIndexesOfTop.push(element);
3062
3871
  }
3063
3872
  else {
3064
3873
  break;
3065
3874
  }
3066
3875
  }
3067
- for (let index = newVisibleIndexes.length - 1; index >= 0; index--) {
3068
- const element = newVisibleIndexes[index];
3069
- if (!oldVisibleIndexes.includes(element)) {
3070
- diffBottomRenderedIndexes.push(element);
3876
+ for (let index = newIndexesInViewport.length - 1; index >= 0; index--) {
3877
+ const element = newIndexesInViewport[index];
3878
+ if (!oldIndexesInViewport.includes(element)) {
3879
+ changedIndexesOfBottom.push(element);
3071
3880
  }
3072
3881
  else {
3073
3882
  break;
3074
3883
  }
3075
3884
  }
3076
3885
  }
3077
- else if (isAddedTop || isMissingBottom) {
3886
+ else if (needAddOnTop || needRemoveOnBottom) {
3078
3887
  // 向上
3079
- for (let index = 0; index < newVisibleIndexes.length; index++) {
3080
- const element = newVisibleIndexes[index];
3081
- if (!oldVisibleIndexes.includes(element)) {
3082
- diffTopRenderedIndexes.push(element);
3888
+ for (let index = 0; index < newIndexesInViewport.length; index++) {
3889
+ const element = newIndexesInViewport[index];
3890
+ if (!oldIndexesInViewport.includes(element)) {
3891
+ changedIndexesOfTop.push(element);
3083
3892
  }
3084
3893
  else {
3085
3894
  break;
3086
3895
  }
3087
3896
  }
3088
- for (let index = oldVisibleIndexes.length - 1; index >= 0; index--) {
3089
- const element = oldVisibleIndexes[index];
3090
- if (!newVisibleIndexes.includes(element)) {
3091
- diffBottomRenderedIndexes.push(element);
3897
+ for (let index = oldIndexesInViewport.length - 1; index >= 0; index--) {
3898
+ const element = oldIndexesInViewport[index];
3899
+ if (!newIndexesInViewport.includes(element)) {
3900
+ changedIndexesOfBottom.push(element);
3092
3901
  }
3093
3902
  else {
3094
3903
  break;
@@ -3096,139 +3905,35 @@ class SlateEditable {
3096
3905
  }
3097
3906
  }
3098
3907
  if (isDebug) {
3099
- console.log(`====== diffVirtualView stage: ${stage} ======`);
3100
- console.log('oldVisibleIndexes:', oldVisibleIndexes);
3101
- console.log('newVisibleIndexes:', newVisibleIndexes);
3102
- console.log('diffTopRenderedIndexes:', isMissingTop ? '-' : isAddedTop ? '+' : '-', diffTopRenderedIndexes, diffTopRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3103
- console.log('diffBottomRenderedIndexes:', isAddedBottom ? '+' : isMissingBottom ? '-' : '+', diffBottomRenderedIndexes, diffBottomRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3104
- const needTop = virtualView.heights.slice(0, newVisibleIndexes[0]).reduce((acc, height) => acc + height, 0);
3908
+ debugLog('log', `====== diffVirtualViewport stage: ${stage} ======`);
3909
+ debugLog('log', 'oldIndexesInViewport:', oldIndexesInViewport);
3910
+ debugLog('log', 'newIndexesInViewport:', newIndexesInViewport);
3911
+ debugLog('log', 'changedIndexesOfTop:', needRemoveOnTop ? '-' : needAddOnTop ? '+' : '-', changedIndexesOfTop, changedIndexesOfTop.map(index => getRealHeightByElement(this.editor, this.editor.children[index], 0)));
3912
+ debugLog('log', 'changedIndexesOfBottom:', needAddOnBottom ? '+' : needRemoveOnBottom ? '-' : '+', changedIndexesOfBottom, changedIndexesOfBottom.map(index => getRealHeightByElement(this.editor, this.editor.children[index], 0)));
3913
+ const needTop = virtualView.heights.slice(0, newIndexesInViewport[0]).reduce((acc, height) => acc + height, 0);
3105
3914
  const needBottom = virtualView.heights
3106
- .slice(newVisibleIndexes[newVisibleIndexes.length - 1] + 1)
3915
+ .slice(newIndexesInViewport[newIndexesInViewport.length - 1] + 1)
3107
3916
  .reduce((acc, height) => acc + height, 0);
3108
- console.log('newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3109
- console.log('newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3110
- console.warn('=========== Dividing line ===========');
3917
+ debugLog('log', needTop - parseFloat(this.virtualTopHeightElement.style.height), 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3918
+ debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3919
+ debugLog('warn', '=========== Dividing line ===========');
3111
3920
  }
3112
3921
  return {
3113
- isDiff: true,
3114
- isMissingTop,
3115
- isAddedTop,
3116
- isMissingBottom,
3117
- isAddedBottom,
3118
- diffTopRenderedIndexes,
3119
- diffBottomRenderedIndexes
3922
+ isDifferent: true,
3923
+ needRemoveOnTop,
3924
+ needAddOnTop,
3925
+ needRemoveOnBottom,
3926
+ needAddOnBottom,
3927
+ changedIndexesOfTop,
3928
+ changedIndexesOfBottom
3120
3929
  };
3121
3930
  }
3122
3931
  return {
3123
- isDiff: false,
3124
- diffTopRenderedIndexes: [],
3125
- diffBottomRenderedIndexes: []
3932
+ isDifferent: false,
3933
+ changedIndexesOfTop: [],
3934
+ changedIndexesOfBottom: []
3126
3935
  };
3127
3936
  }
3128
- getBlockHeight(index, defaultHeight = VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT) {
3129
- const node = this.editor.children[index];
3130
- if (!node) {
3131
- return defaultHeight;
3132
- }
3133
- const key = AngularEditor.findKey(this.editor, node);
3134
- return this.measuredHeights.get(key.id) ?? defaultHeight;
3135
- }
3136
- buildAccumulatedHeight(heights) {
3137
- const accumulatedHeights = new Array(heights.length + 1).fill(0);
3138
- for (let i = 0; i < heights.length; i++) {
3139
- // 存储前 i 个的累计高度
3140
- accumulatedHeights[i + 1] = accumulatedHeights[i] + heights[i];
3141
- }
3142
- return accumulatedHeights;
3143
- }
3144
- getBufferBelowHeight(viewportHeight, visibleStart, bufferCount) {
3145
- let blockHeight = 0;
3146
- let start = visibleStart;
3147
- // 循环累计高度超出视图高度代表找到向下缓冲区的起始位置
3148
- while (blockHeight < viewportHeight) {
3149
- blockHeight += this.getBlockHeight(start);
3150
- start++;
3151
- }
3152
- let bufferHeight = 0;
3153
- for (let i = start; i < start + bufferCount; i++) {
3154
- bufferHeight += this.getBlockHeight(i);
3155
- }
3156
- return bufferHeight;
3157
- }
3158
- scheduleMeasureVisibleHeights() {
3159
- if (!this.shouldUseVirtual()) {
3160
- return;
3161
- }
3162
- this.measureVisibleHeightsAnimId && cancelAnimationFrame(this.measureVisibleHeightsAnimId);
3163
- this.measureVisibleHeightsAnimId = requestAnimationFrame(() => {
3164
- this.measureVisibleHeights();
3165
- });
3166
- }
3167
- measureVisibleHeights() {
3168
- const children = (this.editor.children || []);
3169
- this.virtualVisibleIndexes.forEach(index => {
3170
- const node = children[index];
3171
- if (!node) {
3172
- return;
3173
- }
3174
- const key = AngularEditor.findKey(this.editor, node);
3175
- // 跳过已测过的块
3176
- if (this.measuredHeights.has(key.id)) {
3177
- return;
3178
- }
3179
- const view = ELEMENT_TO_COMPONENT.get(node);
3180
- if (!view) {
3181
- return;
3182
- }
3183
- const ret = view.getRealHeight();
3184
- if (ret instanceof Promise) {
3185
- ret.then(height => {
3186
- this.measuredHeights.set(key.id, height);
3187
- });
3188
- }
3189
- else {
3190
- this.measuredHeights.set(key.id, ret);
3191
- }
3192
- });
3193
- }
3194
- remeasureHeightByIndics(indics) {
3195
- const children = (this.editor.children || []);
3196
- let isHeightChanged = false;
3197
- indics.forEach(index => {
3198
- const node = children[index];
3199
- if (!node) {
3200
- return;
3201
- }
3202
- const key = AngularEditor.findKey(this.editor, node);
3203
- const view = ELEMENT_TO_COMPONENT.get(node);
3204
- if (!view) {
3205
- return;
3206
- }
3207
- const prevHeight = this.measuredHeights.get(key.id);
3208
- const ret = view.getRealHeight();
3209
- if (ret instanceof Promise) {
3210
- ret.then(height => {
3211
- if (height !== prevHeight) {
3212
- this.measuredHeights.set(key.id, height);
3213
- isHeightChanged = true;
3214
- if (isDebug) {
3215
- console.log(`remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${height}`);
3216
- }
3217
- }
3218
- });
3219
- }
3220
- else {
3221
- if (ret !== prevHeight) {
3222
- this.measuredHeights.set(key.id, ret);
3223
- isHeightChanged = true;
3224
- if (isDebug) {
3225
- console.log(`remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${ret}`);
3226
- }
3227
- }
3228
- }
3229
- });
3230
- return isHeightChanged;
3231
- }
3232
3937
  //#region event proxy
3233
3938
  addEventListener(eventName, listener, target = this.elementRef.nativeElement) {
3234
3939
  this.manualListeners.push(this.renderer2.listen(target, eventName, (event) => {
@@ -3269,7 +3974,7 @@ class SlateEditable {
3269
3974
  if (this.editor.selection && Range.equals(range, this.editor.selection) && !hasStringTarget(domSelection)) {
3270
3975
  if (!isTargetInsideVoid(this.editor, activeElement)) {
3271
3976
  // force adjust DOMSelection
3272
- this.toNativeSelection();
3977
+ this.toNativeSelection(false);
3273
3978
  }
3274
3979
  }
3275
3980
  else {
@@ -3753,6 +4458,11 @@ class SlateEditable {
3753
4458
  Transforms.move(editor, { unit: 'word', reverse: isRTL });
3754
4459
  return;
3755
4460
  }
4461
+ if (isKeyHotkey('mod+a', event)) {
4462
+ this.editor.selectAll();
4463
+ event.preventDefault();
4464
+ return;
4465
+ }
3756
4466
  // COMPAT: Certain browsers don't support the `beforeinput` event, so we
3757
4467
  // fall back to guessing at the input intention for hotkeys.
3758
4468
  // COMPAT: In iOS, some of these hotkeys are handled in the
@@ -3920,6 +4630,7 @@ class SlateEditable {
3920
4630
  }
3921
4631
  //#endregion
3922
4632
  ngOnDestroy() {
4633
+ this.editorResizeObserver?.disconnect();
3923
4634
  NODE_TO_ELEMENT.delete(this.editor);
3924
4635
  this.manualListeners.forEach(manualListener => {
3925
4636
  manualListener();
@@ -4108,16 +4819,35 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
4108
4819
  }]
4109
4820
  }], ctorParameters: () => [{ type: i0.ElementRef }] });
4110
4821
 
4822
+ class SlateString {
4823
+ ngOnInit() { }
4824
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateString, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
4825
+ 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 }); }
4826
+ }
4827
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateString, decorators: [{
4828
+ type: Component,
4829
+ args: [{
4830
+ selector: 'span[slateString]',
4831
+ template: '',
4832
+ changeDetection: ChangeDetectionStrategy.OnPush,
4833
+ standalone: true
4834
+ }]
4835
+ }], propDecorators: { context: [{
4836
+ type: Input
4837
+ }], viewContext: [{
4838
+ type: Input
4839
+ }] } });
4840
+
4111
4841
  class SlateModule {
4112
4842
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
4113
- static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, imports: [CommonModule, SlateEditable, SlateChildrenOutlet], exports: [SlateEditable, SlateChildrenOutlet] }); }
4843
+ 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] }); }
4114
4844
  static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, imports: [CommonModule] }); }
4115
4845
  }
4116
4846
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateModule, decorators: [{
4117
4847
  type: NgModule,
4118
4848
  args: [{
4119
- imports: [CommonModule, SlateEditable, SlateChildrenOutlet],
4120
- exports: [SlateEditable, SlateChildrenOutlet],
4849
+ imports: [CommonModule, SlateEditable, SlateChildrenOutlet, SlateString],
4850
+ exports: [SlateEditable, SlateChildrenOutlet, SlateString],
4121
4851
  providers: []
4122
4852
  }]
4123
4853
  }] });
@@ -4233,6 +4963,7 @@ class BaseElementComponent extends BaseComponent {
4233
4963
  if (ELEMENT_TO_COMPONENT.get(this.element) === this) {
4234
4964
  ELEMENT_TO_COMPONENT.delete(this.element);
4235
4965
  }
4966
+ this.listRender.destroy();
4236
4967
  }
4237
4968
  onContextChange() {
4238
4969
  this.childrenContext = this.getChildrenContext();
@@ -4271,7 +5002,7 @@ class BaseElementComponent extends BaseComponent {
4271
5002
  const blockCard = getBlockCardByNativeElement(this.nativeElement);
4272
5003
  const target = blockCard || this.nativeElement;
4273
5004
  const computedStyle = getComputedStyle(target);
4274
- const height = target.offsetHeight + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom);
5005
+ const height = Math.ceil(target.getBoundingClientRect().height) + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom);
4275
5006
  if (this.isStableHeight()) {
4276
5007
  this.stableHeight = height;
4277
5008
  }
@@ -4320,6 +5051,7 @@ class BaseTextComponent extends BaseComponent {
4320
5051
  NODE_TO_ELEMENT.delete(this.text);
4321
5052
  }
4322
5053
  ELEMENT_TO_NODE.delete(this.nativeElement);
5054
+ this.leavesRender.destroy();
4323
5055
  }
4324
5056
  onContextChange() {
4325
5057
  this.updateWeakMap();
@@ -4345,6 +5077,9 @@ class BaseLeafComponent extends BaseComponent {
4345
5077
  super(...arguments);
4346
5078
  this.stringRender = null;
4347
5079
  this.isSlateLeaf = true;
5080
+ this.getOutletParent = () => {
5081
+ return this.elementRef.nativeElement;
5082
+ };
4348
5083
  }
4349
5084
  get text() {
4350
5085
  return this.context && this.context.text;
@@ -4359,7 +5094,7 @@ class BaseLeafComponent extends BaseComponent {
4359
5094
  if (!this.initialized) {
4360
5095
  this.stringRender = new SlateStringRender(this.context, this.viewContext);
4361
5096
  const stringNode = this.stringRender.render();
4362
- this.nativeElement.appendChild(stringNode);
5097
+ this.getOutletParent().appendChild(stringNode);
4363
5098
  }
4364
5099
  else {
4365
5100
  this.stringRender?.update(this.context, this.viewContext);
@@ -4431,5 +5166,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
4431
5166
  * Generated bundle index. Do not edit.
4432
5167
  */
4433
5168
 
4434
- 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 };
5169
+ 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_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 };
4435
5170
  //# sourceMappingURL=slate-angular.mjs.map