+40
-14
@@ -0,1 +1,3 @@ | ||
| import { PlSVGElement } from './pl-element.js'; | ||
| /** | ||
@@ -8,10 +10,11 @@ * | ||
| export function setAttrValue(node, attr, val) { | ||
| if ( node.isSVGCustomElement ) node = node.root; | ||
| if (val !== undefined && val !== false) | ||
| if (node.isSVGCustomElement) node = node.root; | ||
| if (val !== undefined && val !== false) { | ||
| node.setAttribute(attr, val === true ? '' : val); | ||
| else | ||
| } else { | ||
| node.removeAttribute(attr); | ||
| } | ||
| } | ||
| export function getAttrValue(node, attr) { | ||
| if ( node instanceof PlSVGElement) node = node.root; | ||
| if (node instanceof PlSVGElement) node = node.root; | ||
| return node.getAttribute(attr); | ||
@@ -44,3 +47,3 @@ } | ||
| if (path.length > 0 && obj) { | ||
| path.forEach( p => obj = obj?.[p] ); | ||
| path.forEach(p => obj = obj?.[p]); | ||
| } | ||
@@ -57,4 +60,4 @@ return obj; | ||
| export function isSubPath(a, b) { | ||
| let ax = a + '.'; | ||
| let bx = b + '.'; | ||
| const ax = a + '.'; | ||
| const bx = b + '.'; | ||
| return ax.startsWith(bx.slice(0, ax.length)); | ||
@@ -65,9 +68,9 @@ } | ||
| root.querySelectorAll('template') | ||
| .forEach( t => { | ||
| .forEach((t) => { | ||
| cb(t); | ||
| forEachTemplateRecursive(t.content, cb) | ||
| forEachTemplateRecursive(t.content, cb); | ||
| }); | ||
| } | ||
| export function fromDashed (str) { | ||
| export function fromDashed(str) { | ||
| return str.replace(/-[a-z\u00E0-\u00F6\u00F8-\u00FE-]/g, function (match) { | ||
@@ -90,3 +93,3 @@ return match.slice(1).toUpperCase(); | ||
| export function getNPath(root, node) { | ||
| let path = []; | ||
| const path = []; | ||
| while (node && node !== root) { | ||
@@ -99,7 +102,7 @@ path.unshift([...node.parentNode.childNodes].indexOf(node)); | ||
| export function findByNPath(node, path) { | ||
| return path.reduce((n, i) => n.childNodes[i] , node); | ||
| return path.reduce((n, i) => n.childNodes[i], node); | ||
| } | ||
| export function getRandomId() { | ||
| return (Math.random() + 1).toString(36).substring(2) | ||
| return (Math.random() + 1).toString(36).substring(2); | ||
| } | ||
@@ -109,2 +112,25 @@ | ||
| return string.replace(/([a-z\d])([A-Z-])/g, '$1-$2').toLowerCase(); | ||
| } | ||
| } | ||
| export function getBindValue(bind) { | ||
| const dv = bind.depend.map((p) => { | ||
| if (/['"]/.test(p)) { | ||
| return p.replace(/["']/g, ''); | ||
| } else { | ||
| return bind.initiator[p]?.get(p); | ||
| } | ||
| }); | ||
| if (dv.length > 1) { | ||
| const [fn, ...args] = dv; | ||
| if (!fn) { | ||
| console.error('Function not found in context: %s(%s)', bind.depend[0], bind.depend.slice(1).join(',')); | ||
| return; | ||
| } | ||
| // TODO: ctx for function | ||
| return fn.apply(bind.initiator[bind.depend[0]], args); | ||
| } else { | ||
| return dv[0]; | ||
| } | ||
| } | ||
+3
-3
| export function css(str) { | ||
| if (document.adoptedStyleSheets) { | ||
| let sheet = new CSSStyleSheet(); | ||
| const sheet = new CSSStyleSheet(); | ||
| sheet.replaceSync(str); | ||
| return sheet; | ||
| } else { | ||
| let sheet = document.createElement('style'); | ||
| const sheet = document.createElement('style'); | ||
| sheet.innerText = str; | ||
| return sheet; | ||
| } | ||
| } | ||
| } |
+69
-34
@@ -1,2 +0,5 @@ | ||
| import {getProp, isSubPath, normalizePath, stringPath} from "../../common.js"; | ||
| import { getProp, isSubPath, normalizePath, stringPath } from '../../common.js'; | ||
| const WMH_MAX_VALUE = 2 ** 32; | ||
| let wmh = 0; | ||
@@ -7,11 +10,15 @@ export const ContextMixin = s => class dataContext extends s { | ||
| set(path, value, wmh) { | ||
| let xpath = normalizePath(path); | ||
| let x = xpath.pop(); | ||
| let obj = getProp(this, xpath); | ||
| if(obj == null) return; | ||
| let oldValue = obj[x]; | ||
| //TODO: move _props to props mixin | ||
| if (obj._props?.[x]) obj._props[x] = value; else obj[x] = value; | ||
| if (value === oldValue/* && xl === 1*/) return; | ||
| this.notifyChange({ action: 'upd', path, value, oldValue, wmh}); | ||
| const xpath = normalizePath(path); | ||
| const x = xpath.pop(); | ||
| const obj = getProp(this, xpath); | ||
| if (obj === null || obj === undefined) return; | ||
| const oldValue = obj[x]; | ||
| // TODO: move _props to props mixin | ||
| if (obj._props?.[x]) { | ||
| obj._props[x] = value; | ||
| } else { | ||
| obj[x] = value; | ||
| } | ||
| if (value === oldValue/* && xl === 1 */) return; | ||
| this.notifyChange({ action: 'upd', path, value, oldValue, wmh }); | ||
| } | ||
@@ -23,7 +30,8 @@ | ||
| } | ||
| push(path,value) { | ||
| let target = this.get(path); | ||
| push(path, value) { | ||
| const target = this.get(path); | ||
| if (Array.isArray(target)) { | ||
| if (!Array.isArray(value)) value = [value] | ||
| let len = target.push(...value); | ||
| if (!Array.isArray(value)) value = [value]; | ||
| const len = target.push(...value); | ||
| this.notifyChange({ action: 'splice', path, target, index: target.length - value.length, addedCount: value.length, added: value }); | ||
@@ -33,8 +41,16 @@ return len; | ||
| } | ||
| splice(path, index, deletedCount, ...added) { | ||
| let target = this.get(path); | ||
| let deleted = target.splice(index, deletedCount, ...added); | ||
| this.notifyChange({ action: 'splice', path, target, index: index, deletedCount, addedCount: added?.length, added, deleted }); | ||
| const target = this.get(path); | ||
| const deleted = target.splice(index, deletedCount, ...added); | ||
| this.notifyChange({ action: 'splice', path, target, index, deletedCount, addedCount: added?.length, added, deleted }); | ||
| } | ||
| assign(path, object) { | ||
| Object.entries(object) | ||
| .forEach(([property, value]) => { | ||
| this.set(path + '.' + property, value); | ||
| }); | ||
| } | ||
| /** @typedef {Object} DataMutation | ||
@@ -58,9 +74,10 @@ * @property {String} action | ||
| notifyChange(m) { | ||
| let path = normalizePath(m.path); | ||
| const path = normalizePath(m.path); | ||
| const textPath = path.join('.'); | ||
| m.wmh = m.wmh || getNextWM(); | ||
| if (this.wmh[path.join('.')] >= m.wmh ) return; | ||
| this.wmh[path.join('.')] = m.wmh; | ||
| if (this.wmh[textPath] >= m.wmh && this.wmh[textPath] - m.wmh < WMH_MAX_VALUE / 2) return; | ||
| this.wmh[textPath] = m.wmh; | ||
| if (m.value === m.oldValue && m.action === 'upd' && path.length === 1) return; | ||
| this.applyEffects(m); | ||
| let name = path[0]; | ||
| const name = path[0]; | ||
| // Порядок важен, чтобы вызывались сначала внутренние обсерверы компонента, а потом остальные | ||
@@ -72,18 +89,17 @@ if (this._dp?.[name]?.observer) { | ||
| this.dispatchEvent(new CustomEvent(name + '-changed', { detail: m })); | ||
| // Таймаут нужен, чтобы дождаться выполнения всех кольцевых зависимостей | ||
| setTimeout(() => { | ||
| delete this.wmh[path.join('.')]; | ||
| }, 0); | ||
| } | ||
| forwardNotify(mutation, from, to) { | ||
| let r = new RegExp(`^(${from})(\..)?`); | ||
| const r = new RegExp(`^(${from})(\\..)?`); | ||
| let path = mutation.path; | ||
| if (Array.isArray(path)) path = path.join('.'); | ||
| path = path.replace(r, to+'$2'); | ||
| mutation = {...mutation, path}; | ||
| path = path.replace(r, to + '$2'); | ||
| mutation = { ...mutation, path }; | ||
| this.notifyChange(mutation); | ||
| } | ||
| hasProp(name) { | ||
| return name in this; | ||
| } | ||
| addEffect(path, effect) { | ||
@@ -96,2 +112,3 @@ if (this._em[path]) { | ||
| } | ||
| /** | ||
@@ -102,13 +119,31 @@ * | ||
| applyEffects(m) { | ||
| let effectMap = this._em; | ||
| effectMap && Object.keys(effectMap).forEach( k => { | ||
| const effectMap = this._em; | ||
| effectMap && Object.keys(effectMap).forEach((k) => { | ||
| if (!m || isSubPath(stringPath(m.path), k)) { | ||
| effectMap?.[k]?.forEach( f => f(m) ); | ||
| effectMap?.[k]?.forEach(f => f(m)); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| _hooks = new Map(); | ||
| registerHook(hook, cb) { | ||
| if (!this._hooks.has(hook)) this._hooks.set(hook, new Set()); | ||
| this._hooks.get(hook).add(cb); | ||
| } | ||
| runHooks(hook) { | ||
| this._hooks.get(hook)?.forEach(hook => hook()); | ||
| } | ||
| disconnectedCallback() { | ||
| this.runHooks('disconnected'); | ||
| } | ||
| }; | ||
| export function getNextWM() { | ||
| return wmh++; | ||
| } | ||
| wmh++; | ||
| if (wmh >= WMH_MAX_VALUE) { | ||
| wmh = 0; | ||
| } | ||
| return wmh; | ||
| } |
+64
-71
@@ -1,3 +0,3 @@ | ||
| import {findByNPath} from "../../common.js"; | ||
| import {createBind, getBackApl} from "./template.js"; | ||
| import { findByNPath, getBindValue } from '../../common.js'; | ||
| import { createBind, getBackApl } from './template.js'; | ||
@@ -8,45 +8,49 @@ export class TemplateInstance { | ||
| constructor(template) { | ||
| this.tpl = template | ||
| this.tpl = template; | ||
| this.clone = this.tpl.getClone(); | ||
| this.tpl.svgCE.forEach( path => { | ||
| let node = findByNPath(this.clone, path); | ||
| let constr = customElements.get(node.getAttribute('is')); | ||
| node.ctx = new constr({lightDom: true, root: node}); | ||
| this.tpl.afterStampHooks.push( { path , hook: node => node.ctx.connectedCallback() } ) | ||
| }) | ||
| this.tpl.svgCE.forEach((path) => { | ||
| const node = findByNPath(this.clone, path); | ||
| const constr = customElements.get(node.getAttribute('is')); | ||
| node.ctx = new constr({ lightDom: true, root: node }); | ||
| this.tpl.afterStampHooks.push({ path, hook: node => node.ctx.connectedCallback() }); | ||
| }); | ||
| // save clone node list to future detach | ||
| this._nodes = [...this.clone.childNodes]; | ||
| this.binds = this.tpl.binds.map( b => ({...b, node: findByNPath(this.clone, b.path) })); | ||
| this._nodes = [...this.clone.childNodes]; | ||
| this.binds = this.tpl.binds.map(b => ({ ...b, node: findByNPath(this.clone, b.path) })); | ||
| } | ||
| attach(target, before, context) { | ||
| this.ctx = Array.isArray(context) ? context : [context]; | ||
| let tw = document.createTreeWalker(this.clone, NodeFilter.SHOW_COMMENT, { acceptNode: n => n._tpl ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT } ); | ||
| while( tw.nextNode() ) tw.currentNode._hctx = this.ctx; | ||
| const tw = document.createTreeWalker(this.clone, NodeFilter.SHOW_COMMENT, { acceptNode: n => n._tpl ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT }); | ||
| while (tw.nextNode()) tw.currentNode._hctx = this.ctx; | ||
| this.tpl.stampHooks.forEach( h => h.hook(findByNPath(this.clone, h.path), this.ctx)); | ||
| this.tpl.stampHooks.forEach(h => h.hook(findByNPath(this.clone, h.path), this.ctx)); | ||
| // build effect map | ||
| this.binds.forEach(b => { | ||
| this.binds.forEach((b) => { | ||
| this.attachBind(b); | ||
| }) | ||
| }); | ||
| // apply binds | ||
| this.applyBinds(); | ||
| this.insert(target, before); | ||
| this.tpl.afterStampHooks.forEach( h => h.hook(findByNPath({childNodes: this._nodes}, h.path), this.ctx)); | ||
| this.tpl.afterStampHooks.forEach(h => h.hook(findByNPath({ childNodes: this._nodes }, h.path), this.ctx)); | ||
| return this; | ||
| } | ||
| insert(target, before) { | ||
| target = before?.parentNode ?? target; | ||
| if (before) | ||
| if (before) { | ||
| target.insertBefore(this.clone, before); | ||
| else | ||
| } else { | ||
| target.appendChild(this.clone); | ||
| } | ||
| } | ||
| attachBind(bind) { | ||
| let node = bind.node; | ||
| const node = bind.node; | ||
| if (!node) return; | ||
| bind.f = (m) => this.applyBind(bind,m); | ||
| bind.depend?.forEach(d => { | ||
| if (!bind.initiator) bind.initiator = {}; | ||
| bind.f = m => this.applyBind(bind, m); | ||
| if (!bind.initiator) bind.initiator = {}; | ||
| bind.depend?.forEach((d) => { | ||
| bind.initiator[d] = this.addEffect(d, bind.f); | ||
| }) | ||
| }); | ||
| if (bind.twoSide) { | ||
@@ -56,46 +60,31 @@ getBackApl(bind)(bind.node, this.ctx); | ||
| } | ||
| applyBind(bind, m) { | ||
| let node = bind.node; | ||
| const node = bind.node; | ||
| if (!node) return; | ||
| let val = this.getBindValue(bind); | ||
| let val = getBindValue(bind); | ||
| val = bind.negate ? !val : val; | ||
| bind.apl(node.ctx || node , this.ctx, m, val, bind.initiator[bind.depend[0]] ); | ||
| bind.apl(node.ctx || node, this.ctx, m, val, bind.initiator[bind.depend[0]]); | ||
| } | ||
| applyBinds() { | ||
| this.binds.forEach(bind => { | ||
| this.binds.forEach((bind) => { | ||
| this.applyBind(bind); | ||
| }); | ||
| } | ||
| getBindValue(bind) { | ||
| let dv = bind.depend.map(p => { | ||
| if (/['"]/.test(p)) { | ||
| return p.replace(/["']/g, "") | ||
| } else { | ||
| return bind.initiator[p]?.get(p); | ||
| } | ||
| }); | ||
| if (dv.length > 1) { | ||
| let [fn,...args] = dv; | ||
| if (!fn) { | ||
| console.error('Function not found in context: %s(%s)', bind.depend[0], bind.depend.slice(1).join(',')); | ||
| return; | ||
| detach() { | ||
| this.nti.forEach(t => t.detach()); | ||
| // TODO: detach property effects, events etc... | ||
| this.binds.forEach((b) => { | ||
| if (b.initiator) { | ||
| Object.entries(b.initiator).forEach(([k, i]) => { | ||
| const ind = i?._em[k].indexOf(b.f); | ||
| if (ind >= 0) i._em[k].splice(ind, 1); | ||
| }); | ||
| } | ||
| //TODO: ctx for function | ||
| return fn.apply(bind.initiator[bind.depend[0]], args); | ||
| } else { | ||
| return dv[0]; | ||
| } | ||
| } | ||
| detach() { | ||
| this.nti.forEach(t => t.detach() ); | ||
| //TODO: detach property effects, events etc... | ||
| this.binds.forEach(b => { | ||
| if (b.initiator) Object.entries(b.initiator).forEach( ([k,i]) => { | ||
| let ind = i?._em[k].indexOf(b.f); | ||
| if (ind >=0) i._em[k].splice(ind,1); | ||
| }) | ||
| }); | ||
| this._nodes.forEach( n => n.remove() ) | ||
| this._nodes.forEach(n => n.remove()); | ||
| } | ||
| /** | ||
@@ -108,3 +97,6 @@ * | ||
| // find dataContext for property | ||
| let ctx = this.ctx.find( c => c.hasProp?.(path.split('.')[0]) ); | ||
| const prop = path.split('.')[0]; | ||
| const ctx = this.ctx.find((c) => { | ||
| return c.hasProp?.(prop); | ||
| }); | ||
| ctx?.addEffect(path, cb); | ||
@@ -115,19 +107,19 @@ return ctx; | ||
| removeBind(path, prop) { | ||
| let targetNode = findByNPath({childNodes: this._nodes}, path); | ||
| const targetNode = findByNPath({ childNodes: this._nodes }, path); | ||
| if (!targetNode) return; | ||
| let tib = this.binds; | ||
| let binds = tib.filter( i => i.node === targetNode && i.name === prop ); | ||
| const tib = this.binds; | ||
| const binds = tib.filter(i => i.node === targetNode && i.name === prop); | ||
| //check and remove current bind if exist | ||
| binds.forEach( b => { | ||
| // check and remove current bind if exist | ||
| binds.forEach((b) => { | ||
| if (b.twoSide) { | ||
| targetNode.removeEventListener(b.eventBB, b.funcBB); | ||
| } | ||
| b.depend.forEach( d => { | ||
| let em = (b.initiator[d]?._em ?? this._em)[d]; | ||
| if (em ) { | ||
| let ind = em.indexOf(b.f); | ||
| if (ind >= 0) em.splice(ind,1); | ||
| b.depend.forEach((d) => { | ||
| const em = (b.initiator[d]?._em ?? this._em)[d]; | ||
| if (em) { | ||
| const ind = em.indexOf(b.f); | ||
| if (ind >= 0) em.splice(ind, 1); | ||
| } | ||
| }) | ||
| }); | ||
| delete tib[tib.indexOf(b)]; | ||
@@ -139,13 +131,14 @@ }); | ||
| this.removeBind(path, property); | ||
| let bind = createBind(property, value); | ||
| const bind = createBind(property, value); | ||
| bind.path = path; | ||
| this.binds.push(bind); | ||
| this.attachBind(bind); | ||
| //ti.addEffect(bindVal.name2 ?? bindVal.name, bind.f); | ||
| // ti.addEffect(bindVal.name2 ?? bindVal.name, bind.f); | ||
| // call apply to set prop value via property effect | ||
| this.applyBind(bind); | ||
| } | ||
| querySelector(selector) { | ||
| return this.clone.querySelector(selector); | ||
| } | ||
| } | ||
| } |
+44
-25
@@ -1,4 +0,18 @@ | ||
| import {setAttrValue, toDashed} from "../../common.js"; | ||
| import {getNextWM} from "./ctx.js"; | ||
| import { setAttrValue, toDashed } from '../../common.js'; | ||
| import { getNextWM } from './ctx.js'; | ||
| let timer = null; | ||
| let pending = []; | ||
| function addPendingInitialization(f) { | ||
| if (!timer) { | ||
| timer = setTimeout(() => { | ||
| timer = null; | ||
| const current = pending.slice(); | ||
| pending = []; | ||
| current.forEach(f => f()); | ||
| }, 0); | ||
| } | ||
| pending.push(f); | ||
| } | ||
| export const PropertiesMixin = s => class PropMixin extends s { | ||
@@ -10,50 +24,55 @@ _props = {}; | ||
| let inst = this.constructor; | ||
| let pi = []; | ||
| const pi = []; | ||
| while (inst) { | ||
| if (inst.hasOwnProperty('properties')) pi.unshift(inst.properties); | ||
| if (Object.prototype.hasOwnProperty.call(inst, 'properties')) pi.unshift(inst.properties); | ||
| inst = inst.__proto__; | ||
| } | ||
| this._dp = {}; | ||
| //copy props from static properties with destruction to avoid future change default value in prototype | ||
| pi.forEach( i => Object.entries(i).forEach( ([k,v]) => this._dp[k] = {...v})); | ||
| Object.keys(this._dp).forEach( p => { | ||
| // copy props from static properties with destruction to avoid future change default value in prototype | ||
| pi.forEach(i => Object.entries(i).forEach(([k, v]) => this._dp[k] = { ...v })); | ||
| Object.keys(this._dp).forEach((p) => { | ||
| // возможно значение уже назначено снаружи или задано в атрибуте, запоминаем и используем его вместо дефолтного | ||
| let attrVal = (config?.root ?? this).getAttribute?.(toDashed(p)); | ||
| const attrVal = (config?.root ?? this).getAttribute?.(toDashed(p)); | ||
| // убираем атрибуты для свойств, если они не отображаются в атрибуты | ||
| if (attrVal !== null && !this._dp[p].reflectToAttribute) this.removeAttribute?.(toDashed(p)); | ||
| let val = (this.hasOwnProperty(p) ? this[p] : undefined) ?? (this._dp[p].type === Boolean ? (attrVal !== null ? attrVal !== 'false' : undefined) : attrVal); | ||
| const val = (Object.prototype.hasOwnProperty.call(this, p) ? this[p] : undefined) ?? (this._dp[p].type === Boolean ? (attrVal !== null ? attrVal !== 'false' : undefined) : attrVal); | ||
| Object.defineProperty(this, p, { | ||
| get: () => this._props[p], | ||
| set: (value) => { | ||
| let oldValue = this._props[p]; | ||
| const oldValue = this._props[p]; | ||
| this._props[p] = value; | ||
| if (oldValue !== value) this.notifyChange({ action: 'upd', path: p, value, oldValue }); | ||
| }, | ||
| } | ||
| }); | ||
| if (typeof this._dp[p].value === 'function') this._dp[p].value = this._dp[p].value(); | ||
| if (typeof this._dp[p].value === 'function') this._dp[p].value = this._dp[p].value(); | ||
| this._props[p] = val ?? this._dp[p].value; | ||
| if (this._dp[p].reflectToAttribute) { | ||
| this.addEventListener(p+'-changed', () => this.reflectToAttribute(p, this._props[p]) ); | ||
| this.addEventListener(p + '-changed', () => this.reflectToAttribute(p, this._props[p])); | ||
| } | ||
| }); | ||
| setTimeout( () => { | ||
| Object.keys(this._props).forEach( p => { | ||
| if (this._props[p] !== this._dp[p]?.value) this.notifyChange({ action: 'upd', path: p, value: this._props[p], init: true, wmh: getNextWM() }) | ||
| }) | ||
| }) | ||
| addPendingInitialization(() => { | ||
| Object.keys(this._props).forEach((p) => { | ||
| if (this._props[p] !== this._dp[p]?.value) { | ||
| this.notifyChange({action: 'upd', path: p, value: this._props[p], init: true, wmh: getNextWM()}); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| connectedCallback() { | ||
| super.connectedCallback?.(); | ||
| Object.keys(this._dp).forEach( p => { | ||
| Object.keys(this._dp).forEach((p) => { | ||
| if (this._dp[p].reflectToAttribute) { | ||
| //TODO: думается надо делать property effect | ||
| this.reflectToAttribute(p,this._props[p]); | ||
| // TODO: думается надо делать property effect | ||
| this.reflectToAttribute(p, this._props[p]); | ||
| } | ||
| }); | ||
| } | ||
| reflectToAttribute(name, val) { | ||
| if (this._dp[name].type === Boolean) | ||
| val = !!val; | ||
| setAttrValue(/** @type HTMLElement */this,toDashed(name),val); | ||
| if (this._dp[name].type === Boolean) { | ||
| val = Boolean(val); | ||
| } | ||
| setAttrValue(/** @type HTMLElement */this, toDashed(name), val); | ||
| } | ||
| } | ||
| }; |
+56
-48
@@ -1,6 +0,6 @@ | ||
| import {getNPath, getRandomId, normalizePath} from "../../common.js"; | ||
| import {createBind, DIRECTIVE_SCHEME, Template} from "./template.js"; | ||
| import {ContextMixin} from "./ctx.js"; | ||
| import {PropertiesMixin} from "./properties.js"; | ||
| import {TemplateInstance} from "./instance.js"; | ||
| import { getNPath, getRandomId, normalizePath } from '../../common.js'; | ||
| import { createBind, DIRECTIVE_SCHEME, Template } from './template.js'; | ||
| import { ContextMixin } from './ctx.js'; | ||
| import { PropertiesMixin } from './properties.js'; | ||
| import { TemplateInstance } from './instance.js'; | ||
@@ -14,22 +14,25 @@ class Repeater extends PropertiesMixin(ContextMixin(EventTarget)) { | ||
| anchor: { type: Object } | ||
| } | ||
| constructor(tpl,anchor) { | ||
| }; | ||
| constructor(tpl, anchor) { | ||
| super(); | ||
| this.tpl = tpl; | ||
| this.anchor = anchor; | ||
| if(this.items) { | ||
| this.renderItems(this.items,anchor) | ||
| if (this.items) { | ||
| this.renderItems(this.items, anchor); | ||
| } | ||
| } | ||
| renderItems(items, sibling) { | ||
| return items && items.map( i => this.renderItem(i, sibling) ); | ||
| return items && items.map(i => this.renderItem(i, sibling)); | ||
| } | ||
| renderItem(item, sibling) { | ||
| if (!this.tpl) return; | ||
| let inst = new TemplateInstance(this.tpl); | ||
| const inst = new TemplateInstance(this.tpl); | ||
| let itemContext = new RepeatItem(item, this.as, (ctx, m) => this.onItemChanged(ctx, m)); | ||
| itemContext._ti = inst | ||
| const itemContext = new RepeatItem(item, this.as, (ctx, m) => this.onItemChanged(ctx, m)); | ||
| itemContext._ti = inst; | ||
| inst._nodes.forEach(i => i._item = itemContext); | ||
| inst.attach(null, sibling || this.anchor, [itemContext, this, ...this.pti ]); | ||
| inst.attach(null, sibling || this.anchor, [itemContext, this, ...this.pti]); | ||
| return itemContext; | ||
@@ -45,3 +48,3 @@ } | ||
| onChangeData(val, old, mutation) { | ||
| let [, index, ...rest] = normalizePath(mutation.path); | ||
| const [, index, ...rest] = normalizePath(mutation.path); | ||
| switch (mutation.action) { | ||
@@ -51,3 +54,3 @@ case 'splice': | ||
| // ensure that path for this repeater | ||
| //TODO: update clone indexes on splices | ||
| // TODO: update clone indexes on splices | ||
| if (mutation.path === 'items') { | ||
@@ -59,6 +62,5 @@ for (let ind = mutation.index; ind < mutation.deletedCount + mutation.index; ind++) { | ||
| // added | ||
| let sibling = this.clones[mutation.index]?._ti._nodes[0]; | ||
| let clones = this.renderItems(mutation.added, sibling, mutation.index); | ||
| const sibling = this.clones[mutation.index]?._ti._nodes[0]; | ||
| const clones = this.renderItems(mutation.added, sibling, mutation.index); | ||
| this.clones.splice(mutation.index, 0, ...clones); | ||
| this.anchor.dispatchEvent(new CustomEvent('dom-changed', { bubbles: true, composed: true })); | ||
| break; | ||
@@ -68,14 +70,14 @@ } | ||
| case 'upd': | ||
| if (Number.isInteger(+index) && +index >= 0) { | ||
| if (Number.isInteger(Number(index)) && Number(index) >= 0) { | ||
| // ищем клон | ||
| let clone = this.clones[+index]; | ||
| let path = [this.as, ...rest].join('.'); | ||
| const clone = this.clones[Number(index)]; | ||
| const path = [this.as, ...rest].join('.'); | ||
| if (path === this.as) { | ||
| clone.set(this.as, mutation.value); | ||
| } else { | ||
| clone.applyEffects({...mutation, path}); | ||
| clone.applyEffects({ ...mutation, path }); | ||
| } | ||
| } else if (index === undefined) { | ||
| if (old !== val) { | ||
| let items = this.items?.slice?.() || []; | ||
| const items = this.items?.slice?.() || []; | ||
| let i = 0; | ||
@@ -86,9 +88,8 @@ while (items.length && i < this.clones.length) { | ||
| } | ||
| if ( i < this.clones.length ) { | ||
| let deleted = this.clones.splice(i, this.clones.length - i); | ||
| deleted.forEach( c => this.detachClone(c)); | ||
| if (i < this.clones.length) { | ||
| const deleted = this.clones.splice(i, this.clones.length - i); | ||
| deleted.forEach(c => this.detachClone(c)); | ||
| } | ||
| if ( items.length ) this.clones.push(...this.renderItems(items, this.anchor)); | ||
| if (items.length) this.clones.push(...this.renderItems(items, this.anchor)); | ||
| } | ||
| this.anchor.dispatchEvent(new CustomEvent('dom-changed', { bubbles: true, composed: true })); | ||
| } | ||
@@ -98,5 +99,5 @@ break; | ||
| } | ||
| onItemChanged(ctx, m) { | ||
| let ind = this.clones.findIndex( i => i[this.as] === ctx[this.as]); | ||
| const ind = this.clones.findIndex(i => i[this.as] === ctx[this.as]); | ||
| if (ind < 0) console.warn('repeat item not found'); | ||
@@ -106,14 +107,16 @@ if (m.path === this.as) { | ||
| } else { | ||
| this.forwardNotify(m,this.as, 'items.'+ind); | ||
| this.forwardNotify(m, this.as, 'items.' + ind); | ||
| } | ||
| } | ||
| } | ||
| dirtyRefresh() { | ||
| this.clones.forEach(c => this.detachClone(c)); | ||
| this.clones = this.renderItems(this.items, this.anchor) ||[]; | ||
| this.clones = this.renderItems(this.items, this.anchor) || []; | ||
| } | ||
| detachClone(clone) { | ||
| clone._ti.detach(); | ||
| //clone.dom.forEach(n => n.remove()) | ||
| // clone.dom.forEach(n => n.remove()) | ||
| } | ||
| detach() { | ||
@@ -129,4 +132,5 @@ this.clones.forEach(c => this.detachClone(c)); | ||
| this[as] = item; | ||
| this.addEffect(as, m => cb(this, m)) | ||
| this.addEffect(as, m => cb(this, m)); | ||
| } | ||
| get model() { | ||
@@ -138,18 +142,22 @@ return this[this.as]; | ||
| export default function repeatDirective(node, template) { | ||
| let content = template.svg ? template.tpl.content.childNodes[0] : template.tpl.content; | ||
| let nPath = getNPath(content, node); | ||
| const content = template.svg ? template.tpl.content.childNodes[0] : template.tpl.content; | ||
| const nPath = getNPath(content, node); | ||
| // add 'as' bind first to ensure it assigned before 'items' | ||
| let as = node.getAttribute(DIRECTIVE_SCHEME+':as') ?? 'item'; | ||
| const as = node.getAttribute(DIRECTIVE_SCHEME + ':as') ?? 'item'; | ||
| let b = createBind('as', as, nPath); | ||
| if (b) template.binds.push(b); else node.as = node.getAttribute(DIRECTIVE_SCHEME+':as'); | ||
| b = createBind('items', node.getAttribute(DIRECTIVE_SCHEME+':repeat'), nPath); | ||
| if (b) { | ||
| template.binds.push(b); | ||
| } else { | ||
| node.as = node.getAttribute(DIRECTIVE_SCHEME + ':as'); | ||
| } | ||
| b = createBind('items', node.getAttribute(DIRECTIVE_SCHEME + ':repeat'), nPath); | ||
| if (b) template.binds.push(b); | ||
| node.removeAttribute(DIRECTIVE_SCHEME+':repeat'); | ||
| node.removeAttribute(DIRECTIVE_SCHEME+':as'); | ||
| let id = getRandomId(); | ||
| let ph = document.createComment('repeat:'+id); | ||
| node.removeAttribute(DIRECTIVE_SCHEME + ':repeat'); | ||
| node.removeAttribute(DIRECTIVE_SCHEME + ':as'); | ||
| const id = getRandomId(); | ||
| const ph = document.createComment('repeat:' + id); | ||
| node.parentNode.replaceChild(ph, node); | ||
| let tpl = new Template(node); | ||
| const tpl = new Template(node); | ||
| template.nestedTemplates.set(id, tpl); | ||
| template.stampHooks.push({path: nPath, hook: stampRepeater}); | ||
| template.stampHooks.push({ path: nPath, hook: stampRepeater }); | ||
| tpl.id = id; | ||
@@ -167,2 +175,2 @@ tpl.as = as; | ||
| ctx[0]._ti.nti.push(node.ctx); | ||
| } | ||
| } |
+85
-82
@@ -1,5 +0,4 @@ | ||
| import {fixText, fromDashed, getRandomId, normalizePath, setAttrValue} from '../../common.js' | ||
| import {TemplateInstance} from "./instance.js"; | ||
| import { fixText, fromDashed, getRandomId, normalizePath, setAttrValue } from '../../common.js'; | ||
| import { TemplateInstance } from './instance.js'; | ||
| export const DIRECTIVE_SCHEME = 'd'; | ||
@@ -14,3 +13,3 @@ export const Directives = {}; | ||
| if (Array.isArray(str.raw)) str = str.raw.join(''); | ||
| return new Template( str, { svg: true }); | ||
| return new Template(str, { svg: true }); | ||
| } | ||
@@ -26,3 +25,3 @@ | ||
| this.svg = opt?.svg === true; | ||
| /** @type: HTMLTemplateElement */ | ||
| /** @type HTMLTemplateElement */ | ||
| let node; | ||
@@ -33,11 +32,10 @@ if (tpl instanceof HTMLTemplateElement) { | ||
| node = document.createElement('template'); | ||
| if (tpl instanceof Element) | ||
| if (tpl instanceof Element) { | ||
| node.content.replaceChildren(tpl); | ||
| else { | ||
| } else { | ||
| if (this.svg) { | ||
| let svg = document.createElementNS("http://www.w3.org/2000/svg",'svg'); | ||
| const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | ||
| node.content.appendChild(svg); | ||
| svg.innerHTML = tpl; | ||
| } else | ||
| node.innerHTML = tpl; | ||
| } else { node.innerHTML = tpl; } | ||
| } | ||
@@ -48,12 +46,13 @@ } | ||
| this.init(this.svg ? node.content.childNodes[0] : node.content); | ||
| } | ||
| } | ||
| init(content) { | ||
| this.walkNode(content, []); | ||
| } | ||
| walkNode(node, path) { | ||
| if (node.attributes && [...node.attributes].find( n => n.name.indexOf(':') >=0 && n.name.split(':')[0] === DIRECTIVE_SCHEME)) { | ||
| if (node.attributes && [...node.attributes].find(n => n.name.indexOf(':') >= 0 && n.name.split(':')[0] === DIRECTIVE_SCHEME)) { | ||
| // ..directive | ||
| [...node.attributes].forEach( a => { | ||
| let d = Directives[a.name.split(':')[1]]; | ||
| [...node.attributes].forEach((a) => { | ||
| const d = Directives[a.name.split(':')[1]]; | ||
| d?.(node, this); | ||
@@ -63,5 +62,5 @@ }); | ||
| if (node.nodeName === 'TEMPLATE') { | ||
| let id = getRandomId(); | ||
| let ph = document.createComment('tpl:'+id); | ||
| let tpl = new Template(node); | ||
| const id = getRandomId(); | ||
| const ph = document.createComment('tpl:' + id); | ||
| const tpl = new Template(node); | ||
| this.nestedTemplates.set(id, tpl); | ||
@@ -72,10 +71,10 @@ tpl.id = id; | ||
| } | ||
| //TODO: move to form component | ||
| // TODO: move to form component | ||
| if (node.localName?.match(/\w+-/)) { | ||
| if (node.namespaceURI === 'http://www.w3.org/2000/svg') { | ||
| let svg = document.createElementNS("http://www.w3.org/2000/svg",'svg'); | ||
| if (node.namespaceURI === 'http://www.w3.org/2000/svg') { | ||
| const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); | ||
| svg.replaceChildren(...node.childNodes); | ||
| let tpl = new Template(svg, { svg: true }); | ||
| let newNode = document.createElementNS('http://www.w3.org/2000/svg', 'g'); | ||
| [...node.attributes].forEach( i => newNode.setAttribute(i.name, node.getAttribute(i.name)) ); | ||
| const tpl = new Template(svg, { svg: true }); | ||
| const newNode = document.createElementNS('http://www.w3.org/2000/svg', 'g'); | ||
| [...node.attributes].forEach(i => newNode.setAttribute(i.name, node.getAttribute(i.name))); | ||
| newNode.setAttribute('is', node.localName); | ||
@@ -87,32 +86,33 @@ node.parentNode.replaceChild(newNode, node); | ||
| } | ||
| for ( let i of node.childNodes ) { | ||
| this.walkNode(i, [...path, [...node.childNodes].indexOf(i)]) | ||
| for (const i of node.childNodes) { | ||
| this.walkNode(i, [...path, [...node.childNodes].indexOf(i)]); | ||
| } | ||
| if (node instanceof Element) { | ||
| let bind = getAttrBinds(node, path); | ||
| const bind = getAttrBinds(node, path); | ||
| if (bind) this.binds.push(...bind); | ||
| } else if (node.nodeType === document.TEXT_NODE) { | ||
| let bind = getTextBind(node, path); | ||
| const bind = getTextBind(node, path); | ||
| if (bind) this.binds.push(...bind); | ||
| } | ||
| } | ||
| } | ||
| stamp(context) { | ||
| console.log('stamp template, deprecated', this); | ||
| let instance = new TemplateInstance(this); | ||
| console.warn('stamp template, deprecated', this); | ||
| const instance = new TemplateInstance(this); | ||
| instance.attach(context.root); | ||
| return instance; | ||
| } | ||
| getClone() { | ||
| let clone = this.tpl.content.cloneNode(true); | ||
| if (this.svg && clone.childNodes[0]?.nodeName==='svg') { | ||
| let nodes = clone.childNodes[0].childNodes | ||
| const clone = this.tpl.content.cloneNode(true); | ||
| if (this.svg && clone.childNodes[0]?.nodeName === 'svg') { | ||
| const nodes = clone.childNodes[0].childNodes; | ||
| clone.replaceChildren(...nodes); | ||
| } | ||
| if (this.nestedTemplates.size > 0) { | ||
| let nodeIterator = document.createNodeIterator(clone, NodeFilter.SHOW_COMMENT); | ||
| const nodeIterator = document.createTreeWalker(clone, NodeFilter.SHOW_COMMENT); | ||
| let node; | ||
| while (node = nodeIterator.nextNode()) { | ||
| let id = (node.textContent.split(":")[1] || "").trim(); | ||
| while ((node = nodeIterator.nextNode())) { | ||
| const id = (node.textContent.split(':')[1] || '').trim(); | ||
| node._tpl = this.nestedTemplates.get(id); | ||
@@ -125,5 +125,4 @@ } | ||
| let pattern = /(\[\[.*?]]|{{.*?}})/gm; | ||
| const pattern = /(\[\[.*?]]|{{.*?}})/gm; | ||
| /** | ||
@@ -137,10 +136,11 @@ * | ||
| function getAttrBinds(node, path) { | ||
| if (node.attributes) | ||
| if (node.attributes) { | ||
| return [...node.attributes] | ||
| .map( attr => { | ||
| let b = createBind(attr.nodeName, attr.nodeValue, path); | ||
| .map((attr) => { | ||
| const b = createBind(attr.nodeName, attr.nodeValue, path); | ||
| if (b) node.removeAttribute(attr.nodeName); | ||
| return b; | ||
| }) | ||
| .filter( i => i ); | ||
| .filter(i => i); | ||
| } | ||
| } | ||
@@ -151,11 +151,11 @@ | ||
| // split text node and replace original node with parts | ||
| let parts = node.textContent.match(/(\[\[.+?]])|(.+?(?=\[\[))|.+/mg); | ||
| let binds = []; | ||
| let base = path.at(-1); | ||
| const parts = node.textContent.match(/(\[\[.+?]])|(.+?(?=\[\[))|.+/mg); | ||
| const binds = []; | ||
| const base = path.at(-1); | ||
| /** @type Node[] */ | ||
| let nodes = parts.map( (p,index) => { | ||
| const nodes = parts.map((p, index) => { | ||
| if (p[0] === '[') { | ||
| let negate = !!p.match(/\[\[!/); | ||
| let depend = getDependence(p); | ||
| let bind = { path: path.slice(0,-1).concat([base+index]), type: 'text', depend, textContent: p, negate }; | ||
| const negate = Boolean(p.match(/\[\[!/)); | ||
| const depend = getDependence(p); | ||
| const bind = { path: path.slice(0, -1).concat([base + index]), type: 'text', depend, textContent: p, negate }; | ||
| bind.apl = getTextApl(bind); | ||
@@ -167,4 +167,4 @@ binds.push(bind); | ||
| } | ||
| }) | ||
| nodes.forEach( n => { | ||
| }); | ||
| nodes.forEach((n) => { | ||
| node.parentNode.insertBefore(n, node); | ||
@@ -189,7 +189,7 @@ }); | ||
| export function createBind(attrName, attrValue, path) { | ||
| let match = attrValue.match(/(\[\[(.*?)]]|{{(.*?)}})/gm); | ||
| const match = attrValue.match(/(\[\[(.*?)]]|{{(.*?)}})/gm); | ||
| if (match) { | ||
| let depend = getDependence(attrValue); | ||
| const depend = getDependence(attrValue); | ||
| /** @type TemplateBind */ | ||
| let sbnd = { | ||
| const sbnd = { | ||
| path, | ||
@@ -203,3 +203,3 @@ type: null, | ||
| if (attrName.indexOf('on-') === 0) { | ||
| sbnd.type = 'event' | ||
| sbnd.type = 'event'; | ||
| sbnd.name = fromDashed(attrName.slice(3)); | ||
@@ -223,3 +223,3 @@ sbnd.apl = getEventApl(sbnd); | ||
| * Expression can be: | ||
| * {{prop}} - two way bind | ||
| * {{prop}} - two-way bind | ||
| * [[!prop]] - bind with negation | ||
@@ -231,9 +231,9 @@ * [[function(prop,'constString')]] - function with arguments | ||
| export function getDependence(expr) { | ||
| let match = expr.match(/(\[\[(.*?)]]|{{(.*?)(:(?<evt>.*?))?}})/m); | ||
| const match = expr.match(/(\[\[(.*?)]]|{{(.*?)(:(?<evt>.*?))?}})/m); | ||
| if (match) { | ||
| let prop = (match[2] || match[3]).trim(); | ||
| if (prop[0] === '!') prop = prop.slice(1); | ||
| let fm = prop.match(/(?<fname>[\w\d]+)\((?<args>.*)\)/) | ||
| if( fm ) { | ||
| return [fm.groups.fname,...fm.groups.args?.split(',').map(i => i.trim())]; | ||
| const fm = prop.match(/(?<fname>[\w\d]+)\((?<args>.*)\)/); | ||
| if (fm) { | ||
| return [fm.groups.fname, ...fm.groups.args.split(',').map(i => i.trim())]; | ||
| } else { | ||
@@ -243,3 +243,3 @@ return [prop]; | ||
| } else { | ||
| console.log('unsupported expression:', expr); | ||
| console.warn('unsupported expression:', expr); | ||
| } | ||
@@ -252,12 +252,14 @@ } | ||
| if (typeof val === 'object' && mutation) { | ||
| if(b.depend.length === 1) | ||
| if (b.depend.length === 1) { | ||
| node.forwardNotify?.(mutation, b.depend[0], b.name); | ||
| else | ||
| } else { | ||
| node.notifyChange?.({ path: b.name, action: 'upd', wmh: mutation.wmh, value: val }); | ||
| } | ||
| } | ||
| } else { | ||
| if (node.set) | ||
| if (node.set) { | ||
| node.set(b.name, val, mutation?.wmh); | ||
| else | ||
| } else { | ||
| node[b.name] = val; | ||
| } | ||
| } | ||
@@ -273,3 +275,3 @@ }; | ||
| } | ||
| let fn = val; | ||
| const fn = val; | ||
| !node._listeners && (node._listeners = {}); | ||
@@ -280,17 +282,18 @@ if (node._listeners[b.name]) { | ||
| } | ||
| let f = e => { | ||
| let model = {}; | ||
| const f = (e) => { | ||
| const model = {}; | ||
| let found = false; | ||
| ctx.forEach( ctxModel => { | ||
| ctx.forEach((ctxModel) => { | ||
| // поднимаемся наверх по всем вложенным контекстам, собираем полную модель | ||
| if (ctxModel.model) { | ||
| if (ctxModel.as) | ||
| model[ctxModel.as] = ctxModel.model; | ||
| else | ||
| Object.assign( model, ctxModel.model); | ||
| found = true; | ||
| if (ctxModel.model) { | ||
| if (ctxModel.as) { | ||
| model[ctxModel.as] = ctxModel.model; | ||
| } else { | ||
| Object.assign(model, ctxModel.model); | ||
| } | ||
| found = true; | ||
| } | ||
| }); | ||
| if (found) e.model = Object.assign( e.model ?? {}, model); | ||
| if (found) e.model = Object.assign(e.model ?? {}, model); | ||
| fn.call(self || ctx[0], e); | ||
@@ -318,6 +321,6 @@ }; | ||
| if (content instanceof Template) { | ||
| //TODO: move this to instance | ||
| let instance = new TemplateInstance(content); | ||
| // TODO: move this to instance | ||
| const instance = new TemplateInstance(content); | ||
| node._ti = instance; | ||
| instance.attach(null, node, [node, ...ctx, ...(content._hctx)??[]]); | ||
| instance.attach(null, node, [node, ...ctx, ...(content._hctx) ?? []]); | ||
| ctx[0]._ti.nti.push(instance); | ||
@@ -336,3 +339,3 @@ content = ''; | ||
| if (event.detail.wmh <= b.initiator[b.depend[0]]?.wmh[b.name] || event.detail.init) return; | ||
| if (typeof node[b.name] === 'object' && event.detail.value === event.detail.oldValue || normalizePath(event.detail.path).length > 1) { | ||
| if ((typeof node[b.name] === 'object' && event.detail.value === event.detail.oldValue) || normalizePath(event.detail.path).length > 1) { | ||
| b.initiator[b.depend[0]].forwardNotify?.(event.detail, b.name, b.depend[0]); | ||
@@ -343,4 +346,4 @@ } else { | ||
| }; | ||
| (node.ctx || node).addEventListener( b.eventBB, b.funcBB); | ||
| (node.ctx || node).addEventListener(b.eventBB, b.funcBB); | ||
| }; | ||
| } | ||
| } |
+7
-8
@@ -1,9 +0,8 @@ | ||
| export { PlElement, PlSVGElement } from "./pl-element.js" | ||
| export { Template, html, svg } from "./engine/v1/template.js"; | ||
| export { css } from "./engine/v1/css.js" | ||
| export { TemplateInstance } from "./engine/v1/instance.js" | ||
| export { PlElement, PlSVGElement } from './pl-element.js'; | ||
| export { Template, html, svg } from './engine/v1/template.js'; | ||
| export { css } from './engine/v1/css.js'; | ||
| export { TemplateInstance } from './engine/v1/instance.js'; | ||
| import {Directives} from "./engine/v1/template.js"; | ||
| import repeat from "./engine/v1/repeat.js"; | ||
| Directives['repeat'] = repeat; | ||
| import { Directives } from './engine/v1/template.js'; | ||
| import repeat from './engine/v1/repeat.js'; | ||
| Directives['repeat'] = repeat; |
+1
-1
| { | ||
| "name": "polylib", | ||
| "description": "A simple library for creating fast, lightweight web components.", | ||
| "version": "1.1.13", | ||
| "version": "1.2.0", | ||
| "license": "MIT", | ||
@@ -6,0 +6,0 @@ "type": "module", |
+17
-14
@@ -1,7 +0,7 @@ | ||
| import {PlTemplateMixin} from "./template-mixin.js"; | ||
| import {PropertiesMixin} from "./engine/v1/properties.js"; | ||
| import {ContextMixin} from "./engine/v1/ctx.js"; | ||
| import { PlTemplateMixin } from './template-mixin.js'; | ||
| import { PropertiesMixin } from './engine/v1/properties.js'; | ||
| import { ContextMixin } from './engine/v1/ctx.js'; | ||
| export class PlElement extends PlTemplateMixin(PropertiesMixin(ContextMixin(HTMLElement))) { | ||
| //static template; | ||
| // static template; | ||
| _$ = {}; | ||
@@ -16,18 +16,21 @@ /** | ||
| this.$ = new Proxy(this._$, { | ||
| get: (target,name) => { | ||
| get: (target, name) => { | ||
| if (!(name in target)) { | ||
| target[name] = this.root.querySelector('#'+name) ?? this._ti.querySelector('#'+name); | ||
| target[name] = this.root.querySelector('#' + name) ?? this._ti.querySelector('#' + name); | ||
| target.registerHook?.('disconnected', () => { | ||
| delete target[name]; | ||
| }); | ||
| } | ||
| return target[name]; | ||
| } | ||
| }) | ||
| }); | ||
| } | ||
| connectedCallback() { | ||
| super.connectedCallback() | ||
| super.connectedCallback(); | ||
| } | ||
| } | ||
| export class PlSVGElement extends PlTemplateMixin(PropertiesMixin(ContextMixin(EventTarget))) { | ||
| //static template; | ||
| // static template; | ||
| _$ = {}; | ||
@@ -43,10 +46,10 @@ isSVGCustomElement = true; | ||
| this.$ = new Proxy(this._$, { | ||
| get: (target,name) => { | ||
| get: (target, name) => { | ||
| if (!(name in target)) { | ||
| target[name] = this.root.querySelector('#'+name) ?? this._ti.querySelector('#'+name); | ||
| target[name] = this.root.querySelector('#' + name) ?? this._ti.querySelector('#' + name); | ||
| } | ||
| return target[name]; | ||
| } | ||
| }) | ||
| }); | ||
| } | ||
| } | ||
| } |
+55
-8
@@ -1,4 +0,6 @@ | ||
| import {TemplateInstance} from "./index.js"; | ||
| import { TemplateInstance } from './index.js'; | ||
| import { getBindValue } from './common.js'; | ||
| const PlTemplateMixin = s => class plTplMixin extends s { | ||
| _observersBinds = []; | ||
| /** | ||
@@ -9,32 +11,77 @@ * @constructor | ||
| * @param {boolean} [config.delegatesFocus] - delegatesFocus flag for shadowRoot | ||
| * @param {boolean} [config.root] - alternate root for lightDom | ||
| */ | ||
| constructor(config) { | ||
| super(config); | ||
| this.root = config?.lightDom ? config?.root ?? this : this.attachShadow({ mode: 'open', delegatesFocus: config?.delegatesFocus }); | ||
| // setup observers | ||
| if (this.constructor.observers?.length > 0) { | ||
| this.createObserversBinds(); | ||
| } | ||
| this.root = config?.lightDom | ||
| ? config?.root ?? this | ||
| : this.attachShadow({ mode: 'open', delegatesFocus: config?.delegatesFocus }); | ||
| } | ||
| createObserversBinds() { | ||
| this.constructor.observers.forEach((observer) => { | ||
| const match = observer.match(/(?<fname>[\w\d]+)\((?<args>.*)\)/); | ||
| if (match) { | ||
| const args = match.groups.args.split(',').map(i => i.trim()); | ||
| const depend = [match.groups.fname, ...args]; | ||
| const bind = { | ||
| type: 'observer', | ||
| depend | ||
| }; | ||
| this._observersBinds.push(bind); | ||
| } | ||
| }); | ||
| } | ||
| connectedCallback() { | ||
| super.connectedCallback(); | ||
| let tpl = this.constructor.template; | ||
| const tpl = this.constructor.template; | ||
| if (tpl) { | ||
| let inst = new TemplateInstance(tpl); | ||
| const inst = new TemplateInstance(tpl); | ||
| this._ti = inst; | ||
| inst.attach(this.root, undefined, this); | ||
| } | ||
| // attach observers effects | ||
| this._observersBinds.forEach((bind) => { | ||
| attachObserversBind(bind, [this]); | ||
| }); | ||
| // append styles | ||
| if (this.constructor.css) { | ||
| if (this.constructor.css instanceof CSSStyleSheet) { | ||
| if (this.root.adoptedStyleSheets) | ||
| if (this.root.adoptedStyleSheets) { | ||
| this.root.adoptedStyleSheets = [...this.root.adoptedStyleSheets, this.constructor.css]; | ||
| else | ||
| } else { | ||
| this.root.getRootNode().adoptedStyleSheets = [...this.root.getRootNode().adoptedStyleSheets, this.constructor.css]; | ||
| } | ||
| } else { | ||
| this.root.append(this.constructor.css.cloneNode(true)) | ||
| this.root.append(this.constructor.css.cloneNode(true)); | ||
| } | ||
| } | ||
| } | ||
| disconnectedCallback() { | ||
| super.disconnectedCallback(); | ||
| this._ti?.detach(); | ||
| } | ||
| }; | ||
| export function attachObserversBind(bind, contexts) { | ||
| bind.f = () => getBindValue(bind); | ||
| if (!bind.initiator) bind.initiator = {}; | ||
| bind.depend?.forEach((d) => { | ||
| const ctx = contexts.find((c) => { | ||
| return c.hasProp?.(d); | ||
| }); | ||
| bind.initiator[d] = ctx; | ||
| ctx.addEffect(d, bind.f); | ||
| }); | ||
| } | ||
| export {PlTemplateMixin}; | ||
| export { PlTemplateMixin }; |
39716
7.54%1040
11.23%