slate-angular 20.2.0-next.1 → 20.2.0-next.10

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.
@@ -1135,6 +1135,9 @@ const withAngular = (editor, clipboardFormatKey = 'x-slate-fragment') => {
1135
1135
  NODE_TO_KEY.set(node, key);
1136
1136
  }
1137
1137
  };
1138
+ e.selectAll = () => {
1139
+ Transforms.select(e, []);
1140
+ };
1138
1141
  return e;
1139
1142
  };
1140
1143
 
@@ -1739,6 +1742,7 @@ class BaseElementFlavour extends BaseFlavour {
1739
1742
  this.getOutletElement = () => {
1740
1743
  return this.nativeElement.querySelector('.children-outlet');
1741
1744
  };
1745
+ this.stableHeight = null;
1742
1746
  }
1743
1747
  get element() {
1744
1748
  return this._context && this._context.element;
@@ -1824,11 +1828,21 @@ class BaseElementFlavour extends BaseFlavour {
1824
1828
  readonly: this._context.readonly
1825
1829
  };
1826
1830
  }
1831
+ isStableHeight() {
1832
+ return false;
1833
+ }
1827
1834
  getRealHeight() {
1835
+ if (this.isStableHeight() && this.stableHeight !== null) {
1836
+ return this.stableHeight;
1837
+ }
1828
1838
  const blockCard = getBlockCardByNativeElement(this.nativeElement);
1829
1839
  const target = blockCard || this.nativeElement;
1830
1840
  const computedStyle = getComputedStyle(target);
1831
- return Promise.resolve(target.offsetHeight + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom));
1841
+ const height = Math.ceil(target.getBoundingClientRect().height) + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom);
1842
+ if (this.isStableHeight()) {
1843
+ this.stableHeight = height;
1844
+ }
1845
+ return height;
1832
1846
  }
1833
1847
  }
1834
1848
 
@@ -2553,24 +2567,244 @@ function executeAfterViewInit(editor) {
2553
2567
  clearAfterViewInitQueue(editor);
2554
2568
  }
2555
2569
 
