UNPKG

ember-material-icons

Version:

Google Material icons for your ember-cli app

352 lines (280 loc) 10.4 kB
import { CLASS_META } from '@glimmer/object-reference'; import { Dict, dict, assign } from '@glimmer/util'; import GlimmerObject, { GlimmerObjectFactory, ClassMeta, InstanceMeta, turbocharge } from './object'; import { ROOT } from './utils'; const { isArray } = Array; export const DESCRIPTOR = "5d90f84f-908e-4a42-9749-3d0f523c262c"; export const BLUEPRINT = "8d97cf5f-db9e-48d8-a6b2-7a75b7170805"; export abstract class Descriptor { "5d90f84f-908e-4a42-9749-3d0f523c262c" = true; abstract define(prototype: Object, key: string, home: Object); } export abstract class Blueprint { "8d97cf5f-db9e-48d8-a6b2-7a75b7170805" = true; abstract descriptor(target: Object, key: string, classMeta: ClassMeta): Descriptor; } export interface Extensions { concatenatedProperties?: string[] | string; mergedProperties?: string[] | string; _super?: Function; [index: string]: any; } export class Mixin { private extensions = null; private concatenatedProperties: string[] = []; private mergedProperties: string[] = []; private dependencies: Mixin[] = []; static create(...args: (Mixin | Extensions)[]) { let extensions = args[args.length - 1]; if (args.length === 0) { return new this({}, []); } else if (extensions instanceof Mixin) { return new this({}, <Mixin[]>args); } else { let deps = args.slice(0, -1).map(toMixin); return new this(<Extensions>extensions, deps); } } static mixins(obj: any): Mixin[] { if (typeof obj !== 'object' || obj === null) return []; let meta = ClassMeta.for(obj); if (!meta) return []; return meta.getAppliedMixins(); } constructor(extensions: Extensions, mixins: Mixin[]) { this.reopen(extensions); this.dependencies.push(...mixins); } detect(obj: any): boolean { if (typeof obj !== 'object' || obj === null) return false; if (obj instanceof Mixin) { return obj.dependencies.indexOf(this) !== -1; } let meta = ClassMeta.for(obj); return !!meta && meta.hasAppliedMixin(this); } reopen(extensions: Extensions) { if (this.extensions) { this.dependencies.push(toMixin(this.extensions)); } if (typeof extensions === 'object' && 'concatenatedProperties' in extensions) { let concat: string[]; let rawConcat = extensions.concatenatedProperties; if (isArray(rawConcat)) { concat = (<string[]>rawConcat).slice(); } else if (rawConcat === null || rawConcat === undefined) { concat = []; } else { concat = [<string>rawConcat]; } delete extensions.concatenatedProperties; this.concatenatedProperties = concat; } if (typeof extensions === 'object' && 'mergedProperties' in extensions) { let merged: string[]; let rawMerged = extensions.mergedProperties; if (isArray(rawMerged)) { merged = (<string[]>rawMerged).slice(); } else if (rawMerged === null || rawMerged === undefined) { merged = []; } else { merged = [<string>rawMerged]; } delete extensions.mergedProperties; this.mergedProperties = merged; } let normalized: Dict<Blueprint> = Object.keys(extensions).reduce((obj, key) => { let value = extensions[key]; switch (typeof value) { case 'function': obj[key] = new MethodBlueprint({ value }); break; case 'object': if (value && BLUEPRINT in value) { obj[key] = value; break; } /* falls through */ default: obj[key] = new DataBlueprint({ value }); } return obj; }, dict<Blueprint>()); this.extensions = dict<any>(); assign(this.extensions, turbocharge(normalized)); } apply(target: any) { let meta: ClassMeta = target[CLASS_META] = target[CLASS_META] || new ClassMeta(); this.dependencies.forEach(m => m.apply(target)); this.mergeProperties(target, target, meta); meta.addMixin(this); meta.seal(); meta.reseal(target); return target; } extendPrototype(Original: GlimmerObjectFactory<any>) { Original.prototype = Object.create(Original.prototype); this.dependencies.forEach(m => m.extendPrototype(Original)); this.extendPrototypeOnto(Original, Original); } extendPrototypeOnto(Subclass: GlimmerObjectFactory<any>, Parent: GlimmerObjectFactory<any>) { this.dependencies.forEach(m => m.extendPrototypeOnto(Subclass, Parent)); this.mergeProperties(Subclass.prototype, Parent.prototype, Subclass[CLASS_META]); Subclass[CLASS_META].addMixin(this); } extendStatic(Target: GlimmerObjectFactory<any>) { this.dependencies.forEach(m => m.extendStatic(Target)); this.mergeProperties(Target, Object.getPrototypeOf(Target), Target[CLASS_META][CLASS_META]); Target[CLASS_META].addStaticMixin(this); } mergeProperties(target: Object, parent: Object, meta: ClassMeta) { if (meta.hasAppliedMixin(this)) return; meta.addAppliedMixin(this); this.mergedProperties.forEach(k => meta.addMergedProperty(k, parent[k])); this.concatenatedProperties.forEach(k => meta.addConcatenatedProperty(k, [])); new ValueDescriptor({ value: meta.getConcatenatedProperties() }).define(target, <string>'concatenatedProperties', null); new ValueDescriptor({ value: meta.getMergedProperties() }).define(target, <string>'mergedProperties', null); Object.keys(this.extensions).forEach(key => { let extension: Blueprint = this.extensions[key]; let desc = extension.descriptor(target, <string>key, meta); desc.define(target, <string>key, parent); }); new ValueDescriptor({ value: ROOT }).define(target, <string>'_super', null); } } export type Extension = Mixin | Extensions; export function extend<T extends GlimmerObject>(Parent: GlimmerObjectFactory<T>, ...extensions: Extension[]): typeof GlimmerObject { let Super = <typeof GlimmerObject>Parent; let Subclass = class extends Super {}; Subclass[CLASS_META] = InstanceMeta.fromParent(Parent[CLASS_META]); let mixins = extensions.map(toMixin); Parent[CLASS_META].addSubclass(Subclass); mixins.forEach(m => Subclass[CLASS_META].addMixin(m)); ClassMeta.applyAllMixins(Subclass, Parent); return Subclass; } export function relinkSubclasses(Parent: GlimmerObjectFactory<any>) { Parent[CLASS_META].getSubclasses().forEach((Subclass: GlimmerObjectFactory<any>) => { Subclass[CLASS_META].reset(Parent[CLASS_META]); Subclass.prototype = Object.create(Parent.prototype); ClassMeta.applyAllMixins(Subclass, Parent); // recurse into sub-subclasses relinkSubclasses(Subclass); }); } export function toMixin(extension: Extension): Mixin { if (extension instanceof Mixin) return extension; else return new Mixin(<Object>extension, []); } class ValueDescriptor extends Descriptor { public enumerable: boolean; public configurable: boolean; public writable: boolean; public value: any; constructor({ enumerable=true, configurable=true, writable=true, value }: PropertyDescriptor) { super(); this.enumerable = enumerable; this.configurable = configurable; this.writable = writable; this.value = value; } define(target: Object, key: string, home: Object) { Object.defineProperty(target, key, { enumerable: this.enumerable, configurable: this.configurable, writable: this.writable, value: this.value }); } } export class DataBlueprint extends Blueprint { public enumerable: boolean; public configurable: boolean; public value: any; public writable: boolean; constructor({ enumerable=true, configurable=true, writable=true, value }: PropertyDescriptor) { super(); this.enumerable = enumerable; this.configurable = configurable; this.value = value; this.writable = writable; } descriptor(target: Object, key: string, classMeta: ClassMeta): Descriptor { let { enumerable, configurable, writable, value } = this; if (classMeta.hasConcatenatedProperty(<string>key)) { classMeta.addConcatenatedProperty(<string>key, value); value = classMeta.getConcatenatedProperty(<string>key); } else if (classMeta.hasMergedProperty(<string>key)) { classMeta.addMergedProperty(<string>key, value); value = classMeta.getMergedProperty(<string>key); } return new ValueDescriptor({ enumerable, configurable, writable, value }); } } export abstract class AccessorBlueprint extends Blueprint { public enumerable: boolean; public configurable: boolean; get: () => any; set: (value: any) => void; constructor({ enumerable=true, configurable=true, get, set }: PropertyDescriptor) { super(); this.enumerable = enumerable; this.configurable = configurable; this.get = get; this.set = set; } descriptor(target: Object, key: string, classMeta: ClassMeta): Descriptor { return new ValueDescriptor({ enumerable: this.enumerable, configurable: this.configurable, get: this.get, set: this.set }); } } class MethodDescriptor extends ValueDescriptor { define(target: Object, key: string, home: Object) { this.value = wrapMethod(home, key, this.value); super.define(target, key, home); } } class MethodBlueprint extends DataBlueprint { descriptor(target: Object, key: string, classMeta: ClassMeta): MethodDescriptor { let desc = super.descriptor(target, key, classMeta); return new MethodDescriptor(desc); } } export function wrapMethod(home: Object, methodName: string, original: (...args) => any) { if (!(<string>methodName in home)) return maybeWrap(original); let superMethod = home[methodName]; let func = function(...args) { if (!this) return original.apply(this, args); let lastSuper = this._super; this._super = superMethod; try { return original.apply(this, args); } finally { this._super = lastSuper; } }; (<any>func).__wrapped = true; return func; } function maybeWrap(original: Function) { if ('__wrapped' in original) return original; return function(...args) { if (!this) return original.apply(this, args); let lastSuper = this._super; this._super = ROOT; try { return original.apply(this, args); } finally { this._super = lastSuper; } }; }