Skip to content

Commit

Permalink
feat(shadow-dom): getting closer to consistent child observation
Browse files Browse the repository at this point in the history
  • Loading branch information
EisenbergEffect committed May 6, 2018
1 parent db361dc commit 02d71d6
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 180 deletions.
196 changes: 110 additions & 86 deletions scripts/app-bundle.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion scripts/app-bundle.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion scripts/vendor-bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -2145,4 +2145,4 @@ var requirejs, require, define;
}(this, (typeof setTimeout === 'undefined' ? undefined : setTimeout)));

_aureliaConfigureModuleLoader();
function _aureliaConfigureModuleLoader(){requirejs.config({"baseUrl":"src/","paths":{"root":"src","resources":"resources","elements":"resources/elements","attributes":"resources/attributes","valueConverters":"resources/value-converters","bindingBehaviors":"resources/binding-behaviors","app-bundle":"../scripts/app-bundle"},"packages":[],"stubModules":["text"],"shim":{},"bundles":{"app-bundle":["app-config","app","environment","generated-configuration","main","name-tag-config","name-tag","debug/configuration","debug/reporter","debug/task-queue","runtime/aurelia","runtime/decorators","runtime/di","runtime/dom","runtime/interfaces","runtime/platform","runtime/reporter","runtime/task-queue","debug/binding/binding-context","debug/binding/unparser","jit/binding/expression","runtime/binding/array-change-records","runtime/binding/array-observation","runtime/binding/ast","runtime/binding/binding-context","runtime/binding/binding-mode","runtime/binding/binding","runtime/binding/call","runtime/binding/checked-observer","runtime/binding/class-observer","runtime/binding/collection-observation","runtime/binding/connect-queue","runtime/binding/connectable-binding","runtime/binding/dirty-checker","runtime/binding/element-observation","runtime/binding/event-manager","runtime/binding/expression","runtime/binding/listener","runtime/binding/map-change-records","runtime/binding/map-observation","runtime/binding/observation","runtime/binding/observer-locator","runtime/binding/property-observation","runtime/binding/ref","runtime/binding/select-value-observer","runtime/binding/set-observation","runtime/binding/signal","runtime/binding/subscriber-collection","runtime/binding/svg-analyzer","runtime/configuration/standard","runtime/resources/attr-binding-behavior","runtime/resources/binding-mode-behaviors","runtime/resources/compose","runtime/resources/debounce-binding-behavior","runtime/resources/else","runtime/resources/if-core","runtime/resources/if","runtime/resources/replaceable","runtime/resources/sanitize","runtime/resources/self-binding-behavior","runtime/resources/signals","runtime/resources/throttle-binding-behavior","runtime/resources/update-trigger-binding-behavior","runtime/resources/with","runtime/templating/animator","runtime/templating/component","runtime/templating/instructions","runtime/templating/lifecycle","runtime/templating/render-slot","runtime/templating/shadow-dom","runtime/templating/view-engine","runtime/templating/view","svg/binding/svg-analyzer"]}})}
function _aureliaConfigureModuleLoader(){requirejs.config({"baseUrl":"src/","paths":{"root":"src","resources":"resources","elements":"resources/elements","attributes":"resources/attributes","valueConverters":"resources/value-converters","bindingBehaviors":"resources/binding-behaviors","app-bundle":"../scripts/app-bundle"},"packages":[],"stubModules":["text"],"shim":{},"bundles":{"app-bundle":["app-config","app","environment","generated-configuration","main","name-tag-config","name-tag","debug/configuration","debug/reporter","debug/task-queue","runtime/aurelia","runtime/decorators","runtime/di","runtime/dom","runtime/interfaces","runtime/platform","runtime/reporter","runtime/task-queue","debug/binding/binding-context","debug/binding/unparser","jit/binding/expression","runtime/configuration/standard","runtime/binding/array-change-records","runtime/binding/array-observation","runtime/binding/ast","runtime/binding/binding-context","runtime/binding/binding-mode","runtime/binding/binding","runtime/binding/call","runtime/binding/checked-observer","runtime/binding/class-observer","runtime/binding/collection-observation","runtime/binding/connect-queue","runtime/binding/connectable-binding","runtime/binding/dirty-checker","runtime/binding/element-observation","runtime/binding/event-manager","runtime/binding/expression","runtime/binding/listener","runtime/binding/map-change-records","runtime/binding/map-observation","runtime/binding/observation","runtime/binding/observer-locator","runtime/binding/property-observation","runtime/binding/ref","runtime/binding/select-value-observer","runtime/binding/set-observation","runtime/binding/signal","runtime/binding/subscriber-collection","runtime/binding/svg-analyzer","runtime/resources/attr-binding-behavior","runtime/resources/binding-mode-behaviors","runtime/resources/compose","runtime/resources/debounce-binding-behavior","runtime/resources/else","runtime/resources/if-core","runtime/resources/if","runtime/resources/replaceable","runtime/resources/sanitize","runtime/resources/self-binding-behavior","runtime/resources/signals","runtime/resources/throttle-binding-behavior","runtime/resources/update-trigger-binding-behavior","runtime/resources/with","runtime/templating/animator","runtime/templating/component","runtime/templating/instructions","runtime/templating/lifecycle","runtime/templating/render-slot","runtime/templating/shadow-dom","runtime/templating/view-engine","runtime/templating/view","svg/binding/svg-analyzer"]}})}
34 changes: 8 additions & 26 deletions src/runtime/dom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { IContainer, IResolver, DI } from "./di";
import { IElementComponent } from "./templating/component";
import { ContentView } from "./templating/view";

