ember-legacy-class-transform
Version:
The default blueprint for ember-cli addons.
374 lines • 13.4 kB
JavaScript
import { CachedReference, combineTagged, isConst as isConstReference, isModified, ReferenceCache } from '@glimmer/reference';
import { expect, unwrap } from '@glimmer/util';
import { APPEND_OPCODES, UpdatingOpcode } from '../../opcodes';
import { NULL_REFERENCE, PrimitiveReference } from '../../references';
import { Assert } from './vm';
APPEND_OPCODES.add(24 /* Text */, (vm, { op1: text }) => {
vm.elements().appendText(vm.constants.getString(text));
});
APPEND_OPCODES.add(25 /* Comment */, (vm, { op1: text }) => {
vm.elements().appendComment(vm.constants.getString(text));
});
APPEND_OPCODES.add(27 /* OpenElement */, (vm, { op1: tag }) => {
vm.elements().openElement(vm.constants.getString(tag));
});
APPEND_OPCODES.add(28 /* OpenElementWithOperations */, (vm, { op1: tag }) => {
let tagName = vm.constants.getString(tag);
let operations = vm.stack.pop();
vm.elements().openElement(tagName, operations);
});
APPEND_OPCODES.add(29 /* OpenDynamicElement */, vm => {
let operations = vm.stack.pop();
let tagName = vm.stack.pop().value();
vm.elements().openElement(tagName, operations);
});
APPEND_OPCODES.add(36 /* PushRemoteElement */, vm => {
let elementRef = vm.stack.pop();
let nextSiblingRef = vm.stack.pop();
let element;
let nextSibling;
if (isConstReference(elementRef)) {
element = elementRef.value();
} else {
let cache = new ReferenceCache(elementRef);
element = cache.peek();
vm.updateWith(new Assert(cache));
}
if (isConstReference(nextSiblingRef)) {
nextSibling = nextSiblingRef.value();
} else {
let cache = new ReferenceCache(nextSiblingRef);
nextSibling = cache.peek();
vm.updateWith(new Assert(cache));
}
vm.elements().pushRemoteElement(element, nextSibling);
});
APPEND_OPCODES.add(37 /* PopRemoteElement */, vm => vm.elements().popRemoteElement());
class ClassList {
constructor() {
this.list = null;
this.isConst = true;
}
append(reference) {
let { list, isConst } = this;
if (list === null) list = this.list = [];
list.push(reference);
this.isConst = isConst && isConstReference(reference);
}
toReference() {
let { list, isConst } = this;
if (!list) return NULL_REFERENCE;
if (isConst) return PrimitiveReference.create(toClassName(list));
return new ClassListReference(list);
}
}
class ClassListReference extends CachedReference {
constructor(list) {
super();
this.list = [];
this.tag = combineTagged(list);
this.list = list;
}
compute() {
return toClassName(this.list);
}
}
function toClassName(list) {
let ret = [];
for (let i = 0; i < list.length; i++) {
let value = list[i].value();
if (value !== false && value !== null && value !== undefined) ret.push(value);
}
return ret.length === 0 ? null : ret.join(' ');
}
export class SimpleElementOperations {
constructor(env) {
this.env = env;
this.opcodes = null;
this.classList = null;
}
addStaticAttribute(element, name, value) {
if (name === 'class') {
this.addClass(PrimitiveReference.create(value));
} else {
this.env.getAppendOperations().setAttribute(element, name, value);
}
}
addStaticAttributeNS(element, namespace, name, value) {
this.env.getAppendOperations().setAttribute(element, name, value, namespace);
}
addDynamicAttribute(element, name, reference, isTrusting) {
if (name === 'class') {
this.addClass(reference);
} else {
let attributeManager = this.env.attributeFor(element, name, isTrusting);
let attribute = new DynamicAttribute(element, attributeManager, name, reference);
this.addAttribute(attribute);
}
}
addDynamicAttributeNS(element, namespace, name, reference, isTrusting) {
let attributeManager = this.env.attributeFor(element, name, isTrusting, namespace);
let nsAttribute = new DynamicAttribute(element, attributeManager, name, reference, namespace);
this.addAttribute(nsAttribute);
}
flush(element, vm) {
let { env } = vm;
let { opcodes, classList } = this;
for (let i = 0; opcodes && i < opcodes.length; i++) {
vm.updateWith(opcodes[i]);
}
if (classList) {
let attributeManager = env.attributeFor(element, 'class', false);
let attribute = new DynamicAttribute(element, attributeManager, 'class', classList.toReference());
let opcode = attribute.flush(env);
if (opcode) {
vm.updateWith(opcode);
}
}
this.opcodes = null;
this.classList = null;
}
addClass(reference) {
let { classList } = this;
if (!classList) {
classList = this.classList = new ClassList();
}
classList.append(reference);
}
addAttribute(attribute) {
let opcode = attribute.flush(this.env);
if (opcode) {
let { opcodes } = this;
if (!opcodes) {
opcodes = this.opcodes = [];
}
opcodes.push(opcode);
}
}
}
export class ComponentElementOperations {
constructor(env) {
this.env = env;
this.attributeNames = null;
this.attributes = null;
this.classList = null;
}
addStaticAttribute(element, name, value) {
if (name === 'class') {
this.addClass(PrimitiveReference.create(value));
} else if (this.shouldAddAttribute(name)) {
this.addAttribute(name, new StaticAttribute(element, name, value));
}
}
addStaticAttributeNS(element, namespace, name, value) {
if (this.shouldAddAttribute(name)) {
this.addAttribute(name, new StaticAttribute(element, name, value, namespace));
}
}
addDynamicAttribute(element, name, reference, isTrusting) {
if (name === 'class') {
this.addClass(reference);
} else if (this.shouldAddAttribute(name)) {
let attributeManager = this.env.attributeFor(element, name, isTrusting);
let attribute = new DynamicAttribute(element, attributeManager, name, reference);
this.addAttribute(name, attribute);
}
}
addDynamicAttributeNS(element, namespace, name, reference, isTrusting) {
if (this.shouldAddAttribute(name)) {
let attributeManager = this.env.attributeFor(element, name, isTrusting, namespace);
let nsAttribute = new DynamicAttribute(element, attributeManager, name, reference, namespace);
this.addAttribute(name, nsAttribute);
}
}
flush(element, vm) {
let { env } = this;
let { attributes, classList } = this;
for (let i = 0; attributes && i < attributes.length; i++) {
let opcode = attributes[i].flush(env);
if (opcode) {
vm.updateWith(opcode);
}
}
if (classList) {
let attributeManager = env.attributeFor(element, 'class', false);
let attribute = new DynamicAttribute(element, attributeManager, 'class', classList.toReference());
let opcode = attribute.flush(env);
if (opcode) {
vm.updateWith(opcode);
}
}
}
shouldAddAttribute(name) {
return !this.attributeNames || this.attributeNames.indexOf(name) === -1;
}
addClass(reference) {
let { classList } = this;
if (!classList) {
classList = this.classList = new ClassList();
}
classList.append(reference);
}
addAttribute(name, attribute) {
let { attributeNames, attributes } = this;
if (!attributeNames) {
attributeNames = this.attributeNames = [];
attributes = this.attributes = [];
}
attributeNames.push(name);
unwrap(attributes).push(attribute);
}
}
APPEND_OPCODES.add(33 /* FlushElement */, vm => {
let stack = vm.elements();
let action = 'FlushElementOpcode#evaluate';
stack.expectOperations(action).flush(stack.expectConstructing(action), vm);
stack.flushElement();
});
APPEND_OPCODES.add(34 /* CloseElement */, vm => vm.elements().closeElement());
APPEND_OPCODES.add(30 /* StaticAttr */, (vm, { op1: _name, op2: _value, op3: _namespace }) => {
let name = vm.constants.getString(_name);
let value = vm.constants.getString(_value);
if (_namespace) {
let namespace = vm.constants.getString(_namespace);
vm.elements().setStaticAttributeNS(namespace, name, value);
} else {
vm.elements().setStaticAttribute(name, value);
}
});
APPEND_OPCODES.add(35 /* Modifier */, (vm, { op1: _manager }) => {
let manager = vm.constants.getOther(_manager);
let stack = vm.stack;
let args = stack.pop();
let tag = args.tag;
let { constructing: element, updateOperations } = vm.elements();
let dynamicScope = vm.dynamicScope();
let modifier = manager.create(element, args, dynamicScope, updateOperations);
args.clear();
vm.env.scheduleInstallModifier(modifier, manager);
let destructor = manager.getDestructor(modifier);
if (destructor) {
vm.newDestroyable(destructor);
}
vm.updateWith(new UpdateModifierOpcode(tag, manager, modifier));
});
export class UpdateModifierOpcode extends UpdatingOpcode {
constructor(tag, manager, modifier) {
super();
this.tag = tag;
this.manager = manager;
this.modifier = modifier;
this.type = 'update-modifier';
this.lastUpdated = tag.value();
}
evaluate(vm) {
let { manager, modifier, tag, lastUpdated } = this;
if (!tag.validate(lastUpdated)) {
vm.env.scheduleUpdateModifier(modifier, manager);
this.lastUpdated = tag.value();
}
}
toJSON() {
return {
guid: this._guid,
type: this.type
};
}
}
export class StaticAttribute {
constructor(element, name, value, namespace) {
this.element = element;
this.name = name;
this.value = value;
this.namespace = namespace;
}
flush(env) {
env.getAppendOperations().setAttribute(this.element, this.name, this.value, this.namespace);
return null;
}
}
export class DynamicAttribute {
constructor(element, attributeManager, name, reference, namespace) {
this.element = element;
this.attributeManager = attributeManager;
this.name = name;
this.reference = reference;
this.namespace = namespace;
this.cache = null;
this.tag = reference.tag;
}
patch(env) {
let { element, cache } = this;
let value = expect(cache, 'must patch after flush').revalidate();
if (isModified(value)) {
this.attributeManager.updateAttribute(env, element, value, this.namespace);
}
}
flush(env) {
let { reference, element } = this;
if (isConstReference(reference)) {
let value = reference.value();
this.attributeManager.setAttribute(env, element, value, this.namespace);
return null;
} else {
let cache = this.cache = new ReferenceCache(reference);
let value = cache.peek();
this.attributeManager.setAttribute(env, element, value, this.namespace);
return new PatchElementOpcode(this);
}
}
toJSON() {
let { element, namespace, name, cache } = this;
let formattedElement = formatElement(element);
let lastValue = expect(cache, 'must serialize after flush').peek();
if (namespace) {
return {
element: formattedElement,
lastValue,
name,
namespace,
type: 'attribute'
};
}
return {
element: formattedElement,
lastValue,
name,
namespace: namespace === undefined ? null : namespace,
type: 'attribute'
};
}
}
function formatElement(element) {
return JSON.stringify(`<${element.tagName.toLowerCase()} />`);
}
APPEND_OPCODES.add(32 /* DynamicAttrNS */, (vm, { op1: _name, op2: _namespace, op3: trusting }) => {
let name = vm.constants.getString(_name);
let namespace = vm.constants.getString(_namespace);
let reference = vm.stack.pop();
vm.elements().setDynamicAttributeNS(namespace, name, reference, !!trusting);
});
APPEND_OPCODES.add(31 /* DynamicAttr */, (vm, { op1: _name, op2: trusting }) => {
let name = vm.constants.getString(_name);
let reference = vm.stack.pop();
vm.elements().setDynamicAttribute(name, reference, !!trusting);
});
export class PatchElementOpcode extends UpdatingOpcode {
constructor(operation) {
super();
this.type = 'patch-element';
this.tag = operation.tag;
this.operation = operation;
}
evaluate(vm) {
this.operation.patch(vm.env);
}
toJSON() {
let { _guid, type, operation } = this;
return {
details: operation.toJSON(),
guid: _guid,
type
};
}
}