slate-angular 20.2.0-next.6 → 20.2.0-next.9

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.
@@ -2567,6 +2567,235 @@ function executeAfterViewInit(editor) {
2567
2567
  clearAfterViewInitQueue(editor);
2568
2568
  }
2569
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
+
2570
2799
  const JUST_NOW_UPDATED_VIRTUAL_VIEW = new WeakMap();
2571
2800
  // not correctly clipboardData on beforeinput
2572
2801
  const forceOnDOMPaste = IS_SAFARI;
@@ -2574,32 +2803,7 @@ const isDebug = localStorage.getItem(SLATE_DEBUG_KEY) === 'true';
2574
2803
  class SlateEditable {
2575
2804
  set virtualScroll(config) {
2576
2805
  this.virtualConfig = config;
2577
- this.refreshVirtualViewAnimId && cancelAnimationFrame(this.refreshVirtualViewAnimId);
2578
- this.refreshVirtualViewAnimId = requestAnimationFrame(() => {
2579
- let virtualView = this.refreshVirtualView();
2580
- let diff = this.diffVirtualView(virtualView);
2581
- if (!diff.isDiff) {
2582
- return;
2583
- }
2584
- if (diff.isMissingTop) {
2585
- const result = this.remeasureHeightByIndics(diff.diffTopRenderedIndexes);
2586
- if (result) {
2587
- virtualView = this.refreshVirtualView();
2588
- diff = this.diffVirtualView(virtualView, 'second');
2589
- if (!diff.isDiff) {
2590
- return;
2591
- }
2592
- }
2593
- }
2594
- this.applyVirtualView(virtualView);
2595
- if (this.listRender.initialized) {
2596
- this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
2597
- if (!AngularEditor.isReadOnly(this.editor) && this.editor.selection) {
2598
- this.toNativeSelection();
2599
- }
2600
- }
2601
- this.scheduleMeasureVisibleHeights();
2602
- });
2806
+ this.doVirtualScroll();
2603
2807
  }
2604
2808
  get hasBeforeInputSupport() {
2605
2809
  return HAS_BEFORE_INPUT_SUPPORT;
@@ -2752,19 +2956,21 @@ class SlateEditable {
2752
2956
  }
2753
2957
  toNativeSelection() {
2754
2958
  try {
2755
- let { selection: currentSelection } = this.editor;
2756
- let selection = currentSelection;
2757
- if (this.virtualConfig?.enabled) {
2959
+ let { selection } = this.editor;
2960
+ if (this.virtualConfig?.enabled && selection) {
2758
2961
  const indics = Array.from(this.virtualVisibleIndexes.values());
2759
2962
  if (indics.length > 0) {
2760
2963
  const currentVisibleRange = {
2761
2964
  anchor: Editor.start(this.editor, [indics[0]]),
2762
2965
  focus: Editor.end(this.editor, [indics[indics.length - 1]])
2763
2966
  };
2764
- selection = Range.intersection(selection, currentVisibleRange);
2765
- if ((!selection && currentSelection) || (selection && !Range.equals(selection, currentSelection))) {
2967
+ const [start, end] = Range.edges(selection);
2968
+ const forwardSelection = { anchor: start, focus: end };
2969
+ const intersectedSelection = Range.intersection(forwardSelection, currentVisibleRange);
2970
+ if (!intersectedSelection || !Range.equals(intersectedSelection, forwardSelection)) {
2971
+ selection = intersectedSelection;
2766
2972
  if (isDebug) {
2767
- console.log(`selection is not in visible range, selection: ${JSON.stringify(currentSelection)}, intersection selection: ${JSON.stringify(selection)}`);
2973
+ this.debugLog('log', `selection is not in visible range, selection: ${JSON.stringify(selection)}, intersectedSelection: ${JSON.stringify(intersectedSelection)}`);
2768
2974
  }
2769
2975
  }
2770
2976
  }
@@ -2985,6 +3191,19 @@ class SlateEditable {
2985
3191
  this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
2986
3192
  this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
2987
3193
  this.businessHeight = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3194
+ let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect()?.width ?? 0;
3195
+ this.editorResizeObserver = new ResizeObserver(entries => {
3196
+ if (entries.length > 0 && entries[0].contentRect.width !== editorResizeObserverRectWidth) {
3197
+ editorResizeObserverRectWidth = entries[0].contentRect.width;
3198
+ this.remeasureHeightByIndics(Array.from(this.virtualVisibleIndexes));
3199
+ }
3200
+ });
3201
+ this.editorResizeObserver.observe(this.elementRef.nativeElement);
3202
+ if (isDebug) {
3203
+ const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3204
+ this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3205
+ this.debugOverlay.init();
3206
+ }
2988
3207
  }
2989
3208
  }
2990
3209
  changeVirtualHeight(topHeight, bottomHeight) {
@@ -2994,6 +3213,41 @@ class SlateEditable {
2994
3213
  this.virtualTopHeightElement.style.height = `${topHeight}px`;
2995
3214
  this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
2996
3215
  }
3216
+ debugLog(type, ...args) {
3217
+ if (!this.debugOverlay) {
3218
+ const doc = this.elementRef?.nativeElement?.ownerDocument ?? document;
3219
+ this.debugOverlay = new VirtualScrollDebugOverlay(doc);
3220
+ }
3221
+ this.debugOverlay.log(type, ...args);
3222
+ }
3223
+ doVirtualScroll() {
3224
+ this.refreshVirtualViewAnimId && cancelAnimationFrame(this.refreshVirtualViewAnimId);
3225
+ this.refreshVirtualViewAnimId = requestAnimationFrame(() => {
3226
+ let virtualView = this.refreshVirtualView();
3227
+ let diff = this.diffVirtualView(virtualView);
3228
+ if (!diff.isDiff) {
3229
+ return;
3230
+ }
3231
+ if (diff.isMissingTop) {
3232
+ const result = this.remeasureHeightByIndics(diff.diffTopRenderedIndexes);
3233
+ if (result) {
3234
+ virtualView = this.refreshVirtualView();
3235
+ diff = this.diffVirtualView(virtualView, 'second');
3236
+ if (!diff.isDiff) {
3237
+ return;
3238
+ }
3239
+ }
3240
+ }
3241
+ this.applyVirtualView(virtualView);
3242
+ if (this.listRender.initialized) {
3243
+ this.listRender.update(virtualView.renderedChildren, this.editor, this.context);
3244
+ if (!AngularEditor.isReadOnly(this.editor) && this.editor.selection) {
3245
+ this.toNativeSelection();
3246
+ }
3247
+ }
3248
+ this.scheduleMeasureVisibleHeights();
3249
+ });
3250
+ }
2997
3251
  refreshVirtualView() {
2998
3252
  const children = (this.editor.children || []);
2999
3253
  if (!children.length || !this.shouldUseVirtual()) {
@@ -3125,18 +3379,18 @@ class SlateEditable {
3125
3379
  }
3126
3380
  }
3127
3381
  if (isDebug) {
3128
- console.log(`====== diffVirtualView stage: ${stage} ======`);
3129
- console.log('oldVisibleIndexes:', oldVisibleIndexes);
3130
- console.log('newVisibleIndexes:', newVisibleIndexes);
3131
- console.log('diffTopRenderedIndexes:', isMissingTop ? '-' : isAddedTop ? '+' : '-', diffTopRenderedIndexes, diffTopRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3132
- console.log('diffBottomRenderedIndexes:', isAddedBottom ? '+' : isMissingBottom ? '-' : '+', diffBottomRenderedIndexes, diffBottomRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3382
+ this.debugLog('log', `====== diffVirtualView stage: ${stage} ======`);
3383
+ this.debugLog('log', 'oldVisibleIndexes:', oldVisibleIndexes);
3384
+ this.debugLog('log', 'newVisibleIndexes:', newVisibleIndexes);
3385
+ this.debugLog('log', 'diffTopRenderedIndexes:', isMissingTop ? '-' : isAddedTop ? '+' : '-', diffTopRenderedIndexes, diffTopRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3386
+ this.debugLog('log', 'diffBottomRenderedIndexes:', isAddedBottom ? '+' : isMissingBottom ? '-' : '+', diffBottomRenderedIndexes, diffBottomRenderedIndexes.map(index => this.getBlockHeight(index, 0)));
3133
3387
  const needTop = virtualView.heights.slice(0, newVisibleIndexes[0]).reduce((acc, height) => acc + height, 0);
3134
3388
  const needBottom = virtualView.heights
3135
3389
  .slice(newVisibleIndexes[newVisibleIndexes.length - 1] + 1)
3136
3390
  .reduce((acc, height) => acc + height, 0);
3137
- console.log('newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3138
- console.log('newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3139
- console.warn('=========== Dividing line ===========');
3391
+ this.debugLog('log', 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3392
+ this.debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3393
+ this.debugLog('warn', '=========== Dividing line ===========');
3140
3394
  }
3141
3395
  return {
3142
3396
  isDiff: true,
@@ -3227,7 +3481,7 @@ class SlateEditable {
3227
3481
  this.measuredHeights.set(key.id, height);
3228
3482
  isHeightChanged = true;
3229
3483
  if (isDebug) {
3230
- console.log(`remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${height}`);
3484
+ this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${height}`);
3231
3485
  }
3232
3486
  }
3233
3487
  });
@@ -3237,7 +3491,7 @@ class SlateEditable {
3237
3491
  this.measuredHeights.set(key.id, ret);
3238
3492
  isHeightChanged = true;
3239
3493
  if (isDebug) {
3240
- console.log(`remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${ret}`);
3494
+ this.debugLog('log', `remeasureHeightByIndics, index: ${index} prevHeight: ${prevHeight} newHeight: ${ret}`);
3241
3495
  }
3242
3496
  }
3243
3497
  }
@@ -3940,6 +4194,9 @@ class SlateEditable {
3940
4194
  }
3941
4195
  //#endregion
3942
4196
  ngOnDestroy() {
4197
+ this.editorResizeObserver?.disconnect();
4198
+ this.debugOverlay?.dispose();
4199
+ this.debugOverlay = undefined;
3943
4200
  NODE_TO_ELEMENT.delete(this.editor);
3944
4201
  this.manualListeners.forEach(manualListener => {
3945
4202
  manualListener();