From 834aa0ce1f0194a777c6a8ea7b508b1b3ca72fbd Mon Sep 17 00:00:00 2001 From: pubuzhixing8 Date: Fri, 12 Jan 2024 15:02:31 +0800 Subject: [PATCH] fix(list-render): support custom elements to set the position of children elements --- .changeset/calm-jokes-shout.md | 5 + .../editable-button.component.ts | 6 +- demo/app/components/link/link.component.ts | 6 +- .../children/children-outlet.component.ts | 14 ++ .../components/children/children.component.ts | 3 +- .../editable/editable.component.html | 2 +- .../components/editable/editable.component.ts | 10 +- packages/src/module.ts | 5 +- packages/src/testing/create-document.ts | 2 +- .../testing/editable-with-outlet.component.ts | 68 +++++++++ packages/src/testing/index.ts | 1 + packages/src/view/base.ts | 42 +++++- packages/src/view/container.spec.ts | 65 --------- packages/src/view/render/leaves-render.ts | 10 +- packages/src/view/render/list-render.spec.ts | 129 ++++++++++++++++++ packages/src/view/render/list-render.ts | 37 ++++- packages/src/view/render/utils.ts | 25 +++- 17 files changed, 327 insertions(+), 103 deletions(-) create mode 100644 .changeset/calm-jokes-shout.md create mode 100644 packages/src/components/children/children-outlet.component.ts create mode 100644 packages/src/testing/editable-with-outlet.component.ts delete mode 100644 packages/src/view/container.spec.ts create mode 100644 packages/src/view/render/list-render.spec.ts diff --git a/.changeset/calm-jokes-shout.md b/.changeset/calm-jokes-shout.md new file mode 100644 index 00000000..dada0d73 --- /dev/null +++ b/.changeset/calm-jokes-shout.md @@ -0,0 +1,5 @@ +--- +'slate-angular': patch +--- + +support custom elements to set the position of children elements diff --git a/demo/app/components/editable-button/editable-button.component.ts b/demo/app/components/editable-button/editable-button.component.ts index f549cd30..c512af21 100644 --- a/demo/app/components/editable-button/editable-button.component.ts +++ b/demo/app/components/editable-button/editable-button.component.ts @@ -1,13 +1,13 @@ import { ChangeDetectionStrategy, Component, HostListener } from '@angular/core'; import { ButtonElement } from '../../../../custom-types'; import { BaseElementComponent } from 'slate-angular'; -import { SlateChildren } from '../../../../packages/src/components/children/children.component'; +import { SlateChildrenOutlet } from '../../../../packages/src/components/children/children-outlet.component'; @Component({ selector: 'span[demo-element-button]', template: ` {{ inlineChromiumBugfix }} - + {{ inlineChromiumBugfix }} `, host: { @@ -15,7 +15,7 @@ import { SlateChildren } from '../../../../packages/src/components/children/chil }, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [SlateChildren] + imports: [SlateChildrenOutlet] }) export class DemoElementEditableButtonComponent extends BaseElementComponent { // Put this at the start and end of an inline component to work around this Chromium bug: diff --git a/demo/app/components/link/link.component.ts b/demo/app/components/link/link.component.ts index aa70d17e..19ecac63 100644 --- a/demo/app/components/link/link.component.ts +++ b/demo/app/components/link/link.component.ts @@ -1,18 +1,18 @@ import { ChangeDetectionStrategy, Component, HostBinding, HostListener } from '@angular/core'; import { LinkElement } from 'custom-types'; import { BaseElementComponent } from 'slate-angular'; -import { SlateChildren } from '../../../../packages/src/components/children/children.component'; +import { SlateChildrenOutlet } from '../../../../packages/src/components/children/children-outlet.component'; @Component({ selector: 'a[demo-element-link]', template: ` {{ inlineChromiumBugfix }} - + {{ inlineChromiumBugfix }} `, changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, - imports: [SlateChildren] + imports:[SlateChildrenOutlet] }) export class DemoElementLinkComponent extends BaseElementComponent { // Put this at the start and end of an inline component to work around this Chromium bug: diff --git a/packages/src/components/children/children-outlet.component.ts b/packages/src/components/children/children-outlet.component.ts new file mode 100644 index 00000000..5a7eb48b --- /dev/null +++ b/packages/src/components/children/children-outlet.component.ts @@ -0,0 +1,14 @@ +import { ChangeDetectionStrategy, Component, ElementRef } from '@angular/core'; + +@Component({ + selector: 'slate-children-outlet', + template: ``, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true +}) +export class SlateChildrenOutlet { + constructor(private elementRef: ElementRef) {} + getNativeElement() { + return this.elementRef.nativeElement; + } +} diff --git a/packages/src/components/children/children.component.ts b/packages/src/components/children/children.component.ts index 877008a0..444fccf2 100644 --- a/packages/src/components/children/children.component.ts +++ b/packages/src/components/children/children.component.ts @@ -8,8 +8,7 @@ import { NgFor } from '@angular/common'; selector: 'slate-children', template: ``, changeDetection: ChangeDetectionStrategy.OnPush, - standalone: true, - imports: [NgFor] + standalone: true }) export class SlateChildren extends ViewContainer { @Input() children: Descendant[]; diff --git a/packages/src/components/editable/editable.component.html b/packages/src/components/editable/editable.component.html index 15c0a739..24aa52e2 100644 --- a/packages/src/components/editable/editable.component.html +++ b/packages/src/components/editable/editable.component.html @@ -1 +1 @@ - + \ No newline at end of file diff --git a/packages/src/components/editable/editable.component.ts b/packages/src/components/editable/editable.component.ts index 55473de4..99d5815b 100644 --- a/packages/src/components/editable/editable.component.ts +++ b/packages/src/components/editable/editable.component.ts @@ -68,6 +68,7 @@ import { SlateDefaultLeaf } from '../leaf/default-leaf.component'; import { SLATE_DEFAULT_LEAF_COMPONENT_TOKEN } from '../leaf/token'; import { BaseElementComponent, BaseLeafComponent, BaseTextComponent } from '../../view/base'; import { ListRender } from '../../view/render/list-render'; +import { SlateChildrenOutlet } from '../children/children-outlet.component'; // not correctly clipboardData on beforeinput const forceOnDOMPaste = IS_SAFARI; @@ -108,7 +109,7 @@ const forceOnDOMPaste = IS_SAFARI; } ], standalone: true, - imports: [SlateChildren, SlateStringTemplate] + imports: [SlateChildren, SlateStringTemplate, SlateChildrenOutlet] }) export class SlateEditable implements OnInit, OnChanges, OnDestroy, AfterViewChecked, DoCheck { viewContext: SlateViewContext; @@ -191,7 +192,7 @@ export class SlateEditable implements OnInit, OnChanges, OnDestroy, AfterViewChe viewContainerRef = inject(ViewContainerRef); - getOutletElement = () => { + getOutletParent = () => { return this.elementRef.nativeElement; }; @@ -211,8 +212,7 @@ export class SlateEditable implements OnInit, OnChanges, OnDestroy, AfterViewChe public defaultVoidText: ComponentType, @Inject(SLATE_DEFAULT_LEAF_COMPONENT_TOKEN) public defaultLeaf: ComponentType - ) { - } + ) {} ngOnInit() { this.editor.injector = this.injector; @@ -240,7 +240,7 @@ export class SlateEditable implements OnInit, OnChanges, OnDestroy, AfterViewChe // add browser class let browserClass = IS_FIREFOX ? 'firefox' : IS_SAFARI ? 'safari' : ''; browserClass && this.elementRef.nativeElement.classList.add(browserClass); - this.listRender = new ListRender(this.viewContext, this.viewContainerRef, this.getOutletElement); + this.listRender = new ListRender(this.viewContext, this.viewContainerRef, this.getOutletParent, () => null); } ngOnChanges(simpleChanges: SimpleChanges) { diff --git a/packages/src/module.ts b/packages/src/module.ts index 0f5e24c2..1dc8636b 100644 --- a/packages/src/module.ts +++ b/packages/src/module.ts @@ -8,6 +8,7 @@ import { SlateDefaultElement } from './components/element/default-element.compon import { SlateString } from './components/string/string.component'; import { SlateStringTemplate } from './components/string/template.component'; import { SlateChildren } from './components/children/children.component'; +import { SlateChildrenOutlet } from './components/children/children-outlet.component'; import { SlateBlockCard } from './components/block-card/block-card.component'; import { SlateDefaultLeaf } from './components/leaf/default-leaf.component'; import { SlateLeaves } from './components/leaves/leaves.component'; @@ -28,11 +29,13 @@ import { SlateDefaultString } from './components/string/default-string.component SlateBlockCard, SlateLeaves, SlateDefaultLeaf, - SlateDefaultString + SlateDefaultString, + SlateChildrenOutlet ], exports: [ SlateEditable, SlateChildren, + SlateChildrenOutlet, SlateElement, SlateLeaves, SlateString, diff --git a/packages/src/testing/create-document.ts b/packages/src/testing/create-document.ts index 440d0f96..939e08a5 100644 --- a/packages/src/testing/create-document.ts +++ b/packages/src/testing/create-document.ts @@ -15,7 +15,7 @@ export function createDefaultDocument() { ]; } -export function createMutipleParagraph() { +export function createMultipleParagraph() { return [ { type: 'paragraph', diff --git a/packages/src/testing/editable-with-outlet.component.ts b/packages/src/testing/editable-with-outlet.component.ts new file mode 100644 index 00000000..9bac626c --- /dev/null +++ b/packages/src/testing/editable-with-outlet.component.ts @@ -0,0 +1,68 @@ +import { Component, ViewChild } from '@angular/core'; +import { createEditor, Element } from 'slate'; +import { SlateEditable } from '../components/editable/editable.component'; +import { withAngular } from '../plugins/with-angular'; +import { SlateChildrenOutlet } from '../components/children/children-outlet.component'; +import { BaseElementComponent } from '../view/base'; + +const customType = 'custom-with-outlet'; + +@Component({ + selector: 'editable-with-outlet', + template: ` ` +}) +export class EditableWithOutletComponent { + editor = withAngular(createEditor()); + + value: Element[] = createDefaultDocument() as Element[]; + + @ViewChild(SlateEditable, { static: true }) + editableComponent: SlateEditable; + + renderElement() { + return (element: Element) => { + if ((element.type as any) === customType) { + return TestElementWithOutletComponent; + } + return null; + }; + } + + ngModelChange() {} + + constructor() {} +} + +export function createDefaultDocument() { + return [ + { + type: customType, + children: [ + { + type: 'paragraph', + children: [{ text: 'This is editable text!' }] + } + ] + } + ]; +} + +@Component({ + selector: 'div[test-element-with-outlet]', + template: ` +
before
+ +
after
+ `, + host: { + class: 'test-element-with-outlet' + }, + standalone: true, + imports: [SlateChildrenOutlet] +}) +export class TestElementWithOutletComponent extends BaseElementComponent {} diff --git a/packages/src/testing/index.ts b/packages/src/testing/index.ts index 3f26a9be..5c6377ac 100644 --- a/packages/src/testing/index.ts +++ b/packages/src/testing/index.ts @@ -5,4 +5,5 @@ export * from './module'; export * from './basic-editable.component'; export * from './advanced-editable.component'; export * from './image-editable.component'; +export * from './editable-with-outlet.component'; export * from './leaf.component'; diff --git a/packages/src/view/base.ts b/packages/src/view/base.ts index 22aea597..5cfec4d5 100644 --- a/packages/src/view/base.ts +++ b/packages/src/view/base.ts @@ -1,4 +1,15 @@ -import { ChangeDetectorRef, Directive, ElementRef, HostBinding, Input, OnDestroy, OnInit, ViewContainerRef, inject } from '@angular/core'; +import { + ChangeDetectorRef, + Directive, + ElementRef, + HostBinding, + Input, + OnDestroy, + OnInit, + ViewChild, + ViewContainerRef, + inject +} from '@angular/core'; import { AngularEditor } from '../plugins/angular-editor'; import { ELEMENT_TO_COMPONENT, ELEMENT_TO_NODE, NODE_TO_ELEMENT } from '../utils/weak-maps'; import { SlateViewContext, SlateElementContext, SlateTextContext, SlateLeafContext } from './context'; @@ -7,6 +18,7 @@ import { SlateChildrenContext } from './context'; import { hasAfterContextChange, hasBeforeContextChange } from './context-change'; import { ListRender } from './render/list-render'; import { LeavesRender } from './render/leaves-render'; +import { SlateChildrenOutlet } from 'slate-angular/components/children/children-outlet.component'; /** * base class for template @@ -140,6 +152,9 @@ export class BaseElementComponent { + getOutletParent = () => { return this.elementRef.nativeElement; }; + getOutletElement = () => { + if (this.childrenOutletInstance) { + return this.childrenOutletInstance.getNativeElement(); + } + return null; + }; + listRender: ListRender; ngOnInit() { @@ -179,7 +201,7 @@ export class BaseElementComponent extends BaseComponent { + @ViewChild(SlateChildrenOutlet, { static: true }) + childrenOutletInstance?: SlateChildrenOutlet; + + getOutletParent = () => { return this.elementRef.nativeElement; }; + getOutletElement = () => { + if (this.childrenOutletInstance) { + return this.childrenOutletInstance.getNativeElement(); + } + return null; + }; + ngOnInit() { this.initialized = true; - this.leavesRender = new LeavesRender(this.viewContext, this.viewContainerRef, this.getOutletElement); + this.leavesRender = new LeavesRender(this.viewContext, this.viewContainerRef, this.getOutletParent, this.getOutletElement); this.leavesRender.initialize(this.context); } diff --git a/packages/src/view/container.spec.ts b/packages/src/view/container.spec.ts deleted file mode 100644 index 38327ca5..00000000 --- a/packages/src/view/container.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing'; -import { createMutipleParagraph } from '../testing/create-document'; -import { AdvancedEditableComponent, TestingLeafComponent, configureBasicEditableTestingModule } from '../testing'; -import { Editor, Transforms, Node } from 'slate'; -import { AngularEditor } from 'slate-angular'; - -describe('ViewContainer Class', () => { - let component: AdvancedEditableComponent; - let fixture: ComponentFixture; - let editor: Editor; - - beforeEach(fakeAsync(() => { - configureBasicEditableTestingModule([AdvancedEditableComponent, TestingLeafComponent], [TestingLeafComponent]); - fixture = TestBed.createComponent(AdvancedEditableComponent); - component = fixture.componentInstance; - component.value = createMutipleParagraph(); - editor = component.editor; - })); - - describe('move nodes', () => { - it('move node from [0] to [1]', fakeAsync(() => { - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - const parent = AngularEditor.toDOMNode(editor, editor); - Transforms.moveNodes(editor, { at: [0], to: [1] }); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - expect(Node.string(editor.children[0])).toEqual('1'); - const newP0 = parent.children.item(0) as HTMLElement; - const newP1 = parent.children.item(1) as HTMLElement; - expect(newP0.textContent).toEqual('1'); - expect(newP1.textContent).toEqual('0'); - })); - it('move node from [2] to [5]', fakeAsync(() => { - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - const parent = AngularEditor.toDOMNode(editor, editor); - Transforms.moveNodes(editor, { at: [2], to: [5] }); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - const newP5 = parent.children.item(5) as HTMLElement; - const newP3 = parent.children.item(3) as HTMLElement; - expect(newP5.textContent).toEqual('2'); - expect(newP3.textContent).toEqual('4'); - })); - it('move node from [5] to [2]', fakeAsync(() => { - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - const parent = AngularEditor.toDOMNode(editor, editor); - Transforms.moveNodes(editor, { at: [5], to: [2] }); - fixture.detectChanges(); - flush(); - fixture.detectChanges(); - const newP2 = parent.children.item(2) as HTMLElement; - const newP5 = parent.children.item(5) as HTMLElement; - expect(newP2.textContent).toEqual('5'); - expect(newP5.textContent).toEqual('4'); - })); - }); -}); diff --git a/packages/src/view/render/leaves-render.ts b/packages/src/view/render/leaves-render.ts index 81b57c31..02c9607e 100644 --- a/packages/src/view/render/leaves-render.ts +++ b/packages/src/view/render/leaves-render.ts @@ -14,6 +14,7 @@ export class LeavesRender { constructor( private viewContext: SlateViewContext, private viewContainerRef: ViewContainerRef, + private getOutletParent: () => HTMLElement, private getOutletElement: () => HTMLElement ) {} @@ -29,7 +30,7 @@ export class LeavesRender { this.contexts.push(context); this.viewTypes.push(viewType); }); - mount(this.views, null, this.getOutletElement()); + mount(this.views, null, this.getOutletParent(), this.getOutletElement()); const newDiffers = this.viewContainerRef.injector.get(IterableDiffers); this.differ = newDiffers.find(this.leaves).create(trackBy(this.viewContext)); this.differ.diff(this.leaves); @@ -37,9 +38,10 @@ export class LeavesRender { public update(context: SlateTextContext) { const { leaves, contexts } = this.getLeaves(context); - const outletElement = this.getOutletElement(); + const outletParent = this.getOutletParent(); const diffResult = this.differ.diff(leaves); if (diffResult) { + let firstRootNode = getRootNodes(this.views[0])[0]; const newContexts = []; const newViewTypes = []; const newViews = []; @@ -52,7 +54,7 @@ export class LeavesRender { view = createEmbeddedViewOrComponent(viewType, context, this.viewContext, this.viewContainerRef); newContexts.push(context); newViews.push(view); - mountOnItemChange(record.currentIndex, record.item, newViews, null, outletElement, this.viewContext); + mountOnItemChange(record.currentIndex, record.item, newViews, null, outletParent, firstRootNode, this.viewContext); } else { const previousView = this.views[record.previousIndex]; const previousViewType = this.viewTypes[record.previousIndex]; @@ -75,7 +77,7 @@ export class LeavesRender { view.destroy(); }); diffResult.forEachMovedItem(record => { - mountOnItemChange(record.currentIndex, record.item, newViews, null, outletElement, this.viewContext); + mountOnItemChange(record.currentIndex, record.item, newViews, null, outletParent, firstRootNode, this.viewContext); }); this.viewTypes = newViewTypes; this.views = newViews; diff --git a/packages/src/view/render/list-render.spec.ts b/packages/src/view/render/list-render.spec.ts new file mode 100644 index 00000000..b273b377 --- /dev/null +++ b/packages/src/view/render/list-render.spec.ts @@ -0,0 +1,129 @@ +import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing'; +import { createEmptyDocument, createMultipleParagraph } from '../../testing/create-document'; +import { + AdvancedEditableComponent, + EditableWithOutletComponent, + TestElementWithOutletComponent, + TestingLeafComponent, + configureBasicEditableTestingModule +} from '../../testing'; +import { Editor, Transforms, Node, Element } from 'slate'; +import { AngularEditor } from 'slate-angular'; + +describe('list-render', () => { + describe('move nodes', () => { + let component: AdvancedEditableComponent; + let fixture: ComponentFixture; + let editor: Editor; + + beforeEach(fakeAsync(() => { + configureBasicEditableTestingModule([AdvancedEditableComponent, TestingLeafComponent], [TestingLeafComponent]); + fixture = TestBed.createComponent(AdvancedEditableComponent); + component = fixture.componentInstance; + component.value = createMultipleParagraph(); + editor = component.editor; + })); + it('move node from [0] to [1]', fakeAsync(() => { + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const parent = AngularEditor.toDOMNode(editor, editor); + Transforms.moveNodes(editor, { at: [0], to: [1] }); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + expect(Node.string(editor.children[0])).toEqual('1'); + const newP0 = parent.children.item(0) as HTMLElement; + const newP1 = parent.children.item(1) as HTMLElement; + expect(newP0.textContent).toEqual('1'); + expect(newP1.textContent).toEqual('0'); + })); + it('move node from [2] to [5]', fakeAsync(() => { + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const parent = AngularEditor.toDOMNode(editor, editor); + Transforms.moveNodes(editor, { at: [2], to: [5] }); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const newP5 = parent.children.item(5) as HTMLElement; + const newP3 = parent.children.item(3) as HTMLElement; + expect(newP5.textContent).toEqual('2'); + expect(newP3.textContent).toEqual('4'); + })); + it('move node from [5] to [2]', fakeAsync(() => { + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const parent = AngularEditor.toDOMNode(editor, editor); + Transforms.moveNodes(editor, { at: [5], to: [2] }); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const newP2 = parent.children.item(2) as HTMLElement; + const newP5 = parent.children.item(5) as HTMLElement; + expect(newP2.textContent).toEqual('5'); + expect(newP5.textContent).toEqual('4'); + })); + fit('move node from [5] to [0]', fakeAsync(() => { + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const parent = AngularEditor.toDOMNode(editor, editor); + Transforms.moveNodes(editor, { at: [5], to: [0] }); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const newP0 = parent.children.item(0) as HTMLElement; + const newP5 = parent.children.item(5) as HTMLElement; + expect(newP0.textContent).toEqual('5'); + expect(newP5.textContent).toEqual('4'); + })); + }); + describe('children-outlet', () => { + let component: EditableWithOutletComponent; + let fixture: ComponentFixture; + let editor: Editor; + + beforeEach(fakeAsync(() => { + configureBasicEditableTestingModule([EditableWithOutletComponent], [TestElementWithOutletComponent]); + fixture = TestBed.createComponent(EditableWithOutletComponent); + component = fixture.componentInstance; + editor = component.editor; + })); + + fit('should render at correct position', fakeAsync(() => { + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const value = component.value; + const firstRoot = AngularEditor.toDOMNode(editor, value[0]); + expect(firstRoot.firstElementChild.textContent).toEqual('before'); + expect(firstRoot.lastElementChild.textContent).toEqual('after'); + expect(firstRoot.children.item(1).textContent).toEqual(Node.string(value[0])); + expect(firstRoot.children.length).toEqual(3); + })); + + fit('should render at correct position when insert at [0, 0]', fakeAsync(() => { + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const emptyElement = { + type: 'paragraph', + children: [{ text: 'first element' }] + } as Element; + Transforms.insertNodes(editor, emptyElement, { at: [0, 0] }); + fixture.detectChanges(); + flush(); + fixture.detectChanges(); + const value = component.value; + const firstRoot = AngularEditor.toDOMNode(editor, value[0]); + expect(firstRoot.firstElementChild.textContent).toEqual('before'); + expect(firstRoot.lastElementChild.textContent).toEqual('after'); + expect(firstRoot.children.item(1).textContent).toEqual(Node.string(value[0].children[0])); + expect(firstRoot.children.item(2).textContent).toEqual(Node.string(value[0].children[1])); + expect(firstRoot.children.length).toEqual(4); + })); + }); +}); diff --git a/packages/src/view/render/list-render.ts b/packages/src/view/render/list-render.ts index be4d94d5..1b183aca 100644 --- a/packages/src/view/render/list-render.ts +++ b/packages/src/view/render/list-render.ts @@ -21,7 +21,8 @@ export class ListRender { constructor( private viewContext: SlateViewContext, private viewContainerRef: ViewContainerRef, - private getOutletElement: () => HTMLElement + private getOutletParent: () => HTMLElement, + private getOutletElement: () => HTMLElement | null ) {} public initialize(children: Descendant[], parent: Ancestor, childrenContext: SlateChildrenContext) { @@ -40,7 +41,7 @@ export class ListRender { this.viewTypes.push(viewType); this.blockCards.push(blockCard); }); - mount(this.views, this.blockCards, this.getOutletElement()); + mount(this.views, this.blockCards, this.getOutletParent(), this.getOutletElement()); const newDiffers = this.viewContainerRef.injector.get(IterableDiffers); this.differ = newDiffers.find(children).create(trackBy(this.viewContext)); this.differ.diff(children); @@ -51,10 +52,11 @@ export class ListRender { this.initialize(children, parent, childrenContext); return; } - const outletElement = this.getOutletElement(); + const outletParent = this.getOutletParent(); const diffResult = this.differ.diff(children); const parentPath = AngularEditor.findPath(this.viewContext.editor, parent); if (diffResult) { + let firstRootNode = getRootNodes(this.views[0], this.blockCards[0])[0]; const newContexts = []; const newViewTypes = []; const newViews = []; @@ -73,7 +75,15 @@ export class ListRender { newContexts.push(context); newViews.push(view); newBlockCards.push(blockCard); - mountOnItemChange(record.currentIndex, record.item, newViews, newBlockCards, outletElement, this.viewContext); + mountOnItemChange( + record.currentIndex, + record.item, + newViews, + newBlockCards, + outletParent, + firstRootNode, + this.viewContext + ); } else { const previousView = this.views[record.previousIndex]; const previousViewType = this.viewTypes[record.previousIndex]; @@ -101,7 +111,7 @@ export class ListRender { newBlockCards.push(blockCard); } }); - diffResult.forEachOperation((record) => { + diffResult.forEachOperation(record => { // removed if (record.currentIndex === null) { const view = this.views[record.previousIndex]; @@ -111,7 +121,15 @@ export class ListRender { } // moved if (record.previousIndex !== null && record.currentIndex !== null) { - mountOnItemChange(record.currentIndex, record.item, newViews, newBlockCards, outletElement, this.viewContext); + mountOnItemChange( + record.currentIndex, + record.item, + newViews, + newBlockCards, + outletParent, + firstRootNode, + this.viewContext + ); // Solve the block-card DOMElement loss when moving nodes newBlockCards[record.currentIndex]?.instance.append(); } @@ -223,7 +241,12 @@ export function getViewType(item: Descendant, parent: Ancestor, viewContext: Sla } } -export function createBlockCard(item: Descendant, view: EmbeddedViewRef | ComponentRef, viewContainerRef: ViewContainerRef, viewContext: SlateViewContext) { +export function createBlockCard( + item: Descendant, + view: EmbeddedViewRef | ComponentRef, + viewContainerRef: ViewContainerRef, + viewContext: SlateViewContext +) { const isBlockCard = viewContext.editor.isBlockCard(item); if (isBlockCard) { const rootNodes = getRootNodes(view); diff --git a/packages/src/view/render/utils.ts b/packages/src/view/render/utils.ts index b9cecc70..c8cabe8b 100644 --- a/packages/src/view/render/utils.ts +++ b/packages/src/view/render/utils.ts @@ -59,15 +59,21 @@ export function updateContext( export function mount( views: (EmbeddedViewRef | ComponentRef)[], blockCards: (ComponentRef | null)[] | null, - outletElement: HTMLElement + outletParent: HTMLElement, + outletElement: HTMLElement | null ) { if (views.length > 0) { - const result = []; + const fragment = document.createDocumentFragment(); views.forEach((view, index) => { const blockCard = blockCards ? blockCards[index] : undefined; - result.push(...getRootNodes(view, blockCard)); + fragment.append(...getRootNodes(view, blockCard)); }); - outletElement.prepend(...result); + if (outletElement) { + outletParent.insertBefore(fragment, outletElement); + outletElement.remove(); + } else { + outletParent.prepend(fragment); + } } } @@ -102,7 +108,8 @@ export function mountOnItemChange( item: Descendant, views: (EmbeddedViewRef | ComponentRef)[], blockCards: (ComponentRef | null)[] | null, - outletElement: HTMLElement, + outletParent: HTMLElement, + firstRootNode: HTMLElement | null, viewContext: SlateViewContext ) { const view = views[index]; @@ -115,7 +122,13 @@ export function mountOnItemChange( } } if (index === 0) { - outletElement.prepend(...rootNodes); + if (firstRootNode) { + rootNodes.forEach(rootNode => { + firstRootNode.insertAdjacentElement('beforebegin', rootNode); + }); + } else { + outletParent.prepend(...rootNodes); + } } else { const previousView = views[index - 1]; const blockCard = blockCards ? blockCards[index - 1] : null;