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

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();
@@ -2812,8 +3066,9 @@ class SlateEditable {
2812
3066
  this.updateContext();
2813
3067
  const virtualView = this.refreshVirtualView();
2814
3068
  this.applyVirtualView(virtualView);
2815
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2816
- this.scheduleMeasureVisibleHeights();
3069
+ const visibleIndexes = Array.from(this.virtualVisibleIndexes);
3070
+ this.listRender.update(this.renderedChildren, this.editor, this.context);
3071
+ this.remeasureHeightByIndics(visibleIndexes);
2817
3072
  // repair collaborative editing when Chinese input is interrupted by other users' cursors
2818
3073
  // when the DOMElement where the selection is located is removed
2819
3074
  // the compositionupdate and compositionend events will no longer be fired
@@ -2929,13 +3184,29 @@ class SlateEditable {
2929
3184
  this.virtualScrollInitialized = true;
2930
3185
  this.virtualTopHeightElement = document.createElement('div');
2931
3186
  this.virtualTopHeightElement.classList.add('virtual-top-height');
3187
+ this.virtualTopHeightElement.contentEditable = 'false';
2932
3188
  this.virtualBottomHeightElement = document.createElement('div');
2933
3189
  this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
3190
+ this.virtualBottomHeightElement.contentEditable = 'false';
2934
3191
  this.virtualCenterOutlet = document.createElement('div');
2935
3192
  this.virtualCenterOutlet.classList.add('virtual-center-outlet');
2936
3193
  this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
2937
3194
  this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
2938
3195
  this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
3196
+ this.businessHeight = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3197
+ let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect()?.width ?? 0;
3198
+ this.editorResizeObserver = new ResizeObserver(entries => {
3199
+ if (entries.length > 0 && entries[0].contentRect.width !== editorResizeObserverRectWidth) {
3200
+ editorResizeObserverRectWidth = entries[0].contentRect.width;
3201
+ this.remeasureHeightByIndics(Array.from(this.virtualVisibleIndexes));
3202
+ }
3203
+ });
3204
+ this.editorResizeObserver.observe(this.elementRef.nativeElement);
3205
+ if (isDebug) {
3206
+ const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3207
+ this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3208
+ this.debugOverlay.init();
3209
+ }
2939
3210
  }
2940
3211
  }