2570
+ class VirtualScrollDebugOverlay {
2571
+ constructor(doc) {
2572
+ this.doc = doc;
2573
+ this.originalConsoleLog = console.log.bind(console);
2574
+ this.originalConsoleWarn = console.warn.bind(console);
2575
+ this.dragOffsetX = 0;
2576
+ this.dragOffsetY = 0;
2577
+ this.isDragging = false;
2578
+ this.onDragging = (event) => {
2579
+ if (!this.isDragging || !this.container) {
2580
+ return;
2581
+ }
2582
+ const nextLeft = event.clientX - this.dragOffsetX;
2583
+ const nextTop = event.clientY - this.dragOffsetY;
2584
+ this.container.style.left = `${nextLeft}px`;
2585
+ this.container.style.top = `${nextTop}px`;
2586
+ this.container.style.right = 'auto';
2587
+ this.container.style.bottom = 'auto';
2588
+ };
2589
+ this.onDragEnd = () => {
2590
+ if (!this.isDragging) {
2591
+ return;
2592
+ }
2593
+ this.isDragging = false;
2594
+ this.doc.removeEventListener('mousemove', this.onDragging);
2595
+ this.doc.removeEventListener('mouseup', this.onDragEnd);
2596
+ if (this.container) {
2597
+ this.container.style.transition = '';
2598
+ }
2599
+ };
2600
+ }
2601
+ init() {
2602
+ if (!this.container) {
2603
+ this.createContainer();
2604
+ }
2605
+ }
2606
+ log(type, ...args) {
2607
+ this.init();
2608
+ if (type === 'warn') {
2609
+ this.originalConsoleWarn(...args);
2610
+ }
2611
+ else {
2612
+ this.originalConsoleLog(...args);
2613
+ }
2614
+ this.appendLog(type, ...args);
2615
+ }
2616
+ dispose() {
2617
+ this.container?.remove();
2618
+ this.container = undefined;
2619
+ this.logList = undefined;
2620
+ this.selectorInput = undefined;
2621
+ this.distanceInput = undefined;
2622
+ this.doc.removeEventListener('mousemove', this.onDragging);
2623
+ this.doc.removeEventListener('mouseup', this.onDragEnd);
2624
+ this.isDragging = false;
2625
+ }
2626
+ createContainer() {
2627
+ const doc = this.doc;
2628
+ const container = doc.createElement('div');
2629
+ container.style.position = 'fixed';
2630
+ container.style.left = '16px';
2631
+ container.style.top = '16px';
2632
+ container.style.right = 'auto';
2633
+ container.style.bottom = 'auto';
2634
+ container.style.width = '360px';
2635
+ container.style.maxHeight = '70vh';
2636
+ container.style.padding = '12px';
2637
+ container.style.boxSizing = 'border-box';
2638
+ container.style.background = 'rgba(17, 24, 39, 0.95)';
2639
+ container.style.color = '#e5e7eb';
2640
+ container.style.fontSize = '12px';
2641
+ container.style.fontFamily = 'Menlo, Consolas, monospace';
2642
+ container.style.border = '1px solid #1f2937';
2643
+ container.style.borderRadius = '10px';
2644
+ container.style.boxShadow = '0 10px 30px rgba(0, 0, 0, 0.35)';
2645
+ container.style.zIndex = '9999';
2646
+ container.style.display = 'flex';
2647
+ container.style.flexDirection = 'column';
2648
+ container.style.gap = '10px';
2649
+ const header = doc.createElement('div');
2650
+ header.style.display = 'flex';
2651
+ header.style.alignItems = 'center';
2652
+ header.style.justifyContent = 'space-between';
2653
+ header.style.cursor = 'move';
2654
+ header.addEventListener('mousedown', event => {
2655
+ if (!this.container) {
2656
+ return;
2657
+ }
2658
+ const rect = this.container.getBoundingClientRect();
2659
+ this.isDragging = true;
2660
+ this.dragOffsetX = event.clientX - rect.left;
2661
+ this.dragOffsetY = event.clientY - rect.top;
2662
+ this.container.style.transition = 'none';
2663
+ this.doc.addEventListener('mousemove', this.onDragging);
2664
+ this.doc.addEventListener('mouseup', this.onDragEnd);
2665
+ event.preventDefault();
2666
+ });
2667
+ const title = doc.createElement('div');
2668
+ title.textContent = 'Virtual Scroll Debug';
2669
+ title.style.fontWeight = '600';
2670
+ title.style.letterSpacing = '0.3px';
2671
+ const clearButton = doc.createElement('button');
2672
+ clearButton.type = 'button';
2673
+ clearButton.textContent = '清除日志';
2674
+ clearButton.style.background = '#374151';
2675
+ clearButton.style.color = '#e5e7eb';
2676
+ clearButton.style.border = '1px solid #4b5563';
2677
+ clearButton.style.borderRadius = '6px';
2678
+ clearButton.style.padding = '4px 10px';
2679
+ clearButton.style.cursor = 'pointer';
2680
+ clearButton.addEventListener('click', () => {
2681
+ if (this.logList) {
2682
+ this.logList.innerHTML = '';
2683
+ }
2684
+ });
2685
+ header.appendChild(title);
2686
+ header.appendChild(clearButton);
2687
+ const scrollForm = doc.createElement('div');
2688
+ scrollForm.style.display = 'grid';
2689
+ scrollForm.style.gridTemplateColumns = '1fr 90px 70px';
2690
+ scrollForm.style.gap = '6px';
2691
+ scrollForm.style.alignItems = 'center';
2692
+ const selectorInput = doc.createElement('input');
2693
+ selectorInput.placeholder = '滚动容器 selector';
2694
+ selectorInput.style.padding = '6px 8px';
2695
+ selectorInput.style.borderRadius = '6px';
2696
+ selectorInput.style.border = '1px solid #4b5563';
2697
+ selectorInput.style.background = '#111827';
2698
+ selectorInput.style.color = '#e5e7eb';
2699
+ selectorInput.autocomplete = 'off';
2700
+ const distanceInput = doc.createElement('input');
2701
+ distanceInput.placeholder = '滚动距离(px)';
2702
+ distanceInput.type = 'number';
2703
+ distanceInput.style.padding = '6px 8px';
2704
+ distanceInput.style.borderRadius = '6px';
2705
+ distanceInput.style.border = '1px solid #4b5563';
2706
+ distanceInput.style.background = '#111827';
2707
+ distanceInput.style.color = '#e5e7eb';
2708
+ const scrollButton = doc.createElement('button');
2709
+ scrollButton.type = 'button';
2710
+ scrollButton.textContent = '滚动';
2711
+ scrollButton.style.background = '#10b981';
2712
+ scrollButton.style.color = '#0b1c15';
2713
+ scrollButton.style.border = 'none';
2714
+ scrollButton.style.borderRadius = '6px';
2715
+ scrollButton.style.padding = '6px 10px';
2716
+ scrollButton.style.cursor = 'pointer';
2717
+ scrollButton.addEventListener('click', () => {
2718
+ const selector = selectorInput.value.trim();
2719
+ const distanceValue = Number(distanceInput.value ?? 0);
2720
+ const distance = Number.isFinite(distanceValue) ? distanceValue : 0;
2721
+ if (!selector) {
2722
+ this.log('warn', '请先填写滚动容器 selector');
2723
+ return;
2724
+ }
2725
+ const target = doc.querySelector(selector);
2726
+ if (!target) {
2727
+ this.log('warn', `未找到滚动容器: ${selector}`);
2728
+ return;
2729
+ }
2730
+ if (typeof target.scrollTo === 'function') {
2731
+ target.scrollTo({ top: distance, behavior: 'auto' });
2732
+ }
2733
+ else if (Object.prototype.hasOwnProperty.call(target, 'scrollTop')) {
2734
+ target.scrollTop = distance;
2735
+ }
2736
+ else {
2737
+ this.log('warn', '目标元素不支持滚动:', selector);
2738
+ return;
2739
+ }
2740
+ this.log('log', `已将 ${selector} 滚动到`, distance);
2741
+ });
2742
+ scrollForm.appendChild(selectorInput);
2743
+ scrollForm.appendChild(distanceInput);
2744
+ scrollForm.appendChild(scrollButton);
2745
+ const logList = doc.createElement('div');
2746
+ logList.style.height = '260px';
2747
+ logList.style.overflowY = 'auto';
2748
+ logList.style.background = '#0b1220';
2749
+ logList.style.border = '1px solid #1f2937';
2750
+ logList.style.borderRadius = '8px';
2751
+ logList.style.padding = '8px';
2752
+ logList.style.display = 'flex';
2753
+ logList.style.flexDirection = 'column';
2754
+ logList.style.gap = '6px';
2755
+ container.appendChild(header);
2756
+ container.appendChild(scrollForm);
2757
+ container.appendChild(logList);
2758
+ doc.body.appendChild(container);
2759
+ this.container = container;
2760
+ this.logList = logList;
2761
+ this.selectorInput = selectorInput;
2762
+ this.distanceInput = distanceInput;
2763
+ }
2764
+ appendLog(type, ...args) {
2765
+ if (!this.logList) {
2766
+ return;
2767
+ }
2768
+ const item = this.doc.createElement('div');
2769
+ item.style.display = 'flex';
2770
+ item.style.gap = '6px';
2771
+ item.style.alignItems = 'flex-start';
2772
+ item.style.wordBreak = 'break-all';
2773
+ item.style.color = type === 'warn' ? '#fbbf24' : '#9ca3af';
2774
+ const time = this.doc.createElement('span');
2775
+ time.textContent = new Date().toLocaleTimeString();
2776
+ time.style.color = '#6b7280';
2777
+ time.style.flexShrink = '0';
2778
+ time.style.width = '72px';
2779
+ const text = this.doc.createElement('span');
2780
+ text.textContent = `[${type}] ${args.map(arg => this.formatValue(arg)).join(' ')}`;
2781
+ item.appendChild(time);
2782
+ item.appendChild(text);
2783
+ this.logList.appendChild(item);
2784
+ this.logList.scrollTop = this.logList.scrollHeight;
2785
+ }
2786
+ formatValue(value) {
2787
+ if (typeof value === 'string') {
2788
+ return value;
2789
+ }
2790
+ try {
2791
+ return JSON.stringify(value);
2792
+ }
2793
+ catch (error) {
2794
+ return String(value);
2795
+ }
2796
+ }
2797
+ }
2798
+
2556
2799
  const JUST_NOW_UPDATED_VIRTUAL_VIEW = new WeakMap();