export const INode = DI.createInterface('INode');

Expand Down Expand Up @@ -52,30 +51,11 @@ export const DOM = {
return document.createComment('anchor');
},

createChildObserver(parent: INode, callback: () => void, options?: any): IChildObserver {
createChildObserver(parent: INode, onChildrenChanged: () => void, options?: any): IChildObserver {
if (DOM.isUsingSlotEmulation(parent)) {
let component = DOM.getComponentForNode(parent);
let contentView = <ContentView>component.$contentView;
let observer = {
get childNodes() {
return contentView.childNodes;
},
disconnect() {
callback = null;
}
};

// TODO: materialize content

contentView.notifyChildrenChanged = function() {
if (callback !== null) {
callback();
}
};

return observer;
return DOM.getComponentForNode(parent).$contentView.attachChildObserver(onChildrenChanged);
} else {
let observer = new MutationObserver(callback);
let observer = new MutationObserver(onChildrenChanged);
(<any>observer).childNodes = parent.childNodes;
observer.observe(<Node>parent, options || { childList: true });
return <any>observer;
Expand Down Expand Up @@ -120,9 +100,11 @@ export const DOM = {
return name ? name.toLowerCase() : null;
},

removeNode(node: INode) {
if (node.parentNode) {
(<Node>node.parentNode).removeChild(<Node>node);
remove(node: INodeLike) {
if ((<any>node).remove) {
(<any>node).remove();
} else if ((<Node>node).parentNode) {
(<Node>node).parentNode.removeChild(<Node>node);
}
},

Expand Down
1 change: 1 addition & 0 deletions src/runtime/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const PLATFORM = {
global: global,
emptyArray: <Array<any>>Object.freeze([]),
emptyObject: Object.freeze({}),
noop: function() {},

now(): number {
return performance.now();
Expand Down
10 changes: 5 additions & 5 deletions src/runtime/templating/component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ViewEngine, ITemplate, } from "./view-engine";
import { View, IViewOwner } from "./view";
import { View, IViewOwner, IContentView } from "./view";
import { TaskQueue } from "../task-queue";
import { Observer } from "../binding/property-observation";
import { IEmulatedShadowSlot, ShadowDOMEmulation } from "./shadow-dom";
Expand All @@ -16,7 +16,7 @@ import { INode, DOM, IView } from "../dom";

export interface IElementComponent extends IBindSelf, IAttach, IViewOwner {
$view: IView;
$contentView: IView;
$contentView: IContentView;
$slots: Record<string, IEmulatedShadowSlot>;
$usingSlotEmulation: boolean;

Expand Down Expand Up @@ -217,7 +217,7 @@ export const Component = {
$slots: Record<string, IEmulatedShadowSlot> = source.hasSlots ? {} : null;
$usingSlotEmulation = source.hasSlots || false;
$view: IView = null;
$contentView: IView = null;
$contentView: IContentView = null;
$slot: IRenderSlot = null;
$isAttached = false;
$isBound = false;
Expand Down Expand Up @@ -306,8 +306,8 @@ export const Component = {

//Native ShadowDOM would be distributed as soon as we append the view below.
//So, we emulate the distribution of nodes at the same time.
if (this.$contentView !== View.none && this.$slots) {
ShadowDOMEmulation.distributeView(this.$contentView, this.$slots);
if (this.$contentView !== null && this.$slots) {
ShadowDOMEmulation.distributeContent(this.$contentView, this.$slots);
}

if (source.containerless) {
Expand Down
52 changes: 33 additions & 19 deletions src/runtime/templating/render-slot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Reporter } from '../reporter';
import { IAttach, AttachContext, DetachContext } from './lifecycle';
import { DI } from '../di';
import { INode, IView } from '../dom';
import { ContentView } from './view';
import { IContentView } from './view';

function appendVisualToContainer(visual: IVisual, owner: RenderSlotImplementation) {
visual.$view.appendTo(owner.anchor);
Expand All @@ -18,15 +18,22 @@ function addVisualToList(visual: IVisual, owner: RenderSlotImplementation) {
}

function projectAddVisualToList(visual: IVisual, owner: RenderSlotImplementation) {
let contentView = owner.source;
let logicalView = owner.logicalView;
let childObserver = logicalView.childObserver;

if (contentView.notifyChildrenChanged) {
let contentNodes = contentView.childNodes;
let contentIndex = contentNodes.indexOf(owner.anchor) + 1;
if (childObserver) {
let contentNodes = logicalView.childNodes;
let contentIndex = contentNodes.indexOf(owner.anchor) - 1;
let projectedNodes = Array.from(visual.$view.childNodes);

projectedNodes.forEach((node: any) => {
if (node.$isContentProjectionSource) {
node.$slot.logicalView = logicalView;
}
});

contentNodes.splice(contentIndex, 0, ...projectedNodes);
contentView.notifyChildrenChanged();
childObserver.notifyChildrenChanged();
}

visual.$view.remove = () => ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
Expand All @@ -38,15 +45,22 @@ function insertVisualAtIndex(visual: IVisual, owner: RenderSlotImplementation, i
}

function projectInsertVisualAtIndex(visual: IVisual, owner: RenderSlotImplementation, index: number) {
let contentView = owner.source;
let logicalView = owner.logicalView;
let childObserver = logicalView.childObserver;

if (contentView.notifyChildrenChanged) {
let contentNodes = contentView.childNodes;
if (childObserver) {
let contentNodes = logicalView.childNodes;
let contentIndex = contentNodes.indexOf(owner.children[index].$view.firstChild);
let projectedNodes = Array.from(visual.$view.childNodes);

projectedNodes.forEach((node: any) => {
if (node.$isContentProjectionSource) {
node.$slot.logicalView = logicalView;
}
});

contentNodes.splice(index, 0, ...projectedNodes);
contentView.notifyChildrenChanged();
contentNodes.splice(contentIndex, 0, ...projectedNodes);
childObserver.notifyChildrenChanged();
}

visual.$view.remove = () => ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
Expand All @@ -58,15 +72,16 @@ function removeView(visual: IVisual, owner: RenderSlotImplementation) {
}

function projectRemoveView(visual: IVisual, owner: RenderSlotImplementation) {
let contentView = owner.source;
let logicalView = owner.logicalView;
let childObserver = logicalView.childObserver;

if (contentView.notifyChildrenChanged) {
let contentNodes = contentView.childNodes;
if (childObserver) {
let contentNodes = logicalView.childNodes;
let startIndex = contentNodes.indexOf(visual.$view.firstChild);
let endIndex = contentNodes.indexOf(visual.$view.lastChild);

contentNodes.splice(startIndex, (endIndex - startIndex) + 1);
contentView.notifyChildrenChanged();
childObserver.notifyChildrenChanged();
}

ShadowDOMEmulation.undistributeView(visual.$view, owner.slots, owner);
Expand Down Expand Up @@ -142,7 +157,7 @@ export interface IRenderSlot extends IAttach {
*/
removeMany(visualsToRemove: IVisual[], returnToCache?: boolean, skipAnimation?: boolean): void | IVisual[] | Promise<IVisual[]>;

/** @internal */ projectTo(slots: Record<string, IEmulatedShadowSlot>, source: IView): void;
/** @internal */ projectTo(slots: Record<string, IEmulatedShadowSlot>): void;
}

export const RenderSlot = {
Expand All @@ -159,7 +174,7 @@ class RenderSlotImplementation implements IRenderSlot {

/** @internal */ public children: IVisual[] = [];
/** @internal */ public slots: Record<string, IEmulatedShadowSlot> = null;
/** @internal */ public source: ContentView = null;
/** @internal */ public logicalView: IContentView = null;

constructor(public anchor: INode, anchorIsContainer: boolean) {
(<any>anchor).$slot = this; // Usage: Shadow DOM Emulation
Expand Down Expand Up @@ -347,12 +362,11 @@ class RenderSlotImplementation implements IRenderSlot {
}
}

projectTo(slots: Record<string, IEmulatedShadowSlot>, source: IView): void {
projectTo(slots: Record<string, IEmulatedShadowSlot>): void {
this.slots = slots;
this.addVisualCore = projectAddVisualToList;
this.insertVisualCore = projectInsertVisualAtIndex;
this.removeViewCore = projectRemoveView;
this.source = <ContentView>source;

if (this.$isAttached) {
const children = this.children;
Expand Down
61 changes: 35 additions & 26 deletions src/runtime/templating/shadow-dom.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { PLATFORM } from '../platform';;
import { IViewOwner } from './view';
import { IViewOwner, IContentView } from './view';
import { IRenderSlot } from './render-slot';
import { IVisual, IVisualFactory } from './view-engine';
import { IBindScope } from '../binding/observation';
Expand Down Expand Up @@ -31,7 +31,7 @@ export interface IEmulatedShadowSlot extends IBindScope, IAttach {
/** @internal */ projectFrom(view: IView, projectionSource: ProjectionSource);
/** @internal */ addNode(view: IView, node: SlotNode, projectionSource: ProjectionSource, index: number, destination?: string);
/** @internal */ renderFallback(view: IView, nodes: SlotNode[], projectionSource: ProjectionSource, index: number);
/** @internal */ projectTo?(slots: Record<string, IEmulatedShadowSlot>, source: IView);
/** @internal */ projectTo?(slots: Record<string, IEmulatedShadowSlot>);
}

const noNodes = <SlotNode[]>PLATFORM.emptyArray;
Expand Down Expand Up @@ -310,7 +310,7 @@ class ShadowSlot extends ShadowSlotBase implements IEmulatedShadowSlot {
return this.anchor;
}

projectTo(slots: Record<string, IEmulatedShadowSlot>, source: IView) {
projectTo(slots: Record<string, IEmulatedShadowSlot>) {
this.destinationSlots = slots;
}

Expand All @@ -337,7 +337,7 @@ function distributeNodes(view: IView, nodes: SlotNode[], slots: Record<string, I
const currentNode = nodes[i];

if (currentNode.$isContentProjectionSource) {
currentNode.$slot.projectTo(slots, view);
currentNode.$slot.projectTo(slots);

for (const slotName in slots) {
slots[slotName].projectFrom(view, currentNode.$slot);
Expand Down Expand Up @@ -391,8 +391,27 @@ function getSlotName(node: INode): string {
return name;
}

function viewToNodes(view: IView) {
let nodes: SlotNode[];

if (view === null) {
nodes = noNodes;
} else {
const childNodes = view.childNodes;
const ii = childNodes.length;

nodes = new Array(ii);

for (let i = 0; i < ii; ++i) {
nodes[i] = <SlotNode>childNodes[i];
}
}

return nodes;
}

export const ShadowDOMEmulation = {
createSlot(target: INode, owner: IViewOwner, name?: string, destination?: string, fallbackFactory?: IVisualFactory) {
createSlot(target: INode, owner: IViewOwner, name?: string, destination?: string, fallbackFactory?: IVisualFactory): IEmulatedShadowSlot {
const anchor = <SlotNode>DOM.createAnchor();

DOM.replaceNode(anchor, target);
Expand All @@ -404,30 +423,20 @@ export const ShadowDOMEmulation = {
}
},

distributeView(view: IView, slots: Record<string, IEmulatedShadowSlot>, projectionSource: ProjectionSource = null, index = 0, destinationOverride: string = null) {
let nodes;

if (view === null) {
nodes = noNodes;
} else {
const childNodes = view.childNodes;
const ii = childNodes.length;

nodes = new Array(ii);
distributeContent(content: IContentView, slots: Record<string, IEmulatedShadowSlot>): void {
let nodes = viewToNodes(content);

for (let i = 0; i < ii; ++i) {
nodes[i] = childNodes[i];
nodes.forEach(node => {
if (node.$isContentProjectionSource) {
(<any>node.$slot).logicalView = content;
}
}
});

distributeNodes(content, nodes, slots, null, 0, null);
},

distributeNodes(
view,
nodes,
slots,
projectionSource,
index,
destinationOverride
);
distributeView(view: IView, slots: Record<string, IEmulatedShadowSlot>, projectionSource: ProjectionSource = null, index = 0, destinationOverride: string = null) {
distributeNodes(view, viewToNodes(view), slots, projectionSource, index, destinationOverride);
},

undistributeView(view: IView, slots: Record<string, IEmulatedShadowSlot>, projectionSource: ProjectionSource) {
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/templating/view-engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ const interpreter: Record<string, InstructionApplicator> = <any>{
[TargetedInstructionType.textBinding](owner: IViewOwner, instruction: ITextBindingInstruction, target: INode, replacements: Record<string, ICompiledViewSource>, container: ITemplateContainer) {
let next = target.nextSibling;
DOM.treatAsNonWhitespace(next);
DOM.removeNode(target);
DOM.remove(target);
owner.$bindable.push(new Binding(Expression.from(instruction.src), next, 'textContent', BindingMode.oneWay, container));
},
[TargetedInstructionType.oneWayBinding](owner: IViewOwner, instruction: IOneWayBindingInstruction, target: INode, replacements: Record<string, ICompiledViewSource>, container: ITemplateContainer) {
Expand Down
Loading

0 comments on commit 02d71d6

Please sign in to comment.