2941
3212
  changeVirtualHeight(topHeight, bottomHeight) {
@@ -2945,6 +3216,41 @@ class SlateEditable {
2945
3216
  this.virtualTopHeightElement.style.height = `${topHeight}px`;
2946
3217
  this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
2947
3218
  }
3219
+ debugLog(type, ...args) {
3220
+ if (!this.debugOverlay) {
3221
+ const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3222
+ this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3223
+ }
3224
+ this.debugOverlay.log(type, ...args);
3225
+ }
3226
+ doVirtualScroll() {
3227
+ this.refreshVirtualViewAnimId && cancelAnimationFrame(this.refreshVirtualViewAnimId);
3228
+ this.refreshVirtualViewAnimId = requestAnimationFrame(() => {
3229
+ let virtualView = this.refreshVirtualView();
3230
+ let diff = this.diffVirtualView(virtualView);
3231
+ if (!diff.isDiff) {
3232
+ return;
3233
+ }
3234
+ if (diff.isMissingTop) {
3235
+ const result = this.remeasureHeightByIndics(diff.diffTopRenderedIndexes);
3236
+ if (result) {
3237
+ virtualView = this.refreshVirtualView();
3238
+ diff = this.diffVirtualView(virtualView, 'second');
3239
+ if (!diff.isDiff) {
3240
+ return;
3241
+ }
3242
+ }
3243
+ }
3244
+ this.applyVirtualView(virtualView);
3245
+ if (this.listRender.initialized) {
3246
+ this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
3247
+ if (!AngularEditor.isReadOnly(this.editor) && this.editor.selection) {
3248
+ this.toNativeSelection();
3249
+ }
3250
+ }
3251
+ this.scheduleMeasureVisibleHeights();
3252
+ });
3253
+ }
2948
3254
  refreshVirtualView() {
2949
3255
  const children = (this.editor.children || []);
2950
3256
  if (!children.length || !this.shouldUseVirtual()) {
@@ -2956,10 +3262,9 @@ class SlateEditable {
2956
3262
  heights: []
2957
3263
  };
2958
3264
  }
2959
- const scrollTop = this.virtualConfig.scrollTop ?? 0;
3265
+ const scrollTop = this.virtualConfig.scrollTop;
2960
3266
  const viewportHeight = this.virtualConfig.viewportHeight ?? 0;
2961
3267
  if (!viewportHeight) {
2962
- // 已经启用虚拟滚动,但可视区域高度还未获取到,先置空不渲染
2963
3268
  return {
2964
3269
  renderedChildren: [],
2965
3270
  visibleIndexes: new Set(),
@@ -2968,36 +3273,41 @@ class SlateEditable {
2968
3273
  heights: []
2969
3274
  };
2970
3275
  }
2971
- const bufferCount = this.virtualConfig.bufferCount ?? VIRTUAL_SCROLL_DEFAULT_BUFFER_COUNT;
3276
+ const elementLength = children.length;
3277
+ const adjustedScrollTop = Math.max(0, scrollTop - this.businessHeight);
2972
3278
  const heights = children.map((_, idx) => this.getBlockHeight(idx));
2973
3279
  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;
3280
+ const totalHeight = accumulatedHeights[elementLength];
3281
+ const maxScrollTop = Math.max(0, totalHeight - viewportHeight);
3282
+ const limitedScrollTop = Math.min(adjustedScrollTop, maxScrollTop);
3283
+ const viewBottom = limitedScrollTop + viewportHeight + this.businessHeight;
3284
+ let accumulatedOffset = 0;
3285
+ let visibleStartIndex = -1;
2984
3286
  const visible = [];
2985
3287
  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);
3288
+ for (let i = 0; i < elementLength && accumulatedOffset < viewBottom; i++) {
3289
+ const currentHeight = heights[i];
3290
+ const nextOffset = accumulatedOffset + currentHeight;
3291
+ // 可视区域有交集,加入渲染
3292
+ if (nextOffset > limitedScrollTop && accumulatedOffset < viewBottom) {
3293
+ if (visibleStartIndex === -1)
3294
+ visibleStartIndex = i; // 第一个相交起始位置
3295
+ visible.push(children[i]);
3296
+ visibleIndexes.push(i);
3297
+ }
3298
+ accumulatedOffset = nextOffset;
3299
+ }
3300
+ if (visibleStartIndex === -1 && elementLength) {
3301
+ visibleStartIndex = elementLength - 1;
3302
+ visible.push(children[visibleStartIndex]);
3303
+ visibleIndexes.push(visibleStartIndex);
3304
+ }
3305
+ const visibleEndIndex = visibleStartIndex === -1 ? elementLength - 1 : (visibleIndexes[visibleIndexes.length - 1] ?? visibleStartIndex);
3306
+ const top = visibleStartIndex === -1 ? 0 : accumulatedHeights[visibleStartIndex];
3307
+ const bottom = totalHeight - accumulatedHeights[visibleEndIndex + 1];
2998
3308
  return {
2999
- renderedChildren,
3000
- visibleIndexes: visibleIndexesSet,
3309
+ renderedChildren: visible.length ? visible : children,
3310
+ visibleIndexes: new Set(visibleIndexes),
3001
3311
  top,
3002
3312
  bottom,
3003
3313
  heights
@@ -3008,86 +3318,106 @@ class SlateEditable {
3008
3318
  this.changeVirtualHeight(virtualView.top, virtualView.bottom);
3009
3319
  this.virtualVisibleIndexes = virtualView.visibleIndexes;
3010
3320
  }
3011
- diffVirtualView(virtualView) {
3321
+ diffVirtualView(virtualView, stage = 'first') {
3012
3322
  if (!this.renderedChildren.length) {
3013
- return true;
3323
+ return {
3324
+ isDiff: true,
3325
+ diffTopRenderedIndexes: [],
3326
+ diffBottomRenderedIndexes: []
3327
+ };
3014
3328
  }
3015
3329
  const oldVisibleIndexes = [...this.virtualVisibleIndexes];
3016
3330
  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
- }
3331
+ const firstNewIndex = newVisibleIndexes[0];
3332
+ const lastNewIndex = newVisibleIndexes[newVisibleIndexes.length - 1];
3333
+ const firstOldIndex = oldVisibleIndexes[0];
3334
+ const lastOldIndex = oldVisibleIndexes[oldVisibleIndexes.length - 1];
3335
+ if (firstNewIndex !== firstOldIndex || lastNewIndex !== lastOldIndex) {
3336
+ const diffTopRenderedIndexes = [];
3337
+ const diffBottomRenderedIndexes = [];
3338
+ const isMissingTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
3339
+ const isAddedTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
3340
+ const isMissingBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
3341
+ const isAddedBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
3342
+ if (isMissingTop || isAddedBottom) {
3343
+ // 向下
3344
+ for (let index = 0; index < oldVisibleIndexes.length; index++) {
3345
+ const element = oldVisibleIndexes[index];
3346
+ if (!newVisibleIndexes.includes(element)) {
3347
+ diffTopRenderedIndexes.push(element);
3034
3348
  }
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
- }
3349
+ else {
3350
+ break;
3043
3351
  }
3044
3352
  }
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
- }
3353
+ for (let index = newVisibleIndexes.length - 1; index >= 0; index--) {
3354
+ const element = newVisibleIndexes[index];
3355
+ if (!oldVisibleIndexes.includes(element)) {
3356
+ diffBottomRenderedIndexes.push(element);
3056
3357
  }
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
- }
3358
+ else {
3359
+ break;
3360
+ }
3361
+ }
3362
+ }
3363
+ else if (isAddedTop || isMissingBottom) {
3364
+ // 向上
3365
+ for (let index = 0; index < newVisibleIndexes.length; index++) {
3366
+ const element = newVisibleIndexes[index];
3367
+ if (!oldVisibleIndexes.includes(element)) {
3368
+ diffTopRenderedIndexes.push(element);
3369
+ }
3370
+ else {
3371
+ break;
3065
3372
  }
3066
3373
  }
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]));
3374
+ for (let index = oldVisibleIndexes.length - 1; index >= 0; index--) {
3375
+ const element = oldVisibleIndexes[index];
3376
+ if (!newVisibleIndexes.includes(element)) {
3377
+ diffBottomRenderedIndexes.push(element);
3378
+ }
3379
+ else {
3380
+ break;
3381
+ }
3382
+ }
3383
+ }
3384
+ if (isDebug) {
3385
+ this.debugLog('log', `====== diffVirtualView stage: ${stage} ======`);
3386
+ this.debugLog('log', 'oldVisibleIndexes:', oldVisibleIndexes);
3387
+ this.debugLog('log', 'newVisibleIndexes:', newVisibleIndexes);
3388
+ this.debugLog('log', 'diffTopRenderedIndexes:', isMissingTop ? '-' : isAddedTop ? '+' : '-', diffTopRenderedIndexes, diffTopRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3389
+ this.debugLog('log', 'diffBottomRenderedIndexes:', isAddedBottom ? '+' : isMissingBottom ? '-' : '+', diffBottomRenderedIndexes, diffBottomRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3072
3390
  const needTop = virtualView.heights.slice(0, newVisibleIndexes[0]).reduce((acc, height) => acc + height, 0);
3073
3391
  const needBottom = virtualView.heights
3074
3392
  .slice(newVisibleIndexes[newVisibleIndexes.length - 1] + 1)
3075
3393
  .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 ===========');
3394
+ this.debugLog('log', 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3395
+ this.debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3396
+ this.debugLog('warn', '=========== Dividing line ===========');
3079
3397
  }
3080
- return true;
3398
+ return {
3399
+ isDiff: true,
3400
+ isMissingTop,
3401
+ isAddedTop,
3402
+ isMissingBottom,
3403
+ isAddedBottom,
3404
+ diffTopRenderedIndexes,
3405
+ diffBottomRenderedIndexes
3406
+ };
3081
3407
  }
3082
- return false;
3408
+ return {
3409
+ isDiff: false,
3410
+ diffTopRenderedIndexes: [],
3411
+ diffBottomRenderedIndexes: []
3412
+ };
3083
3413
  }
3084
- getBlockHeight(index) {
3414
+ getBlockHeight(index, defaultHeight = VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT) {
3085
3415
  const node = this.editor.children[index];
3086
3416
  if (!node) {
3087
- return VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT;
3417
+ return defaultHeight;
3088
3418
  }
3089
3419
  const key = AngularEditor.findKey(this.editor, node);
3090
- return this.measuredHeights.get(key.id) ?? VIRTUAL_SCROLL_DEFAULT_BLOCK_HEIGHT;
3420
+ return this.measuredHeights.get(key.id) ?? defaultHeight;
3091
3421
  }
3092
3422
  buildAccumulatedHeight(heights) {
3093
3423
  const accumulatedHeights = new Array(heights.length + 1).fill(0);
@@ -3097,32 +3427,13 @@ class SlateEditable {
3097
3427
  }
3098
3428
  return accumulatedHeights;
3099
3429
  }
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
3430
  scheduleMeasureVisibleHeights() {
3115
3431
  if (!this.shouldUseVirtual()) {
3116
3432
  return;
3117
3433
  }
3118
- if (this.measurePending) {
3119
- return;
3120
- }
3121
- this.measurePending = true;
3122
3434
  this.measureVisibleHeightsAnimId && cancelAnimationFrame(this.measureVisibleHeightsAnimId);
3123
3435
  this.measureVisibleHeightsAnimId = requestAnimationFrame(() => {
3124
3436
  this.measureVisibleHeights();
3125
- this.measurePending = false;
3126
3437
  });
3127
3438
  }
3128
3439
  measureVisibleHeights() {
@@ -3133,7 +3444,7 @@ class SlateEditable {
3133
3444
  return;
3134
3445
  }
3135
3446
  const key = AngularEditor.findKey(this.editor, node);
3136
- // 跳过已测过的块
3447
+ // 跳过已测过的块,除非强制测量
3137
3448
  if (this.measuredHeights.has(key.id)) {
3138
3449
  return;
3139
3450
  }
@@ -3141,10 +3452,54 @@ class SlateEditable {
3141
3452
  if (!view) {
3142
3453
  return;
3143
3454
  }
3144
- view.getRealHeight()?.then(height => {
3145
- this.measuredHeights.set(key.id, height);
3146
- });
3455
+ const ret = view.getRealHeight();
3456
+ if (ret instanceof Promise) {
3457
+ ret.then(height => {
3458
+ this.measuredHeights.set(key.id, height);
3459
+ });
3460
+ }
3461
+ else {
3462
+ this.measuredHeights.set(key.id, ret);
3463
+ }
3464
+ });
3465
+ }
3466
+ remeasureHeightByIndics(indics) {
3467
+ const children = (this.editor.children || []);
3468
+ let isHeightChanged = false;
3469
+ indics.forEach(index => {
3470
+ const node = children[index];
3471
+ if (!node) {
3472
+ return;
3473
+ }
3474
+ const key = AngularEditor.findKey(this.editor, node);
3475
+ const view = ELEMENT_TO_COMPONENT.get(node);
3476
+ if (!view) {
3477
+ return;
3478
+ }
3479
+ const prevHeight = this.measuredHeights.get(key.id);
3480
+ const ret = view.getRealHeight();
3481
+ if (ret instanceof Promise) {
3482
+ ret.then(height => {
3483
+ if (height !== prevHeight) {
3484
+ this.measuredHeights.set(key.id, height);
3485
+ isHeightChanged = true;
3486
+ if (isDebug) {
3487
+ this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${height}`);
3488
+ }
3489
+ }
3490
+ });
3491
+ }
3492
+ else {
3493
+ if (ret !== prevHeight) {
3494
+ this.measuredHeights.set(key.id, ret);
3495
+ isHeightChanged = true;
3496
+ if (isDebug) {
3497
+ this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${ret}`);
3498
+ }
3499
+ }
3500
+ }
3147
3501
  });
3502
+ return isHeightChanged;
3148
3503
  }
3149
3504
  //#region event proxy
3150
3505
  addEventListener(eventName, listener, target = this.elementRef.nativeElement) {
@@ -3670,6 +4025,11 @@ class SlateEditable {
3670
4025
  Transforms.move(editor, { unit: 'word', reverse: isRTL });
3671
4026
  return;
3672
4027
  }
4028
+ if (isKeyHotkey('mod+a', event)) {
4029
+ this.editor.selectAll();
4030
+ event.preventDefault();
4031
+ return;
4032
+ }
3673
4033
  // COMPAT: Certain browsers don't support the `beforeinput` event, so we
3674
4034
  // fall back to guessing at the input intention for hotkeys.
3675
4035
  // COMPAT: In iOS, some of these hotkeys are handled in the
@@ -3837,6 +4197,9 @@ class SlateEditable {
3837
4197
  }
3838
4198
  //#endregion
3839
4199
  ngOnDestroy() {
4200
+ this.editorResizeObserver?.disconnect();
4201
+ this.debugOverlay?.dispose();
4202
+ this.debugOverlay = undefined;
3840
4203
  NODE_TO_ELEMENT.delete(this.editor);
3841
4204
  this.manualListeners.forEach(manualListener => {
3842
4205
  manualListener();
@@ -4096,6 +4459,7 @@ class BaseElementComponent extends BaseComponent {
4096
4459
  }
4097
4460
  return null;
4098
4461
  };
4462
+ this.stableHeight = null;
4099
4463
  }
4100
4464
  get element() {
4101
4465
  return this._context && this._context.element;
@@ -4177,11 +4541,21 @@ class BaseElementComponent extends BaseComponent {
4177
4541
  readonly: this._context.readonly
4178
4542
  };
4179
4543
  }
4544
+ isStableHeight() {
4545
+ return false;
4546
+ }
4180
4547
  getRealHeight() {
4548
+ if (this.isStableHeight() && this.stableHeight !== null) {
4549
+ return this.stableHeight;
4550
+ }
4181
4551
  const blockCard = getBlockCardByNativeElement(this.nativeElement);
4182
4552
  const target = blockCard || this.nativeElement;
4183
4553
  const computedStyle = getComputedStyle(target);
4184
- return Promise.resolve(target.offsetHeight + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom));
4554
+ const height = Math.ceil(target.getBoundingClientRect().height) + parseFloat(computedStyle.marginTop) + parseFloat(computedStyle.marginBottom);
4555
+ if (this.isStableHeight()) {
4556
+ this.stableHeight = height;
4557
+ }
4558
+ return height;
4185
4559
  }
4186
4560
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.12", ngImport: i0, type: BaseElementComponent, deps: null, target: i0.ɵɵFactoryTarget.Directive }); }
4187
4561
  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 +4711,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.12", ngImpo
4337
4711
  * Generated bundle index. Do not edit.
4338
4712
  */
4339
4713
 
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 };
4714
+ 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
4715
  //# sourceMappingURL=slate-angular.mjs.map