UNPKG

bd-widgets

Version:

a library of user interface widgets built with Backdraft

1,327 lines (1,202 loc) 169 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.bd = {})); }(this, (function (exports) { 'use strict'; let _global = 0; let watchers = []; function setGlobal(theGlobal) { if (!_global) { _global = theGlobal; watchers.forEach(handler => handler(theGlobal)); watchers = null; } else { throw new Error('illegal to mutate global space'); } } function adviseGlobal(handler) { if (_global) { handler(_global); } else { watchers.push(handler); } } const postProcessingFuncs = Object.create(null); function insPostProcessingFunction(name, transform, func) { if (typeof transform === 'string') { // transform is an alias for name if (!postProcessingFuncs[name]) { throw Error(`cannot alias to a non-existing post-processing function: ${name}`); } postProcessingFuncs[transform] = postProcessingFuncs[name]; return; } if (arguments.length === 3) { if (typeof transform !== 'function') { transform = (prop, value) => prop ? {[prop]: value} : value; } } else { func = transform; transform = (prop, value) => value; } func.bdTransform = transform; if (postProcessingFuncs[name]) { throw Error(`duplicate postprocessing function name: ${name}`); } postProcessingFuncs[name] = func; } function getPostProcessingFunction(name) { return postProcessingFuncs[name]; } function noop() { // do nothing } class Destroyable { constructor(proc, container, onEmpty) { const result = this; result.proc = proc; if (container) { result.destroy = () => { result.destroy = result.proc = noop; const index = container.indexOf(result); if (index !== -1) { container.splice(index, 1); } !container.length && onEmpty && onEmpty(); }; container.push(result); } else { result.destroy = () => { result.destroy = result.proc = noop; }; } } static destroyAll(container) { // deterministic and robust algorithm to destroy handles: // * deterministic even when handle destructors insert handles (though the new handles will not be destroyed) // * robust even when handle destructors cause other handles to be destroyed if (Array.isArray(container)) { container.slice().forEach(h => h.destroy()); }// else container was likely falsy and never used } } const STAR = Symbol('bd-star'); const eqlComparators = new Map(); function eql(refValue, otherValue) { if (!refValue) { return otherValue === refValue; } if (refValue instanceof Object) { const comparator = eqlComparators.get(refValue.constructor); if (comparator) { return comparator(refValue, otherValue); } } if (otherValue instanceof Object) { const comparator = eqlComparators.get(otherValue.constructor); if (comparator) { return comparator(otherValue, refValue); } } return refValue === otherValue; } const watcherCatalog = new WeakMap(); const OWNER = Symbol('bd-owner'); const UNKNOWN_OLD_VALUE = { value: 'UNKNOWN_OLD_VALUE' }; const pWatchableWatchers = Symbol('bd-pWatchableWatchers'); const pWatchableHandles = Symbol('bd-pWatchableHandles'); const pWatchableSetup = Symbol('bd-pWatchableSetup'); class WatchableRef { constructor(referenceObject, prop, formatter) { if (typeof prop === 'function') { // no prop,...star watcher formatter = prop; prop = STAR; } Object.defineProperty(this, 'value', { enumerable: true, // eslint-disable-next-line func-names get: ((function () { if (formatter) { if (prop === STAR) { return () => formatter(referenceObject); } else { return () => formatter(referenceObject[prop]); } } else if (prop === STAR) { return () => referenceObject; } else { return () => referenceObject[prop]; } })()) }); // if (referenceObject[OWNER] && prop === STAR), then we cValue===newValue===referenceObject... // therefore can't detect internal mutations to referenceObject, so don't try const cannotDetectMutations = prop === STAR && referenceObject[OWNER]; this[pWatchableWatchers] = []; let cValue; const callback = (newValue, oldValue, target, referenceProp) => { if (formatter) { oldValue = oldValue === UNKNOWN_OLD_VALUE ? oldValue : formatter(oldValue); newValue = formatter(newValue); } if (cannotDetectMutations || oldValue === UNKNOWN_OLD_VALUE || !eql(cValue, newValue)) { this[pWatchableWatchers].slice().forEach( destroyable => destroyable.proc((cValue = newValue), oldValue, target, referenceProp) ); } }; this[pWatchableSetup] = () => { cValue = this.value; if (referenceObject[OWNER]) { this[pWatchableHandles] = [watch(referenceObject, prop, (newValue, oldValue, receiver, _prop) => { if (prop === STAR) { callback(referenceObject, UNKNOWN_OLD_VALUE, referenceObject, _prop); } else { callback(newValue, oldValue, referenceObject, _prop); } })]; } else if (referenceObject.watch) { this[pWatchableHandles] = [ referenceObject.watch(prop, (newValue, oldValue, target) => { callback(newValue, oldValue, target, prop); if (this[pWatchableHandles].length === 2) { this[pWatchableHandles].pop().destroy(); } if (newValue && newValue[OWNER]) { // value is a watchable // eslint-disable-next-line no-shadow this[pWatchableHandles].push(watch(newValue, (newValue, oldValue, receiver, referenceProp) => { callback(receiver, UNKNOWN_OLD_VALUE, referenceObject, referenceProp); })); } }) ]; const value = referenceObject[prop]; if (value && value[OWNER]) { // value is a watchable this[pWatchableHandles].push(watch(value, (newValue, oldValue, receiver, referenceProp) => { callback(receiver, UNKNOWN_OLD_VALUE, referenceObject, referenceProp); })); } referenceObject.own && referenceObject.own(this); } else { throw new Error("don't know how to watch referenceObject"); } }; } destroy() { Destroyable.destroyAll(this[pWatchableWatchers]); } watch(watcher) { this[pWatchableHandles] || this[pWatchableSetup](); return new Destroyable(watcher, this[pWatchableWatchers], () => { Destroyable.destroyAll(this[pWatchableHandles]); delete this[pWatchableHandles]; }); } } WatchableRef.pWatchableWatchers = pWatchableWatchers; WatchableRef.pWatchableHandles = pWatchableHandles; WatchableRef.pWatchableSetup = pWatchableSetup; WatchableRef.UNKNOWN_OLD_VALUE = UNKNOWN_OLD_VALUE; WatchableRef.STAR = STAR; function getWatchableRef(referenceObject, referenceProp, formatter) { // (referenceObject, referenceProp, formatter) // (referenceObject, referenceProp) // (referenceObject, formatter) => (referenceObject, STAR, formatter) // (referenceObject) => (referenceObject, STAR) if (typeof referenceProp === 'function') { // no referenceProp,...star watcher formatter = referenceProp; referenceProp = STAR; } return new WatchableRef(referenceObject, referenceProp || STAR, formatter); } function watch(watchable, name, watcher) { if (typeof name === 'function') { watcher = name; name = STAR; } let variables = watcherCatalog.get(watchable); if (!variables) { watcherCatalog.set(watchable, (variables = {})); } // eslint-disable-next-line no-shadow const insWatcher = (name, watcher) => new Destroyable(watcher, variables[name] || (variables[name] = [])); if (!watcher) { const hash = name; // eslint-disable-next-line no-shadow return Reflect.ownKeys(hash).map(name => insWatcher(name, hash[name])); } else if (Array.isArray(name)) { // eslint-disable-next-line no-shadow return name.map(name => insWatcher(name, watcher)); } else { return insWatcher(name, watcher); } } const onMutateBeforeNames = {}; const onMutateNames = {}; function mutate(owner, name, privateName, newValue) { const oldValue = owner[privateName]; if (eql(oldValue, newValue)) { return false; } else { let onMutateBeforeName, onMutateName; if (typeof name !== 'symbol') { onMutateBeforeName = onMutateBeforeNames[name]; if (!onMutateBeforeName) { const suffix = name.substring(0, 1).toUpperCase() + name.substring(1); onMutateBeforeName = onMutateBeforeNames[name] = `onMutateBefore${suffix}`; onMutateName = onMutateNames[name] = `onMutate${suffix}`; } else { onMutateName = onMutateNames[name]; } } if (onMutateBeforeName && owner[onMutateBeforeName]) { if (owner[onMutateBeforeName](newValue, oldValue) === false) { // the proposed mutation is illegal return false; } } if (owner.hasOwnProperty(privateName)) { owner[privateName] = newValue; } else { // not enumerable or configurable Object.defineProperty(owner, privateName, {writable: true, value: newValue}); } onMutateName && owner[onMutateName] && owner[onMutateName](newValue, oldValue); return [name, newValue, oldValue]; } } function getWatcher(owner, watcher) { return typeof watcher === 'function' ? watcher : owner[watcher].bind(owner); } function watchHub(superClass) { return class extends (superClass || class { }) { // protected interface... bdMutateNotify(name, newValue, oldValue) { const variables = watcherCatalog.get(this); if (!variables) { return; } if (Array.isArray(name)) { // each element in name is either a triple ([name, oldValue, newValue]) or false let doStar = false; name.forEach(p => { if (p) { doStar = true; const watchers = variables[p[0]]; if (watchers) { newValue = p[1]; oldValue = p[2]; watchers.slice().forEach(destroyable => destroyable.proc(newValue, oldValue, this, name)); } } }); if (doStar) { const watchers = variables[STAR]; if (watchers) { watchers.slice().forEach(destroyable => destroyable.proc(this, oldValue, this, name)); } } } else { let watchers = variables[name]; if (watchers) { watchers.slice().forEach(destroyable => destroyable.proc(newValue, oldValue, this, name)); } watchers = variables[STAR]; if (watchers) { watchers.slice().forEach(destroyable => destroyable.proc(newValue, oldValue, this, name)); } } } bdMutate(name, privateName, newValue) { if (arguments.length > 3) { let i = 0; const results = []; let mutateOccurred = false; while (i < arguments.length) { // eslint-disable-next-line prefer-rest-params const mutateResult = mutate(this, arguments[i++], arguments[i++], arguments[i++]); mutateOccurred = mutateOccurred || mutateResult; results.push(mutateResult); } if (mutateOccurred) { this.bdMutateNotify(results); return results; } return false; } else { const result = mutate(this, name, privateName, newValue); if (result) { this.bdMutateNotify(...result); return result; } return false; } } // public interface... get isBdWatchHub() { return true; } watch(...args) { // possible sigs: // 1: name, watcher // 2: name[], watcher // 3: hash: name -> watcher // 4: watchable, name, watcher // 5: watchable, name[], watcher // 6: watchable, hash: name -> watcher // 7: watchable, watcher // STAR watcher if (arguments.length === 1) { // sig 3 const hash = args[0]; return Reflect.ownKeys(hash).map(name => this.watch(name, hash[name])); } if (args[0][OWNER]) { // sig 4-6 let result; if (arguments.length === 2) { if (typeof args[1] === 'object') { // sig 6 const hash = args[1]; Reflect.ownKeys(hash).map(name => (hash[name] = getWatcher(this, hash[name]))); result = watch(args[0], hash); } else { // sig 7 result = watch(args[0], STAR, getWatcher(this, args[1])); } } else { // sig 4 or 5 result = watch(args[0], args[1], getWatcher(this, args[2])); } this.own && this.own(result); return result; } if (Array.isArray(args[0])) { // sig 2 return args[0].map(name => this.watch(name, getWatcher(this, args[1]))); } // sig 1 const name = args[0]; const watcher = getWatcher(this, args[1]); let variables = watcherCatalog.get(this); if (!variables) { watcherCatalog.set(this, (variables = {})); } const result = new Destroyable(watcher, variables[name] || (variables[name] = [])); this.own && this.own(result); return result; } destroyWatch(name) { const variables = watcherCatalog.get(this); function destroyList(list) { if (list) { while (list.length) list.shift().destroy(); } } if (variables) { if (name) { destroyList(variables[name]); delete variables[name]; } else { Reflect.ownKeys(variables).forEach(k => destroyList(variables[k])); watcherCatalog.delete(this); } } } getWatchableRef(name, formatter) { const result = new WatchableRef(this, name, formatter); this.own && this.own(result); return result; } }; } const WatchHub = watchHub(); function withWatchables(superClass, ...args) { const publicPropNames = []; function def(name) { let pname; if (Array.isArray(name)) { pname = name[1]; name = name[0]; } else { pname = `_${name}`; } publicPropNames.push(name); // eslint-disable-next-line no-use-before-define Object.defineProperty(prototype, name, { enumerable: true, get() { return this[pname]; }, set(value) { this.bdMutate(name, pname, value); } }); } function init(owner, kwargs) { publicPropNames.forEach(name => { if (kwargs.hasOwnProperty(name)) { owner[name] = kwargs[name]; } }); } const result = class extends superClass { constructor(kwargs) { kwargs = kwargs || {}; super(kwargs); init(this, kwargs); } }; const prototype = result.prototype; args.forEach(def); result.watchables = publicPropNames.concat(superClass.watchables || []); return result; } let window$1 = 0; let document$1 = 0; adviseGlobal(global => { window$1 = global; document$1 = window$1.document; }); function setAttr(node, name, value) { if (arguments.length === 2) { // name is a hash Object.keys(name).forEach(n => setAttr(node, n, name[n])); } else if (name === 'style') { setStyle(node, value); } else if (name === 'innerHTML' || (name in node && node instanceof HTMLElement)) { node[name] = value; } else { node.setAttribute(name, value); } } function getAttr(node, name) { if (name in node && node instanceof HTMLElement) { return node[name]; } else { return node.getAttribute(name); } } let lastComputedStyleNode = 0; let lastComputedStyle = 0; function getComputedStyle(node) { if (lastComputedStyleNode !== node) { lastComputedStyle = window$1.getComputedStyle((lastComputedStyleNode = node)); } return lastComputedStyle; } function getStyle(node, property) { if (lastComputedStyleNode !== node) { lastComputedStyle = window$1.getComputedStyle((lastComputedStyleNode = node)); } const result = lastComputedStyle[property]; return (typeof result === 'string' && /px$/.test(result)) ? parseFloat(result) : result; } function getStyles(node, ...styleNames) { if (lastComputedStyleNode !== node) { lastComputedStyle = window$1.getComputedStyle((lastComputedStyleNode = node)); } let styles = []; styleNames.forEach(styleName => { if (Array.isArray(styleName)) { styles = styles.concat(styleName); } else if (typeof styleName === 'string') { styles.push(styleName); } else { // styleName is a hash Object.keys(styleName).forEach(p => styles.push(p)); } }); const result = {}; styles.forEach(property => { const value = lastComputedStyle[property]; result[property] = (typeof value === 'string' && /px$/.test(value)) ? parseFloat(value) : value; }); return result; } function setStyle(node, property, value) { if (arguments.length === 2) { if (typeof property === 'string') { node.style = property; } else { // property is a hash Object.keys(property).forEach(p => { node.style[p] = property[p]; }); } } else { node.style[property] = value; } } function getPosit(node) { const result = node.getBoundingClientRect(); result.t = result.top; result.b = result.bottom; result.l = result.left; result.r = result.right; result.h = result.height; result.w = result.width; return result; } function positStyle(v) { return v === false ? '' : `${v}px`; } function setPosit(node, posit) { // eslint-disable-next-line guard-for-in,no-restricted-syntax Object.keys(posit).forEach(p => { switch (p) { case 't': node.style.top = positStyle(posit.t); break; case 'b': node.style.bottom = positStyle(posit.b); break; case 'l': node.style.left = positStyle(posit.l); break; case 'r': node.style.right = positStyle(posit.r); break; case 'h': node.style.height = positStyle(posit.h); break; case 'w': node.style.width = positStyle(posit.w); break; case 'maxH': node.style.maxHeight = positStyle(posit.maxH); break; case 'maxW': node.style.maxWidth = positStyle(posit.maxW); break; case 'minH': node.style.minHeight = positStyle(posit.minH); break; case 'minW': node.style.minWidth = positStyle(posit.minW); break; case 'z': node.style.zIndex = posit.z === false ? '' : posit.z; break; // ignore...this allows clients to stuff other properties into posit for other reasons } }); } function insertBefore(node, refNode) { refNode.parentNode.insertBefore(node, refNode); } function insertAfter(node, refNode) { const parent = refNode.parentNode; if (parent.lastChild === refNode) { parent.appendChild(node); } else { parent.insertBefore(node, refNode.nextSibling); } } function insert(node, refNode, position) { if (position === undefined || position === 'last') { // short circuit the common case refNode.appendChild(node); } else { switch (position) { case 'before': insertBefore(node, refNode); break; case 'after': insertAfter(node, refNode); break; case 'replace': refNode.parentNode.replaceChild(node, refNode); return (refNode); case 'only': { const result = []; while (refNode.firstChild) { result.push(refNode.removeChild(refNode.firstChild)); } refNode.appendChild(node); return result; } case 'first': if (refNode.firstChild) { insertBefore(node, refNode.firstChild); } else { refNode.appendChild(node); } break; default: if (typeof position === 'number') { const children = refNode.childNodes; if (!children.length || children.length <= position) { refNode.appendChild(node); } else { insertBefore(node, children[position < 0 ? Math.max(0, children.length + position) : position]); } } else { throw new Error('illegal position'); } } } return 0; } function create(tag, props) { const result = Array.isArray(tag) ? document$1.createElementNS(`${tag[0]}`, tag[1]) : document$1.createElement(tag); if (props) { Reflect.ownKeys(props).forEach(p => setAttr(result, p, props[p])); } return result; } function getMaxZIndex(parent) { const children = parent.childNodes; const end = children.length; let node, cs, max = 0, i = 0; while (i < end) { node = children[i++]; cs = node && node.nodeType === 1 && getComputedStyle(node); max = Math.max(max, (cs && cs.zIndex && Number(cs.zIndex)) || 0); } return max; } function connect(target, type, listener, useCapture) { let destroyed = false; useCapture = !!useCapture; target.addEventListener(type, listener, useCapture); return { destroy() { if (!destroyed) { destroyed = true; target.removeEventListener(type, listener, useCapture); } } }; } function stopEvent(event) { if (event && event.preventDefault) { event.preventDefault(); event.stopPropagation(); } } function flattenChildren(children) { // children can be falsey, single children (of type Element or string), or arrays of single children, arbitrarily deep const result = []; function flatten_(child) { if (Array.isArray(child)) { child.forEach(flatten_); } else if (child) { result.push(child); } } flatten_(children); return result; } class Element { constructor(type, props, ...children) { if (type instanceof Element) { // copy constructor this.type = type.type; type.isComponentType && (this.isComponentType = type.isComponentType); type.ctorProps && (this.ctorProps = type.ctorProps); type.ppFuncs && (this.ppFuncs = type.ppFuncs); type.children && (this.children = type.children); } else { // type must either be a constructor (a function) or a string; guarantee that as follows... if (type instanceof Function) { this.isComponentType = true; this.type = type; } else if (type) { // leave this.isComponentType === undefined this.type = Array.isArray(type) ? type : `${type}`; } else { throw new Error('type is required'); } // if the second arg is an Object and not an Element or and Array, then it is props... if (props) { if (props instanceof Element || Array.isArray(props)) { children.unshift(props); this.ctorProps = {}; } else if (props instanceof Object) { const ctorProps = {}; const ppFuncs = {}; let ppFuncsExist = false; let match, ppf; const setPpFuncs = (ppKey, value) => { if (ppFuncs[ppKey]) { const dest = ppFuncs[ppKey]; Reflect.ownKeys(value).forEach(k => (dest[k] = value[k])); } else { ppFuncsExist = true; ppFuncs[ppKey] = value; } }; Reflect.ownKeys(props).forEach(k => { if ((ppf = getPostProcessingFunction(k))) { const value = ppf.bdTransform(null, props[k]); setPpFuncs(k, value); } else if ((match = k.match(/^([A-Za-z0-9$]+)_(.+)$/)) && (ppf = getPostProcessingFunction(match[1]))) { const ppKey = match[1]; const value = ppf.bdTransform(match[2], props[k]); setPpFuncs(ppKey, value); } else { ctorProps[k] = props[k]; } }); this.ctorProps = Object.freeze(ctorProps); if (ppFuncsExist) { this.ppFuncs = Object.freeze(ppFuncs); } } else { children.unshift(props); this.ctorProps = {}; } } else { this.ctorProps = {}; } const flattenedChildren = flattenChildren(children); if (flattenedChildren.length === 1) { const child = flattenedChildren[0]; this.children = child instanceof Element ? child : `${child}`; } else if (flattenedChildren.length) { this.children = flattenedChildren.map(child => (child instanceof Element ? child : `${child}`)); Object.freeze(this.children); }// else children.length===0; therefore, no children } Object.freeze(this); } } function element(type, props, ...children) { // make elements without having to use new return new Element(type, props, children); } element.addElementType = function addElementType(type) { // type is either a constructor (a function) or a string if (typeof type === 'function') { if (type.name in element) { // eslint-disable-next-line no-console console.error(type.name, 'already in element'); } else { element[type.name] = (props, ...children) => new Element(type, props, children); } } else { // eslint-disable-next-line no-lonely-if if (type in element) { // eslint-disable-next-line no-console console.error(type, 'already in element'); } else { element[type] = (props, ...children) => new Element(type, props, children); } } }; 'a.abbr.address.area.article.aside.audio.base.bdi.bdo.blockquote.br.button.canvas.caption.cite.code.col.colgroup.data.datalist.dd.del.details.dfn.div.dl.dt.em.embed.fieldset.figcaption.figure.footer.form.h1.head.header.hr.html.i.iframe.img.input.ins.kbd.label.legend.li.link.main.map.mark.meta.meter.nav.noscript.object.ol.optgroup.option.output.p.param.picture.pre.progress.q.rb.rp.rt.rtc.ruby.s.samp.script.section.select.slot.small.source.span.strong.style.sub.summary.sup.table.tbody.td.template.textarea.tfoot.th.thead.time.title.tr.track.u.ul.var.video.wbr'.split('.').forEach(element.addElementType); const SVG = Object.create(null, { toString: { value: () => 'http://www.w3.org/2000/svg' } }); Object.freeze(SVG); const listenerCatalog = new WeakMap(); function eventHub(superClass) { return class extends (superClass || class { }) { // protected interface... bdNotify(e) { const events = listenerCatalog.get(this); if (!events) { return; } let handlers; if (e instanceof Event) { handlers = events[e.type]; } else { // eslint-disable-next-line no-lonely-if if (e.type) { handlers = events[e.type]; e.target = this; } else if (!e.name) { handlers = events[e]; e = {type: e, name: e, target: this}; } else { // eslint-disable-next-line no-console console.warn('event.name is deprecated; use event.type'); handlers = events[e.name]; e.type = e.name; e.target = this; } } if (handlers) { handlers.slice().forEach(theDestroyable => theDestroyable.proc(e)); } if ((handlers = events[STAR])) { handlers.slice().forEach(theDestroyable => theDestroyable.proc(e)); } } // public interface... get isBdEventHub() { return true; } advise(eventName, handler) { if (!handler) { const hash = eventName; return Reflect.ownKeys(hash).map(key => this.advise(key, hash[key])); } else if (Array.isArray(eventName)) { return eventName.map(name => this.advise(name, handler)); } else { let events = listenerCatalog.get(this); if (!events) { listenerCatalog.set(this, (events = {})); } const result = new Destroyable(handler, events[eventName] || (events[eventName] = [])); this.own && this.own(result); return result; } } destroyAdvise(eventName) { const events = listenerCatalog.get(this); if (!events) { return; } if (eventName) { const handlers = events[eventName]; if (handlers) { handlers.forEach(h => h.destroy()); delete events[eventName]; } } else { // eslint-disable-next-line no-shadow Reflect.ownKeys(events).forEach(eventName => { events[eventName].forEach(h => h.destroy()); }); listenerCatalog.delete(this); } } }; } const EventHub = eventHub(); let document$2 = 0; adviseGlobal(window => { document$2 = window.document; }); function cleanClassName(s) { return s.replace(/\s{2,}/g, ' ').trim(); } function conditionClassNameArgs(args) { return args.reduce((acc, item) => { if (item instanceof RegExp) { acc.push(item); } else if (item) { acc = acc.concat(cleanClassName(item).split(' ')); } return acc; }, []); } function classValueToRegExp(v, args) { return v instanceof RegExp ? v : RegExp(` ${v} `, args); } function calcDomClassName(component) { const staticClassName = component.staticClassName; const className = component.bdClassName; return staticClassName && className ? (`${staticClassName} ${className}`) : (staticClassName || className); } function addChildToDomNode(parent, domNode, child, childIsComponent) { if (childIsComponent) { const childDomRoot = child.bdDom.root; if (Array.isArray(childDomRoot)) { childDomRoot.forEach(node => insert(node, domNode)); } else { insert(childDomRoot, domNode); } parent.bdAdopt(child); } else { insert(child, domNode); } } function validateElements(elements) { if (Array.isArray(elements)) { elements.forEach(validateElements); } else if (elements.isComponentType) { throw new Error('Illegal: root element(s) for a Component cannot be Components'); } } function postProcess(ppFuncs, owner, target) { Reflect.ownKeys(ppFuncs).forEach(ppf => { const args = ppFuncs[ppf]; if (Array.isArray(args)) { getPostProcessingFunction(ppf)(owner, target, ...args); } else { getPostProcessingFunction(ppf)(owner, target, args); } }); } function noop$1() { // do nothing } function pushHandles(dest, ...handles) { handles.forEach(h => { if (Array.isArray(h)) { pushHandles(dest, ...h); } else if (h) { const destroy = h.destroy.bind(h); h.destroy = () => { destroy(); const index = dest.indexOf(h); if (index !== -1) { dest.splice(index, 1); } h.destroy = noop$1; }; dest.push(h); } }); } const ownedHandlesCatalog = new WeakMap(); const domNodeToComponent = new Map(); class Component extends eventHub(WatchHub) { constructor(kwargs = {}) { // notice that this class requires only the per-instance data actually used by its subclass/instance super(); if (!this.constructor.noKwargs) { this.kwargs = kwargs; } // id, if provided, is read-only if (kwargs.id) { Object.defineProperty(this, 'id', { value: `${kwargs.id}`, enumerable: true }); } if (kwargs.className) { Array.isArray(kwargs.className) ? this.addClassName(...kwargs.className) : this.addClassName(kwargs.className); } if (kwargs.tabIndex !== undefined) { this.tabIndex = kwargs.tabIndex; } if (kwargs.title) { this.title = kwargs.title; } if (kwargs.disabled || (kwargs.enabled !== undefined && !kwargs.enabled)) { this.disabled = true; } if (kwargs.visible !== undefined) { this.visible = kwargs.visible; } if (kwargs.elements) { if (typeof kwargs.elements === 'function') { this.bdElements = kwargs.elements; } else { this.bdElements = () => kwargs.elements; } } if (kwargs.postRender) { this.postRender = kwargs.postRender; } if (kwargs.mix) { Reflect.ownKeys(kwargs.mix).forEach(p => (this[p] = kwargs.mix[p])); } if (kwargs.callbacks) { const events = this.constructor.events; Reflect.ownKeys(kwargs.callbacks).forEach(key => { if (events.indexOf(key) !== -1) { this.advise(key, kwargs.callbacks[key]); } else { this.watch(key, kwargs.callbacks[key]); } }); } } destroy() { if (!this.destroyed) { this.destroyed = 'in-prog'; this.unrender(); const handles = ownedHandlesCatalog.get(this); if (handles) { Destroyable.destroyAll(handles); ownedHandlesCatalog.delete(this); } this.destroyWatch(); this.destroyAdvise(); delete this.kwargs; this.destroyed = true; } } render( proc // [function, optional] called after this class's render work is done, called in context of this ) { if (!this.bdDom) { const dom = this.bdDom = this._dom = {}; const elements = this.bdElements(); validateElements(elements); const root = dom.root = this.constructor.renderElements(this, elements); if (Array.isArray(root)) { root.forEach(node => domNodeToComponent.set(node, this)); } else { domNodeToComponent.set(root, this); if (this.id) { root.id = this.id; } this.addClassName(root.getAttribute('class') || ''); const className = calcDomClassName(this); if (className) { root.setAttribute('class', className); } if (this.bdDom.tabIndexNode) { if (this.bdTabIndex === undefined) { this.bdTabIndex = this.bdDom.tabIndexNode.tabIndex; } else { this.bdDom.tabIndexNode.tabIndex = this.bdTabIndex; } } else if (this.bdTabIndex !== undefined) { (this.bdDom.tabIndexNode || this.bdDom.root).tabIndex = this.bdTabIndex; } if (this.bdTitle !== undefined) { (this.bdDom.titleNode || this.bdDom.root).title = this.bdTitle; } this[this.bdDisabled ? 'addClassName' : 'removeClassName']('bd-disabled'); if (!this.visible) { this._hiddenDisplayStyle = root.style.display; root.style.display = 'none'; } } this.ownWhileRendered(this.postRender()); proc && proc.call(this); this.bdMutateNotify('rendered', true, false); } return this.bdDom.root; } postRender() { // no-op } bdElements() { return new Element('div', {}); } unrender() { if (this.rendered) { if (this.bdParent) { this.bdParent.delChild(this, true); } if (this.children) { this.children.slice().forEach(child => { child.destroy(); }); } delete this.children; const root = this.bdDom.root; if (Array.isArray(root)) { root.forEach(node => { domNodeToComponent.delete(node); node.parentNode && node.parentNode.removeChild(node); }); } else { domNodeToComponent.delete(root); root.parentNode && root.parentNode.removeChild(root); } Destroyable.destroyAll(this.bdDom.handles); delete this.bdDom; delete this._dom; delete this._hiddenDisplayStyle; this.bdAttachToDoc(false); this.bdMutateNotify('rendered', false, true); } } get rendered() { return !!(this.bdDom && this.bdDom.root); } own(...handles) { let _handles = ownedHandlesCatalog.get(this); if (!_handles) { ownedHandlesCatalog.set(this, (_handles = [])); } pushHandles(_handles, ...handles); } ownWhileRendered(...handles) { pushHandles(this.bdDom.handles || (this.bdDom.handles = []), ...handles); } get parent() { return this.bdParent; } bdAdopt(child) { if (child.bdParent) { throw new Error('unexpected'); } (this.children || (this.children = [])).push(child); child.bdMutate('parent', 'bdParent', this); child.bdAttachToDoc(this.bdAttachedToDoc); } bdAttachToDoc(value) { if (this.bdMutate('attachedToDoc', 'bdAttachedToDoc', !!value)) { if (value && this.resize) { this.resize(); } this.children && this.children.forEach(child => child.bdAttachToDoc(value)); return true; } else { return false; } } get attachedToDoc() { return !!this.bdAttachedToDoc; } insChild(...args) { if (!this.rendered) { throw new Error('parent component must be rendered before explicitly inserting a child'); } let { src, attachPoint, position } = decodeRender(args); let child; if (src instanceof Component) { child = src; if (child.parent) { child.parent.delChild(child, true); } child.render(); } else { // child instanceof Element if (!src.isComponentType) { src = new Element(Component, { elements: src }); } child = this.constructor.renderElements(this, src); } if (/before|after|replace|only|first|last/.test(attachPoint) || typeof attachPoint === 'number') { position = attachPoint; attachPoint = 0; } if (attachPoint) { if (attachPoint in this) { // node reference attachPoint = this[attachPoint]; } else if (typeof attachPoint === 'string') { attachPoint = document$2.getElementById(attachPoint); if (!attachPoint) { throw new Error('unexpected'); } } else if (position !== undefined) { // attachPoint must be a child Component const index = this.children ? this.children.indexOf(attachPoint) : -1; if (index !== -1) { // attachPoint is a child attachPoint = attachPoint.bdDom.root; if (Array.isArray(attachPoint)) { switch (position) { case 'replace': case 'only': case 'before': attachPoint = attachPoint[0]; break; case 'after': attachPoint = attachPoint[attachPoint.length - 1]; break; default: throw new Error('unexpected'); } } } else { throw new Error('unexpected'); } } else { // attachPoint without a position must give a node reference throw new Error('unexpected'); } } else if (child.bdPa