UNPKG

rahisi

Version:

UI library for prototyping ideas for reactive programming.

371 lines 12.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createRef = (() => { let id = 0; // possible collision return () => `id_${id++}`; })(); exports.mounted = "mounted"; exports.unmounted = "unmounted"; const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === "childList") { mutation.addedNodes.forEach((n) => n.dispatchEvent(new Event(exports.mounted))); mutation.removedNodes.forEach((n) => n.dispatchEvent(new Event(exports.unmounted))); } }); }); document.addEventListener("DOMContentLoaded", () => observer.observe(document.body, { attributes: false, characterData: false, childList: true, subtree: true, }), false); class Notifier { constructor() { this.nextId = 0; this.subscribers = new Map(); } start() { const notify = () => { this.subscribers.forEach((v) => v()); window.requestAnimationFrame(notify); }; window.requestAnimationFrame(notify); } subscribe(onNext, dependency) { const currentId = this.nextId; this.nextId++; this.subscribers.set(currentId, onNext); dependency.addEventListener(exports.unmounted, () => this.subscribers.delete(currentId)); } } class VersionedList { constructor(items = new Array()) { this.items = items; this.nextKey = 0; // tslint:disable-next-line:no-empty this.addListener = () => { }; // tslint:disable-next-line:no-empty this.removeListener = () => { }; } getItems() { return this.items.map((a) => a.value); } getItem(index) { return this.items[index].value; } count() { return this.items.length; } add(item) { const val = { key: this.nextKey, value: item }; this.items.push(val); this.nextKey++; this.addListener([val]); } delete(itemIndex) { const val = this.items[itemIndex]; this.items.splice(itemIndex, 1); this.nextKey++; this.removeListener([val]); } remove(item) { this.delete(this.indexOf(item)); } clear() { const cleared = this.items.splice(0); this.items.length = 0; this.nextKey++; this.removeListener(cleared); } indexOf(obj, fromIndex = 0) { if (fromIndex < 0) { fromIndex = Math.max(0, this.items.length + fromIndex); } for (let i = fromIndex, j = this.items.length; i < j; i++) { if (this.items[i].value === obj) { return i; } } return -1; } forEach(action) { this.getItems().forEach(action); } filter(filter) { return this.getItems().filter(filter); } setListeners(addListener, removeListener) { this.addListener = addListener; this.removeListener = removeListener; this.addListener(this.items); } } exports.VersionedList = VersionedList; class BaseElement { constructor(elementName, attributes = new Array(), children = new Array()) { this.elementName = elementName; this.attributes = attributes; this.children = children; } // factor out mount(parent) { const notifier = new Notifier(); const v = this.render(parent, notifier, false); notifier.start(); return v; } render(parent, watch, isSvg) { const useSvg = isSvg || this.elementName === "svg"; if (this.elementName == null) { // it's a fragment const view = document.createDocumentFragment(); this.children.forEach((a) => a.render(view, watch, useSvg)); parent.appendChild(view); return parent; } const view = useSvg ? document.createElementNS("http://www.w3.org/2000/svg", this.elementName) : document.createElement(this.elementName); this.attributes.forEach((a) => a.set(view, watch, useSvg)); this.children.forEach((a) => a.render(view, watch, useSvg)); parent.appendChild(view); return view; } } exports.BaseElement = BaseElement; class ConditionalRenderElement { constructor(source, def) { this.source = source; this.def = def; this.currentNode = document.createTextNode(""); this.fallback = { test: () => true, renderable: def }; this.currentSource = source.find((a) => a.test()) || this.fallback; } mount(parent) { const notifier = new Notifier(); const v = this.render(parent, notifier, false); notifier.start(); return v; } render(parent, watch, isSvg) { this.currentNode = this.currentSource .renderable() .render(parent, watch, isSvg); const gen = this.source; watch.subscribe(() => { const s = gen.find((a) => a.test()); if (this.currentSource !== s) { this.currentSource = s || this.fallback; const replacement = this.currentSource .renderable() .render(document.createDocumentFragment(), watch, isSvg); parent.replaceChild(replacement, this.currentNode); this.currentNode = replacement; } }, parent); return this.currentNode; } } exports.ConditionalRenderElement = ConditionalRenderElement; class TemplateElement { constructor(source, template, placeholder) { this.source = source; this.template = template; this.placeholder = placeholder; this.nodes = new Map(); this.currentValue = new VersionedList(); } mount(parent) { const notifier = new Notifier(); const v = this.render(parent, notifier, false); notifier.start(); return v; } render(o, watch, isSvg) { const placeholderNode = this.placeholder ? this.placeholder.render(document.createDocumentFragment(), watch, isSvg) : null; const showPlaceHolder = () => { if (!placeholderNode) { return; } if (this.nodes.size === 0) { const _ = placeholderNode.parentElement === o || o.appendChild(placeholderNode); } else { const _ = placeholderNode.parentElement === o && o.removeChild(placeholderNode); } }; const subscribe = () => { this.nodes.forEach((child, _) => o.removeChild(child)); this.nodes.clear(); this.currentValue.setListeners((items) => { const fragment = document.createDocumentFragment(); items.forEach((i) => { const child = this.template(i.value).render(fragment, watch, isSvg); this.nodes.set(i.key, child); }); o.appendChild(fragment); showPlaceHolder(); }, (items) => { items.forEach((i) => { o.removeChild(this.nodes.get(i.key)); this.nodes.delete(i.key); }); showPlaceHolder(); }); showPlaceHolder(); }; if (this.source instanceof VersionedList) { this.currentValue = this.source; subscribe(); } else { this.currentValue = this.source(); subscribe(); const gen = this.source; watch.subscribe(() => { const s = gen(); if (this.currentValue !== s) { this.currentValue = s; subscribe(); } }, o); } return o; } } exports.TemplateElement = TemplateElement; class TextElement { constructor(textContent) { this.textContent = textContent; this.currentValue = ""; } mount(parent) { const notifier = new Notifier(); const v = this.render(parent, notifier, false); notifier.start(); return v; } render(parent, watch, _) { const o = document.createTextNode(""); if (typeof this.textContent !== "function") { this.currentValue = typeof this.textContent === "boolean" ? "" : this.textContent; o.textContent = this.currentValue; } else { const gen = this.textContent; const getValue = () => typeof gen() === "boolean" ? "" : gen(); this.currentValue = getValue(); o.textContent = this.currentValue; watch.subscribe(() => { const s = getValue(); if (this.currentValue !== s) { this.currentValue = s; o.textContent = this.currentValue; } }, o); } parent.appendChild(o); return o; } } exports.TextElement = TextElement; // xss via href class NativeAttribute { constructor(attribute, value) { this.attribute = attribute; this.value = value; this.currentValue = ""; } set(o, watch, isSvg) { if (typeof this.value !== "function") { this.currentValue = this.value; NativeAttribute.setAttribute(this.attribute, o, this.currentValue, isSvg); } else { this.currentValue = this.value(); NativeAttribute.setAttribute(this.attribute, o, this.currentValue, isSvg); const gen = this.value; watch.subscribe(() => { const s = gen(); if (this.currentValue !== s) { this.currentValue = s; NativeAttribute.setAttribute(this.attribute, o, this.currentValue, isSvg); } }, o); } } } NativeAttribute.setAttribute = (attribute, element, value, isSvg) => { if (attribute === "style") { for (const key of Object.keys(value)) { const style = value == null || value[key] == null ? "" : value[key]; if (key[0] === "-") { element[attribute].setProperty(key, style); } else { element[attribute][key] = style; } } } else if (attribute in element && attribute !== "list" && attribute !== "type" && attribute !== "draggable" && attribute !== "spellcheck" && attribute !== "translate" && !isSvg) { element[attribute] = value == null ? "" : value; } else if (value != null && value !== false) { element.setAttribute(attribute, value); } if (value == null || value === false) { element.removeAttribute(attribute); } }; exports.NativeAttribute = NativeAttribute; // lose focus when body is clicked class FocusA { constructor(focus) { this.focus = focus; this.currentValue = false; } set(o, watch) { if (typeof this.focus !== "function") { this.currentValue = this.focus; if (this.currentValue) { o.focus(); } } else { this.currentValue = this.focus(); if (this.currentValue) { o.focus(); } const gen = this.focus; watch.subscribe(() => { const s = gen(); if (this.currentValue !== s) { this.currentValue = s; } if (this.currentValue && document.activeElement !== o) { o.focus(); } }, o); } } } exports.FocusA = FocusA; class OnHandlerA { constructor(eventName, handler) { this.eventName = eventName; this.handler = handler; } set(o) { o.addEventListener(this.eventName, this.handler); } } exports.OnHandlerA = OnHandlerA; exports.Template = (props) => { const { source, template, placeholder } = props; return new TemplateElement(source, template, placeholder || null); // no props }; //# sourceMappingURL=index.js.map