UNPKG

@twobirds/microcomponents

Version:

Micro Components Organization Class

249 lines 9.68 kB
'use strict'; import { debounce, deepEqual, flattenObject, copyGettersSetters, } from './helpers.js'; import { DC } from './MC.js'; function isObservableObject(value) { return value != null && typeof value === 'object'; } function getPlaceholders(target) { let placeholders = new Set(), regEx = /\{[^\{\}]*\}/g; target .getAttributeNames() .filter((attr) => target.getAttribute(attr)?.match(regEx)) .forEach((attr) => { let phs = new Set(target.getAttribute(attr)?.match(regEx)); phs.forEach((placeholder) => placeholders.add(placeholder.replace(/[\{\}]/g, ''))); }); [...target.childNodes] .filter((childNode) => { return (childNode.nodeType === 3 && (childNode.nodeValue || '').match(regEx)); }) .forEach((childNode) => { (childNode.nodeValue || '') .match(regEx) .forEach((placeholder) => { placeholders.add(placeholder.replace(/[\{\}]/g, '')); }); }); return [...placeholders].map((placeholder) => placeholder.replace(/[\{\}]/g, '')); } const inputs = [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement]; class Placeholders extends DC { data = {}; oldDisplay = ''; constructor(target) { super(target); let that = this; getPlaceholders(target).forEach((property) => (that.data[property] = '')); observe(that); that.#attachCallbacks(); delete that._mc; } #attachCallbacks() { let that = this, target = that.target, hide = false; target .getAttributeNames() .filter((attr) => target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g)) .forEach((attr) => { let phs = new Set(target.getAttribute(attr)?.match(/\{[^\{\}]*\}/g)); that.data.observe(((template) => (data) => { let result = template; phs.forEach((placeholder) => { result = result.replace(placeholder, data[placeholder.replace(/[\{\}]/g, '')]); }); target.setAttribute(attr, result); })(target.getAttribute(attr))); }); [...target.childNodes] .filter((childNode) => { return (childNode.nodeType === 3 && (childNode.nodeValue || '').match(/\{[^\{\}]*\}/g)); }) .forEach((childNode) => { hide = true; let phs = new Set(childNode.nodeValue.match(/\{[^\{\}]*\}/g)); that.data.observe(((template) => (data) => { let result = template; phs.forEach((placeholder) => { result = result.replace(placeholder, data[placeholder.replace(/[\{\}]/g, '')]); }); if (Object.keys(data).some((key) => { return !!data[key]; })) { if (!!that.oldDisplay) { target.style = that.oldDisplay; } else { target.removeAttribute('style'); } delete that.oldDisplay; } childNode.nodeValue = result; })(childNode.nodeValue)); that.oldDisplay = target.getAttribute('style)') || ''; if (hide && !inputs.includes(target.constructor) && target.tagName !== 'FIELDSET') { target.style.display = 'none'; } }); } onData(ev) { let that = this, data = {}; Object.keys(ev.data).forEach((key) => { if (that.data.hasOwnProperty(key)) data[key] = ev.data[key]; }); if (JSON.stringify(data) !== JSON.stringify(that.data)) Object.assign(that.data, data); } } function getPlaceholderElements(target) { let elements = (target instanceof HTMLElement ? [target] : []) .concat([...target.querySelectorAll('*')]) .filter((e) => !!getPlaceholders(e).length) .filter((e) => e.tagName !== 'STYLE'); return elements; } function makePlaceholders(element) { getPlaceholderElements(element).forEach((e) => { if (e?._mc?._placeholders) return; DC.add(e, '_placeholders', new Placeholders(e)); }); } const classRepo = {}; function makeObservable(val) { const Constructor = val.constructor, ConstructorName = Constructor.name; let ObservableClass = class extends Constructor { #notify = true; #callbacks = []; constructor(val) { super(val); this.constructor.callbacks = []; if (isObservableObject(val)) { Object.getOwnPropertyNames(val).forEach((key) => { this[key] = val[key]; }); } } get callbacks() { return this.#callbacks; } set callbacks(callbacks) { this.#callbacks = callbacks; } observe(f) { this.#callbacks.push(f); return this; } bind(target) { const that = this, iso = isObservableObject(that.valueOf()); if (iso) { makePlaceholders(target); const placeholders = [ ...target.querySelectorAll('[_mc]'), ].filter((e) => !!e?._mc?._placeholders); const func = function callback(data) { let d = flattenObject(data); placeholders.forEach((e) => { if (!e) return; e._mc.trigger('data', d); }); }; that.#callbacks.push(func); that.notify(); } else { console.warn('IGNORED - cannot bind a non-Object to the dom, IS:', typeof that.valueOf(), that); } return this; } notify(notify) { let that = this, data = isObservableObject(that.valueOf()) && that.valueOf() instanceof Array === false ? structuredClone(Object.assign({}, that)) : that.valueOf(); if (notify !== undefined) { that.#notify = notify; } that.#callbacks.forEach((f) => { f(data); }); return that; } }; classRepo[ConstructorName] = ObservableClass; return ObservableClass; } function getConstructor(val) { let key = val.constructor.name; return classRepo[key] || makeObservable(val); } export const observe = (target, propertyName) => { let keys = !propertyName ? [...Object.getOwnPropertyNames(target)] : [propertyName]; keys.forEach((key) => { if (target.hasOwnProperty(key) && typeof target[key] !== 'function' && !target?.[key]?.constructor?.prototype?.observe) { if (key[0] === '_' || target?.[key] === undefined) return target; let val = target[key]; let o = new (getConstructor(val))(val); Object.defineProperty(target, key, { enumerable: true, get() { if (isObservableObject(o.valueOf())) { let check = structuredClone(Object.assign({}, o)); setTimeout(debounce(function checkChanges() { let state = structuredClone(Object.assign({}, o)); if (!deepEqual(state, check)) { o.notify(); } }, 0), 0); } return o; }, set(val) { if (typeof val === typeof o.valueOf()) { let iso = isObservableObject(o.valueOf()) && o.valueOf() instanceof Array === false; if (iso) { let old = structuredClone(Object.assign({}, o)); if (o?.constructor?.prototype?.observe === undefined) { o = new (getConstructor(val))(structuredClone(Object.assign({}, val))); copyGettersSetters(val, o); o.seal(); } Object.assign(o, val); setTimeout(debounce(function checkChanges() { let state = structuredClone(Object.assign({}, o)); if (!deepEqual(state, old)) { o.notify(); } }, 0), 0); } else { let old = o.valueOf(), callbacks = o.callbacks; if (val !== old) { o = new (getConstructor(val))(val); o.callbacks = callbacks; o.notify(); } } } else { console.warn('IGNORED - cannot change observable types, IS:', typeof o.valueOf(), o, '- CANNOT BECOME:', typeof val); } }, }); target[key] = val; } ; }); return target; }; //# sourceMappingURL=observables.js.map