bd-widgets
Version:
a library of user interface widgets built with Backdraft
1,327 lines (1,202 loc) • 169 kB
JavaScript
(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