ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
297 lines • 9.54 kB
JavaScript
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) {}
}