2800
+ const ELEMENT_KEY_TO_HEIGHTS = new WeakMap();
2557
2801
  // not correctly clipboardData on beforeinput
2558
2802
  const forceOnDOMPaste = IS_SAFARI;
2803
+ const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
2559
2804
  class SlateEditable {
2560
2805
  set virtualScroll(config) {
2561
2806
  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
- });
2807
+ this.doVirtualScroll();
2574
2808
  }
2575
2809
  get hasBeforeInputSupport() {
2576
2810
  return HAS_BEFORE_INPUT_SUPPORT;
@@ -2623,7 +2857,8 @@ class SlateEditable {
2623
2857
  this.renderedChildren = [];
2624
2858
  this.virtualVisibleIndexes = new Set();
2625
2859
  this.measuredHeights = new Map();
2626
- this.measurePending = false;
2860
+ // the height from scroll container top to editor top height element
2861
+ this.businessHeight = 0;
2627
2862
  this.virtualScrollInitialized = false;
2628
2863
  }
2629
2864
  ngOnInit() {
@@ -2635,6 +2870,7 @@ class SlateEditable {
2635
2870
  NODE_TO_ELEMENT.set(this.editor, this.elementRef.nativeElement);
2636
2871
  ELEMENT_TO_NODE.set(this.elementRef.nativeElement, this.editor);
2637
2872
  IS_READ_ONLY.set(this.editor, this.readonly);
2873
+ ELEMENT_KEY_TO_HEIGHTS.set(this.editor, this.measuredHeights);
2638
2874
  EDITOR_TO_ON_CHANGE.set(this.editor, () => {
2639
2875
  this.ngZone.run(() => {
2640
2876
  this.onChange();
@@ -2722,7 +2958,25 @@ class SlateEditable {
2722
2958
  }
2723
2959
  toNativeSelection() {
2724
2960
  try {
2725
- const { selection } = this.editor;
2961
+ let { selection } = this.editor;
2962
+ if (this.virtualConfig?.enabled && selection) {
2963
+ const indics = Array.from(this.virtualVisibleIndexes.values());
2964
+ if (indics.length > 0) {
2965
+ const currentVisibleRange = {
2966
+ anchor: Editor.start(this.editor, [indics[0]]),
2967
+ focus: Editor.end(this.editor, [indics[indics.length - 1]])
2968
+ };
2969
+ const [start, end] = Range.edges(selection);
2970
+ const forwardSelection = { anchor: start, focus: end };
2971
+ const intersectedSelection = Range.intersection(forwardSelection, currentVisibleRange);
2972
+ if (!intersectedSelection || !Range.equals(intersectedSelection, forwardSelection)) {
2973
+ selection = intersectedSelection;
2974
+ if (isDebug) {
2975
+ this.debugLog('log', `selection is not in visible range, selection: ${JSON.stringify(selection)}, intersectedSelection: ${JSON.stringify(intersectedSelection)}`);
2976
+ }
2977
+ }
2978
+ }
2979
+ }
2726
2980
  const root = AngularEditor.findDocumentOrShadowRoot(this.editor);
2727
2981
  const { activeElement } = root;
2728
2982
  const domSelection = root.getSelection();
@@ -2810,10 +3064,18 @@ class SlateEditable {
2810
3064
  ngDoCheck() { }
2811
3065
  forceRender() {
2812
3066
  this.updateContext();
2813
- const virtualView = this.refreshVirtualView();
2814
- this.applyVirtualView(virtualView);
2815
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2816
- this.scheduleMeasureVisibleHeights();
3067
+ let visibleIndexes = Array.from(this.virtualVisibleIndexes);
3068
+ const isFirstRender = visibleIndexes.length === 0;
3069
+ if (isFirstRender) {
3070
+ const virtualView = this.refreshVirtualView();
3071
+ this.applyVirtualView(virtualView);
3072
+ visibleIndexes = Array.from(this.virtualVisibleIndexes);
3073
+ }
3074
+ else {
3075
+ this.renderedChildren = visibleIndexes.map(index => this.editor.children[index]);
3076
+ }
3077
+ this.listRender.update(this.renderedChildren, this.editor, this.context);
3078
+ this.remeasureHeightByIndics(visibleIndexes);
2817
3079
  // repair collaborative editing when Chinese input is interrupted by other users' cursors
2818
3080
  // when the DOMElement where the selection is located is removed
2819
3081
  // the compositionupdate and compositionend events will no longer be fired
@@ -2929,13 +3191,29 @@ class SlateEditable {
2929
3191
  this.virtualScrollInitialized = true;
2930
3192
  this.virtualTopHeightElement = document.createElement('div');
2931
3193
  this.virtualTopHeightElement.classList.add('virtual-top-height');
3194
+ this.virtualTopHeightElement.contentEditable = 'false';
2932
3195
  this.virtualBottomHeightElement = document.createElement('div');
2933
3196
  this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
3197
+ this.virtualBottomHeightElement.contentEditable = 'false';
2934
3198
  this.virtualCenterOutlet = document.createElement('div');
2935
3199
  this.virtualCenterOutlet.classList.add('virtual-center-outlet');
2936
3200
  this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
2937
3201
  this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
2938
3202
  this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
3203
+ this.businessHeight = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3204
+ let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect()?.width ?? 0;
3205
+ this.editorResizeObserver = new ResizeObserver(entries => {
3206
+ if (entries.length > 0 && entries[0].contentRect.width !== editorResizeObserverRectWidth) {
3207
+ editorResizeObserverRectWidth = entries[0].contentRect.width;
3208
+ this.remeasureHeightByIndics(Array.from(this.virtualVisibleIndexes));
3209
+ }
3210
+ });
3211
+ this.editorResizeObserver.observe(this.elementRef.nativeElement);
3212
+ if (isDebug) {
3213
+ const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3214
+ this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3215
+ this.debugOverlay.init();
3216
+ }
2939
3217
  }
2940
3218
  }
2941
3219
  changeVirtualHeight(topHeight, bottomHeight) {
@@ -2945,6 +3223,41 @@ class SlateEditable {
2945
3223
  this.virtualTopHeightElement.style.height = `${topHeight}px`;
2946
3224
  this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
2947
3225
  }
3226
+ debugLog(type, ...args) {
3227
+ if (!this.debugOverlay) {
3228
+ const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3229
+ this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3230
+ }
3231
+ this.debugOverlay.log(type, ...args);
3232
+ }
3233
+ doVirtualScroll() {
3234
+ this.refreshVirtualViewAnimId && cancelAnimationFrame(this.refreshVirtualViewAnimId);
3235
+ this.refreshVirtualViewAnimId = requestAnimationFrame(() => {
3236
+ let virtualView = this.refreshVirtualView();
3237
+ let diff = this.diffVirtualView(virtualView);
3238
+ if (!diff.isDiff) {
3239
+ return;
3240
+ }
3241
+ if (diff.isMissingTop) {
3242
+ const result = this.remeasureHeightByIndics(diff.diffTopRenderedIndexes);
3243
+ if (result) {
3244
+ virtualView = this.refreshVirtualView();
3245
+ diff = this.diffVirtualView(virtualView, 'second');
3246
+ if (!diff.isDiff) {
3247
+ return;
3248
+ }
3249
+ }
3250
+ }
3251
+ this.applyVirtualView(virtualView);
3252
+ if (this.listRender.initialized) {
3253
+ this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
3254
+ if (!AngularEditor.isReadOnly(this.editor) && this.editor.selection) {
3255
+ this.toNativeSelection();
3256
+ }
3257
+ }
3258
+ this.scheduleMeasureVisibleHeights();
3259
+ });
3260
+ }
2948
3261
  refreshVirtualView() {
2949
3262
  const children = (this.editor.children || []);
2950
3263
  if (!children.length || !this.shouldUseVirtual()) {
@@ -2956,10 +3269,9 @@ class SlateEditable {
2956
3269
  heights: []
2957
3270
  };
2958
3271
  }
2959
- const scrollTop = this.virtualConfig.scrollTop ?? 0;
3272
+ const scrollTop = this.virtualConfig.scrollTop;
2960
3273
  const viewportHeight = this.virtualConfig.viewportHeight ?? 0;
2961
3274
  if (!viewportHeight) {
2962
- // 已经启用虚拟滚动,但可视区域高度还未获取到,先置空不渲染
2963
3275
  return {
2964
3276
  renderedChildren: [],
2965
3277
  visibleIndexes: new Set(),
@@ -2968,36 +3280,41 @@ class SlateEditable {
2968
3280
  heights: []
2969
3281
  };
2970
3282
  }
2971
- const bufferCount = this.virtualConfig.bufferCount ?? VIRTUAL_SCROLL_DEFAULT_BUFFER_COUNT;
3283
+ const elementLength = children.length;
3284
+ const adjustedScrollTop = Math.max(0, scrollTop - this.businessHeight);
2972
3285
  const heights = children.map((_, idx) => this.getBlockHeight(idx));
2973
3286
  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;
3287
+ const totalHeight = accumulatedHeights[elementLength];
3288
+ const maxScrollTop = Math.max(0, totalHeight - viewportHeight);
3289
+ const limitedScrollTop = Math.min(adjustedScrollTop, maxScrollTop);
3290
+ const viewBottom = limitedScrollTop + viewportHeight + this.businessHeight;
3291
+ let accumulatedOffset = 0;
3292
+ let visibleStartIndex = -1;
2984
3293
  const visible = [];
2985
3294
  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);
3295
+ for (let i = 0; i < elementLength && accumulatedOffset < viewBottom; i++) {
3296
+ const currentHeight = heights[i];
3297
+ const nextOffset = accumulatedOffset + currentHeight;
3298
+ // 可视区域有交集,加入渲染
3299
+ if (nextOffset > limitedScrollTop && accumulatedOffset < viewBottom) {
3300
+ if (visibleStartIndex === -1)
3301
+ visibleStartIndex = i; // 第一个相交起始位置
3302
+ visible.push(children[i]);
3303
+ visibleIndexes.push(i);
3304
+ }
3305
+ accumulatedOffset = nextOffset;
3306
+ }
3307
+ if (visibleStartIndex === -1 && elementLength) {
3308
+ visibleStartIndex = elementLength - 1;
3309
+ visible.push(children[visibleStartIndex]);
3310
+ visibleIndexes.push(visibleStartIndex);
3311
+ }
3312
+ const visibleEndIndex = visibleStartIndex === -1 ? elementLength - 1 : (visibleIndexes[visibleIndexes.length - 1] ?? visibleStartIndex);
3313
+ const top = visibleStartIndex === -1 ? 0 : accumulatedHeights[visibleStartIndex];
3314
+ const bottom = totalHeight - accumulatedHeights[visibleEndIndex + 1];
2998
3315
  return {
2999
- renderedChildren,
3000
- visibleIndexes: visibleIndexesSet,
3316
+ renderedChildren: visible.length ? visible : children,
3317
+ visibleIndexes: new Set(visibleIndexes),
3001
3318
  top,
3002
3319
  bottom,
3003
3320
  heights
@@ -3008,86 +3325,106 @@ class SlateEditable {
3008
3325
  this.changeVirtualHeight(virtualView.top, virtualView.bottom);
3009
3326
  this.virtualVisibleIndexes = virtualView.visibleIndexes;
3010
3327
  }
3011
- diffVirtualView(virtualView) {
3328
+ diffVirtualView(virtualView, stage = 'first') {
3012
3329
  if (!this.renderedChildren.length) {
3013
- return true;
3330
+ return {
3331
+ isDiff: true,
3332
+ diffTopRenderedIndexes: [],
3333
+ diffBottomRenderedIndexes: []
3334
+ };
3014
3335
  }
3015
3336
  const oldVisibleIndexes = [...this.virtualVisibleIndexes];
3016
3337
  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
- }
3338
+ const firstNewIndex = newVisibleIndexes[0];
3339
+ const lastNewIndex = newVisibleIndexes[newVisibleIndexes.length - 1];
3340
+ const firstOldIndex = oldVisibleIndexes[0];
3341
+ const lastOldIndex = oldVisibleIndexes[oldVisibleIndexes.length - 1];
3342
+ if (firstNewIndex !== firstOldIndex || lastNewIndex !== lastOldIndex) {
3343
+ const diffTopRenderedIndexes = [];
3344
+ const diffBottomRenderedIndexes = [];
3345
+ const isMissingTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
3346
+ const isAddedTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
3347
+ const isMissingBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
3348
+ const isAddedBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
3349
+ if (isMissingTop || isAddedBottom) {
3350
+ // 向下
3351
+ for (let index = 0; index < oldVisibleIndexes.length; index++) {
3352
+ const element = oldVisibleIndexes[index];
3353
+ if (!newVisibleIndexes.includes(element)) {
3354
+ diffTopRenderedIndexes.push(element);
3034
3355
  }
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
- }
3356
+ else {
3357
+ break;
3043
3358
  }
3044
3359
  }
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
- }
3360
+ for (let index = newVisibleIndexes.length - 1; index >= 0; index--) {
3361
+ const element = newVisibleIndexes[index];
3362
+ if (!oldVisibleIndexes.includes(element)) {
3363
+ diffBottomRenderedIndexes.push(element);
3056
3364
  }
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
- }
3365
+ else {
3366
+ break;
3367
+ }
3368
+ }
3369
+ }
3370
+ else if (isAddedTop || isMissingBottom) {
3371
+ // 向上
3372
+ for (let index = 0; index < newVisibleIndexes.length; index++) {
3373
+ const element = newVisibleIndexes[index];
3374
+ if (!oldVisibleIndexes.includes(element)) {
3375
+ diffTopRenderedIndexes.push(element);
3376
+ }
3377
+ else {
3378
+ break;
3065
3379
  }
3066
3380
  }
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]));
3381
+ for (let index = oldVisibleIndexes.length - 1; index >= 0; index--) {
3382
+ const element = oldVisibleIndexes[index];
3383
+ if (!newVisibleIndexes.includes(element)) {
3384
+ diffBottomRenderedIndexes.push(element);
3385
+ }
3386
+ else {
3387
+ break;
3388
+ }
3389
+ }
3390
+ }
3391
+ if (isDebug) {
3392
+ this.debugLog('log', `====== diffVirtualView stage: ${stage} ======`);
3393
+ this.debugLog('log', 'oldVisibleIndexes:', oldVisibleIndexes);
3394
+ this.debugLog('log', 'newVisibleIndexes:', newVisibleIndexes);
3395
+ this.debugLog('log', 'diffTopRenderedIndexes:', isMissingTop ? '-' : isAddedTop ? '+' : '-', diffTopRenderedIndexes, diffTopRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3396
+ this.debugLog('log', 'diffBottomRenderedIndexes:', isAddedBottom ? '+' : isMissingBottom ? '-' : '+', diffBottomRenderedIndexes, diffBottomRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3072
3397
  const needTop = virtualView.heights.slice(0, newVisibleIndexes[0]).reduce((acc, height) => acc + height, 0);
3073
3398
  const needBottom = virtualView.heights
3074
3399
  .slice(newVisibleIndexes[newVisibleIndexes.length - 1] + 1)
3075
3400
  .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 ===========');
3401
+ this.debugLog('log', 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3402
+ this.debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3403
+ this.debugLog('warn', '=========== Dividing line ===========');
3079
3404
  }
3080
- return true;
3405
+ return {
3406
+ isDiff: true,
3407
+ isMissingTop,
3408
+ isAddedTop,
3409
+ isMissingBottom,
3410
+ isAddedBottom,
3411
+ diffTopRenderedIndexes,
3412
+ diffBottomRenderedIndexes
3413
+ };
3081
3414
  }
3082
- return false;
3415
+ return {
3416
+ isDiff: false,
3417
+ diffTopRenderedIndexes: [],
3418
+ diffBottomRenderedIndexes: []
3419
+ };
3083
3420
  }
3084
- getBlockHeight(index) {
3421
+ getBlockHeight(index, defaultHeight = VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT) {
3085
3422
  const node = this.editor.children[index];
3086
3423
  if (!node) {
3087
- return VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT;
3424
+ return defaultHeight;
3088
3425
  }
3089
3426
  const key = AngularEditor.findKey(this.editor, node);
3090
- return this.measuredHeights.get(key.id) ?? VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT;
3427
+ return this.measuredHeights.get(key.id) ?? defaultHeight;
3091
3428
  }
3092
3429
  buildAccumulatedHeight(heights) {
3093
3430
  const accumulatedHeights = new Array(heights.length + 1).fill(0);
@@ -3097,32 +3434,13 @@ class SlateEditable {
3097
3434
  }
3098
3435
  return accumulatedHeights;
3099
3436
  }
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
3437
  scheduleMeasureVisibleHeights() {
3115
3438
  if (!this.shouldUseVirtual()) {
3116
3439
  return;
3117
3440
  }
3118
- if (this.measurePending) {
3119
- return;
3120
- }
3121
- this.measurePending = true;
3122
3441
  this.measureVisibleHeightsAnimId && cancelAnimationFrame(this.measureVisibleHeightsAnimId);
3123
3442
  this.measureVisibleHeightsAnimId = requestAnimationFrame(() => {
3124
3443
  this.measureVisibleHeights();
3125
- this.measurePending = false;
3126
3444
  });
3127
3445
  }
3128
3446
  measureVisibleHeights() {
@@ -3133,7 +3451,7 @@ class SlateEditable {
3133
3451
  return;
3134
3452
  }
3135
3453
  const key = AngularEditor.findKey(this.editor, node);
3136
- // 跳过已测过的块
3454
+ // 跳过已测过的块,除非强制测量
3137
3455
  if (this.measuredHeights.has(key.id)) {
3138
3456
  return;
3139
3457
  }
@@ -3141,11 +3459,55 @@ class SlateEditable {
3141
3459
  if (!view) {
3142
3460
  return;
3143
3461
  }
3144
- view.getRealHeight()?.then(height => {
3145
- this.measuredHeights.set(key.id, height);
3146
- });
3462
+ const ret = view.getRealHeight();
3463
+ if (ret instanceof Promise) {
3464
+ ret.then(height => {
3465
+ this.measuredHeights.set(key.id, height);
3466
+ });
3467
+ }
3468
+ else {
3469
+ this.measuredHeights.set(key.id, ret);
3470
+ }
3147
3471
  });
3148
3472
  }
3473
+ remeasureHeightByIndics(indics) {
3474
+ const children = (this.editor.children || []);
3475
+ let isHeightChanged = false;
3476
+ indics.forEach(index => {
3477
+ const node = children[index];
3478
+ if (!node) {
3479
+ return;
3480
+ }
3481
+ const key = AngularEditor.findKey(this.editor, node);
3482
+ const view = ELEMENT_TO_COMPONENT.get(node);
3483
+ if (!view) {
3484
+ return;
3485
+ }
3486
+ const prevHeight = this.measuredHeights.get(key.id);
3487
+ const ret = view.getRealHeight();
3488
+ if (ret instanceof Promise) {
3489
+ ret.then(height => {
3490
+ if (height !== prevHeight) {
3491
+ this.measuredHeights.set(key.id, height);
3492
+ isHeightChanged = true;
3493
+ if (isDebug) {
3494
+ this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${height}`);
3495
+ }
3496
+ }
3497
+ });
3498
+ }
3499
+ else {
3500
+ if (ret !== prevHeight) {
3501
+ this.measuredHeights.set(key.id, ret);
3502
+ isHeightChanged = true;
3503
+ if (isDebug) {
3504
+ this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${ret}`);
3505
+ }
3506
+ }
3507
+ }
3508
+ });
3509
+ return isHeightChanged;
3510
+ }
3149
3511
  //#region event proxy
3150
3512
  addEventListener(eventName, listener, target = this.elementRef.nativeElement) {
3151
3513
  this.manualListeners.push(this.renderer2.listen(target, eventName, (event) => {
@@ -3670,6 +4032,11 @@ class SlateEditable {
3670
4032
  Transforms.move(editor, { unit: 'word', reverse: isRTL });
3671
4033
  return;
3672
4034
  }
4035
+ if (isKeyHotkey('mod+a', event)) {
4036
+ this.editor.selectAll();
4037
+ event.preventDefault();
4038
+ return;
4039
+ }
3673
4040
  // COMPAT: Certain browsers don't support the `beforeinput` event, so we
3674
4041
  // fall back to guessing at the input intention for hotkeys.
3675
4042
  // COMPAT: In iOS, some of these hotkeys are handled in the
@@ -3837,6 +4204,9 @@ class SlateEditable {
3837
4204
  }
3838
4205
  //#endregion
3839
4206
  ngOnDestroy() {
4207
+ this.editorResizeObserver?.disconnect();
4208
+ this.debugOverlay?.dispose();
4209
+ this.debugOverlay = undefined;
3840
4210
  NODE_TO_ELEMENT.delete(this.editor);
3841
4211
  this.manualListeners.forEach(manualListener => {
3842
4212
  manualListener();
@@ -4096,6 +4466,7 @@ class BaseElementComponent extends BaseComponent {
4096
4466
  }
4097
4467
  return null;
4098
4468
  };
4469
+ this.stableHeight = null;
4099
4470
  }
4100
4471
  get element() {
4101
4472
  return this._context && this._context.element;
@@ -4177,11 +4548,21 @@ class BaseElementComponent extends BaseComponent {
4177
4548
  readonly: this._context.readonly
4178
4549
  };
4179
4550
  }
4551
+ isStableHeight() {
4552
+ return false;
4553
+ }
4180
4554
  getRealHeight() {
4555
+ if (this.isStableHeight() && this.stableHeight !== null) {
4556
+ return this.stableHeight;
4557
+ }
4181
4558
  const blockCard = getBlockCardByNativeElement(this.nativeElement);
4182
4559
  const target = blockCard || this.nativeElement;
4183
4560
  const computedStyle = getComputedStyle(target);
4184
- return Promise.resolve(target.offsetHeight + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom));
4561
+ const height = Math.ceil(target.getBoundingClientRect().height) + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom);
4562
+ if (this.isStableHeight()) {
4563
+ this.stableHeight = height;
4564
+ }
4565
+ return height;
4185
4566
  }
4186
4567
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: BaseElementComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
4187
4568
  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 }); }
@@ -4337,5 +4718,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
4337
4718
  * Generated bundle index. Do not edit.
4338
4719
  */
4339
4720
 
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 };
4721
+ export { AngularEditor, BaseComponent, BaseElementComponent, BaseElementFlavour, BaseFlavour, BaseLeafComponent, BaseLeafFlavour, BaseTextComponent, BaseTextFlavour, BlockCardRef, DEFAULT_ELEMENT_HEIGHT, DefaultTextFlavour, EDITOR_TO_AFTER_VIEW_INIT_QUEUE, 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_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 };
4341
4722
  //# sourceMappingURL=slate-angular.mjs.map