slate-angular 20.1.0 → 20.2.0-next.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.
@@ -470,6 +470,9 @@ const HAS_BEFORE_INPUT_SUPPORT = !IS_CHROME_LEGACY &&
470
470
  globalThis.InputEvent &&
471
471
  // @ts-ignore The `getTargetRanges` property isn't recognized.
472
472
  typeof globalThis.InputEvent.prototype.getTargetRanges === 'function';
473
+ const VIRTUAL_SCROLL_DEFAULT_BUFFER_COUNT = 3;
474
+ const VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT = 40;
475
+ const SLATE_DEBUG_KEY = '__SLATE_DEBUG__';
473
476
 
474
477
  /**
475
478
  * Hotkey mappings for each platform.
@@ -1683,6 +1686,50 @@ class BaseFlavour {
1683
1686
  }
1684
1687
  }
1685
1688
 
1689
+ const SLATE_BLOCK_CARD_CLASS_NAME = 'slate-block-card';
1690
+ class SlateBlockCard {
1691
+ onInit() {
1692
+ const nativeElement = document.createElement('div');
1693
+ nativeElement.classList.add(SLATE_BLOCK_CARD_CLASS_NAME);
1694
+ this.nativeElement = nativeElement;
1695
+ this.createContent();
1696
+ }
1697
+ createContent() {
1698
+ const leftCaret = document.createElement('span');
1699
+ leftCaret.setAttribute(`card-target`, 'card-left');
1700
+ leftCaret.classList.add('card-left');
1701
+ leftCaret.appendChild(getZeroTextNode());
1702
+ const rightCaret = document.createElement('span');
1703
+ rightCaret.setAttribute(`card-target`, 'card-right');
1704
+ rightCaret.classList.add('card-right');
1705
+ rightCaret.appendChild(getZeroTextNode());
1706
+ const center = document.createElement('div');
1707
+ center.setAttribute(`card-target`, 'card-center');
1708
+ this.nativeElement.appendChild(leftCaret);
1709
+ this.nativeElement.appendChild(center);
1710
+ this.nativeElement.appendChild(rightCaret);
1711
+ this.centerContainer = center;
1712
+ }
1713
+ append() {
1714
+ this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
1715
+ }
1716
+ initializeCenter(rootNodes) {
1717
+ this.centerRootNodes = rootNodes;
1718
+ this.append();
1719
+ }
1720
+ onDestroy() {
1721
+ this.nativeElement.remove();
1722
+ }
1723
+ }
1724
+ const getBlockCardByNativeElement = (nativeElement) => {
1725
+ const blockCardElement = nativeElement?.parentElement?.parentElement;
1726
+ if (blockCardElement && blockCardElement.classList.contains(SLATE_BLOCK_CARD_CLASS_NAME)) {
1727
+ return blockCardElement;
1728
+ }
1729
+ return null;
1730
+ };
1731
+
1732
+ const DEFAULT_ELEMENT_HEIGHT = 24;
1686
1733
  class BaseElementFlavour extends BaseFlavour {
1687
1734
  constructor() {
1688
1735
  super(...arguments);
@@ -1777,6 +1824,12 @@ class BaseElementFlavour extends BaseFlavour {
1777
1824
  readonly: this._context.readonly
1778
1825
  };
1779
1826
  }
1827
+ getRealHeight() {
1828
+ const blockCard = getBlockCardByNativeElement(this.nativeElement);
1829
+ const target = blockCard || this.nativeElement;
1830
+ const computedStyle = getComputedStyle(target);
1831
+ return Promise.resolve(target.offsetHeight + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom));
1832
+ }
1780
1833
  }
1781
1834
 
1782
1835
  class DefaultElementFlavour extends BaseElementFlavour {
@@ -2203,49 +2256,6 @@ const createText = (text) => {
2203
2256
  return { nativeElement };
2204
2257
  };
2205
2258
 
2206
- const SLATE_BLOCK_CARD_CLASS_NAME = 'slate-block-card';
2207
- class SlateBlockCard {
2208
- onInit() {
2209
- const nativeElement = document.createElement('div');
2210
- nativeElement.classList.add(SLATE_BLOCK_CARD_CLASS_NAME);
2211
- this.nativeElement = nativeElement;
2212
- this.createContent();
2213
- }
2214
- createContent() {
2215
- const leftCaret = document.createElement('span');
2216
- leftCaret.setAttribute(`card-target`, 'card-left');
2217
- leftCaret.classList.add('card-left');
2218
- leftCaret.appendChild(getZeroTextNode());
2219
- const rightCaret = document.createElement('span');
2220
- rightCaret.setAttribute(`card-target`, 'card-right');
2221
- rightCaret.classList.add('card-right');
2222
- rightCaret.appendChild(getZeroTextNode());
2223
- const center = document.createElement('div');
2224
- center.setAttribute(`card-target`, 'card-center');
2225
- this.nativeElement.appendChild(leftCaret);
2226
- this.nativeElement.appendChild(center);
2227
- this.nativeElement.appendChild(rightCaret);
2228
- this.centerContainer = center;
2229
- }
2230
- append() {
2231
- this.centerRootNodes.forEach(rootNode => !this.centerContainer.contains(rootNode) && this.centerContainer.appendChild(rootNode));
2232
- }
2233
- initializeCenter(rootNodes) {
2234
- this.centerRootNodes = rootNodes;
2235
- this.append();
2236
- }
2237
- onDestroy() {
2238
- this.nativeElement.remove();
2239
- }
2240
- }
2241
- const getBlockCardByNativeElement = (nativeElement) => {
2242
- const blockCardElement = nativeElement?.parentElement?.parentElement;
2243
- if (blockCardElement && blockCardElement.classList.contains(SLATE_BLOCK_CARD_CLASS_NAME)) {
2244
- return blockCardElement;
2245
- }
2246
- return null;
2247
- };
2248
-
2249
2259
  class ListRender {
2250
2260
  constructor(viewContext, viewContainerRef, getOutletParent, getOutletElement) {
2251
2261
  this.viewContext = viewContext;
@@ -2262,11 +2272,13 @@ class ListRender {
2262
2272
  initialize(children, parent, childrenContext) {
2263
2273
  this.initialized = true;
2264
2274
  this.children = children;
2275
+ const isRoot = parent === this.viewContext.editor;
2276
+ const firstIndex = isRoot ? this.viewContext.editor.children.indexOf(children[0]) : 0;
2265
2277
  const parentPath = AngularEditor.findPath(this.viewContext.editor, parent);
2266
- children.forEach((descendant, index) => {
2267
- NODE_TO_INDEX.set(descendant, index);
2278
+ children.forEach((descendant, _index) => {
2279
+ NODE_TO_INDEX.set(descendant, firstIndex + _index);
2268
2280
  NODE_TO_PARENT.set(descendant, parent);
2269
- const context = getContext(index, descendant, parentPath, childrenContext, this.viewContext);
2281
+ const context = getContext(firstIndex + _index, descendant, parentPath, childrenContext, this.viewContext);
2270
2282
  const viewType = getViewType(descendant, parent, this.viewContext);
2271
2283
  const view = createEmbeddedViewOrComponentOrFlavour(viewType, context, this.viewContext, this.viewContainerRef);
2272
2284
  const blockCard = createBlockCard(descendant, view, this.viewContext);
@@ -2294,6 +2306,8 @@ class ListRender {
2294
2306
  const outletParent = this.getOutletParent();
2295
2307
  const diffResult = this.differ.diff(children);
2296
2308
  const parentPath = AngularEditor.findPath(this.viewContext.editor, parent);
2309
+ const isRoot = parent === this.viewContext.editor;
2310
+ const firstIndex = isRoot ? this.viewContext.editor.children.indexOf(children[0]) : 0;
2297
2311
  if (diffResult) {
2298
2312
  let firstRootNode = getRootNodes(this.views[0], this.blockCards[0])[0];
2299
2313
  const newContexts = [];
@@ -2301,9 +2315,10 @@ class ListRender {
2301
2315
  const newViews = [];
2302
2316
  const newBlockCards = [];
2303
2317
  diffResult.forEachItem(record => {
2304
- NODE_TO_INDEX.set(record.item, record.currentIndex);
2318
+ const currentIndex = firstIndex + record.currentIndex;
2319
+ NODE_TO_INDEX.set(record.item, currentIndex);
2305
2320
  NODE_TO_PARENT.set(record.item, parent);
2306
- let context = getContext(record.currentIndex, record.item, parentPath, childrenContext, this.viewContext);
2321
+ let context = getContext(currentIndex, record.item, parentPath, childrenContext, this.viewContext);
2307
2322
  const viewType = getViewType(record.item, parent, this.viewContext);
2308
2323
  newViewTypes.push(viewType);
2309
2324
  let view;
@@ -2371,16 +2386,16 @@ class ListRender {
2371
2386
  }
2372
2387
  else {
2373
2388
  const newContexts = [];
2374
- this.children.forEach((child, index) => {
2375
- NODE_TO_INDEX.set(child, index);
2389
+ this.children.forEach((child, _index) => {
2390
+ NODE_TO_INDEX.set(child, firstIndex + _index);
2376
2391
  NODE_TO_PARENT.set(child, parent);
2377
- let context = getContext(index, child, parentPath, childrenContext, this.viewContext);
2378
- const previousContext = this.contexts[index];
2392
+ let context = getContext(firstIndex + _index, child, parentPath, childrenContext, this.viewContext);
2393
+ const previousContext = this.contexts[_index];
2379
2394
  if (memoizedContext(this.viewContext, child, previousContext, context)) {
2380
2395
  context = previousContext;
2381
2396
  }
2382
2397
  else {
2383
- updateContext(this.views[index], context, this.viewContext);
2398
+ updateContext(this.views[_index], context, this.viewContext);
2384
2399
  }
2385
2400
  newContexts.push(context);
2386
2401
  });
@@ -2538,9 +2553,25 @@ function executeAfterViewInit(editor) {
2538
2553
  clearAfterViewInitQueue(editor);
2539
2554
  }
2540
2555
 
2556
+ const JUST_NOW_UPDATED_VIRTUAL_VIEW = new WeakMap();
2541
2557
  // not correctly clipboardData on beforeinput
2542
2558
  const forceOnDOMPaste = IS_SAFARI;
2543
2559
  class SlateEditable {
2560
+ set virtualScroll(config) {
2561
+ this.virtualConfig = config;
2562
+ this.refreshVirtualViewAnimId && cancelAnimationFrame(this.refreshVirtualViewAnimId);
2563
+ this.refreshVirtualViewAnimId = requestAnimationFrame(() => {
2564
+ const virtualView = this.refreshVirtualView();
2565
+ const diff = this.diffVirtualView(virtualView);
2566
+ if (diff) {
2567
+ this.applyVirtualView(virtualView);
2568
+ if (this.listRender.initialized) {
2569
+ this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2570
+ }
2571
+ this.scheduleMeasureVisibleHeights();
2572
+ }
2573
+ });
2574
+ }
2544
2575
  get hasBeforeInputSupport() {
2545
2576
  return HAS_BEFORE_INPUT_SUPPORT;
2546
2577
  }
@@ -2576,6 +2607,24 @@ class SlateEditable {
2576
2607
  this.getOutletParent = () => {
2577
2608
  return this.elementRef.nativeElement;
2578
2609
  };
2610
+ this.getOutletElement = () => {
2611
+ if (this.virtualScrollInitialized) {
2612
+ return this.virtualCenterOutlet;
2613
+ }
2614
+ else {
2615
+ return null;
2616
+ }
2617
+ };
2618
+ this.virtualConfig = {
2619
+ enabled: false,
2620
+ scrollTop: 0,
2621
+ viewportHeight: 0
2622
+ };
2623
+ this.renderedChildren = [];
2624
+ this.virtualVisibleIndexes = new Set();
2625
+ this.measuredHeights = new Map();
2626
+ this.measurePending = false;
2627
+ this.virtualScrollInitialized = false;
2579
2628
  }
2580
2629
  ngOnInit() {
2581
2630
  this.editor.injector = this.injector;
@@ -2599,7 +2648,8 @@ class SlateEditable {
2599
2648
  // add browser class
2600
2649
  let browserClass = IS_FIREFOX ? 'firefox' : IS_SAFARI ? 'safari' : '';
2601
2650
  browserClass && this.elementRef.nativeElement.classList.add(browserClass);
2602
- this.listRender = new ListRender(this.viewContext, this.viewContainerRef, this.getOutletParent, () => null);
2651
+ this.initializeVirtualScrolling();
2652
+ this.listRender = new ListRender(this.viewContext, this.viewContainerRef, this.getOutletParent, this.getOutletElement);
2603
2653
  }
2604
2654
  ngOnChanges(simpleChanges) {
2605
2655
  if (!this.initialized) {
@@ -2630,12 +2680,16 @@ class SlateEditable {
2630
2680
  if (value && value.length) {
2631
2681
  this.editor.children = value;
2632
2682
  this.initializeContext();
2683
+ const virtualView = this.refreshVirtualView();
2684
+ this.applyVirtualView(virtualView);
2685
+ const childrenForRender = virtualView.renderedChildren;
2633
2686
  if (!this.listRender.initialized) {
2634
- this.listRender.initialize(this.editor.children, this.editor, this.context);
2687
+ this.listRender.initialize(childrenForRender, this.editor, this.context);
2635
2688
  }
2636
2689
  else {
2637
- this.listRender.update(this.editor.children, this.editor, this.context);
2690
+ this.listRender.update(childrenForRender, this.editor, this.context);
2638
2691
  }
2692
+ this.scheduleMeasureVisibleHeights();
2639
2693
  this.cdr.markForCheck();
2640
2694
  }
2641
2695
  }
@@ -2756,7 +2810,10 @@ class SlateEditable {
2756
2810
  ngDoCheck() { }
2757
2811
  forceRender() {
2758
2812
  this.updateContext();
2759
- this.listRender.update(this.editor.children, this.editor, this.context);
2813
+ const virtualView = this.refreshVirtualView();
2814
+ this.applyVirtualView(virtualView);
2815
+ this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2816
+ this.scheduleMeasureVisibleHeights();
2760
2817
  // repair collaborative editing when Chinese input is interrupted by other users' cursors
2761
2818
  // when the DOMElement where the selection is located is removed
2762
2819
  // the compositionupdate and compositionend events will no longer be fired
@@ -2795,7 +2852,10 @@ class SlateEditable {
2795
2852
  render() {
2796
2853
  const changed = this.updateContext();
2797
2854
  if (changed) {
2798
- this.listRender.update(this.editor.children, this.editor, this.context);
2855
+ const virtualView = this.refreshVirtualView();
2856
+ this.applyVirtualView(virtualView);
2857
+ this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2858
+ this.scheduleMeasureVisibleHeights();
2799
2859
  }
2800
2860
  }
2801
2861
  updateContext() {
@@ -2858,6 +2918,234 @@ class SlateEditable {
2858
2918
  decorations.push(...placeholderDecorations);
2859
2919
  return decorations;
2860
2920
  }
2921
+ shouldUseVirtual() {
2922
+ return !!(this.virtualConfig && this.virtualConfig.enabled);
2923
+ }
2924
+ initializeVirtualScrolling() {
2925
+ if (this.virtualScrollInitialized) {
2926
+ return;
2927
+ }
2928
+ if (this.virtualConfig && this.virtualConfig.enabled) {
2929
+ this.virtualScrollInitialized = true;
2930
+ this.virtualTopHeightElement = document.createElement('div');
2931
+ this.virtualTopHeightElement.classList.add('virtual-top-height');
2932
+ this.virtualBottomHeightElement = document.createElement('div');
2933
+ this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
2934
+ this.virtualCenterOutlet = document.createElement('div');
2935
+ this.virtualCenterOutlet.classList.add('virtual-center-outlet');
2936
+ this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
2937
+ this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
2938
+ this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
2939
+ }
2940
+ }
2941
+ changeVirtualHeight(topHeight, bottomHeight) {
2942
+ if (!this.virtualScrollInitialized) {
2943
+ return;
2944
+ }
2945
+ this.virtualTopHeightElement.style.height = `${topHeight}px`;
2946
+ this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
2947
+ }
2948
+ refreshVirtualView() {
2949
+ const children = (this.editor.children || []);
2950
+ if (!children.length || !this.shouldUseVirtual()) {
2951
+ return {
2952
+ renderedChildren: children,
2953
+ visibleIndexes: new Set(),
2954
+ top: 0,
2955
+ bottom: 0,
2956
+ heights: []
2957
+ };
2958
+ }
2959
+ const scrollTop = this.virtualConfig.scrollTop ?? 0;
2960
+ const viewportHeight = this.virtualConfig.viewportHeight ?? 0;
2961
+ if (!viewportHeight) {
2962
+ // 已经启用虚拟滚动,但可视区域高度还未获取到,先置空不渲染
2963
+ return {
2964
+ renderedChildren: [],
2965
+ visibleIndexes: new Set(),
2966
+ top: 0,
2967
+ bottom: 0,
2968
+ heights: []
2969
+ };
2970
+ }
2971
+ const bufferCount = this.virtualConfig.bufferCount ?? VIRTUAL_SCROLL_DEFAULT_BUFFER_COUNT;
2972
+ const heights = children.map((_, idx) => this.getBlockHeight(idx));
2973
+ const accumulatedHeights = this.buildAccumulatedHeight(heights);
2974
+ let visibleStart = 0;
2975
+ // 按真实或估算高度往后累加,找到滚动起点所在块
2976
+ while (visibleStart < heights.length && accumulatedHeights[visibleStart + 1] <= scrollTop) {
2977
+ visibleStart++;
2978
+ }
2979
+ // 向上预留 bufferCount 块
2980
+ const startIndex = Math.max(0, visibleStart - bufferCount);
2981
+ const top = accumulatedHeights[startIndex];
2982
+ const bufferBelowHeight = this.getBufferBelowHeight(viewportHeight, visibleStart, bufferCount);
2983
+ const targetHeight = accumulatedHeights[visibleStart] - top + viewportHeight + bufferBelowHeight;
2984
+ const visible = [];
2985
+ const visibleIndexes = [];
2986
+ let accumulated = 0;
2987
+ let cursor = startIndex;
2988
+ // 循环累计高度超出目标高度(可视高度 + 上下 buffer)
2989
+ while (cursor < children.length && accumulated < targetHeight) {
2990
+ visible.push(children[cursor]);
2991
+ visibleIndexes.push(cursor);
2992
+ accumulated += this.getBlockHeight(cursor);
2993
+ cursor++;
2994
+ }
2995
+ const bottom = heights.slice(cursor).reduce((acc, height) => acc + height, 0);
2996
+ const renderedChildren = visible.length ? visible : children;
2997
+ const visibleIndexesSet = new Set(visibleIndexes);
2998
+ return {
2999
+ renderedChildren,
3000
+ visibleIndexes: visibleIndexesSet,
3001
+ top,
3002
+ bottom,
3003
+ heights
3004
+ };
3005
+ }
3006
+ applyVirtualView(virtualView) {
3007
+ this.renderedChildren = virtualView.renderedChildren;
3008
+ this.changeVirtualHeight(virtualView.top, virtualView.bottom);
3009
+ this.virtualVisibleIndexes = virtualView.visibleIndexes;
3010
+ }
3011
+ diffVirtualView(virtualView) {
3012
+ if (!this.renderedChildren.length) {
3013
+ return true;
3014
+ }
3015
+ const oldVisibleIndexes = [...this.virtualVisibleIndexes];
3016
+ const newVisibleIndexes = [...virtualView.visibleIndexes];
3017
+ if (newVisibleIndexes[0] !== oldVisibleIndexes[0] ||
3018
+ newVisibleIndexes[newVisibleIndexes.length - 1] !== oldVisibleIndexes[oldVisibleIndexes.length - 1]) {
3019
+ if (localStorage.getItem(SLATE_DEBUG_KEY) === 'true') {
3020
+ const diffTopRenderedIndexes = [];
3021
+ const diffBottomRenderedIndexes = [];
3022
+ let direction = '';
3023
+ if (newVisibleIndexes[0] > oldVisibleIndexes[0]) {
3024
+ // 向下
3025
+ direction = 'down';
3026
+ for (let index = 0; index < oldVisibleIndexes.length; index++) {
3027
+ const element = oldVisibleIndexes[index];
3028
+ if (!newVisibleIndexes.includes(element)) {
3029
+ diffTopRenderedIndexes.push(element);
3030
+ }
3031
+ else {
3032
+ break;
3033
+ }
3034
+ }
3035
+ for (let index = newVisibleIndexes.length - 1; index >= 0; index--) {
3036
+ const element = newVisibleIndexes[index];
3037
+ if (!oldVisibleIndexes.includes(element)) {
3038
+ diffBottomRenderedIndexes.push(element);
3039
+ }
3040
+ else {
3041
+ break;
3042
+ }
3043
+ }
3044
+ }
3045
+ else {
3046
+ // 向上
3047
+ direction = 'up';
3048
+ for (let index = 0; index < newVisibleIndexes.length; index++) {
3049
+ const element = newVisibleIndexes[index];
3050
+ if (!oldVisibleIndexes.includes(element)) {
3051
+ diffTopRenderedIndexes.push(element);
3052
+ }
3053
+ else {
3054
+ break;
3055
+ }
3056
+ }
3057
+ for (let index = oldVisibleIndexes.length - 1; index >= 0; index--) {
3058
+ const element = oldVisibleIndexes[index];
3059
+ if (!newVisibleIndexes.includes(element)) {
3060
+ diffBottomRenderedIndexes.push(element);
3061
+ }
3062
+ else {
3063
+ break;
3064
+ }
3065
+ }
3066
+ }
3067
+ console.log('oldVisibleIndexes:', oldVisibleIndexes);
3068
+ console.log('newVisibleIndexes:', newVisibleIndexes);
3069
+ const directionStr = direction === 'down' ? '+' : '-';
3070
+ console.log('diffTopRenderedIndexes:', directionStr, diffTopRenderedIndexes, diffTopRenderedIndexes.map(index => virtualView.heights[index]));
3071
+ console.log('diffBottomRenderedIndexes:', directionStr, diffBottomRenderedIndexes, diffBottomRenderedIndexes.map(index => virtualView.heights[index]));
3072
+ const needTop = virtualView.heights.slice(0, newVisibleIndexes[0]).reduce((acc, height) => acc + height, 0);
3073
+ const needBottom = virtualView.heights
3074
+ .slice(newVisibleIndexes[newVisibleIndexes.length - 1] + 1)
3075
+ .reduce((acc, height) => acc + height, 0);
3076
+ console.log('newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3077
+ console.log('newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3078
+ console.warn('=========== Dividing line ===========');
3079
+ }
3080
+ return true;
3081
+ }
3082
+ return false;
3083
+ }
3084
+ getBlockHeight(index) {
3085
+ const node = this.editor.children[index];
3086
+ if (!node) {
3087
+ return VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT;
3088
+ }
3089
+ const key = AngularEditor.findKey(this.editor, node);
3090
+ return this.measuredHeights.get(key.id) ?? VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT;
3091
+ }
3092
+ buildAccumulatedHeight(heights) {
3093
+ const accumulatedHeights = new Array(heights.length + 1).fill(0);
3094
+ for (let i = 0; i < heights.length; i++) {
3095
+ // 存储前 i 个的累计高度
3096
+ accumulatedHeights[i + 1] = accumulatedHeights[i] + heights[i];
3097
+ }
3098
+ return accumulatedHeights;
3099
+ }
3100
+ getBufferBelowHeight(viewportHeight, visibleStart, bufferCount) {
3101
+ let blockHeight = 0;
3102
+ let start = visibleStart;
3103
+ // 循环累计高度超出视图高度代表找到向下缓冲区的起始位置
3104
+ while (blockHeight < viewportHeight) {
3105
+ blockHeight += this.getBlockHeight(start);
3106
+ start++;
3107
+ }
3108
+ let bufferHeight = 0;
3109
+ for (let i = start; i < start + bufferCount; i++) {
3110
+ bufferHeight += this.getBlockHeight(i);
3111
+ }
3112
+ return bufferHeight;
3113
+ }
3114
+ scheduleMeasureVisibleHeights() {
3115
+ if (!this.shouldUseVirtual()) {
3116
+ return;
3117
+ }
3118
+ if (this.measurePending) {
3119
+ return;
3120
+ }
3121
+ this.measurePending = true;
3122
+ this.measureVisibleHeightsAnimId && cancelAnimationFrame(this.measureVisibleHeightsAnimId);
3123
+ this.measureVisibleHeightsAnimId = requestAnimationFrame(() => {
3124
+ this.measureVisibleHeights();
3125
+ this.measurePending = false;
3126
+ });
3127
+ }
3128
+ measureVisibleHeights() {
3129
+ const children = (this.editor.children || []);
3130
+ this.virtualVisibleIndexes.forEach(index => {
3131
+ const node = children[index];
3132
+ if (!node) {
3133
+ return;
3134
+ }
3135
+ const key = AngularEditor.findKey(this.editor, node);
3136
+ // 跳过已测过的块
3137
+ if (this.measuredHeights.has(key.id)) {
3138
+ return;
3139
+ }
3140
+ const view = ELEMENT_TO_COMPONENT.get(node);
3141
+ if (!view) {
3142
+ return;
3143
+ }
3144
+ view.getRealHeight()?.then(height => {
3145
+ this.measuredHeights.set(key.id, height);
3146
+ });
3147
+ });
3148
+ }
2861
3149
  //#region event proxy
2862
3150
  addEventListener(eventName, listener, target = this.elementRef.nativeElement) {
2863
3151
  this.manualListeners.push(this.renderer2.listen(target, eventName, (event) => {
@@ -3557,7 +3845,7 @@ class SlateEditable {
3557
3845
  EDITOR_TO_ON_CHANGE.delete(this.editor);
3558
3846
  }
3559
3847
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: SlateEditable, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }, { token: i0.ChangeDetectorRef }, { token: i0.NgZone }, { token: i0.Injector }], target: i0.ɵɵFactoryTarget.Component }); }
3560
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.12", type: SlateEditable, isStandalone: true, selector: "slate-editable", inputs: { editor: "editor", renderElement: "renderElement", renderLeaf: "renderLeaf", renderText: "renderText", decorate: "decorate", placeholderDecorate: "placeholderDecorate", scrollSelectionIntoView: "scrollSelectionIntoView", isStrictDecorate: "isStrictDecorate", trackBy: "trackBy", readonly: "readonly", placeholder: "placeholder", beforeInput: "beforeInput", blur: "blur", click: "click", compositionEnd: "compositionEnd", compositionUpdate: "compositionUpdate", compositionStart: "compositionStart", copy: "copy", cut: "cut", dragOver: "dragOver", dragStart: "dragStart", dragEnd: "dragEnd", drop: "drop", focus: "focus", keydown: "keydown", paste: "paste", spellCheck: "spellCheck", autoCorrect: "autoCorrect", autoCapitalize: "autoCapitalize" }, host: { properties: { "attr.contenteditable": "readonly ? undefined : true", "attr.role": "readonly ? undefined : 'textbox'", "attr.spellCheck": "!hasBeforeInputSupport ? false : spellCheck", "attr.autoCorrect": "!hasBeforeInputSupport ? 'false' : autoCorrect", "attr.autoCapitalize": "!hasBeforeInputSupport ? 'false' : autoCapitalize", "attr.data-slate-editor": "this.dataSlateEditor", "attr.data-slate-node": "this.dataSlateNode", "attr.data-gramm": "this.dataGramm" }, classAttribute: "slate-editable-container" }, providers: [
3848
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.12", type: SlateEditable, isStandalone: true, selector: "slate-editable", inputs: { editor: "editor", renderElement: "renderElement", renderLeaf: "renderLeaf", renderText: "renderText", decorate: "decorate", placeholderDecorate: "placeholderDecorate", scrollSelectionIntoView: "scrollSelectionIntoView", isStrictDecorate: "isStrictDecorate", trackBy: "trackBy", readonly: "readonly", placeholder: "placeholder", virtualScroll: "virtualScroll", beforeInput: "beforeInput", blur: "blur", click: "click", compositionEnd: "compositionEnd", compositionUpdate: "compositionUpdate", compositionStart: "compositionStart", copy: "copy", cut: "cut", dragOver: "dragOver", dragStart: "dragStart", dragEnd: "dragEnd", drop: "drop", focus: "focus", keydown: "keydown", paste: "paste", spellCheck: "spellCheck", autoCorrect: "autoCorrect", autoCapitalize: "autoCapitalize" }, host: { properties: { "attr.contenteditable": "readonly ? undefined : true", "attr.role": "readonly ? undefined : 'textbox'", "attr.spellCheck": "!hasBeforeInputSupport ? false : spellCheck", "attr.autoCorrect": "!hasBeforeInputSupport ? 'false' : autoCorrect", "attr.autoCapitalize": "!hasBeforeInputSupport ? 'false' : autoCapitalize", "attr.data-slate-editor": "this.dataSlateEditor", "attr.data-slate-node": "this.dataSlateNode", "attr.data-gramm": "this.dataGramm" }, classAttribute: "slate-editable-container" }, providers: [
3561
3849
  {
3562
3850
  provide: NG_VALUE_ACCESSOR,
3563
3851
  useExisting: forwardRef(() => SlateEditable),
@@ -3610,6 +3898,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
3610
3898
  type: Input
3611
3899
  }], placeholder: [{
3612
3900
  type: Input
3901
+ }], virtualScroll: [{
3902
+ type: Input
3613
3903
  }], beforeInput: [{
3614
3904
  type: Input
3615
3905
  }], blur: [{
@@ -3887,6 +4177,12 @@ class BaseElementComponent extends BaseComponent {
3887
4177
  readonly: this._context.readonly
3888
4178
  };
3889
4179
  }
4180
+ getRealHeight() {
4181
+ const blockCard = getBlockCardByNativeElement(this.nativeElement);
4182
+ const target = blockCard || this.nativeElement;
4183
+ const computedStyle = getComputedStyle(target);
4184
+ return Promise.resolve(target.offsetHeight + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom));
4185
+ }
3890
4186
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: BaseElementComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
3891
4187
  static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "20.3.12", type: BaseElementComponent, isStandalone: true, viewQueries: [{ propertyName: "childrenOutletInstance", first: true, predicate: SlateChildrenOutlet, descendants: true, static: true }], usesInheritance: true, ngImport: i0 }); }
3892
4188
  }
@@ -4041,5 +4337,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
4041
4337
  * Generated bundle index. Do not edit.
4042
4338
  */
4043
4339
 
4044
- export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, 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, PLACEHOLDER_SYMBOL, SLATE_BLOCK_CARD_CLASS_NAME, SlateBlockCard, SlateChildrenOutlet, SlateEditable, SlateErrorCode, SlateFragmentAttributeKey, SlateModule, 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 };
4340
+ 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 };
4045
4341
  //# sourceMappingURL=slate-angular.mjs.map