UNPKG

ember-legacy-class-transform

Version:
297 lines 9.54 kB
import { clear } from './bounds'; import { Stack, assert, expect } from '@glimmer/util'; import { SimpleElementOperations } from './compiled/opcodes/dom'; class First { constructor(node) { this.node = node; } firstNode() { return this.node; } } class Last { constructor(node) { this.node = node; } lastNode() { return this.node; } } export class Fragment { constructor(bounds) { this.bounds = bounds; } parentElement() { return this.bounds.parentElement(); } firstNode() { return this.bounds.firstNode(); } lastNode() { return this.bounds.lastNode(); } update(bounds) { this.bounds = bounds; } } export class ElementStack { constructor(env, parentNode, nextSibling) { this.constructing = null; this.operations = null; this.elementStack = new Stack(); this.nextSiblingStack = new Stack(); this.blockStack = new Stack(); this.env = env; this.dom = env.getAppendOperations(); this.updateOperations = env.getDOM(); this.element = parentNode; this.nextSibling = nextSibling; this.defaultOperations = new SimpleElementOperations(env); this.pushSimpleBlock(); this.elementStack.push(this.element); this.nextSiblingStack.push(this.nextSibling); } static forInitialRender(env, parentNode, nextSibling) { return new ElementStack(env, parentNode, nextSibling); } static resume(env, tracker, nextSibling) { let parentNode = tracker.parentElement(); let stack = new ElementStack(env, parentNode, nextSibling); stack.pushBlockTracker(tracker); return stack; } expectConstructing(method) { return expect(this.constructing, `${method} should only be called while constructing an element`); } expectOperations(method) { return expect(this.operations, `${method} should only be called while constructing an element`); } block() { return expect(this.blockStack.current, "Expected a current block tracker"); } popElement() { let { elementStack, nextSiblingStack } = this; let topElement = elementStack.pop(); nextSiblingStack.pop(); // LOGGER.debug(`-> element stack ${this.elementStack.toArray().map(e => e.tagName).join(', ')}`); this.element = expect(elementStack.current, "can't pop past the last element"); this.nextSibling = nextSiblingStack.current; return topElement; } pushSimpleBlock() { let tracker = new SimpleBlockTracker(this.element); this.pushBlockTracker(tracker); return tracker; } pushUpdatableBlock() { let tracker = new UpdatableBlockTracker(this.element); this.pushBlockTracker(tracker); return tracker; } pushBlockTracker(tracker, isRemote = false) { let current = this.blockStack.current; if (current !== null) { current.newDestroyable(tracker); if (!isRemote) { current.newBounds(tracker); } } this.blockStack.push(tracker); return tracker; } pushBlockList(list) { let tracker = new BlockListTracker(this.element, list); let current = this.blockStack.current; if (current !== null) { current.newDestroyable(tracker); current.newBounds(tracker); } this.blockStack.push(tracker); return tracker; } popBlock() { this.block().finalize(this); return expect(this.blockStack.pop(), "Expected popBlock to return a block"); } openElement(tag, _operations) { // workaround argument.length transpile of arg initializer let operations = _operations === undefined ? this.defaultOperations : _operations; let element = this.dom.createElement(tag, this.element); this.constructing = element; this.operations = operations; return element; } flushElement() { let parent = this.element; let element = expect(this.constructing, `flushElement should only be called when constructing an element`); this.dom.insertBefore(parent, element, this.nextSibling); this.constructing = null; this.operations = null; this.pushElement(element, null); this.block().openElement(element); } pushRemoteElement(element, nextSibling = null) { this.pushElement(element, nextSibling); let tracker = new RemoteBlockTracker(element); this.pushBlockTracker(tracker, true); } popRemoteElement() { this.popBlock(); this.popElement(); } pushElement(element, nextSibling) { this.element = element; this.elementStack.push(element); // LOGGER.debug(`-> element stack ${this.elementStack.toArray().map(e => e.tagName).join(', ')}`); this.nextSibling = nextSibling; this.nextSiblingStack.push(nextSibling); } newDestroyable(d) { this.block().newDestroyable(d); } newBounds(bounds) { this.block().newBounds(bounds); } appendText(string) { let { dom } = this; let text = dom.createTextNode(string); dom.insertBefore(this.element, text, this.nextSibling); this.block().newNode(text); return text; } appendComment(string) { let { dom } = this; let comment = dom.createComment(string); dom.insertBefore(this.element, comment, this.nextSibling); this.block().newNode(comment); return comment; } setStaticAttribute(name, value) { this.expectOperations('setStaticAttribute').addStaticAttribute(this.expectConstructing('setStaticAttribute'), name, value); } setStaticAttributeNS(namespace, name, value) { this.expectOperations('setStaticAttributeNS').addStaticAttributeNS(this.expectConstructing('setStaticAttributeNS'), namespace, name, value); } setDynamicAttribute(name, reference, isTrusting) { this.expectOperations('setDynamicAttribute').addDynamicAttribute(this.expectConstructing('setDynamicAttribute'), name, reference, isTrusting); } setDynamicAttributeNS(namespace, name, reference, isTrusting) { this.expectOperations('setDynamicAttributeNS').addDynamicAttributeNS(this.expectConstructing('setDynamicAttributeNS'), namespace, name, reference, isTrusting); } closeElement() { this.block().closeElement(); this.popElement(); } } export class SimpleBlockTracker { constructor(parent) { this.parent = parent; this.first = null; this.last = null; this.destroyables = null; this.nesting = 0; } destroy() { let { destroyables } = this; if (destroyables && destroyables.length) { for (let i = 0; i < destroyables.length; i++) { destroyables[i].destroy(); } } } parentElement() { return this.parent; } firstNode() { return this.first && this.first.firstNode(); } lastNode() { return this.last && this.last.lastNode(); } openElement(element) { this.newNode(element); this.nesting++; } closeElement() { this.nesting--; } newNode(node) { if (this.nesting !== 0) return; if (!this.first) { this.first = new First(node); } this.last = new Last(node); } newBounds(bounds) { if (this.nesting !== 0) return; if (!this.first) { this.first = bounds; } this.last = bounds; } newDestroyable(d) { this.destroyables = this.destroyables || []; this.destroyables.push(d); } finalize(stack) { if (!this.first) { stack.appendComment(''); } } } class RemoteBlockTracker extends SimpleBlockTracker { destroy() { super.destroy(); clear(this); } } export class UpdatableBlockTracker extends SimpleBlockTracker { reset(env) { let { destroyables } = this; if (destroyables && destroyables.length) { for (let i = 0; i < destroyables.length; i++) { env.didDestroy(destroyables[i]); } } let nextSibling = clear(this); this.first = null; this.last = null; this.destroyables = null; this.nesting = 0; return nextSibling; } } class BlockListTracker { constructor(parent, boundList) { this.parent = parent; this.boundList = boundList; this.parent = parent; this.boundList = boundList; } destroy() { this.boundList.forEachNode(node => node.destroy()); } parentElement() { return this.parent; } firstNode() { let head = this.boundList.head(); return head && head.firstNode(); } lastNode() { let tail = this.boundList.tail(); return tail && tail.lastNode(); } openElement(_element) { assert(false, 'Cannot openElement directly inside a block list'); } closeElement() { assert(false, 'Cannot closeElement directly inside a block list'); } newNode(_node) { assert(false, 'Cannot create a new node directly inside a block list'); } newBounds(_bounds) {} newDestroyable(_d) {} finalize(_stack) {} }