UNPKG

oda-framework

Version:

It's an ES Progressive Framework based on the technology of Web Components and designed especially for creating custom UI/UX of any complexity for web and cross-platform PWA mobile applications.

1,193 lines (1,163 loc) 87.1 kB
/* * 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 && !this.$wake) return root; 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); } // else if (root?.$node?.isSlot){ // // for (let el of root.assignedElements?.() || []){ /