UNPKG

ember-legacy-class-transform

Version:
374 lines 13.4 kB
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 }; } }