slate-angular 20.2.14 → 20.2.16

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.
@@ -3508,668 +3508,707 @@ class SlateEditable {
3508
3508
  this.addEventListener(event.name, () => { });
3509
3509
  });
3510
3510
  }
3511
- calculateVirtualScrollSelection(selection) {
3512
- if (selection) {
3513
- const isBlockCardCursor = AngularEditor.isBlockCardLeftCursor(this.editor) || AngularEditor.isBlockCardRightCursor(this.editor);
3514
- const indics = this.inViewportIndics;
3515
- if (indics.length > 0) {
3516
- const currentVisibleRange = {
3517
- anchor: Editor.start(this.editor, [indics[0]]),
3518
- focus: Editor.end(this.editor, [indics[indics.length - 1]])
3519
- };
3520
- const [start, end] = Range.edges(selection);
3521
- let forwardSelection = { anchor: start, focus: end };
3522
- if (!isBlockCardCursor) {
3523
- forwardSelection = { anchor: start, focus: end };
3524
- }
3525
- else {
3526
- forwardSelection = { anchor: { path: start.path, offset: 0 }, focus: { path: end.path, offset: 0 } };
3527
- }
3528
- const intersectedSelection = Range.intersection(forwardSelection, currentVisibleRange);
3529
- if (intersectedSelection && isBlockCardCursor) {
3530
- return selection;
3511
+ isEnabledVirtualScroll() {
3512
+ return !!(this.virtualScrollConfig && this.virtualScrollConfig.enabled);
3513
+ }
3514
+ initializeVirtualScroll() {
3515
+ if (this.virtualScrollInitialized) {
3516
+ return;
3517
+ }
3518
+ if (this.isEnabledVirtualScroll()) {
3519
+ this.virtualScrollInitialized = true;
3520
+ this.virtualTopHeightElement = document.createElement('div');
3521
+ this.virtualTopHeightElement.classList.add('virtual-top-height');
3522
+ this.virtualTopHeightElement.contentEditable = 'false';
3523
+ this.virtualBottomHeightElement = document.createElement('div');
3524
+ this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
3525
+ this.virtualBottomHeightElement.contentEditable = 'false';
3526
+ this.virtualCenterOutlet = document.createElement('div');
3527
+ this.virtualCenterOutlet.classList.add('virtual-center-outlet');
3528
+ this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
3529
+ this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
3530
+ this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
3531
+ let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect().width;
3532
+ EDITOR_TO_ROOT_NODE_WIDTH.set(this.editor, this.virtualTopHeightElement.offsetWidth);
3533
+ this.editorResizeObserver = new ResizeObserver(entries => {
3534
+ if (entries.length > 0 && entries[0].contentRect.width !== editorResizeObserverRectWidth) {
3535
+ editorResizeObserverRectWidth = entries[0].contentRect.width;
3536
+ this.keyHeightMap.clear();
3537
+ let target = this.virtualTopHeightElement;
3538
+ if (this.inViewportChildren[0]) {
3539
+ const firstElement = this.inViewportChildren[0];
3540
+ const firstDomElement = AngularEditor.toDOMNode(this.editor, firstElement);
3541
+ target = firstDomElement;
3542
+ }
3543
+ EDITOR_TO_ROOT_NODE_WIDTH.set(this.editor, target.offsetWidth);
3544
+ updatePreRenderingElementWidth(this.editor);
3545
+ if (isDebug) {
3546
+ debugLog('log', 'editorResizeObserverRectWidth: ', editorResizeObserverRectWidth, 'EDITOR_TO_ROOT_NODE_WIDTH: ', EDITOR_TO_ROOT_NODE_WIDTH.get(this.editor));
3547
+ }
3531
3548
  }
3532
- EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, intersectedSelection);
3533
- if (!intersectedSelection || !Range.equals(intersectedSelection, forwardSelection)) {
3549
+ });
3550
+ this.editorResizeObserver.observe(this.elementRef.nativeElement);
3551
+ let pendingRemeasureIndics = [];
3552
+ this.indicsOfNeedBeMeasured$
3553
+ .pipe(tap((previousValue) => {
3554
+ previousValue.forEach((index) => {
3555
+ if (!pendingRemeasureIndics.includes(index)) {
3556
+ pendingRemeasureIndics.push(index);
3557
+ }
3558
+ });
3559
+ }), debounceTime(500), filter(() => pendingRemeasureIndics.length > 0))
3560
+ .subscribe(() => {
3561
+ const changed = measureHeightByIndics(this.editor, pendingRemeasureIndics, true);
3562
+ if (changed) {
3563
+ this.tryUpdateVirtualViewport();
3534
3564
  if (isDebug) {
3535
- debugLog('log', `selection is not in visible range, selection: ${JSON.stringify(selection)}, currentVisibleRange: ${JSON.stringify(currentVisibleRange)}, intersectedSelection: ${JSON.stringify(intersectedSelection)}`);
3565
+ debugLog('log', 'exist pendingRemeasureIndics: ', pendingRemeasureIndics, 'will try to update virtual viewport');
3536
3566
  }
3537
- return intersectedSelection;
3538
3567
  }
3539
- return selection;
3568
+ pendingRemeasureIndics = [];
3569
+ });
3570
+ }
3571
+ }
3572
+ getChangedIndics(previousValue) {
3573
+ const remeasureIndics = [];
3574
+ this.inViewportChildren.forEach((child, index) => {
3575
+ if (previousValue.indexOf(child) === -1) {
3576
+ remeasureIndics.push(this.inViewportIndics[index]);
3540
3577
  }
3578
+ });
3579
+ return remeasureIndics;
3580
+ }
3581
+ setVirtualSpaceHeight(topHeight, bottomHeight) {
3582
+ if (!this.virtualScrollInitialized) {
3583
+ return;
3584
+ }
3585
+ this.virtualTopHeightElement.style.height = `${topHeight}px`;
3586
+ if (bottomHeight !== undefined) {
3587
+ this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
3541
3588
  }
3542
- EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, null);
3543
- return selection;
3544
3589
  }
3545
- isSelectionInvisible(selection) {
3546
- const anchorIndex = selection.anchor.path[0];
3547
- const focusIndex = selection.focus.path[0];
3548
- const anchorElement = this.editor.children[anchorIndex];
3549
- const focusElement = this.editor.children[focusIndex];
3550
- return !anchorElement || !focusElement || !this.editor.isVisible(anchorElement) || !this.editor.isVisible(focusElement);
3590
+ getActualVirtualTopHeight() {
3591
+ if (!this.virtualScrollInitialized) {
3592
+ return 0;
3593
+ }
3594
+ return parseFloat(this.virtualTopHeightElement.style.height.replace('px', ''));
3551
3595
  }
3552
- toNativeSelection(autoScroll = true) {
3553
- try {
3554
- let { selection } = this.editor;
3555
- if (this.isEnabledVirtualScroll()) {
3556
- selection = this.calculateVirtualScrollSelection(selection);
3557
- }
3558
- const root = AngularEditor.findDocumentOrShadowRoot(this.editor);
3559
- const { activeElement } = root;
3560
- const domSelection = root.getSelection();
3561
- if ((this.isComposing && !IS_ANDROID) || !domSelection || !AngularEditor.isFocused(this.editor)) {
3562
- return;
3563
- }
3564
- const hasDomSelection = domSelection.type !== 'None';
3565
- // If the DOM selection is properly unset, we're done.
3566
- if (!selection && !hasDomSelection) {
3567
- return;
3596
+ handlePreRendering(visibleStates) {
3597
+ let preRenderingCount = 0;
3598
+ const childrenWithPreRendering = [...this.inViewportChildren];
3599
+ const childrenWithPreRenderingIndics = [...this.inViewportIndics];
3600
+ const firstIndex = this.inViewportIndics[0];
3601
+ for (let index = firstIndex - 1; index >= 0; index--) {
3602
+ const element = this.editor.children[index];
3603
+ if (visibleStates[index]) {
3604
+ childrenWithPreRendering.unshift(element);
3605
+ childrenWithPreRenderingIndics.unshift(index);
3606
+ preRenderingCount = 1;
3607
+ break;
3568
3608
  }
3569
- // If the DOM selection is already correct, we're done.
3570
- // verify that the dom selection is in the editor
3571
- const editorElement = EDITOR_TO_ELEMENT.get(this.editor);
3572
- let hasDomSelectionInEditor = false;
3573
- if (editorElement.contains(domSelection.anchorNode) && editorElement.contains(domSelection.focusNode)) {
3574
- hasDomSelectionInEditor = true;
3609
+ }
3610
+ const lastIndex = this.inViewportIndics[this.inViewportIndics.length - 1];
3611
+ for (let index = lastIndex + 1; index < this.editor.children.length; index++) {
3612
+ const element = this.editor.children[index];
3613
+ if (visibleStates[index]) {
3614
+ childrenWithPreRendering.push(element);
3615
+ childrenWithPreRenderingIndics.push(index);
3616
+ break;
3575
3617
  }
3576
- // If the DOM selection is in the editor and the editor selection is already correct, we're done.
3577
- if (hasDomSelection && hasDomSelectionInEditor && selection && hasStringTarget(domSelection)) {
3578
- const rangeFromDOMSelection = AngularEditor.toSlateRange(this.editor, domSelection, {
3579
- exactMatch: false,
3580
- suppressThrow: true
3581
- });
3582
- if (rangeFromDOMSelection && Range.equals(rangeFromDOMSelection, selection)) {
3583
- return;
3618
+ }
3619
+ return { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics };
3620
+ }
3621
+ tryUpdateVirtualViewport() {
3622
+ if (isDebug) {
3623
+ debugLog('log', 'tryUpdateVirtualViewport');
3624
+ }
3625
+ const isFromScrollTo = EDITOR_TO_IS_FROM_SCROLL_TO.get(this.editor);
3626
+ if (this.inViewportIndics.length > 0 && !isFromScrollTo) {
3627
+ const topHeight = this.getActualVirtualTopHeight();
3628
+ const visibleStates = this.editor.getAllVisibleStates();
3629
+ const refreshVirtualTopHeight = calculateVirtualTopHeight(this.editor, this.inViewportIndics[0], visibleStates);
3630
+ if (topHeight !== refreshVirtualTopHeight) {
3631
+ if (isDebug) {
3632
+ debugLog('log', 'update top height since dirty state(正数减去高度,负数代表增加高度): ', topHeight - refreshVirtualTopHeight);
3584
3633
  }
3585
- }
3586
- // prevent updating native selection when active element is void element
3587
- if (isTargetInsideVoid(this.editor, activeElement)) {
3634
+ this.setVirtualSpaceHeight(refreshVirtualTopHeight);
3588
3635
  return;
3589
3636
  }
3590
- // when <Editable/> is being controlled through external value
3591
- // then its children might just change - DOM responds to it on its own
3592
- // but Slate's value is not being updated through any operation
3593
- // and thus it doesn't transform selection on its own
3594
- if (selection && !AngularEditor.hasRange(this.editor, selection)) {
3595
- this.editor.selection = AngularEditor.toSlateRange(this.editor, domSelection, { exactMatch: false, suppressThrow: false });
3596
- return;
3637
+ }
3638
+ this.tryUpdateVirtualViewportAnimId && cancelAnimationFrame(this.tryUpdateVirtualViewportAnimId);
3639
+ this.tryUpdateVirtualViewportAnimId = requestAnimationFrame(() => {
3640
+ if (isDebug) {
3641
+ debugLog('log', 'tryUpdateVirtualViewport Anim start');
3597
3642
  }
3598
- // Otherwise the DOM selection is out of sync, so update it.
3599
- const el = AngularEditor.toDOMNode(this.editor, this.editor);
3600
- this.isUpdatingSelection = true;
3601
- const newDomRange = selection && AngularEditor.toDOMRange(this.editor, selection);
3602
- if (newDomRange) {
3603
- // COMPAT: Since the DOM range has no concept of backwards/forwards
3604
- // we need to check and do the right thing here.
3605
- if (Range.isBackward(selection)) {
3606
- // eslint-disable-next-line max-len
3607
- domSelection.setBaseAndExtent(newDomRange.endContainer, newDomRange.endOffset, newDomRange.startContainer, newDomRange.startOffset);
3643
+ const visibleStates = this.editor.getAllVisibleStates();
3644
+ let virtualView = this.calculateVirtualViewport(visibleStates);
3645
+ let diff = this.diffVirtualViewport(virtualView);
3646
+ if (diff.isDifferent && diff.needRemoveOnTop && !isFromScrollTo) {
3647
+ const remeasureIndics = diff.changedIndexesOfTop;
3648
+ const changed = measureHeightByIndics(this.editor, remeasureIndics);
3649
+ if (changed) {
3650
+ virtualView = this.calculateVirtualViewport(visibleStates);
3651
+ diff = this.diffVirtualViewport(virtualView, 'second');
3608
3652
  }
3609
- else {
3610
- // eslint-disable-next-line max-len
3611
- domSelection.setBaseAndExtent(newDomRange.startContainer, newDomRange.startOffset, newDomRange.endContainer, newDomRange.endOffset);
3653
+ }
3654
+ if (diff.isDifferent) {
3655
+ this.applyVirtualView(virtualView);
3656
+ if (this.listRender.initialized) {
3657
+ const { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics } = this.handlePreRendering(visibleStates);
3658
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount, childrenWithPreRenderingIndics);
3659
+ if (diff.needAddOnTop && !isFromScrollTo) {
3660
+ const remeasureAddedIndics = diff.changedIndexesOfTop;
3661
+ if (isDebug) {
3662
+ debugLog('log', 'needAddOnTop to remeasure heights: ', remeasureAddedIndics);
3663
+ }
3664
+ const startIndexBeforeAdd = diff.changedIndexesOfTop[diff.changedIndexesOfTop.length - 1] + 1;
3665
+ const topHeightBeforeAdd = virtualView.accumulatedHeights[startIndexBeforeAdd];
3666
+ const changed = measureHeightByIndics(this.editor, remeasureAddedIndics);
3667
+ if (changed) {
3668
+ const newHeights = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
3669
+ const actualTopHeightAfterAdd = newHeights.accumulatedHeights[startIndexBeforeAdd];
3670
+ const newTopHeight = virtualView.top - (actualTopHeightAfterAdd - topHeightBeforeAdd);
3671
+ this.setVirtualSpaceHeight(newTopHeight);
3672
+ if (isDebug) {
3673
+ debugLog('log', `update top height since will add element in top(正数减去高度,负数代表增加高度): ${actualTopHeightAfterAdd - topHeightBeforeAdd}`);
3674
+ }
3675
+ }
3676
+ }
3677
+ if (this.editor.selection) {
3678
+ this.toNativeSelection(false);
3679
+ }
3612
3680
  }
3613
3681
  }
3614
- else {
3615
- domSelection.removeAllRanges();
3682
+ if (isDebug) {
3683
+ debugLog('log', 'tryUpdateVirtualViewport Anim end');
3616
3684
  }
3617
- setTimeout(() => {
3618
- if (this.isEnabledVirtualScroll() &&
3619
- !selection &&
3620
- this.editor.selection &&
3621
- autoScroll &&
3622
- this.virtualScrollConfig.scrollContainer) {
3623
- this.virtualScrollConfig.scrollContainer.scrollTop = this.virtualScrollConfig.scrollContainer.scrollTop + 100;
3624
- this.isUpdatingSelection = false;
3625
- return;
3626
- }
3627
- else {
3628
- // handle scrolling in setTimeout because of
3629
- // dom should not have updated immediately after listRender's updating
3630
- newDomRange && autoScroll && this.scrollSelectionIntoView(this.editor, newDomRange);
3631
- // COMPAT: In Firefox, it's not enough to create a range, you also need
3632
- // to focus the contenteditable element too. (2016/11/16)
3633
- if (newDomRange && IS_FIREFOX) {
3634
- el.focus();
3635
- }
3636
- }
3637
- this.isUpdatingSelection = false;
3638
- });
3639
- }
3640
- catch (error) {
3641
- this.editor.onError({
3642
- code: SlateErrorCode.ToNativeSelectionError,
3643
- nativeError: error
3644
- });
3645
- this.isUpdatingSelection = false;
3646
- }
3647
- }
3648
- onChange() {
3649
- this.forceRender();
3650
- this.onChangeCallback(this.editor.children);
3685
+ });
3651
3686
  }
3652
- ngAfterViewChecked() { }
3653
- ngDoCheck() { }
3654
- forceRender() {
3655
- this.updateContext();
3656
- if (this.isEnabledVirtualScroll()) {
3657
- this.updateListRenderAndRemeasureHeights();
3687
+ calculateVirtualViewport(visibleStates) {
3688
+ const children = (this.editor.children || []);
3689
+ if (!children.length || !this.isEnabledVirtualScroll()) {
3690
+ return {
3691
+ inViewportChildren: children,
3692
+ inViewportIndics: [],
3693
+ top: 0,
3694
+ bottom: 0,
3695
+ heights: []
3696
+ };
3658
3697
  }
3659
- else {
3660
- this.listRender.update(this.editor.children, this.editor, this.context);
3698
+ const scrollTop = this.virtualScrollConfig.scrollTop;
3699
+ let viewportHeight = this.virtualScrollConfig.viewportHeight ?? 0;
3700
+ if (!viewportHeight) {
3701
+ return {
3702
+ inViewportChildren: [],
3703
+ inViewportIndics: [],
3704
+ top: 0,
3705
+ bottom: 0,
3706
+ heights: []
3707
+ };
3661
3708
  }
3662
- // repair collaborative editing when Chinese input is interrupted by other users' cursors
3663
- // when the DOMElement where the selection is located is removed
3664
- // the compositionupdate and compositionend events will no longer be fired
3665
- // so isComposing needs to be corrected
3666
- // need exec after this.cdr.detectChanges() to render HTML
3667
- // need exec before this.toNativeSelection() to correct native selection
3668
- if (this.isComposing) {
3669
- // Composition input text be not rendered when user composition input with selection is expanded
3670
- // At this time, the following matching conditions are met, assign isComposing to false, and the status is wrong
3671
- // this time condition is true and isComposing is assigned false
3672
- // Therefore, need to wait for the composition input text to be rendered before performing condition matching
3709
+ const elementLength = children.length;
3710
+ if (!EDITOR_TO_BUSINESS_TOP.has(this.editor)) {
3711
+ EDITOR_TO_BUSINESS_TOP.set(this.editor, 0);
3673
3712
  setTimeout(() => {
3674
- const textNode = Node.get(this.editor, this.editor.selection.anchor.path);
3675
- const textDOMNode = AngularEditor.toDOMNode(this.editor, textNode);
3676
- let textContent = '';
3677
- // skip decorate text
3678
- textDOMNode.querySelectorAll('[editable-text]').forEach(stringDOMNode => {
3679
- let text = stringDOMNode.textContent;
3680
- const zeroChar = '\uFEFF';
3681
- // remove zero with char
3682
- if (text.startsWith(zeroChar)) {
3683
- text = text.slice(1);
3684
- }
3685
- if (text.endsWith(zeroChar)) {
3686
- text = text.slice(0, text.length - 1);
3687
- }
3688
- textContent += text;
3689
- });
3690
- if (Node.string(textNode).endsWith(textContent)) {
3691
- this.isComposing = false;
3713
+ const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3714
+ const businessTop = Math.ceil(virtualTopBoundingTop) +
3715
+ Math.ceil(this.virtualScrollConfig.scrollTop) -
3716
+ Math.floor(this.virtualScrollConfig.viewportBoundingTop);
3717
+ EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
3718
+ if (isDebug) {
3719
+ debugLog('log', 'businessTop', businessTop);
3720
+ this.virtualTopHeightElement.setAttribute('data-business-top', businessTop.toString());
3692
3721
  }
3693
- }, 0);
3694
- }
3695
- if (this.editor.selection && this.isSelectionInvisible(this.editor.selection)) {
3696
- Transforms.deselect(this.editor);
3697
- return;
3722
+ }, 100);
3698
3723
  }
3699
- else {
3700
- this.toNativeSelection();
3724
+ const businessTop = getBusinessTop(this.editor);
3725
+ const { heights, accumulatedHeights } = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
3726
+ const totalHeight = accumulatedHeights[elementLength] + businessTop;
3727
+ let startPosition = Math.max(scrollTop - businessTop, 0);
3728
+ let endPosition = startPosition + viewportHeight;
3729
+ if (scrollTop < businessTop) {
3730
+ endPosition = startPosition + viewportHeight - (businessTop - scrollTop);
3701
3731
  }
3702
- }
3703
- render() {
3704
- const changed = this.updateContext();
3705
- if (changed) {
3706
- if (this.isEnabledVirtualScroll()) {
3707
- this.updateListRenderAndRemeasureHeights();
3732
+ let accumulatedOffset = 0;
3733
+ let inViewportStartIndex = -1;
3734
+ const visible = [];
3735
+ const inViewportIndics = [];
3736
+ for (let i = 0; i < elementLength && accumulatedOffset < endPosition; i++) {
3737
+ const currentHeight = heights[i];
3738
+ const nextOffset = accumulatedOffset + currentHeight;
3739
+ const isVisible = visibleStates[i];
3740
+ if (!isVisible) {
3741
+ accumulatedOffset = nextOffset;
3742
+ continue;
3708
3743
  }
3709
- else {
3710
- this.listRender.update(this.editor.children, this.editor, this.context);
3744
+ // 可视区域有交集,加入渲染
3745
+ if (nextOffset > startPosition && accumulatedOffset < endPosition) {
3746
+ if (inViewportStartIndex === -1)
3747
+ inViewportStartIndex = i; // 第一个相交起始位置
3748
+ visible.push(children[i]);
3749
+ inViewportIndics.push(i);
3711
3750
  }
3751
+ accumulatedOffset = nextOffset;
3712
3752
  }
3753
+ const inViewportEndIndex = inViewportStartIndex === -1 ? elementLength - 1 : (inViewportIndics[inViewportIndics.length - 1] ?? inViewportStartIndex);
3754
+ const top = inViewportStartIndex === -1 ? 0 : accumulatedHeights[inViewportStartIndex];
3755
+ const bottom = totalHeight - accumulatedHeights[inViewportEndIndex + 1];
3756
+ return {
3757
+ inViewportChildren: visible.length ? visible : children,
3758
+ inViewportIndics,
3759
+ top,
3760
+ bottom,
3761
+ heights,
3762
+ accumulatedHeights
3763
+ };
3713
3764
  }
3714
- updateListRenderAndRemeasureHeights() {
3715
- const visibleStates = this.editor.getAllVisibleStates();
3716
- const previousInViewportChildren = [...this.inViewportChildren];
3717
- let virtualView = this.calculateVirtualViewport(visibleStates);
3718
- let diff = this.diffVirtualViewport(virtualView, 'onChange');
3719
- if (diff.isDifferent && diff.needRemoveOnTop) {
3720
- const remeasureIndics = diff.changedIndexesOfTop;
3721
- const changed = measureHeightByIndics(this.editor, remeasureIndics);
3722
- if (changed) {
3723
- virtualView = this.calculateVirtualViewport(visibleStates);
3724
- diff = this.diffVirtualViewport(virtualView, 'second');
3765
+ applyVirtualView(virtualView) {
3766
+ this.inViewportChildren = virtualView.inViewportChildren;
3767
+ this.setVirtualSpaceHeight(virtualView.top, virtualView.bottom);
3768
+ this.inViewportIndics = virtualView.inViewportIndics;
3769
+ }
3770
+ diffVirtualViewport(virtualView, stage = 'first') {
3771
+ if (!this.inViewportChildren.length) {
3772
+ if (isDebug) {
3773
+ debugLog('log', 'diffVirtualViewport', stage, 'empty inViewportChildren', virtualView.inViewportIndics);
3725
3774
  }
3775
+ return {
3776
+ isDifferent: true,
3777
+ changedIndexesOfTop: [],
3778
+ changedIndexesOfBottom: []
3779
+ };
3726
3780
  }
3727
- this.applyVirtualView(virtualView);
3728
- const { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics } = this.handlePreRendering(visibleStates);
3729
- this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount, childrenWithPreRenderingIndics);
3730
- const remeasureIndics = this.getChangedIndics(previousInViewportChildren);
3731
- if (remeasureIndics.length) {
3732
- this.indicsOfNeedBeMeasured$.next(remeasureIndics);
3781
+ const oldIndexesInViewport = [...this.inViewportIndics];
3782
+ const newIndexesInViewport = [...virtualView.inViewportIndics];
3783
+ const firstNewIndex = newIndexesInViewport[0];
3784
+ const lastNewIndex = newIndexesInViewport[newIndexesInViewport.length - 1];
3785
+ const firstOldIndex = oldIndexesInViewport[0];
3786
+ const lastOldIndex = oldIndexesInViewport[oldIndexesInViewport.length - 1];
3787
+ const isSameViewport = oldIndexesInViewport.length === newIndexesInViewport.length &&
3788
+ oldIndexesInViewport.every((index, i) => index === newIndexesInViewport[i]);
3789
+ if (firstNewIndex === firstOldIndex && lastNewIndex === lastOldIndex) {
3790
+ return {
3791
+ isDifferent: !isSameViewport,
3792
+ changedIndexesOfTop: [],
3793
+ changedIndexesOfBottom: []
3794
+ };
3733
3795
  }
3734
- }
3735
- updateContext() {
3736
- const decorations = this.generateDecorations();
3737
- if (this.context.selection !== this.editor.selection ||
3738
- this.context.decorate !== this.decorate ||
3739
- this.context.readonly !== this.readonly ||
3740
- !isDecoratorRangeListEqual(this.context.decorations, decorations)) {
3741
- this.context = {
3742
- parent: this.editor,
3743
- selection: this.editor.selection,
3744
- decorations: decorations,
3745
- decorate: this.decorate,
3746
- readonly: this.readonly
3796
+ if (firstNewIndex !== firstOldIndex || lastNewIndex !== lastOldIndex) {
3797
+ const changedIndexesOfTop = [];
3798
+ const changedIndexesOfBottom = [];
3799
+ const needRemoveOnTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
3800
+ const needAddOnTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
3801
+ const needRemoveOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
3802
+ const needAddOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
3803
+ if (needRemoveOnTop || needAddOnBottom) {
3804
+ // 向下
3805
+ for (let index = 0; index < oldIndexesInViewport.length; index++) {
3806
+ const element = oldIndexesInViewport[index];
3807
+ if (!newIndexesInViewport.includes(element)) {
3808
+ changedIndexesOfTop.push(element);
3809
+ }
3810
+ else {
3811
+ break;
3812
+ }
3813
+ }
3814
+ for (let index = newIndexesInViewport.length - 1; index >= 0; index--) {
3815
+ const element = newIndexesInViewport[index];
3816
+ if (!oldIndexesInViewport.includes(element)) {
3817
+ changedIndexesOfBottom.push(element);
3818
+ }
3819
+ else {
3820
+ break;
3821
+ }
3822
+ }
3823
+ }
3824
+ else if (needAddOnTop || needRemoveOnBottom) {
3825
+ // 向上
3826
+ for (let index = 0; index < newIndexesInViewport.length; index++) {
3827
+ const element = newIndexesInViewport[index];
3828
+ if (!oldIndexesInViewport.includes(element)) {
3829
+ changedIndexesOfTop.push(element);
3830
+ }
3831
+ else {
3832
+ break;
3833
+ }
3834
+ }
3835
+ for (let index = oldIndexesInViewport.length - 1; index >= 0; index--) {
3836
+ const element = oldIndexesInViewport[index];
3837
+ if (!newIndexesInViewport.includes(element)) {
3838
+ changedIndexesOfBottom.push(element);
3839
+ }
3840
+ else {
3841
+ break;
3842
+ }
3843
+ }
3844
+ }
3845
+ if (isDebug) {
3846
+ debugLog('log', `====== diffVirtualViewport stage: ${stage} ======`);
3847
+ debugLog('log', 'oldIndexesInViewport:', oldIndexesInViewport);
3848
+ debugLog('log', 'newIndexesInViewport:', newIndexesInViewport);
3849
+ // this.editor.children[index] will be undefined when it is removed
3850
+ debugLog('log', 'changedIndexesOfTop:', needRemoveOnTop ? '-' : needAddOnTop ? '+' : '-', changedIndexesOfTop, changedIndexesOfTop.map(index => (this.editor.children[index] &&
3851
+ getCachedHeightByElement(this.editor, this.editor.children[index])) ||
3852
+ 0));
3853
+ debugLog('log', 'changedIndexesOfBottom:', needAddOnBottom ? '+' : needRemoveOnBottom ? '-' : '+', changedIndexesOfBottom, changedIndexesOfBottom.map(index => (this.editor.children[index] &&
3854
+ getCachedHeightByElement(this.editor, this.editor.children[index])) ||
3855
+ 0));
3856
+ const needTop = virtualView.heights.slice(0, newIndexesInViewport[0]).reduce((acc, height) => acc + height, 0);
3857
+ const needBottom = virtualView.heights
3858
+ .slice(newIndexesInViewport[newIndexesInViewport.length - 1] + 1)
3859
+ .reduce((acc, height) => acc + height, 0);
3860
+ debugLog('log', needTop - parseFloat(this.virtualTopHeightElement.style.height), 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
3861
+ debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
3862
+ debugLog('warn', '=========== Dividing line ===========');
3863
+ }
3864
+ return {
3865
+ isDifferent: true,
3866
+ needRemoveOnTop,
3867
+ needAddOnTop,
3868
+ needRemoveOnBottom,
3869
+ needAddOnBottom,
3870
+ changedIndexesOfTop,
3871
+ changedIndexesOfBottom
3747
3872
  };
3748
- return true;
3749
3873
  }
3750
- return false;
3751
- }
3752
- initializeContext() {
3753
- this.context = {
3754
- parent: this.editor,
3755
- selection: this.editor.selection,
3756
- decorations: this.generateDecorations(),
3757
- decorate: this.decorate,
3758
- readonly: this.readonly
3874
+ return {
3875
+ isDifferent: false,
3876
+ changedIndexesOfTop: [],
3877
+ changedIndexesOfBottom: []
3759
3878
  };
3760
3879
  }
3761
- initializeViewContext() {
3762
- this.viewContext = {
3763
- editor: this.editor,
3764
- renderElement: this.renderElement,
3765
- renderLeaf: this.renderLeaf,
3766
- renderText: this.renderText,
3767
- trackBy: this.trackBy,
3768
- isStrictDecorate: this.isStrictDecorate
3769
- };
3880
+ //#region event proxy
3881
+ addEventListener(eventName, listener, target = this.elementRef.nativeElement) {
3882
+ this.manualListeners.push(this.renderer2.listen(target, eventName, (event) => {
3883
+ const beforeInputEvent = extractBeforeInputEvent(event.type, null, event, event.target);
3884
+ if (beforeInputEvent) {
3885
+ this.onFallbackBeforeInput(beforeInputEvent);
3886
+ }
3887
+ listener(event);
3888
+ }));
3770
3889
  }
3771
- composePlaceholderDecorate(editor) {
3772
- if (this.placeholderDecorate) {
3773
- return this.placeholderDecorate(editor) || [];
3774
- }
3775
- if (this.placeholder && editor.children.length === 1 && Array.from(Node.texts(editor)).length === 1 && Node.string(editor) === '') {
3776
- const start = Editor.start(editor, []);
3777
- return [
3778
- {
3779
- placeholder: this.placeholder,
3780
- anchor: start,
3781
- focus: start
3890
+ calculateVirtualScrollSelection(selection) {
3891
+ if (selection) {
3892
+ const isBlockCardCursor = AngularEditor.isBlockCardLeftCursor(this.editor) || AngularEditor.isBlockCardRightCursor(this.editor);
3893
+ const indics = this.inViewportIndics;
3894
+ if (indics.length > 0) {
3895
+ const currentVisibleRange = {
3896
+ anchor: Editor.start(this.editor, [indics[0]]),
3897
+ focus: Editor.end(this.editor, [indics[indics.length - 1]])
3898
+ };
3899
+ const [start, end] = Range.edges(selection);
3900
+ let forwardSelection = { anchor: start, focus: end };
3901
+ if (!isBlockCardCursor) {
3902
+ forwardSelection = { anchor: start, focus: end };
3782
3903
  }
3783
- ];
3784
- }
3785
- else {
3786
- return [];
3787
- }
3788
- }
3789
- generateDecorations() {
3790
- const decorations = this.decorate([this.editor, []]);
3791
- const placeholderDecorations = this.isComposing ? [] : this.composePlaceholderDecorate(this.editor);
3792
- decorations.push(...placeholderDecorations);
3793
- return decorations;
3794
- }
3795
- isEnabledVirtualScroll() {
3796
- return !!(this.virtualScrollConfig && this.virtualScrollConfig.enabled);
3797
- }
3798
- initializeVirtualScroll() {
3799
- if (this.virtualScrollInitialized) {
3800
- return;
3801
- }
3802
- if (this.isEnabledVirtualScroll()) {
3803
- this.virtualScrollInitialized = true;
3804
- this.virtualTopHeightElement = document.createElement('div');
3805
- this.virtualTopHeightElement.classList.add('virtual-top-height');
3806
- this.virtualTopHeightElement.contentEditable = 'false';
3807
- this.virtualBottomHeightElement = document.createElement('div');
3808
- this.virtualBottomHeightElement.classList.add('virtual-bottom-height');
3809
- this.virtualBottomHeightElement.contentEditable = 'false';
3810
- this.virtualCenterOutlet = document.createElement('div');
3811
- this.virtualCenterOutlet.classList.add('virtual-center-outlet');
3812
- this.elementRef.nativeElement.appendChild(this.virtualTopHeightElement);
3813
- this.elementRef.nativeElement.appendChild(this.virtualCenterOutlet);
3814
- this.elementRef.nativeElement.appendChild(this.virtualBottomHeightElement);
3815
- let editorResizeObserverRectWidth = this.elementRef.nativeElement.getBoundingClientRect().width;
3816
- EDITOR_TO_ROOT_NODE_WIDTH.set(this.editor, this.virtualTopHeightElement.offsetWidth);
3817
- this.editorResizeObserver = new ResizeObserver(entries => {
3818
- if (entries.length > 0 && entries[0].contentRect.width !== editorResizeObserverRectWidth) {
3819
- editorResizeObserverRectWidth = entries[0].contentRect.width;
3820
- this.keyHeightMap.clear();
3821
- let target = this.virtualTopHeightElement;
3822
- if (this.inViewportChildren[0]) {
3823
- const firstElement = this.inViewportChildren[0];
3824
- const firstDomElement = AngularEditor.toDOMNode(this.editor, firstElement);
3825
- target = firstDomElement;
3826
- }
3827
- EDITOR_TO_ROOT_NODE_WIDTH.set(this.editor, target.offsetWidth);
3828
- updatePreRenderingElementWidth(this.editor);
3829
- if (isDebug) {
3830
- debugLog('log', 'editorResizeObserverRectWidth: ', editorResizeObserverRectWidth, 'EDITOR_TO_ROOT_NODE_WIDTH: ', EDITOR_TO_ROOT_NODE_WIDTH.get(this.editor));
3831
- }
3904
+ else {
3905
+ forwardSelection = { anchor: { path: start.path, offset: 0 }, focus: { path: end.path, offset: 0 } };
3832
3906
  }
3833
- });
3834
- this.editorResizeObserver.observe(this.elementRef.nativeElement);
3835
- let pendingRemeasureIndics = [];
3836
- this.indicsOfNeedBeMeasured$
3837
- .pipe(tap((previousValue) => {
3838
- previousValue.forEach((index) => {
3839
- if (!pendingRemeasureIndics.includes(index)) {
3840
- pendingRemeasureIndics.push(index);
3841
- }
3842
- });
3843
- }), debounceTime(500), filter(() => pendingRemeasureIndics.length > 0))
3844
- .subscribe(() => {
3845
- const changed = measureHeightByIndics(this.editor, pendingRemeasureIndics, true);
3846
- if (changed) {
3847
- this.tryUpdateVirtualViewport();
3907
+ const intersectedSelection = Range.intersection(forwardSelection, currentVisibleRange);
3908
+ if (intersectedSelection && isBlockCardCursor) {
3909
+ return selection;
3910
+ }
3911
+ EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, intersectedSelection);
3912
+ if (!intersectedSelection || !Range.equals(intersectedSelection, forwardSelection)) {
3848
3913
  if (isDebug) {
3849
- debugLog('log', 'exist pendingRemeasureIndics: ', pendingRemeasureIndics, 'will try to update virtual viewport');
3914
+ debugLog('log', `selection is not in visible range, selection: ${JSON.stringify(selection)}, currentVisibleRange: ${JSON.stringify(currentVisibleRange)}, intersectedSelection: ${JSON.stringify(intersectedSelection)}`);
3850
3915
  }
3916
+ return intersectedSelection;
3851
3917
  }
3852
- pendingRemeasureIndics = [];
3853
- });
3854
- }
3855
- }
3856
- getChangedIndics(previousValue) {
3857
- const remeasureIndics = [];
3858
- this.inViewportChildren.forEach((child, index) => {
3859
- if (previousValue.indexOf(child) === -1) {
3860
- remeasureIndics.push(this.inViewportIndics[index]);
3918
+ return selection;
3861
3919
  }
3862
- });
3863
- return remeasureIndics;
3864
- }
3865
- setVirtualSpaceHeight(topHeight, bottomHeight) {
3866
- if (!this.virtualScrollInitialized) {
3867
- return;
3868
- }
3869
- this.virtualTopHeightElement.style.height = `${topHeight}px`;
3870
- if (bottomHeight !== undefined) {
3871
- this.virtualBottomHeightElement.style.height = `${bottomHeight}px`;
3872
3920
  }
3921
+ EDITOR_TO_VIRTUAL_SCROLL_SELECTION.set(this.editor, null);
3922
+ return selection;
3873
3923
  }
3874
- getActualVirtualTopHeight() {
3875
- if (!this.virtualScrollInitialized) {
3876
- return 0;
3877
- }
3878
- return parseFloat(this.virtualTopHeightElement.style.height.replace('px', ''));
3924
+ isSelectionInvisible(selection) {
3925
+ const anchorIndex = selection.anchor.path[0];
3926
+ const focusIndex = selection.focus.path[0];
3927
+ const anchorElement = this.editor.children[anchorIndex];
3928
+ const focusElement = this.editor.children[focusIndex];
3929
+ return !anchorElement || !focusElement || !this.editor.isVisible(anchorElement) || !this.editor.isVisible(focusElement);
3879
3930
  }
3880
- handlePreRendering(visibleStates) {
3881
- let preRenderingCount = 0;
3882
- const childrenWithPreRendering = [...this.inViewportChildren];
3883
- const childrenWithPreRenderingIndics = [...this.inViewportIndics];
3884
- const firstIndex = this.inViewportIndics[0];
3885
- for (let index = firstIndex - 1; index >= 0; index--) {
3886
- const element = this.editor.children[index];
3887
- if (visibleStates[index]) {
3888
- childrenWithPreRendering.unshift(element);
3889
- childrenWithPreRenderingIndics.unshift(index);
3890
- preRenderingCount = 1;
3891
- break;
3931
+ toNativeSelection(autoScroll = true) {
3932
+ try {
3933
+ let { selection } = this.editor;
3934
+ if (this.isEnabledVirtualScroll()) {
3935
+ selection = this.calculateVirtualScrollSelection(selection);
3892
3936
  }
3893
- }
3894
- const lastIndex = this.inViewportIndics[this.inViewportIndics.length - 1];
3895
- for (let index = lastIndex + 1; index < this.editor.children.length; index++) {
3896
- const element = this.editor.children[index];
3897
- if (visibleStates[index]) {
3898
- childrenWithPreRendering.push(element);
3899
- childrenWithPreRenderingIndics.push(index);
3900
- break;
3937
+ const root = AngularEditor.findDocumentOrShadowRoot(this.editor);
3938
+ const { activeElement } = root;
3939
+ const domSelection = root.getSelection();
3940
+ if ((this.isComposing && !IS_ANDROID) || !domSelection) {
3941
+ return;
3901
3942
  }
3902
- }
3903
- return { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics };
3904
- }
3905
- tryUpdateVirtualViewport() {
3906
- if (isDebug) {
3907
- debugLog('log', 'tryUpdateVirtualViewport');
3908
- }
3909
- const isFromScrollTo = EDITOR_TO_IS_FROM_SCROLL_TO.get(this.editor);
3910
- if (this.inViewportIndics.length > 0 && !isFromScrollTo) {
3911
- const topHeight = this.getActualVirtualTopHeight();
3912
- const visibleStates = this.editor.getAllVisibleStates();
3913
- const refreshVirtualTopHeight = calculateVirtualTopHeight(this.editor, this.inViewportIndics[0], visibleStates);
3914
- if (topHeight !== refreshVirtualTopHeight) {
3915
- if (isDebug) {
3916
- debugLog('log', 'update top height since dirty state(正数减去高度,负数代表增加高度): ', topHeight - refreshVirtualTopHeight);
3917
- }
3918
- this.setVirtualSpaceHeight(refreshVirtualTopHeight);
3943
+ const hasDomSelection = domSelection.type !== 'None';
3944
+ // If the DOM selection is properly unset, we're done.
3945
+ if (!selection && !hasDomSelection) {
3919
3946
  return;
3920
3947
  }
3921
- }
3922
- this.tryUpdateVirtualViewportAnimId && cancelAnimationFrame(this.tryUpdateVirtualViewportAnimId);
3923
- this.tryUpdateVirtualViewportAnimId = requestAnimationFrame(() => {
3924
- if (isDebug) {
3925
- debugLog('log', 'tryUpdateVirtualViewport Anim start');
3948
+ // If the DOM selection is already correct, we're done.
3949
+ // verify that the dom selection is in the editor
3950
+ const editorElement = EDITOR_TO_ELEMENT.get(this.editor);
3951
+ let hasDomSelectionInEditor = false;
3952
+ if (editorElement.contains(domSelection.anchorNode) && editorElement.contains(domSelection.focusNode)) {
3953
+ hasDomSelectionInEditor = true;
3926
3954
  }
3927
- const visibleStates = this.editor.getAllVisibleStates();
3928
- let virtualView = this.calculateVirtualViewport(visibleStates);
3929
- let diff = this.diffVirtualViewport(virtualView);
3930
- if (diff.isDifferent && diff.needRemoveOnTop && !isFromScrollTo) {
3931
- const remeasureIndics = diff.changedIndexesOfTop;
3932
- const changed = measureHeightByIndics(this.editor, remeasureIndics);
3933
- if (changed) {
3934
- virtualView = this.calculateVirtualViewport(visibleStates);
3935
- diff = this.diffVirtualViewport(virtualView, 'second');
3955
+ // If the DOM selection is in the editor and the editor selection is already correct, we're done.
3956
+ if (hasDomSelection && hasDomSelectionInEditor && selection && hasStringTarget(domSelection)) {
3957
+ const rangeFromDOMSelection = AngularEditor.toSlateRange(this.editor, domSelection, {
3958
+ exactMatch: false,
3959
+ suppressThrow: true
3960
+ });
3961
+ if (rangeFromDOMSelection && Range.equals(rangeFromDOMSelection, selection)) {
3962
+ return;
3936
3963
  }
3937
3964
  }
3938
- if (diff.isDifferent) {
3939
- this.applyVirtualView(virtualView);
3940
- if (this.listRender.initialized) {
3941
- const { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics } = this.handlePreRendering(visibleStates);
3942
- this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount, childrenWithPreRenderingIndics);
3943
- if (diff.needAddOnTop && !isFromScrollTo) {
3944
- const remeasureAddedIndics = diff.changedIndexesOfTop;
3945
- if (isDebug) {
3946
- debugLog('log', 'needAddOnTop to remeasure heights: ', remeasureAddedIndics);
3947
- }
3948
- const startIndexBeforeAdd = diff.changedIndexesOfTop[diff.changedIndexesOfTop.length - 1] + 1;
3949
- const topHeightBeforeAdd = virtualView.accumulatedHeights[startIndexBeforeAdd];
3950
- const changed = measureHeightByIndics(this.editor, remeasureAddedIndics);
3951
- if (changed) {
3952
- const newHeights = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
3953
- const actualTopHeightAfterAdd = newHeights.accumulatedHeights[startIndexBeforeAdd];
3954
- const newTopHeight = virtualView.top - (actualTopHeightAfterAdd - topHeightBeforeAdd);
3955
- this.setVirtualSpaceHeight(newTopHeight);
3956
- if (isDebug) {
3957
- debugLog('log', `update top height since will add element in top(正数减去高度,负数代表增加高度): ${actualTopHeightAfterAdd - topHeightBeforeAdd}`);
3958
- }
3959
- }
3960
- }
3961
- if (this.editor.selection) {
3962
- this.toNativeSelection(false);
3963
- }
3965
+ // prevent updating native selection when active element is void element
3966
+ if (isTargetInsideVoid(this.editor, activeElement)) {
3967
+ return;
3968
+ }
3969
+ // when <Editable/> is being controlled through external value
3970
+ // then its children might just change - DOM responds to it on its own
3971
+ // but Slate's value is not being updated through any operation
3972
+ // and thus it doesn't transform selection on its own
3973
+ if (selection && !AngularEditor.hasRange(this.editor, selection)) {
3974
+ this.editor.selection = AngularEditor.toSlateRange(this.editor, domSelection, { exactMatch: false, suppressThrow: false });
3975
+ return;
3976
+ }
3977
+ // Otherwise the DOM selection is out of sync, so update it.
3978
+ const el = AngularEditor.toDOMNode(this.editor, this.editor);
3979
+ this.isUpdatingSelection = true;
3980
+ const newDomRange = selection && AngularEditor.toDOMRange(this.editor, selection);
3981
+ if (newDomRange) {
3982
+ // COMPAT: Since the DOM range has no concept of backwards/forwards
3983
+ // we need to check and do the right thing here.
3984
+ if (Range.isBackward(selection)) {
3985
+ // eslint-disable-next-line max-len
3986
+ domSelection.setBaseAndExtent(newDomRange.endContainer, newDomRange.endOffset, newDomRange.startContainer, newDomRange.startOffset);
3987
+ }
3988
+ else {
3989
+ // eslint-disable-next-line max-len
3990
+ domSelection.setBaseAndExtent(newDomRange.startContainer, newDomRange.startOffset, newDomRange.endContainer, newDomRange.endOffset);
3964
3991
  }
3965
3992
  }
3966
- if (isDebug) {
3967
- debugLog('log', 'tryUpdateVirtualViewport Anim end');
3993
+ else {
3994
+ domSelection.removeAllRanges();
3968
3995
  }
3969
- });
3970
- }
3971
- calculateVirtualViewport(visibleStates) {
3972
- const children = (this.editor.children || []);
3973
- if (!children.length || !this.isEnabledVirtualScroll()) {
3974
- return {
3975
- inViewportChildren: children,
3976
- inViewportIndics: [],
3977
- top: 0,
3978
- bottom: 0,
3979
- heights: []
3980
- };
3981
- }
3982
- const scrollTop = this.virtualScrollConfig.scrollTop;
3983
- let viewportHeight = this.virtualScrollConfig.viewportHeight ?? 0;
3984
- if (!viewportHeight) {
3985
- return {
3986
- inViewportChildren: [],
3987
- inViewportIndics: [],
3988
- top: 0,
3989
- bottom: 0,
3990
- heights: []
3991
- };
3992
- }
3993
- const elementLength = children.length;
3994
- if (!EDITOR_TO_BUSINESS_TOP.has(this.editor)) {
3995
- EDITOR_TO_BUSINESS_TOP.set(this.editor, 0);
3996
3996
  setTimeout(() => {
3997
- const virtualTopBoundingTop = this.virtualTopHeightElement.getBoundingClientRect()?.top ?? 0;
3998
- const businessTop = Math.ceil(virtualTopBoundingTop) +
3999
- Math.ceil(this.virtualScrollConfig.scrollTop) -
4000
- Math.floor(this.virtualScrollConfig.viewportBoundingTop);
4001
- EDITOR_TO_BUSINESS_TOP.set(this.editor, businessTop);
4002
- if (isDebug) {
4003
- debugLog('log', 'businessTop', businessTop);
4004
- this.virtualTopHeightElement.setAttribute('data-business-top', businessTop.toString());
3997
+ if (this.isEnabledVirtualScroll() &&
3998
+ !selection &&
3999
+ this.editor.selection &&
4000
+ autoScroll &&
4001
+ this.virtualScrollConfig.scrollContainer) {
4002
+ this.virtualScrollConfig.scrollContainer.scrollTop = this.virtualScrollConfig.scrollContainer.scrollTop + 100;
4003
+ this.isUpdatingSelection = false;
4004
+ return;
4005
4005
  }
4006
- }, 100);
4007
- }
4008
- const businessTop = getBusinessTop(this.editor);
4009
- const { heights, accumulatedHeights } = buildHeightsAndAccumulatedHeights(this.editor, visibleStates);
4010
- const totalHeight = accumulatedHeights[elementLength] + businessTop;
4011
- let startPosition = Math.max(scrollTop - businessTop, 0);
4012
- let endPosition = startPosition + viewportHeight;
4013
- if (scrollTop < businessTop) {
4014
- endPosition = startPosition + viewportHeight - (businessTop - scrollTop);
4006
+ else {
4007
+ // handle scrolling in setTimeout because of
4008
+ // dom should not have updated immediately after listRender's updating
4009
+ newDomRange && autoScroll && this.scrollSelectionIntoView(this.editor, newDomRange);
4010
+ // COMPAT: In Firefox, it's not enough to create a range, you also need
4011
+ // to focus the contenteditable element too. (2016/11/16)
4012
+ if (newDomRange && IS_FIREFOX) {
4013
+ el.focus();
4014
+ }
4015
+ }
4016
+ this.isUpdatingSelection = false;
4017
+ });
4015
4018
  }
4016
- let accumulatedOffset = 0;
4017
- let inViewportStartIndex = -1;
4018
- const visible = [];
4019
- const inViewportIndics = [];
4020
- for (let i = 0; i < elementLength && accumulatedOffset < endPosition; i++) {
4021
- const currentHeight = heights[i];
4022
- const nextOffset = accumulatedOffset + currentHeight;
4023
- const isVisible = visibleStates[i];
4024
- if (!isVisible) {
4025
- accumulatedOffset = nextOffset;
4026
- continue;
4027
- }
4028
- // 可视区域有交集,加入渲染
4029
- if (nextOffset > startPosition && accumulatedOffset < endPosition) {
4030
- if (inViewportStartIndex === -1)
4031
- inViewportStartIndex = i; // 第一个相交起始位置
4032
- visible.push(children[i]);
4033
- inViewportIndics.push(i);
4034
- }
4035
- accumulatedOffset = nextOffset;
4019
+ catch (error) {
4020
+ this.editor.onError({
4021
+ code: SlateErrorCode.ToNativeSelectionError,
4022
+ nativeError: error
4023
+ });
4024
+ this.isUpdatingSelection = false;
4036
4025
  }
4037
- const inViewportEndIndex = inViewportStartIndex === -1 ? elementLength - 1 : (inViewportIndics[inViewportIndics.length - 1] ?? inViewportStartIndex);
4038
- const top = inViewportStartIndex === -1 ? 0 : accumulatedHeights[inViewportStartIndex];
4039
- const bottom = totalHeight - accumulatedHeights[inViewportEndIndex + 1];
4040
- return {
4041
- inViewportChildren: visible.length ? visible : children,
4042
- inViewportIndics,
4043
- top,
4044
- bottom,
4045
- heights,
4046
- accumulatedHeights
4047
- };
4048
4026
  }
4049
- applyVirtualView(virtualView) {
4050
- this.inViewportChildren = virtualView.inViewportChildren;
4051
- this.setVirtualSpaceHeight(virtualView.top, virtualView.bottom);
4052
- this.inViewportIndics = virtualView.inViewportIndics;
4027
+ onChange() {
4028
+ this.forceRender();
4029
+ this.onChangeCallback(this.editor.children);
4053
4030
  }
4054
- diffVirtualViewport(virtualView, stage = 'first') {
4055
- if (!this.inViewportChildren.length) {
4056
- if (isDebug) {
4057
- debugLog('log', 'diffVirtualViewport', stage, 'empty inViewportChildren', virtualView.inViewportIndics);
4058
- }
4059
- return {
4060
- isDifferent: true,
4061
- changedIndexesOfTop: [],
4062
- changedIndexesOfBottom: []
4063
- };
4031
+ ngAfterViewChecked() { }
4032
+ ngDoCheck() { }
4033
+ forceRender() {
4034
+ this.updateContext();
4035
+ if (this.isEnabledVirtualScroll()) {
4036
+ this.updateListRenderAndRemeasureHeights();
4064
4037
  }
4065
- const oldIndexesInViewport = [...this.inViewportIndics];
4066
- const newIndexesInViewport = [...virtualView.inViewportIndics];
4067
- const firstNewIndex = newIndexesInViewport[0];
4068
- const lastNewIndex = newIndexesInViewport[newIndexesInViewport.length - 1];
4069
- const firstOldIndex = oldIndexesInViewport[0];
4070
- const lastOldIndex = oldIndexesInViewport[oldIndexesInViewport.length - 1];
4071
- const isSameViewport = oldIndexesInViewport.length === newIndexesInViewport.length &&
4072
- oldIndexesInViewport.every((index, i) => index === newIndexesInViewport[i]);
4073
- if (firstNewIndex === firstOldIndex && lastNewIndex === lastOldIndex) {
4074
- return {
4075
- isDifferent: !isSameViewport,
4076
- changedIndexesOfTop: [],
4077
- changedIndexesOfBottom: []
4078
- };
4038
+ else {
4039
+ this.listRender.update(this.editor.children, this.editor, this.context);
4079
4040
  }
4080
- if (firstNewIndex !== firstOldIndex || lastNewIndex !== lastOldIndex) {
4081
- const changedIndexesOfTop = [];
4082
- const changedIndexesOfBottom = [];
4083
- const needRemoveOnTop = firstNewIndex !== firstOldIndex && firstNewIndex > firstOldIndex;
4084
- const needAddOnTop = firstNewIndex !== firstOldIndex && firstNewIndex < firstOldIndex;
4085
- const needRemoveOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex > lastNewIndex;
4086
- const needAddOnBottom = lastNewIndex !== lastOldIndex && lastOldIndex < lastNewIndex;
4087
- if (needRemoveOnTop || needAddOnBottom) {
4088
- // 向下
4089
- for (let index = 0; index < oldIndexesInViewport.length; index++) {
4090
- const element = oldIndexesInViewport[index];
4091
- if (!newIndexesInViewport.includes(element)) {
4092
- changedIndexesOfTop.push(element);
4093
- }
4094
- else {
4095
- break;
4096
- }
4097
- }
4098
- for (let index = newIndexesInViewport.length - 1; index >= 0; index--) {
4099
- const element = newIndexesInViewport[index];
4100
- if (!oldIndexesInViewport.includes(element)) {
4101
- changedIndexesOfBottom.push(element);
4041
+ // repair collaborative editing when Chinese input is interrupted by other users' cursors
4042
+ // when the DOMElement where the selection is located is removed
4043
+ // the compositionupdate and compositionend events will no longer be fired
4044
+ // so isComposing needs to be corrected
4045
+ // need exec after this.cdr.detectChanges() to render HTML
4046
+ // need exec before this.toNativeSelection() to correct native selection
4047
+ if (this.isComposing) {
4048
+ // Composition input text be not rendered when user composition input with selection is expanded
4049
+ // At this time, the following matching conditions are met, assign isComposing to false, and the status is wrong
4050
+ // this time condition is true and isComposing is assigned false
4051
+ // Therefore, need to wait for the composition input text to be rendered before performing condition matching
4052
+ setTimeout(() => {
4053
+ const textNode = Node.get(this.editor, this.editor.selection.anchor.path);
4054
+ const textDOMNode = AngularEditor.toDOMNode(this.editor, textNode);
4055
+ let textContent = '';
4056
+ // skip decorate text
4057
+ textDOMNode.querySelectorAll('[editable-text]').forEach(stringDOMNode => {
4058
+ let text = stringDOMNode.textContent;
4059
+ const zeroChar = '\uFEFF';
4060
+ // remove zero with char
4061
+ if (text.startsWith(zeroChar)) {
4062
+ text = text.slice(1);
4102
4063
  }
4103
- else {
4104
- break;
4064
+ if (text.endsWith(zeroChar)) {
4065
+ text = text.slice(0, text.length - 1);
4105
4066
  }
4067
+ textContent += text;
4068
+ });
4069
+ if (Node.string(textNode).endsWith(textContent)) {
4070
+ this.isComposing = false;
4106
4071
  }
4072
+ }, 0);
4073
+ }
4074
+ if (this.editor.selection && this.isSelectionInvisible(this.editor.selection)) {
4075
+ Transforms.deselect(this.editor);
4076
+ return;
4077
+ }
4078
+ else {
4079
+ this.toNativeSelection();
4080
+ }
4081
+ }
4082
+ render() {
4083
+ const changed = this.updateContext();
4084
+ if (changed) {
4085
+ if (this.isEnabledVirtualScroll()) {
4086
+ this.updateListRenderAndRemeasureHeights();
4107
4087
  }
4108
- else if (needAddOnTop || needRemoveOnBottom) {
4109
- // 向上
4110
- for (let index = 0; index < newIndexesInViewport.length; index++) {
4111
- const element = newIndexesInViewport[index];
4112
- if (!oldIndexesInViewport.includes(element)) {
4113
- changedIndexesOfTop.push(element);
4114
- }
4115
- else {
4116
- break;
4117
- }
4088
+ else {
4089
+ this.listRender.update(this.editor.children, this.editor, this.context);
4090
+ }
4091
+ }
4092
+ }
4093
+ updateListRenderAndRemeasureHeights() {
4094
+ const operations = this.editor.operations;
4095
+ const firstIndex = this.inViewportIndics[0];
4096
+ const operationsOfFirstElementMerged = operations.filter(op => op.type === 'merge_node' && op.path.length === 1 && firstIndex === op.path[0] - 1);
4097
+ const operationsOfFirstElementSplitted = operations.filter(op => op.type === 'split_node' && op.path.length === 1 && firstIndex === op.path[0]);
4098
+ const mutationOfFirstElementHeight = operationsOfFirstElementSplitted.length > 0 || operationsOfFirstElementMerged.length > 0;
4099
+ const visibleStates = this.editor.getAllVisibleStates();
4100
+ const previousInViewportChildren = [...this.inViewportChildren];
4101
+ // the first element height will reset to default height when split or merge
4102
+ // if the most top content of the first element is not in viewport, the change of height will cause the viewport to scroll
4103
+ // to keep viewport stable, we need to use the current inViewportIndics temporarily
4104
+ if (mutationOfFirstElementHeight) {
4105
+ const newInViewportIndics = [];
4106
+ const newInViewportChildren = [];
4107
+ this.inViewportIndics.forEach(index => {
4108
+ const element = this.editor.children[index];
4109
+ const isVisible = visibleStates[index];
4110
+ if (isVisible) {
4111
+ newInViewportIndics.push(index);
4112
+ newInViewportChildren.push(element);
4118
4113
  }
4119
- for (let index = oldIndexesInViewport.length - 1; index >= 0; index--) {
4120
- const element = oldIndexesInViewport[index];
4121
- if (!newIndexesInViewport.includes(element)) {
4122
- changedIndexesOfBottom.push(element);
4123
- }
4124
- else {
4114
+ });
4115
+ if (operationsOfFirstElementSplitted.length > 0) {
4116
+ const lastIndex = newInViewportIndics[newInViewportIndics.length - 1];
4117
+ for (let i = lastIndex + 1; i < this.editor.children.length; i++) {
4118
+ const element = this.editor.children[i];
4119
+ const isVisible = visibleStates[i];
4120
+ if (isVisible) {
4121
+ newInViewportIndics.push(i);
4122
+ newInViewportChildren.push(element);
4125
4123
  break;
4126
4124
  }
4127
4125
  }
4128
4126
  }
4127
+ this.inViewportIndics = newInViewportIndics;
4128
+ this.inViewportChildren = newInViewportChildren;
4129
4129
  if (isDebug) {
4130
- debugLog('log', `====== diffVirtualViewport stage: ${stage} ======`);
4131
- debugLog('log', 'oldIndexesInViewport:', oldIndexesInViewport);
4132
- debugLog('log', 'newIndexesInViewport:', newIndexesInViewport);
4133
- // this.editor.children[index] will be undefined when it is removed
4134
- debugLog('log', 'changedIndexesOfTop:', needRemoveOnTop ? '-' : needAddOnTop ? '+' : '-', changedIndexesOfTop, changedIndexesOfTop.map(index => (this.editor.children[index] &&
4135
- getCachedHeightByElement(this.editor, this.editor.children[index])) ||
4136
- 0));
4137
- debugLog('log', 'changedIndexesOfBottom:', needAddOnBottom ? '+' : needRemoveOnBottom ? '-' : '+', changedIndexesOfBottom, changedIndexesOfBottom.map(index => (this.editor.children[index] &&
4138
- getCachedHeightByElement(this.editor, this.editor.children[index])) ||
4139
- 0));
4140
- const needTop = virtualView.heights.slice(0, newIndexesInViewport[0]).reduce((acc, height) => acc + height, 0);
4141
- const needBottom = virtualView.heights
4142
- .slice(newIndexesInViewport[newIndexesInViewport.length - 1] + 1)
4143
- .reduce((acc, height) => acc + height, 0);
4144
- debugLog('log', needTop - parseFloat(this.virtualTopHeightElement.style.height), 'newTopHeight:', needTop, 'prevTopHeight:', parseFloat(this.virtualTopHeightElement.style.height));
4145
- debugLog('log', 'newBottomHeight:', needBottom, 'prevBottomHeight:', parseFloat(this.virtualBottomHeightElement.style.height));
4146
- debugLog('warn', '=========== Dividing line ===========');
4130
+ debugLog('log', 'updateListRenderAndRemeasureHeights', 'mutationOfFirstElementHeight', 'newInViewportIndics', newInViewportIndics);
4147
4131
  }
4148
- return {
4149
- isDifferent: true,
4150
- needRemoveOnTop,
4151
- needAddOnTop,
4152
- needRemoveOnBottom,
4153
- needAddOnBottom,
4154
- changedIndexesOfTop,
4155
- changedIndexesOfBottom
4132
+ }
4133
+ else {
4134
+ let virtualView = this.calculateVirtualViewport(visibleStates);
4135
+ let diff = this.diffVirtualViewport(virtualView, 'onChange');
4136
+ if (diff.isDifferent && diff.needRemoveOnTop) {
4137
+ const remeasureIndics = diff.changedIndexesOfTop;
4138
+ const changed = measureHeightByIndics(this.editor, remeasureIndics);
4139
+ if (changed) {
4140
+ virtualView = this.calculateVirtualViewport(visibleStates);
4141
+ diff = this.diffVirtualViewport(virtualView, 'second');
4142
+ }
4143
+ }
4144
+ this.applyVirtualView(virtualView);
4145
+ }
4146
+ const { preRenderingCount, childrenWithPreRendering, childrenWithPreRenderingIndics } = this.handlePreRendering(visibleStates);
4147
+ this.listRender.update(childrenWithPreRendering, this.editor, this.context, preRenderingCount, childrenWithPreRenderingIndics);
4148
+ const remeasureIndics = this.getChangedIndics(previousInViewportChildren);
4149
+ if (remeasureIndics.length) {
4150
+ this.indicsOfNeedBeMeasured$.next(remeasureIndics);
4151
+ }
4152
+ }
4153
+ updateContext() {
4154
+ const decorations = this.generateDecorations();
4155
+ if (this.context.selection !== this.editor.selection ||
4156
+ this.context.decorate !== this.decorate ||
4157
+ this.context.readonly !== this.readonly ||
4158
+ !isDecoratorRangeListEqual(this.context.decorations, decorations)) {
4159
+ this.context = {
4160
+ parent: this.editor,
4161
+ selection: this.editor.selection,
4162
+ decorations: decorations,
4163
+ decorate: this.decorate,
4164
+ readonly: this.readonly
4156
4165
  };
4166
+ return true;
4157
4167
  }
4158
- return {
4159
- isDifferent: false,
4160
- changedIndexesOfTop: [],
4161
- changedIndexesOfBottom: []
4168
+ return false;
4169
+ }
4170
+ initializeContext() {
4171
+ this.context = {
4172
+ parent: this.editor,
4173
+ selection: this.editor.selection,
4174
+ decorations: this.generateDecorations(),
4175
+ decorate: this.decorate,
4176
+ readonly: this.readonly
4162
4177
  };
4163
4178
  }
4164
- //#region event proxy
4165
- addEventListener(eventName, listener, target = this.elementRef.nativeElement) {
4166
- this.manualListeners.push(this.renderer2.listen(target, eventName, (event) => {
4167
- const beforeInputEvent = extractBeforeInputEvent(event.type, null, event, event.target);
4168
- if (beforeInputEvent) {
4169
- this.onFallbackBeforeInput(beforeInputEvent);
4170
- }
4171
- listener(event);
4172
- }));
4179
+ initializeViewContext() {
4180
+ this.viewContext = {
4181
+ editor: this.editor,
4182
+ renderElement: this.renderElement,
4183
+ renderLeaf: this.renderLeaf,
4184
+ renderText: this.renderText,
4185
+ trackBy: this.trackBy,
4186
+ isStrictDecorate: this.isStrictDecorate
4187
+ };
4188
+ }
4189
+ composePlaceholderDecorate(editor) {
4190
+ if (this.placeholderDecorate) {
4191
+ return this.placeholderDecorate(editor) || [];
4192
+ }
4193
+ if (this.placeholder && editor.children.length === 1 && Array.from(Node.texts(editor)).length === 1 && Node.string(editor) === '') {
4194
+ const start = Editor.start(editor, []);
4195
+ return [
4196
+ {
4197
+ placeholder: this.placeholder,
4198
+ anchor: start,
4199
+ focus: start
4200
+ }
4201
+ ];
4202
+ }
4203
+ else {
4204
+ return [];
4205
+ }
4206
+ }
4207
+ generateDecorations() {
4208
+ const decorations = this.decorate([this.editor, []]);
4209
+ const placeholderDecorations = this.isComposing ? [] : this.composePlaceholderDecorate(this.editor);
4210
+ decorations.push(...placeholderDecorations);
4211
+ return decorations;
4173
4212
  }
4174
4213
  toSlateSelection() {
4175
4214
  if ((!this.isComposing || IS_ANDROID) && !this.isUpdatingSelection && !this.isDraggingInternally) {