oda-framework
Version:
1,188 lines (1,158 loc) • 87.3 kB
JavaScript
/* * oda.js v3.0
* (c) 2019-2022 Roman Perepyolkin, Vadim Biryuk, Alexander Uvarov
* Under the MIT License.
*/
import './rocks.js';
import aliases from './aliases.js';
import ODAStyles from './tools/styles/styles.js'
'use strict';
if (!window.ODA?.IsReady) {
// document.body.style.visibility = 'hidden';
const domParser = new DOMParser();
const pointerDownListen = (win = window) => {
try { //cross-origin
win.addEventListener('pointerdown', (e) => {
if (win !== top)
ODA.top.dispatchEvent(new PointerEvent("pointerdown", e));
})
// ToDo: not works dropdown (without parent) in modal:
win.addEventListener('pointerdown', (e) => {
ODA.mousePos = new DOMRect(e.pageX, e.pageY);
}, true)
// Array.from(win).forEach(w => pointerDownListen(w));
} catch (err) {
console.error(err);
}
}
function isObject(obj) {
return obj && typeof obj === 'object';
}
function loadImports(prototype) {
if (prototype.imports) {
if (!Array.isArray(prototype.imports)) {
prototype.imports = prototype.imports.split(',').map(i => i.trim());
}
const imports = prototype.imports.map(s => s.trim()).filter(Boolean).map(i => ODA.import(i, prototype));
if (imports.some(i => i.then)) {
return Promise.all(imports);
}
else {
return imports;
}
}
else {
return [];
}
}
async function getParents(prototype) {
clearExtends(prototype);
let parents = prototype.extends.map(s => s.trim()).filter(Boolean).reduce((res, ext) => {
ext = ext.trim();
if (ext === 'this')
res.push(ext);
else if (ext.includes('-')) {
let parent = ODA.telemetry.components[ext] || ODA.waitDependence(ext);
res.push(parent);
}
return res;
}, []);
return Promise.all(parents);
}
function finalizeRegistration(prototype, imports = [], parents = []) {
if (window.customElements.get(prototype.is)) {
return prototype.is;
}
try {
let template = prototype.template || '';
if (parents.length) {
let templateExt = '';
for (let parent of parents) {
if (parent === 'this') {
templateExt += template;
template = null;
}
else
templateExt += parent.prototype.$template;
}
if (template)
templateExt += template;
template = templateExt;
parents = parents.filter(i => i?.constructor === Object);
prototype.extends = parents.map(i => i.el);
}
const doc = domParser.parseFromString(`<template>${template || ''}</template>`, 'text/html');
template = doc.querySelector('template');
const styles = Array.prototype.filter.call(template.content.children, i => i.localName === 'style');
for (let style of styles) {
let css = style.textContent;//.split('\n')
while (css.includes('@apply'))
css = ODAStyles.applyStyleMixins(css);
let ss = new CSSStyleSheet();
ss.replaceSync(css);
style.textContent = css;
// template.content.insertBefore(style, template.content.children[0])
// todo доделать слияние стилей
}
while (styles.length) {
template.content.insertBefore(styles.pop(), template.content.firstChild);
}
prototype.$template = template.innerHTML.trim();
delete prototype.template;
prototype.$system.$styles = [...(ODAStyles.adopted || [])]
const children = parseJSX(prototype, prototype.$template);
const sk = Object.getOwnPropertyDescriptor(prototype, '$saveKey');
Object.defineProperty(prototype, '$saveKey', {
configurable: true,
enumerable: false,
get() {
let value = sk?.get?.call(this);
if (value === undefined)
value = '';
return value;
},
set(n) {
this[CORE_KEY].loaded = {};
this['#$savePath'] = undefined;
Object.values(this.constructor.__rocks__.descrs).filter(i => i.$save).forEach(i => {
const val = this.$loadPropValue(i.name);
if (val !== undefined)
this[i.name] = val;
});
}
});
const el = class extends odaComponent.ROCKS(prototype) {
constructor() {
super(...arguments);
Object.assign(this[CORE_KEY], {
test: {},
children,
pdp,
attributes: new Map(),
slotted: new Set()
});
if (this.isConnected) {
for (let i in this.constructor.__rocks__.descrs) {
const desc = Object.getOwnPropertyDescriptor(this, i);
if (desc) {
delete this[i];
this[i] = desc.value;
}
}
}
this[CORE_KEY].shadowRoot = this.attachShadow({ mode: 'closed' });
this[CORE_KEY].shadowRoot.adoptedStyleSheets = prototype.$system.$styles;
this.async(() => {
for (let a of this.attributes) {
let val = a.value;
val = (val === '') ? true : (val === undefined ? false : val);
const key = a.name.toCamelCase();
const prop = this.constructor.__rocks__.descrs[key];
if (prop && !prop.$readOnly)
this[key] = prop.toType(val);
}
prototype.$listeners.keydown = function (e) {
const e_key = e.key.toLowerCase();
const e_code = e.code.toLowerCase();
const key = Object.keys(this.$keys).find(key => {
return key.toLowerCase().split(',').some(v => {
return v.split('+').every(s => {
if (!s) return false;
const k = s.trim() || ' ';
switch (k) {
case 'ctrl':
return e.ctrlKey;
case 'shift':
return e.shiftKey;
case 'alt':
return e.altKey;
default:
return k === e_key || k === e_code || `key${k}` === e_code;
}
})
});
});
if (key) {
let handler = this.$keys[key];
if (typeof handler === 'string')
handler = this[handler];
if (!handler) {
throw new Error('no keybinding handler')
}
handler.call(this, e);
}
}
for (let n in prototype.$listeners) {
const fn = prototype.$listeners[n];
let args = false;
if (fn.constructor === String) {
prototype.$listeners[n] = this.__proto__[fn];
}
// if(n === 'mousewheel' || n === 'wheel') args = {passive: true}
this.addEventListener(n, prototype.$listeners[n].bind(this), args);
}
for (let n in prototype.$innerEvents) {
const fn = prototype.$innerEvents[n];
if (fn.constructor === String) {
prototype.$innerEvents[n] = this.__proto__[fn];
}
this[CORE_KEY].shadowRoot.addEventListener(n, prototype.$innerEvents[n].bind(this));
}
observedAttrProps.forEach(i => { //инициализация $attrs свойств ВАЖНО!!!
this[i.name];
});
this.ready?.();
});
}
static get observedAttributes() {
return observedAttributes;
}
attributeChangedCallback(name, o, n) {
if (name === 'slot') {
this.throttle('$render', () => {
this.$render();
}, 16)
}
else {
n = (n === '') ? true : n;
observedAttrProps.filter(i => i.$attr === name).forEach(i => {
this[i.name] = n;
});
}
}
$super(parentName, name, ...args) {
const components = ODA.telemetry.components;
if (parentName && components[parentName]) {
const proto = components[parentName].prototype;
const descriptor = Object.getOwnPropertyDescriptor(proto, name);
if (!descriptor) {
throw new Error(`Not found super: "${name}" `);
}
if (typeof descriptor.value === 'function')
return descriptor.value.call(this, ...args);
if (descriptor.get)
return descriptor.get.call(this);
return undefined;
}
}
};
const pdp = Object.values(el.__rocks__.descrs).filter(i => i.$pdp && !COMPONENT_HOOKS.includes(i.name)).map(i => {
const name = i.name;
return (i.value?.constructor === Function) ?
{ name, value: i.value } :
{
name, get() { return this.domHost[name]; },
set(val) { this.domHost[name] = val; }
};
});
const observedAttrProps = Object.values(el.__rocks__.descrs).filter(i => i.$attr);
const observedAttributes = observedAttrProps.filter(i => !i.$readOnly).map(i => {
if (i.$attr === true)
return i.name;
return i.$attr;
});
observedAttributes.push('slot');
Object.defineProperty(el, 'name', { value: prototype.is });
Object.defineProperty(el, Symbol.toStringTag, { value: '<' + prototype.is + '>', enumerable: false, configurable: false });
window.customElements.define(prototype.is, el);
if (!el) {
console.warn(`No custom element class in "${prototype.is}"`);
}
const component = ODA.telemetry.components[prototype.is] = { prototype, el };
ODA.telemetry.last = prototype.is;
if (ODA.wait?.[prototype.is]?.reg) {
ODA.wait[prototype.is].reg(component);
delete ODA.wait[prototype.is];
}
return prototype.is;
}
catch (e) {
console.error(prototype.is, e);
}
}
async function regComponent(prototype) {
if (window.customElements.get(prototype.is)){
return prototype.is;
}
const imports = await loadImports(prototype);
const parents = await getParents(prototype);
return finalizeRegistration(prototype, imports, parents);
}
const regexUrl = /https?:\/\/(?:.+\/)[^:?#&]+/g
const clearExtends = (proto, exts) => {
const toRemove = [];
if (!exts) {
for (const ext of proto.extends) {
const parentExtends = ODA.telemetry.prototypes[ext]?.extends;
if (parentExtends?.length) toRemove.add(...clearExtends(proto, parentExtends));
}
for (const rm of toRemove) {
const idx = proto.extends.indexOf(rm);
if (~idx) {
proto.extends.splice(idx, 1);
}
}
return toRemove;
}
for (const ext of exts) {
if (proto.extends.includes(ext)) {
toRemove.add(ext);
}
const parentExtends = ODA.telemetry.prototypes[ext]?.extends;
if (parentExtends?.length) clearExtends(proto, parentExtends);
}
return toRemove;
}
class odaComponent extends HTMLElement {
connectedCallback() {
ODA.resizeObserver.observe(this);
ODA.intersectionObserver.observe(this);
this.__pdp?.forEach(i => {
if (i.name in this)
return;
Object.defineProperty(this, i.name, i)
})
if (this._on_disconnect_timer) {
clearTimeout(this._on_disconnect_timer)
this._on_disconnect_timer = 0;
return;
}
this.async(async () => {
await this.$render();
this.async(() => {
this.attached?.();
});
})
}
disconnectedCallback() {
ODA.resizeObserver.unobserve(this);
ODA.intersectionObserver.unobserve(this);
this._on_disconnect_timer = setTimeout(() => {
this._on_disconnect_timer = 0;
this.detached?.();
this.detached?.();
}, 100)
if (this[CORE_KEY].slotted?.size) {
this.async(() => {
this.$render(true);
})
}
}
setProperty(name, v) {
if (name.includes('.')) {
let path = name.split('.');
let step;
for (let i = 0; i < path.length; i++) {
let key = path[i].toCamelCase();
if (i === 0) {
if (this.props && key in this.props) {
step = this[key] ??= {}
}
else break;
}
else if (isObject(step)) {
if (i < path.length - 1) {
step = step[key] ??= {};
} else {
step[key] = v;
return;
}
}
}
}
else if (name in this.__proto__) {
this[name] = v;
}
else if (!isObject(v)) {
try {
name = name.toKebabCase();
if (v || v === 0)
this.setAttribute(name, v === true ? '' : v);
else
this.removeAttribute(name);
}
catch (e) {
console.log(e)
}
}
else {
this[name] = v;
}
}
get __pdp() {
const pdps = this.domHost?.__pdp || [];
pdps.push(...(this.domHost?.[CORE_KEY].pdp || []));
return pdps?.filter(desc => {
return !Object.keys(this.constructor.__rocks__.descrs).some(key => key === desc.name);
})
}
$updateStyle(styles = {}) {
this[CORE_KEY].style = Object.assign({}, this[CORE_KEY].style, styles);
this.$render();
}
$notify($prop, val) {
if ($prop?.$attr) {
if (val || val === 0)
this.setAttribute($prop?.$attr, val === true ? '' : val);
else
this.removeAttribute($prop?.$attr);
}
this.updated?.();
}
$render(force) {
if (this.isConnected || force) {
if (this.domHost?.__render && !force)
return this.domHost?.__render;
return this.__render ??= new Promise(async resolve => {
await renderChildren.call(this, this[CORE_KEY].shadowRoot)
this[CORE_KEY].test['render'] = (this[CORE_KEY].test['render'] || 0) + 1;
this.onRender?.();
resolve(this);
this.__render = undefined;
})
}
return this.__render;
}
get $keys() {
if (!this['#$keys']) {
this['#$keys'] = {};
for (let i in this.constructor.__rocks__.prototype.$keyBindings || {}) {
let handler = this.constructor.__rocks__.prototype.$keyBindings[i];
this['#$keys'][i] = (typeof handler === 'function')
? handler.bind(this)
: handler;
}
}
return this['#$keys'];
}
get $body() {
return this.domHost?.body || this.parentNode?.body || this.parentElement;
}
get $savePath() {
return `${this.localName}${this.$saveKey && ('/' + this.$saveKey) || ''}`;
}
$loadPropValue(key) {
this[CORE_KEY].loaded ??= {};
this[CORE_KEY].loaded[key] = true;
const value = ODA.LocalStorage.create(this.$savePath).getItem(key);
switch (value?.constructor) {
case Object: return { ...value };
case Array: return [...value];
case Date:
case Number:
case String:
case Boolean:
default: return value;
}
}
$savePropValue(key, value) {
if (!this[CORE_KEY].loaded?.[key]) return;
ODA.LocalStorage.create(this.$savePath).setItem(key, value);
}
$resetSettings() {
ODA.LocalStorage.create(this.$savePath).clear();
}
get $slotted() {
return Array.prototype.reduce.call(this[CORE_KEY].shadowRoot.childNodes, (res, i) => {
if (i?.slotTarget?.parentElement)
res.add(i.slotTarget.parentElement);
return res;
}, [])
}
$(path) {
if (!path) return null;
let result = this[CORE_KEY].shadowRoot?.querySelector(path);
if (!result) {
for (let i of this.$slotted) {
result = i.querySelector(path);
if (result && result.domHost === this)
break;
else
result = null;
}
}
return result
}
$$(path) {
if (!path) return [];
let result = Array.from(this[CORE_KEY].shadowRoot.querySelectorAll(path));
for (let i of this.$slotted) {
const res = i.querySelectorAll(path);
for (let el of res) {
if (el.domHost === this);
result.add(el)
}
}
return result;
}
get $url() {
this.constructor.__rocks__.prototype.$system.url;
}
$next(handler, tacts = 0) {
if (tacts > 0)
requestAnimationFrame(() => {
this.$next(handler, tacts - 1)
})
else {
if (typeof handler === 'string')
handler = this[handler].bind(this);
requestAnimationFrame(handler);
}
}
}
function ODA(prototype = {}) {
prototype.is = prototype.is.toLowerCase();
prototype.$system ??= Object.create(null);
if (window.customElements.get(prototype.is) || ODA.telemetry.prototypes[prototype.is]) {
ODA.telemetry.prototypes[prototype.is] = prototype;
return prototype.is;
}
else {
const matches = (new Error()).stack.trim().match(regexUrl);
prototype.$system.url = matches[matches.length - 1];
prototype.$system.dir = prototype.$system.url.substring(0, prototype.$system.url.lastIndexOf('/')) + '/';
prototype.extends = str2arr(prototype.extends);
let name;
ODA.telemetry.prototypes[prototype.is] = regComponent(prototype).then(res => {
name = res;
return ODA.telemetry.prototypes[prototype.is] = prototype;
});
return ODA.telemetry.prototypes[prototype.is].then(() => name);
}
}
// ODA.isHidden = true;
ODA.regHotKey = function (key, handle) {
ODA.$hotKeys = ODA.$hotKeys || {};
ODA.$hotKeys[key] = handle;
}
document.addEventListener('keydown', async (e) => {
if (!e.code?.startsWith?.('Key')) return;
let key = (e.ctrlKey ? 'ctrl+' : '') + (e.altKey ? 'alt+' : '') + (e.shiftKey ? 'shift+' : '') + e.code.replace('Key', '').toLowerCase();
ODA.$hotKeys?.[key]?.(e);
});
ODA.regTool = function (name) {
return ODA[name] || (ODA[name] = Object.create(null));
}
ODA.rootPath = import.meta.url;
ODA.rootPath = ODA.rootPath.split('/').slice(0, -1).join('/');
/** @type {{[key: string]: {promise: Promise<string>|undefined, reg: function|undefined, err: function|undefined}}} */
ODA.wait = {};
/** @param {string} tag */
ODA.waitDependence = function (tag) {
if (tag in ODA.telemetry.components) {
return ODA.telemetry.components[tag].prototype;
}
ODA.wait[tag] ??= { promise: undefined, reg: undefined, err: undefined };
ODA.wait[tag].promise ??= new Promise((resolve, reject) => {
ODA.wait[tag].reg = prototype => resolve(prototype);
ODA.wait[tag].err = error => reject(error);
});
return ODA.wait[tag].promise;
}
/** @param {string} tag */
ODA.waitReg = function (tag) {
return ODA.telemetry.prototypes[tag] || ODA.waitDependence(tag);
}
window.ODA = ODA;
const apples = ['Mac68K', 'MacPPC', 'MacIntel', 'iPhone', 'iPod', 'iPad',]
ODA.isApple = apples.includes(navigator.platform);
localStorage: {
ODA.LocalStorage = class odaLocalStorage extends ROCKS({
get data() {
try {
const data = JSON.parse(globalThis.localStorage.getItem(this.path) || '{}');
data.$$stamp ??= Date.now();
return data;
}
catch (e) {
console.warn(e)
}
return {};
},
getItem(key) {
return this.data[key];
},
getFromItem(key, subKey) {
return this.data[key]?.[subKey];
},
getByPath(path) {
const [key, ...subKeys] = path.split('/');
let res = this.data[key];
for (const subKey of subKeys) {
if (!res) break;
res = res[subKey];
}
return res;
},
setItem(key, value) {
this.data[key] = value;
this.save();
},
setToItem(key, subKey, value) {
key = this.data[key] ??= {};
key[subKey] = value;
this.save();
},
setByPath(path, value) {
const [key, ...subKeys] = path.split('/');
if (!subKeys.length) {
this.data[key] = value;
}
else {
let res = this.data[key] ??= {};
for (const subKey of subKeys.slice(0, -1)) {
res = res[subKey] ??= {};
}
res[subKeys.at(-1)] = value;
}
this.save();
},
save() {
if (this['#data'] === undefined) return;
if (this.raf)
cancelAnimationFrame(this.raf);
this.raf = requestAnimationFrame(() => {
globalThis.localStorage.setItem(this.path, JSON.stringify(this.data));
this.raf = 0;
})
},
get version() {
return this.data.$$stamp;
},
clear() {
this.data = undefined;
this.version = undefined;
globalThis.localStorage.removeItem(this.path);
}
}) {
constructor(path) {
super();
this.path = path;
}
static items = {}
static create(path) {
return ODA.LocalStorage.items[path] ??= new ODA.LocalStorage(path);
}
}
}
Object.defineProperty(ODA, 'top', {
get() {
try {
if (window.parent !== window)
return window.parent.ODA?.top || window;
return window;
}
catch (err) {
console.warn(err);
return window;
}
}
});
class ODAServiceManeger {
constructor() {
throw new TypeError('Illegal invocation');
}
static registerService(name, service) {
if (this.#services[name]) {
throw new Error('The service is already registered!');
}
this.#services[name] = service;
}
static getService(name) {
let w = window;
let service = this.#services[name];
while (!service && w !== w.parent && (w = w.parent)) {
service = w.ODA?.services.getService(name);
}
return service;
}
static unregisterService(name) {
delete this.#services[name];
}
static #services = {};
}
ODA.services = ODAServiceManeger;
class VNode {
constructor(el, vars) {
this.id = ++VNode.sid;
this.vars = vars;
el.$node = this;
this.el = el;
this.tag = el.nodeName;
this.fn = {};
this.children = [];
if (el.parentNode?.$node?.isSvg || el.nodeName === 'svg')
this.isSvg = true;
else if (el.nodeName === 'SLOT')
this.isSlot = true;
this.listeners = {};
}
}
VNode.sid = 0
const dirRE = /^((oda|[a-z])?-)|~/;
//var localizationPhrase = {}
const INLINE_EXPRESSION = /\{\{((?:.|\n)+?)\}\}/;
function parseJSX(prototype, el, vars = []) {
if (!el) return []
if (typeof el === 'string') {
let tmp = document.createElement('template');
tmp.innerHTML = el;
tmp = tmp.content.childNodes;
return Array.prototype.map.call(tmp, el => parseJSX(prototype, el)).filter(i => (i && !i.$remove));
}
let src = new VNode(el, vars);
if (el.nodeType === 3) {
let value = el.textContent.trim();
if (!value) return;
if (INLINE_EXPRESSION.test(value)) {
let expr = '""+' + value.replace(/^|$/g, "'").replace(/{{/g, "'+(").replace(/}}/g, ")+'").replace(/\n/g, "\\n").replace(/\+\'\'/g, "").replace(/\'\'\+/g, "");
if (prototype[expr])
expr += '()';
const fn = createFunc(vars.join(','), expr, prototype);
src.text ??= [];
if (el.parentElement?.localName === 'style') {
let name = '_style' + el.$node.id;
const key = '#' + name;
prototype[name] = {
get() {
return exec.call(this, fn);
}
}
src.text.push(function ($el) {
if (this[key]) return;
$el.textContent = this[name] || '';
})
}
else {
src.text.push(function ($el) {
let text = String(exec.call(this, fn, $el, $el.__for));
if ($el.___textContent == text)
return;
$el.textContent = $el.___textContent = text;
})
}
}
else if (el.parentElement?.localName === 'style' && !el.parentElement.parentElement) {
el.parentElement.$node.$remove = true;
let ss = new CSSStyleSheet();
while (value.includes('@apply'))
value = ODA.applyStyleMixins(value);
ss.replaceSync(value);
prototype.$system.$styles.push(ss);
return;
}
else
src.textContent = value;
}
else if (el.nodeType === 8) {
src.textContent = el.textContent;
}
else {
for (const attr of el.attributes) {
let name = attr.name;
let expr = attr.value;
let modifiers;
if (typeof Object.getOwnPropertyDescriptor(prototype, expr)?.value === "function")
expr += '()';
if (/^(:|bind:)/.test(attr.name)) {
name = name.replace(/^(::?|:|bind::?)/g, '');
if (expr === '')
expr = attr.name.replace(/:+/, '').toCamelCase();
let fn = createFunc(vars.join(','), expr, prototype);
if (/::/.test(attr.name)) {
const params = ['$value', ...(vars || [])];
src.listeners.input = function func2wayInput(e) {
if (!e.target.parentNode || !(name === 'value' || name === 'checked')) return;
e.stopPropagation();
const target = e.target;
let value = target.value;
switch (target.type) {
case 'checkbox': {
value = target.checked;
}
}
target.__lockBind = name;
const handle = () => {
target.__lockBind = false;
target.removeEventListener('blur', handle);
};
target.addEventListener('blur', handle);
target.dispatchEvent(new CustomEvent(name + '-changed', { detail: { value } }));
};
const func = new Function('$this,' + params.join(','), `with (this) {${expr} = $value}`);
src.listeners[name + '-changed'] = function func2wayBind(e, d) {
if (!e.target.parentNode) return;
let res = e.detail.value === undefined ? e.target[name] : e.detail.value;
if (e.target.$node.vars.length) {
let idx = e.target.$node.vars.indexOf(expr);
if (idx % 2 === 0) {
const array = e.target.__for[idx + 2];
const index = e.target.__for[idx + 1];
array[index] = e.target[name];
return;
}
}
if (res !== undefined || e.target.__for !== undefined) //todo понаблюдать
exec.call(this, func, e.target, [res, ...(e.target.__for || [])]);
};
src.listeners[name + '-changed'].notify = name;
src.listeners[name + '-changed'].expr = expr;
}
const h = function (params, $el) {
return exec.call(this, fn, params, $el);
};
h.modifiers = modifiers;
src.bind = src.bind || {};
src.bind[name.toCamelCase()] = h;
}
else if (dirRE.test(name)) {
name = name.replace(dirRE, '');
if (name === 'for')
return forDirective(prototype, src, name, expr, vars, attr.name);
else if (tags[name])
new Tags(src, name, expr, vars, prototype);
else if (directives[name])
new Directive(src, name, expr, vars, prototype);
else
throw new Error('Unknown directive ' + attr.name);
}
else if (/^@/.test(attr.name)) {
modifiers = parseModifiers(name);
if (modifiers)
name = name.replace(modifierRE, '');
if (expr === (attr.value + '()'))
expr = attr.value + '($event, $detail)';
name = name.replace(/^@/g, '');
const params = ['$this', '$event', '$detail', ...(vars || [])];
const fn = new Function(params.join(','), `with (this) {${expr}}`);
src.listeners ??= src.listeners;
// const handler = prototype[expr];
const func = attr.value.trim();
src.listeners[name] = async function (e) {
modifiers && modifiers.stop && e.stopPropagation();
modifiers && modifiers.prevent && e.preventDefault();
modifiers && modifiers.immediate && e.stopImmediatePropagation();
let result;
const descriptor = Object.getOwnPropertyDescriptor(this, expr)
const handler = this[func];
if (typeof handler === 'function')
result = handler.call(this, e, e.detail);
else
result = exec.call(this, fn, e.target, [e, e.detail, ...(e.target.__for || [])]);
if (result?.then) {
try {
await result;
}
catch (e) {
}
}
};
src.listeners[name].modifiers = modifiers;
}
else if (name === 'is')
src.tag = expr.toUpperCase();
else {
src.attrs = src.attrs || {};
src.attrs[name] = expr;
}
}
if (src.attrs && src.dirs) {
for (const a of Object.keys(src.attrs)) {
if (src.dirs.find(f => f.name === a)) {
src.vals = src.vals || {};
switch (a) {
case 'style': {
src.vals[a] = styleStringToObject(src.attrs[a]);
} break;
default: {
src.vals[a] = src.attrs[a];
}
}
delete src.attrs[a];
}
}
}
if (prototype.$system.shared && src.tag !== 'STYLE') {
for (let key of prototype.$system.shared) {
if (src.bind?.[key] === undefined) {
src.bind = src.bind || {};
let fn = createFunc(vars.join(','), key, prototype);
src.bind[key] = function ($el, params) {
let result = exec.call(this, fn, $el, params);
if (result === undefined)
result = $el[key];
if (result === undefined)
result = prototype.props[key]?.default;
return result;
}
}
}
}
src.children = Array.from(el.childNodes).map(el => {
return parseJSX(prototype, el, vars)
}).filter(i => i);
}
if (src.children.length === 1 && src.children[0].tag === '#text')
src.ignoreChildren = true;
if (src.bind?.slot || src.attrs?.slot)
src.isSlotted = true;
return src;
}
const tags = {
if(tag, fn, p, $el) {
return exec.call(this, fn, $el, p) ? tag : false;
},
'else-if'(tag, fn, p, $el) {
if ($el?.previousElementSibling?.nodeType !== 1)
return exec.call(this, fn, $el, p) ? tag : false;
},
else(tag, fn, p, $el) {
if ($el?.previousElementSibling?.nodeType !== 1)
return tag;
},
is(tag, fn, p, $el) {
if (tag.startsWith('#'))
return tag;
return (exec.call(this, fn, $el, p) || '')?.toUpperCase() || tag;
}
};
const directives = {
wake($el, fn, p) {
$el.$wake = exec.call(this, fn, $el, p);
},
'save-key'($el, fn, p) {
if ($el[CORE_KEY]) {
$el.$saveKey = exec.call(this, fn, $el, p);
}
},
props($el, fn, p) {
const props = exec.call(this, fn, $el, p);
if ($el.__dir_props__) {
if (Object.equal($el.__dir_props__.last, props, true))
return;
}
else {
$el.__dir_props__ = { current: {}, last: {} };
}
const keys = new Set([$el.__dir_props__.last, props].map(o => (o && Object.keys(o)) || []).flat());
for (const k of keys) {
$el.__dir_props__.current[k] = props?.[k];
}
$el.__dir_props__.last = props;
$el.assignProps($el.__dir_props__.current);
},
show($el, fn, p) {
$el.style.display = exec.call(this, fn, $el, p) ? '' : 'none';
},
html($el, fn, p) {
const html = exec.call(this, fn, $el, p) ?? '';
if (html instanceof HTMLElement) {
if ($el.firstChild === html)
return;
$el.innerHTML = '';
$el.appendChild(html)
} else {
if ($el.___innerHTML == html)
return;
$el.___innerHTML = html;
$el.innerHTML = html
}
},
text($el, fn, p) {
const text = exec.call(this, fn, $el, p) ?? '';
if ($el.___textContent == text)
return;
$el.textContent = $el.___textContent = text;
},
class($el, fn, p) {
let s = exec.call(this, fn, $el, p) ?? '';
if (!Object.equal($el.$class, s)) {
$el.$class = s;
if (Array.isArray(s))
s = s.join(' ');
else if (typeof s === 'object')
s = Object.keys(s).filter(i => s[i]).join(' ');
if ($el.$node?.vals?.class)
s = (s ? (s + ' ') : '') + $el.$node.vals.class;
// this.throttle('set-class', ()=>{
$el.setAttribute('class', s);
// })
}
},
style($el, fn, p) {
let s = exec.call(this, fn, $el, p) ?? '';
if (!Object.equal($el.$style, s, true)) {
$el.$style = s;
if (typeof s === 'string') s = styleStringToArray(s);
if (Array.isArray(s)) s = styleArrayToObject(s);
s = Object.keys(s).reduce((res, a) => {
res[a.trim().toKebabCase()] = s[a]; // minHeight -> min-height
return res;
}, {})
const def = $el.$node?.vals?.style || {};
const cur = styleStringToObject($el.getAttribute('style'));
s = { ...def, ...cur, ...s };
s = styleObjectToString(s);
// this.throttle('set-style', ()=>{
$el.setAttribute('style', s);
// })
}
}
};
class Directive {
constructor(src, name, expr, vars, prototype) {
if (expr === '')
expr = name.replace(/:+/, '').toCamelCase();
src.fn[name] = createFunc(vars.join(','), expr, prototype);
src.fn[name].expr = expr;
src.dirs = src.dirs || [];
src.dirs.push(directives[name])
}
}
class Tags {
constructor(src, name, expr, vars, prototype) {
src.fn[name] = expr ? createFunc(vars.join(','), expr, prototype) : null;
src.tags = src.tags || [];
src.tags.push(tags[name])
}
}
function forDirective(prototype, src, name, expr, vars, attrName) {
const idx = vars.length + 1;
src.vars = [...vars, '$'.repeat(idx) + 'for'];
src.el.removeAttribute(attrName);
const child = parseJSX(prototype, src.el, src.vars);
const fn = createFunc(src.vars.join(','), expr, prototype);
const h = function (p = []) {
let items = exec.call(this, fn, undefined, p);
if (typeof items === 'string')
items = items.split('');
else if (isObject(items) && !Array.isArray(items)) {
return Object.keys(items).map((key, index) => {
return { child, params: [...p, { item: items[key], index, items, key }] }
});
}
if (!Array.isArray(items)) {
items = +items || 0;
if (items < 0)
items = 0
items = new Array(items);
for (let i = 0; i < items.length; items[i++] = i);
}
const res = [];
for (let index = 0; index < items.length; index++) {
if (!(index in items)) continue;
const item = items[index];
res.push({ child, params: [...p, { item, index, items, key: index }] });
}
return res;
};
h.src = child;
return h;
}
const _createElement = document.createElement;
document.createElement = function (tag, ...args) {
if (tag.includes('-')) {
const prototype = ODA.telemetry.prototypes[tag];
if (prototype?.then) {
prototype.then(prototype => {
ODA.wait?.[tag]?.reg?.({ prototype });
delete ODA.wait?.[tag]
})
}
else {
ODA.wait?.[tag]?.reg?.({ prototype });
delete ODA.wait?.[tag]
}
}
return _createElement.call(this, tag, ...args);
}
function createElement(src, tag, old, __for) {
let $el;
if (tag === '#comment')
$el = document.createComment((src.textContent || src.id) + (old ? (': ' + old.tagName) : ''));
else if (tag === '#text')
$el = document.createTextNode('');
else {
if (src.isSvg)
$el = document.createElementNS(svgNS, tag.toLowerCase());
else {
$el = ODA.createElement.call(this, tag);
}
switch (tag) {
case 'STYLE': {
} break;
case 'IFRAME': {
$el.addEventListener('load', e => {
try {
if (!e.target.contentDocument.ODA) {
pointerDownListen(e.target.contentWindow);
}
}
catch (e) {
console.warn(e)
}
})
} break;
default: {
if (!src.isSvg && !src.isSlot) {
ODA.intersectionObserver.observe($el);
ODA.resizeObserver.observe($el);
}
}
}
if (src.attrs)
for (let i in src.attrs)
$el.setAttribute(i, src.attrs[i]);
for (const e in src.listeners || {}) {
const event = (ev) => {
src.listeners[e].call(this, ev);
}
$el.addEventListener(e, event, { passive: ['wheel'].some(i => i === e), capture: src.listeners[e].modifiers?.capture });
}
}
$el.$node = src;
$el.domHost = this;
if (__for) $el.__for = __for;
return $el;
}
async function renderChildren(root) {
if (!root.$sleep || root.$wake) {
let el, idx = 0;
for (let h of (root?.$node || this[CORE_KEY]).children || []) {
if (h.call) { // table list
let items = h.call(this, root.__for);
for (let i = 0; i < items.length; i++) {
const node = items[i];
if (node) {
while (el = root.childNodes[idx]) {
if (el.$node === node.child) break;
if (!el.$node) {
idx++;
continue;
}
//понаблюдать 👀
else if (el.$node.vars !== node.child.vars) {
break;
}
root.removeChild(el);
}
el = root.childNodes[idx + i];
el = await renderElement.call(this, node.child, el, root, node.params);
}
}
idx += items.length;
}
else { // single element
while (el = root.childNodes[idx]) {
if (el.$node === h) break;
if (!el.$node) {
idx++;
continue;
}
root.removeChild(el);
}
el = await renderElement.call(this, h, el, root, root.__for);
idx++;
}
}
if (root[CORE_KEY] && root.isConnected) {
await root.$render(this);