UNPKG

ember-source

Version:

A JavaScript framework for creating ambitious web applications

406 lines (400 loc) 11.7 kB
import { b as DOMOperations, d as CursorImpl, C as ConcreteBounds, e as dynamicAttribute, j as clear } from './dynamic-CuBsUXX8.js'; import { a as assert } from './assert-CUCJBR2C.js'; import { s as setLocalDebugType } from './debug-brand-B1TWjOCH.js'; import './debug-to-string-BsFOvUtQ.js'; import { S as StackImpl, e as expect } from './collections-B8me-ZlQ.js'; import '@embroider/macros'; import { destroy, registerDestructor } from '../@glimmer/destroyable/index.js'; class TreeConstruction extends DOMOperations { createElementNS(namespace, tag) { return this.document.createElementNS(namespace, tag); } setAttribute(element, name, value, namespace = null) { if (namespace) { element.setAttributeNS(namespace, name, value); } else { element.setAttribute(name, value); } } } const DOMTreeConstruction = TreeConstruction; class First { constructor(node) { this.node = node; } firstNode() { return this.node; } } class Last { constructor(node) { this.node = node; } lastNode() { return this.node; } } class NewTreeBuilder { dom; updateOperations; constructing = null; operations = null; env; cursors = new StackImpl(); modifierStack = new StackImpl(); blockStack = new StackImpl(); static forInitialRender(env, cursor) { return new this(env, cursor.element, cursor.nextSibling).initialize(); } static resume(env, block) { let parentNode = block.parentElement(); let nextSibling = block.reset(env); let stack = new this(env, parentNode, nextSibling).initialize(); stack.pushBlock(block); return stack; } constructor(env, parentNode, nextSibling) { this.pushElement(parentNode, nextSibling); this.env = env; this.dom = env.getAppendOperations(); this.updateOperations = env.getDOM(); } initialize() { this.pushAppendingBlock(); return this; } debugBlocks() { return this.blockStack.toArray(); } get element() { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme return this.cursors.current.element; } get nextSibling() { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme return this.cursors.current.nextSibling; } get hasBlocks() { return this.blockStack.size > 0; } block() { return expect(this.blockStack.current); } popElement() { this.cursors.pop(); expect(this.cursors.current); } pushAppendingBlock() { return this.pushBlock(new AppendingBlockImpl(this.element)); } pushResettableBlock() { return this.pushBlock(new ResettableBlockImpl(this.element)); } pushBlockList(list) { return this.pushBlock(new AppendingBlockList(this.element, list)); } pushBlock(block, isRemote = false) { let current = this.blockStack.current; if (current !== null) { if (!isRemote) { current.didAppendBounds(block); } } this.__openBlock(); this.blockStack.push(block); return block; } popBlock() { this.block().finalize(this); this.__closeBlock(); return expect(this.blockStack.pop()); } __openBlock() {} __closeBlock() {} // todo return seems unused openElement(tag) { let element = this.__openElement(tag); this.constructing = element; return element; } __openElement(tag) { return this.dom.createElement(tag, this.element); } flushElement(modifiers) { let parent = this.element; let element = expect(this.constructing); this.__flushElement(parent, element); this.constructing = null; this.operations = null; this.pushModifiers(modifiers); this.pushElement(element, null); this.didOpenElement(element); } __flushElement(parent, constructing) { this.dom.insertBefore(parent, constructing, this.nextSibling); } closeElement() { this.willCloseElement(); this.popElement(); return this.popModifiers(); } pushRemoteElement(element, guid, insertBefore) { return this.__pushRemoteElement(element, guid, insertBefore); } __pushRemoteElement(element, _guid, insertBefore) { this.pushElement(element, insertBefore); if (insertBefore === undefined) { while (element.lastChild) { element.removeChild(element.lastChild); } } let block = new RemoteBlock(element); return this.pushBlock(block, true); } popRemoteElement() { const block = this.popBlock(); this.popElement(); return block; } pushElement(element, nextSibling = null) { this.cursors.push(new CursorImpl(element, nextSibling)); } pushModifiers(modifiers) { this.modifierStack.push(modifiers); } popModifiers() { return this.modifierStack.pop(); } didAppendBounds(bounds) { this.block().didAppendBounds(bounds); return bounds; } didAppendNode(node) { this.block().didAppendNode(node); return node; } didOpenElement(element) { this.block().openElement(element); return element; } willCloseElement() { this.block().closeElement(); } appendText(string) { return this.didAppendNode(this.__appendText(string)); } __appendText(text) { let { dom, element, nextSibling } = this; let node = dom.createTextNode(text); dom.insertBefore(element, node, nextSibling); return node; } __appendNode(node) { this.dom.insertBefore(this.element, node, this.nextSibling); return node; } __appendFragment(fragment) { let first = fragment.firstChild; if (first) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme let ret = new ConcreteBounds(this.element, first, fragment.lastChild); this.dom.insertBefore(this.element, fragment, this.nextSibling); return ret; } else { const comment = this.__appendComment(''); return new ConcreteBounds(this.element, comment, comment); } } __appendHTML(html) { return this.dom.insertHTMLBefore(this.element, this.nextSibling, html); } appendDynamicHTML(value) { let bounds = this.trustedContent(value); this.didAppendBounds(bounds); } appendDynamicText(value) { let node = this.untrustedContent(value); this.didAppendNode(node); return node; } appendDynamicFragment(value) { let bounds = this.__appendFragment(value); this.didAppendBounds(bounds); } appendDynamicNode(value) { let node = this.__appendNode(value); let bounds = new ConcreteBounds(this.element, node, node); this.didAppendBounds(bounds); } trustedContent(value) { return this.__appendHTML(value); } untrustedContent(value) { return this.__appendText(value); } appendComment(string) { return this.didAppendNode(this.__appendComment(string)); } __appendComment(string) { let { dom, element, nextSibling } = this; let node = dom.createComment(string); dom.insertBefore(element, node, nextSibling); return node; } __setAttribute(name, value, namespace) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme this.dom.setAttribute(this.constructing, name, value, namespace); } __setProperty(name, value) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme this.constructing[name] = value; } setStaticAttribute(name, value, namespace) { this.__setAttribute(name, value, namespace); } setDynamicAttribute(name, value, trusting, namespace) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- @fixme let element = this.constructing; let attribute = dynamicAttribute(element, name, namespace, trusting); attribute.set(this, value, this.env); return attribute; } } class AppendingBlockImpl { first = null; last = null; nesting = 0; constructor(parent) { this.parent = parent; setLocalDebugType('block:simple', this); } parentElement() { return this.parent; } firstNode() { let first = expect(this.first); return first.firstNode(); } lastNode() { let last = expect(this.last); return last.lastNode(); } openElement(element) { this.didAppendNode(element); this.nesting++; } closeElement() { this.nesting--; } didAppendNode(node) { if (this.nesting !== 0) return; if (!this.first) { this.first = new First(node); } this.last = new Last(node); } didAppendBounds(bounds) { if (this.nesting !== 0) return; if (!this.first) { this.first = bounds; } this.last = bounds; } finalize(stack) { if (this.first === null) { stack.appendComment(''); } } } class RemoteBlock extends AppendingBlockImpl { constructor(parent) { super(parent); setLocalDebugType('block:remote', this); registerDestructor(this, () => { // In general, you only need to clear the root of a hierarchy, and should never // need to clear any child nodes. This is an important constraint that gives us // a strong guarantee that clearing a subtree is a single DOM operation. // // Because remote blocks are not normally physically nested inside of the tree // that they are logically nested inside, we manually clear remote blocks when // a logical parent is cleared. // // HOWEVER, it is currently possible for a remote block to be physically nested // inside of the block it is logically contained inside of. This happens when // the remote block is appended to the end of the application's entire element. // // The problem with that scenario is that Glimmer believes that it owns more of // the DOM than it actually does. The code is attempting to write past the end // of the Glimmer-managed root, but Glimmer isn't aware of that. // // The correct solution to that problem is for Glimmer to be aware of the end // of the bounds that it owns, and once we make that change, this check could // be removed. // // For now, a more targeted fix is to check whether the node was already removed // and avoid clearing the node if it was. In most cases this shouldn't happen, // so this might hide bugs where the code clears nested nodes unnecessarily, // so we should eventually try to do the correct fix. if (this.parentElement() === this.firstNode().parentNode) { clear(this); } }); } } class ResettableBlockImpl extends AppendingBlockImpl { constructor(parent) { super(parent); setLocalDebugType('block:resettable', this); } reset() { destroy(this); let nextSibling = clear(this); this.first = null; this.last = null; this.nesting = 0; return nextSibling; } } // FIXME: All the noops in here indicate a modelling problem class AppendingBlockList { constructor(parent, boundList) { this.parent = parent; this.boundList = boundList; this.parent = parent; this.boundList = boundList; } parentElement() { return this.parent; } firstNode() { let head = expect(this.boundList[0]); return head.firstNode(); } lastNode() { let boundList = this.boundList; let tail = expect(boundList[boundList.length - 1]); return tail.lastNode(); } openElement(_element) { } closeElement() { } didAppendNode(_node) { } didAppendBounds(_bounds) {} finalize(_stack) { assert(this.boundList.length > 0); } } function clientBuilder(env, cursor) { return NewTreeBuilder.forInitialRender(env, cursor); } export { DOMTreeConstruction as D, NewTreeBuilder as N, RemoteBlock as R, ResettableBlockImpl as a, clientBuilder as c };