oxe
Version:
A mighty tiny web components framework/library
1,181 lines (1,161 loc) • 81.2 kB
JavaScript
/*!
Name: oxe
Version: 6.0.7
License: MPL-2.0
Author: Alexander Elias
Email: alex.steven.elis@gmail.com
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Oxe = factory());
})(this, (function () { 'use strict';
const get = function (task, path, target, key, receiver) {
const value = Reflect.get(target, key, receiver);
if (value && typeof value === 'object') {
path = path ? `${path}.${key}` : `${key}`;
return new Proxy(value, {
get: get.bind(null, task, path),
set: set.bind(null, task, path),
deleteProperty: deleteProperty.bind(null, task, path)
});
}
else {
return value;
}
};
const deleteProperty = function (task, path, target, key) {
if (target instanceof Array) {
target.splice(key, 1);
}
else {
Reflect.deleteProperty(target, key);
}
task(path ? `${path}.${key}` : `${key}`, 'unrender');
return true;
};
const set = function (task, path, target, key, to, receiver) {
const from = Reflect.get(target, key, receiver);
if (key === 'length') {
task(path, 'render');
task(path ? `${path}.${key}` : `${key}`, 'render');
return true;
}
else if (from === to) {
return true;
}
Reflect.set(target, key, to, receiver);
// console.log(path, key, from, to);
// if (from && typeof from === 'object') {
// if (to && typeof to === 'object') {
// const tasks = [];
// for (const child in from) {
// if (!(child in to)) {
// tasks.push(task(path ? `${path}.${key}.${child}` : `${key}.${child}`, 'unrender'));
// }
// }
// Promise.all(tasks).then(() => task(path ? `${path}.${key}` : `${key}`, 'render'));
// } else {
// task(path ? `${path}.${key}` : `${key}`, 'unrender').then(() => task(path ? `${path}.${key}` : `${key}`, 'render'));
// }
// } else {
// task(path ? `${path}.${key}` : `${key}`, 'render');
// }
task(path ? `${path}.${key}` : `${key}`, 'render');
return true;
};
const observer = function (source, task, path = '') {
return new Proxy(source, {
get: get.bind(null, task, path),
set: set.bind(null, task, path),
deleteProperty: deleteProperty.bind(null, task, path)
});
};
var booleanTypes = [
'allowfullscreen', 'async', 'autofocus', 'autoplay', 'checked', 'compact', 'controls', 'declare', 'default',
'defaultchecked', 'defaultmuted', 'defaultselected', 'defer', 'disabled', 'draggable', 'enabled', 'formnovalidate',
'indeterminate', 'inert', 'ismap', 'itemscope', 'loop', 'multiple', 'muted', 'nohref', 'noresize', 'noshade', 'hidden',
'novalidate', 'nowrap', 'open', 'pauseonexit', 'readonly', 'required', 'reversed', 'scoped', 'seamless', 'selected',
'sortable', 'spellcheck', 'translate', 'truespeed', 'typemustmatch', 'visible'
];
const format = (data) => data === undefined ? '' : typeof data === 'object' ? JSON.stringify(data) : data;
const standardRender = async function (binder) {
let data = await binder.compute();
const boolean = booleanTypes.includes(binder.name);
binder.node.value = '';
if (boolean) {
data = data ? true : false;
if (data)
binder.owner.setAttributeNode(binder.node);
else
binder.owner.removeAttribute(binder.name);
}
else {
data = format(data);
binder.owner[binder.name] = data;
binder.owner.setAttribute(binder.name, data);
}
};
const standardUnrender = async function (binder) {
const boolean = booleanTypes.includes(binder.name);
if (boolean) {
binder.owner.removeAttribute(binder.name);
}
else {
binder.owner.setAttribute(binder.name, '');
}
};
var standard = { render: standardRender, unrender: standardUnrender };
const flag = Symbol('RadioFlag');
const handler = async function (binder, event) {
const checked = binder.owner.checked;
const computed = await binder.compute({ $event: event, $checked: checked, $assignment: !!event });
if (computed) {
binder.owner.setAttributeNode(binder.node);
}
else {
binder.owner.removeAttribute('checked');
}
};
const checkedRender = async function (binder) {
if (!binder.meta.setup) {
binder.node.value = '';
binder.meta.setup = true;
if (binder.owner.type === 'radio') {
binder.owner.addEventListener('input', async (event) => {
if (event.detail === flag)
return handler(binder, event);
const parent = binder.owner.form || binder.owner.getRootNode();
const radios = parent.querySelectorAll(`[type="radio"][name="${binder.owner.name}"]`);
const input = new CustomEvent('input', { detail: flag });
for (const radio of radios) {
if (radio === event.target) {
await handler(binder, event);
}
else {
let checked;
const bounds = binder.binder.ownerBinders.get(binder.owner);
if (bounds) {
for (const bound of bounds) {
if (bound.name === 'checked') {
checked = bound;
break;
}
}
}
if (checked) {
radio.dispatchEvent(input);
}
else {
radio.checked = !event.target.checked;
if (radio.checked) {
radio.setAttribute('checked', '');
}
else {
radio.removeAttribute('checked');
}
}
}
}
});
}
else {
binder.owner.addEventListener('input', event => handler(binder, event));
}
}
await handler(binder);
};
const checkedUnrender = async function (binder) {
binder.owner.removeAttribute('checked');
};
var checked = { render: checkedRender, unrender: checkedUnrender };
const inheritRender = async function (binder) {
if (!binder.meta.setup) {
binder.meta.setup = true;
binder.node.value = '';
}
if (!binder.owner.inherited) {
return console.warn(`inherited not implemented ${binder.owner.localName}`);
}
const inherited = await binder.compute();
binder.owner.inherited?.(inherited);
};
const inheritUnrender = async function (binder) {
if (!binder.owner.inherited) {
return console.warn(`inherited not implemented ${binder.owner.localName}`);
}
binder.owner.inherited?.();
};
var inherit = { render: inheritRender, unrender: inheritUnrender };
var dateTypes = ['date', 'datetime-local', 'month', 'time', 'week'];
console.warn('value: setter/getter issue with multiselect');
const defaultInputEvent = new Event('input');
const parseable = function (value) {
return !isNaN(value) && value !== null && value !== undefined && typeof value !== 'string';
};
const stampFromView = function (data) {
const date = new Date(data);
return new Date(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()).getTime();
};
const stampToView = function (data) {
const date = new Date(data);
return new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds())).getTime();
};
const input = async function (binder, event) {
const { owner } = binder;
const { type } = owner;
let display, computed;
if (type === 'select-one') {
const [option] = owner.selectedOptions;
const value = option ? '$value' in option ? option.$value : option.value : undefined;
computed = await binder.compute({ $event: event, $value: value, $assignment: true });
display = format(computed);
}
else if (type === 'select-multiple') {
const value = [];
for (const option of owner.selectedOptions) {
value.push('$value' in option ? option.$value : option.value);
}
computed = await binder.compute({ $event: event, $value: value, $assignment: true });
display = format(computed);
}
else if (type === 'number' || type === 'range') {
computed = await binder.compute({ $event: event, $value: owner.valueAsNumber, $assignment: true });
// if (typeof computed === 'number' && computed !== Infinity) owner.valueAsNumber = computed;
// else owner.value = computed;
owner.value = computed;
display = owner.value;
}
else if (dateTypes.includes(type)) {
const value = typeof owner.$value === 'string' ? owner.value : stampFromView(owner.valueAsNumber);
computed = await binder.compute({ $event: event, $value: value, $assignment: true });
if (typeof owner.$value === 'string')
owner.value = computed;
else
owner.valueAsNumber = stampToView(computed);
display = owner.value;
}
else {
const value = '$value' in owner && parseable(owner.$value) ? JSON.parse(owner.value) : owner.value;
const checked = '$value' in owner && parseable(owner.$value) ? JSON.parse(owner.checked) : owner.checked;
computed = await binder.compute({ $event: event, $value: value, $checked: checked, $assignment: true });
display = format(computed);
owner.value = display;
}
owner.$value = computed;
if (type === 'checked' || type === 'radio')
owner.$checked = computed;
owner.setAttribute('value', display);
};
const valueRender = async function (binder) {
const { owner, meta } = binder;
if (!meta.setup) {
meta.setup = true;
owner.addEventListener('input', event => input(binder, event));
}
const computed = await binder.compute();
let display;
if (binder.owner.type === 'select-one') {
owner.value = undefined;
for (const option of owner.options) {
const optionValue = '$value' in option ? option.$value : option.value;
if (option.selected = optionValue === computed)
break;
}
if (computed === undefined && owner.options.length && !owner.selectedOptions.length) {
const [option] = owner.options;
option.selected = true;
return owner.dispatchEvent(defaultInputEvent);
}
display = format(computed);
owner.value = display;
}
else if (binder.owner.type === 'select-multiple') {
for (const option of owner.options) {
const optionValue = '$value' in option ? option.$value : option.value;
option.selected = computed?.includes(optionValue);
}
display = format(computed);
}
else if (binder.owner.type === 'number' || binder.owner.type === 'range') {
if (typeof computed === 'number' && computed !== Infinity)
owner.valueAsNumber = computed;
else
owner.value = computed;
display = owner.value;
}
else if (dateTypes.includes(binder.owner.type)) {
if (typeof computed === 'string')
owner.value = computed;
else
owner.valueAsNumber = stampToView(computed);
display = owner.value;
}
else {
display = format(computed);
owner.value = display;
}
owner.$value = computed;
if (binder.owner.type === 'checked' || binder.owner.type === 'radio')
owner.$checked = computed;
owner.setAttribute('value', display);
};
const valueUnrender = async function (binder) {
if (binder.owner.type === 'select-one' || binder.owner.type === 'select-multiple') {
for (const option of binder.owner.options) {
option.selected = false;
}
}
binder.owner.value = undefined;
binder.owner.$value = undefined;
if (binder.owner.type === 'checked' || binder.owner.type === 'radio')
binder.owner.$checked = undefined;
binder.owner.setAttribute('value', '');
};
var value = { render: valueRender, unrender: valueUnrender };
const space = /\s+/;
const prepare = /{{\s*(.*?)\s+(of|in)\s+(.*?)\s*}}/;
// const nextFrame = async function () {
// return new Promise((resolve: any) =>
// window.requestAnimationFrame(() =>
// window.requestAnimationFrame(() => resolve())
// )
// );
// };
const wait = async function () {
return new Promise((resolve) => setTimeout(() => resolve(), 0));
};
const eachHas = function (binder, indexValue, keyValue, target, key) {
return key === binder.meta.variableName ||
key === binder.meta.indexName ||
key === binder.meta.keyName ||
key in target;
};
const eachGet = function (binder, indexValue, keyValue, target, key) {
if (key === binder.meta.variableName) {
let result = binder.context;
for (const part of binder.meta.parts) {
result = result[part];
if (!result)
return;
}
return typeof result === 'object' ? result[keyValue] : undefined;
}
else if (key === binder.meta.indexName) {
return indexValue;
}
else if (key === binder.meta.keyName) {
return keyValue;
}
else {
return binder.context[key];
}
};
const eachSet = function (binder, indexValue, keyValue, target, key, value) {
if (key === binder.meta.variableName) {
let result = binder.context;
for (const part of binder.meta.parts) {
result = result[part];
if (!result)
return true;
}
typeof result === 'object' ? result[keyValue] = value : undefined;
}
else if (key === binder.meta.indexName || key === binder.meta.keyName) {
return true;
}
else {
binder.context[key] = value;
}
return true;
};
const eachUnrender = async function (binder) {
binder.meta.tasks = [];
binder.meta.targetLength = 0;
binder.meta.currentLength = 0;
return Promise.all([
(async () => {
let node;
while (node = binder.owner.lastChild)
binder.binder.remove(binder.owner.removeChild(node));
})(),
(async () => {
let node;
while (node = binder.meta.queueElement.content.lastChild)
binder.meta.queueElement.content.removeChild(node);
})()
]);
};
const eachRender = async function (binder) {
if (!binder.meta.setup) {
binder.node.value = '';
const [path, variable, index, key] = binder.value.replace(prepare, '$1,$3').split(/\s*,\s*/).reverse();
binder.meta.path = path;
binder.meta.keyName = key;
binder.meta.indexName = index;
binder.meta.parts = path.split('.');
binder.meta.variableName = variable;
binder.meta.variableNamePattern = new RegExp(`([^.a-zA-Z0-9$_\\[\\]])(${variable})\\b`);
// binder.meta.variableNamePattern = new RegExp(`^${variable}\\b`);
binder.meta.keys = [];
binder.meta.tasks = [];
binder.meta.setup = true;
binder.meta.targetLength = 0;
binder.meta.currentLength = 0;
binder.meta.templateLength = 0;
binder.meta.queueElement = document.createElement('template');
binder.meta.templateElement = document.createElement('template');
let node = binder.owner.firstChild;
while (node) {
if (space.test(node.nodeValue)) {
binder.owner.removeChild(node);
}
else {
binder.meta.templateLength++;
binder.meta.templateElement.content.appendChild(node);
}
node = binder.owner.firstChild;
}
}
const data = await binder.compute();
if (data?.constructor === Array) {
binder.meta.targetLength = data.length;
}
else {
binder.meta.keys = Object.keys(data || {});
binder.meta.targetLength = binder.meta.keys.length;
}
if (binder.meta.currentLength > binder.meta.targetLength) {
while (binder.meta.currentLength > binder.meta.targetLength) {
let count = binder.meta.templateLength;
while (count--) {
const node = binder.owner.lastChild;
binder.owner.removeChild(node);
binder.meta.tasks.push(binder.binder.remove(node));
}
binder.meta.currentLength--;
}
}
else if (binder.meta.currentLength < binder.meta.targetLength) {
while (binder.meta.currentLength < binder.meta.targetLength) {
const indexValue = binder.meta.currentLength;
const keyValue = binder.meta.keys[binder.meta.currentLength] ?? binder.meta.currentLength;
const variableValue = `${binder.meta.path}.${binder.meta.keys[binder.meta.currentLength] ?? binder.meta.currentLength}`;
const context = new Proxy(binder.context, {
has: eachHas.bind(null, binder, indexValue, keyValue),
get: eachGet.bind(null, binder, indexValue, keyValue),
set: eachSet.bind(null, binder, indexValue, keyValue),
});
const rewrites = binder.rewrites?.slice() || [];
if (binder.meta.keyName)
rewrites.unshift([binder.meta.keyName, keyValue]);
// if (binder.meta.indexName) rewrites.unshift([ binder.meta.indexName, indexValue ]);
// if (binder.meta.variableName) rewrites.unshift([ binder.meta.variableName, variableValue ]);
if (binder.meta.variableName)
rewrites.unshift([binder.meta.variableNamePattern, variableValue]);
const clone = binder.meta.templateElement.content.cloneNode(true);
let node = clone.firstChild;
if (node) {
do {
binder.meta.tasks.push(binder.binder.add(node, binder.container, context, rewrites));
} while (node = node.nextSibling);
}
binder.meta.queueElement.content.appendChild(clone);
// var d = document.createElement('div');
// d.classList.add('box');
// var t = document.createTextNode('{{item.number}}');
// binder.meta.tasks.push(binder.binder.add(t, binder.container, context, rewrites));
// d.appendChild(t);
// binder.meta.queueElement.content.appendChild(d);
binder.meta.currentLength++;
}
}
if (binder.meta.currentLength === binder.meta.targetLength) {
await Promise.all(binder.meta.tasks.splice(0, binder.meta.length - 1));
binder.owner.appendChild(binder.meta.queueElement.content);
await wait();
}
if (binder.owner.nodeName === 'SELECT') {
binder.binder.nodeBinders.get(binder.owner.attributes['value'])?.render();
}
};
var each = { render: eachRender, unrender: eachUnrender };
const htmlRender = async function (binder) {
const tasks = [];
let data = await binder.compute();
if (typeof data !== 'string') {
data = '';
console.warn('html binder requires a string');
}
let removeChild;
while (removeChild = binder.owner.lastChild) {
binder.owner.removeChild(removeChild);
tasks.push(binder.binder.remove(removeChild));
}
const template = document.createElement('template');
template.innerHTML = data;
let addChild = template.content.firstChild;
while (addChild) {
tasks.push(binder.binder.add.bind(binder.binder, addChild, binder.container));
addChild = addChild.nextSibling;
}
await Promise.all(tasks);
binder.owner.appendChild(template.content);
};
const htmlUnrender = async function (binder) {
const tasks = [];
let node;
while (node = binder.owner.lastChild) {
tasks.push(binder.binder.remove(node));
binder.owner.removeChild(node);
}
await Promise.all(tasks);
};
var html = { render: htmlRender, unrender: htmlUnrender };
const textRender = async function (binder) {
let data = await binder.compute();
binder.owner.textContent = format(data);
};
const textUnrender = async function (binder) {
binder.owner.textContent = '';
};
var text = { render: textRender, unrender: textUnrender };
const Value = function (element) {
if (!element)
return undefined;
else if ('$value' in element)
return element.$value ? JSON.parse(JSON.stringify(element.$value)) : element.$value;
else if (element.type === 'number' || element.type === 'range')
return element.valueAsNumber;
else
return element.value;
};
const submit = async function (event, binder) {
event.preventDefault();
const form = {};
const target = event.target;
// const elements = target?.elements || target?.form?.elements;
const elements = (target?.form || target)?.querySelectorAll('[name]');
for (const element of elements) {
const { type, name, checked, hidden } = element;
if (!name)
continue;
if (hidden)
continue;
if (type === 'radio' && !checked)
continue;
if (type === 'submit' || type === 'button')
continue;
let value;
if (type === 'select-multiple') {
value = [];
for (const option of element.selectedOptions) {
value.push(Value(option));
}
}
else if (type === 'select-one') {
const [option] = element.selectedOptions;
value = Value(option);
}
else {
value = Value(element);
}
let data = form;
name.split(/\s*\.\s*/).forEach((part, index, parts) => {
const next = parts[index + 1];
if (next) {
if (!data[part]) {
data[part] = /[0-9]+/.test(next) ? [] : {};
}
data = data[part];
}
else {
data[part] = value;
}
});
}
await binder.compute({ $form: form, $event: event });
if (target.getAttribute('reset'))
target.reset();
return false;
};
const reset = async function (event, binder) {
event.preventDefault();
const target = event.target;
// const elements = target?.elements || target?.form?.elements;
const elements = (target?.form || target)?.querySelectorAll('[name]');
for (const element of elements) {
const { type, name, checked, hidden, nodeName } = element;
if (!name)
continue;
if (hidden)
continue;
if (type === 'radio' && !checked)
continue;
if (type === 'submit' || type === 'button')
continue;
if (type === 'select-one') {
element.selectedIndex = 0;
}
else if (type === 'select-multiple') {
element.selectedIndex = -1;
}
else if (type === 'radio' || type === 'checkbox') {
element.checked = false;
}
else {
element.value = undefined;
}
element.dispatchEvent(new Event('input'));
}
await binder.compute({ $event: event });
return false;
};
const onRender = async function (binder) {
binder.owner[binder.name] = null;
const name = binder.name.slice(2);
if (!binder.meta.setup) {
binder.meta.setup = true;
binder.node.value = '';
}
if (binder.meta.method) {
binder.owner.removeEventListener(name, binder.meta.method);
}
binder.meta.method = event => {
if (name === 'reset') {
return reset(event, binder);
}
else if (name === 'submit') {
return submit(event, binder);
}
else {
return binder.compute({ $event: event });
}
};
binder.owner.addEventListener(name, binder.meta.method);
};
const onUnrender = async function (binder) {
binder.owner[binder.name] = null;
const name = binder.name.slice(2);
if (binder.meta.method) {
binder.owner.removeEventListener(name, binder.meta.method);
}
};
var on = { render: onRender, unrender: onUnrender };
const caches = new Map();
const splitPattern = /\s*{{\s*|\s*}}\s*/;
const instancePattern = /(\$\w+)/;
const bracketPattern = /({{)|(}})/;
const eachPattern = /({{.*?\s+(of|in)\s+(.*?)}})/;
const assignmentPattern = /({{(.*?)([_$a-zA-Z0-9.?\[\]]+)([-+?^*%|\\ ]*=[-+?^*%|\\ ]*)([^<>=].*?)}})/;
const codePattern = new RegExp(`${eachPattern.source}|${assignmentPattern.source}|${instancePattern.source}|${bracketPattern.source}`, 'g');
const ignores = [
// '$assignee', '$instance', '$binder', '$event', '$value', '$checked', '$form', '$e', '$v', '$c', '$f',
// '$e', '$v', '$c', '$f',
'$instance', '$event', '$value', '$checked', '$form',
'this', 'window', 'document', 'console', 'location',
'globalThis', 'Infinity', 'NaN', 'undefined',
'isFinite', 'isNaN', 'parseFloat', 'parseInt', 'decodeURI', 'decodeURIComponent', 'encodeURI', 'encodeURIComponent ',
'Error', 'EvalError', 'RangeError', 'ReferenceError', 'SyntaxError', 'TypeError', 'URIError', 'AggregateError',
'Object', 'Function', 'Boolean', 'Symbole', 'Array',
'Number', 'Math', 'Date', 'BigInt',
'String', 'RegExp',
'Array', 'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array', 'Uint16Array',
'Int32Array', 'Uint32Array', 'BigInt64Array', 'BigUint64Array', 'Float32Array', 'Float64Array',
'Map', 'Set', 'WeakMap', 'WeakSet',
'ArrayBuffer', 'SharedArrayBuffer', 'DataView', 'Atomics', 'JSON',
'Promise', 'GeneratorFunction', 'AsyncGeneratorFunction', 'Generator', 'AsyncGenerator', 'AsyncFunction',
'Reflect', 'Proxy',
];
const has = function (target, key) {
return ignores.includes(key) ? false : key in target;
};
const computer = function (binder) {
let cache = caches.get(binder.value);
if (!cache) {
let code = binder.value;
const convert = code.split(splitPattern).filter(part => part).length > 1;
const isChecked = binder.node.name === 'checked';
const isValue = binder.node.name === 'value';
let reference = '';
let assignment = '';
let usesInstance = false;
// let hasEvent, hasForm, hasValue, hasChecked;
code = code.replace(codePattern, function (match, g1, g2, ofInRight, assignee, assigneeLeft, ref, assigneeMiddle, assigneeRight, instance, bracketLeft, bracketRight) {
if (bracketLeft)
return convert ? `' + (` : '(';
if (bracketRight)
return convert ? `) + '` : ')';
if (ofInRight)
return `(${ofInRight})`;
if (instance) {
usesInstance = true;
return match;
}
if (assignee) {
if (isValue || isChecked) {
reference = ref;
usesInstance = true;
assignment = assigneeLeft + assigneeRight;
return (convert ? `' + (` : '(') + assigneeLeft + ref + assigneeMiddle + assigneeRight + (convert ? `) + '` : ')');
}
else {
return (convert ? `' + (` : '(') + assigneeLeft + ref + assigneeMiddle + assigneeRight + (convert ? `) + '` : ')');
}
}
});
code = convert ? `'${code}'` : code;
if (usesInstance) {
code = `
$instance = $instance || {};
with ($instance) {
with ($context) {
if ($instance.$assignment) {
return ${code};
} else {
${isValue ? `$instance.$value = ${reference || `undefined`};` : ''}
${isChecked ? `$instance.$checked = ${reference || `undefined`};` : ''}
return ${assignment || code};
}
}
}
`;
}
else {
code = `with ($context) { return ${code}; }`;
}
code = `
try {
${code}
} catch (error){
console.error(error);
}
`;
cache = new Function('$context', '$binder', '$instance', code);
caches.set(binder.value, cache);
}
return cache.bind(null, new Proxy(binder.context, { has }), binder);
// return cache.bind(null, binder.context, binder);
};
const normalizeReference = /\s*(\??\.|\[\s*([0-9]+)\s*\])\s*/g;
const referenceMatch = new RegExp([
'(".*?[^\\\\]*"|\'.*?[^\\\\]*\'|`.*?[^\\\\]*`)',
'([^{}]*{{.*?\\s+(?:of|in)\\s+)',
'((?:^|}}).*?{{)',
'(}}.*?(?:{{|$))',
`(
(?:\\$assignee|\\$instance|\\$binder|\\$event|\\$value|\\$checked|\\$form|\\$e|\\$v|\\$c|\\$f|
this|window|document|console|location|
globalThis|Infinity|NaN|undefined|
isFinite|isNaN|parseFloat|parseInt|decodeURI|decodeURIComponent|encodeURI|encodeURIComponent|
Error|EvalError|RangeError|ReferenceError|SyntaxError|TypeError|URIError|AggregateError|
Object|Function|Boolean|Symbole|Array|
Number|Math|Date|BigInt|
String|RegExp|
Array|Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|
Int32Array|Uint32Array|BigInt64Array|BigUint64Array|Float32Array|Float64Array|
Map|Set|WeakMap|WeakSet|
ArrayBuffer|SharedArrayBuffer|DataView|Atomics|JSON|
Promise|GeneratorFunction|AsyncGeneratorFunction|Generator|AsyncGenerator|AsyncFunction|
Reflect|Proxy|
true|false|null|undefined|NaN|of|in|do|if|for|new|try|case|else|with|await|break|catch|class|super|throw|while|
yield|delete|export|import|return|switch|default|extends|finally|continue|debugger|function|arguments|typeof|instanceof|void)
[a-zA-Z0-9$_.?\\[\\]]*
)`,
'([a-zA-Z$_][a-zA-Z0-9$_.?\\[\\]]*)' // reference
].join('|').replace(/\s|\t|\n/g, ''), 'g');
const cache = new Map();
const parser = function (data, rewrites) {
data = data.replace(normalizeReference, '.$2');
if (rewrites) {
for (const [name, value] of rewrites) {
data = data.replace(name, `$1${value}`);
}
}
const cached = cache.get(data);
if (cached)
return cached;
const references = [];
cache.set(data, references);
let match;
while (match = referenceMatch.exec(data)) {
let reference = match[6];
if (reference) {
references.push(reference);
}
}
// console.log(data, references);
return references;
};
const TN = Node.TEXT_NODE;
const EN = Node.ELEMENT_NODE;
// const AN = Node.ATTRIBUTE_NODE;
class Binder {
prefix = 'o-';
prefixEach = 'o-each';
prefixValue = 'o-value';
syntaxEnd = '}}';
syntaxStart = '{{';
syntaxLength = 2;
syntaxMatch = new RegExp('{{.*?}}');
prefixReplace = new RegExp('^o-');
syntaxReplace = new RegExp('{{|}}', 'g');
nodeBinders = new Map();
ownerBinders = new Map();
pathBinders = new Map();
binders = {
standard,
checked,
inherit,
value,
each,
html,
text,
on,
};
get(data) {
if (typeof data === 'string') {
return this.pathBinders.get(data);
}
else {
return this.nodeBinders.get(data);
}
}
async unbind(node) {
const ownerBinders = this.ownerBinders.get(node);
if (!ownerBinders)
return;
for (const ownerBinder of ownerBinders) {
this.nodeBinders.delete(ownerBinder.node);
for (const path of ownerBinder.paths) {
const pathBinders = this.pathBinders.get(path);
if (!pathBinders)
continue;
pathBinders.delete(ownerBinder);
if (!pathBinders.size)
this.pathBinders.delete(path);
}
}
this.nodeBinders.delete(node);
this.ownerBinders.delete(node);
}
async bind(node, container, name, value, owner, context, rewrites) {
const type = name.startsWith('on') ? 'on' : name in this.binders ? name : 'standard';
const handler = this.binders[type];
const binder = {
meta: {},
ready: true,
binder: this,
paths: undefined,
render: undefined,
compute: undefined,
unrender: undefined,
binders: this.pathBinders,
node, owner, name, value, rewrites, context, container, type,
};
const [paths, compute] = await Promise.all([
parser(value, rewrites),
computer(binder)
]);
binder.paths = paths;
binder.compute = compute;
binder.render = handler.render.bind(null, binder);
binder.unrender = handler.unrender.bind(null, binder);
for (const reference of paths) {
const binders = binder.binders.get(reference);
if (binders) {
binders.add(binder);
}
else {
binder.binders.set(reference, new Set([binder]));
}
}
this.nodeBinders.set(node, binder);
const ownerBinders = this.ownerBinders.get(binder.owner);
if (ownerBinders) {
ownerBinders.add(binder);
}
else {
this.ownerBinders.set(binder.owner, new Set([binder]));
}
return binder.render();
}
;
async remove(node) {
const tasks = [];
// if (node.nodeType === AN) {
// tasks.push(this.unbind(node));
if (node.nodeType === TN) {
this.unbind(node);
}
else if (node.nodeType === EN) {
this.unbind(node);
const attributes = node.attributes;
for (const attribute of attributes) {
tasks.push(this.unbind(attribute));
}
let child = node.firstChild;
while (child) {
// this.remove(child);
tasks.push(this.remove(child));
child = child.nextSibling;
}
}
return Promise.all(tasks);
}
async add(node, container, context, rewrites) {
// if (node.nodeType === AN) {
// const attribute = (node as Attr);
// if (this.syntaxMatch.test(attribute.value)) {
// tasks.push(this.bind(node, container, attribute.name, attribute.value, attribute.ownerElement, context, rewrites));
// }
// } else
if (node.nodeType === TN) {
const tasks = [];
const start = node.nodeValue.indexOf(this.syntaxStart);
if (start === -1)
return;
if (start !== 0)
node = node.splitText(start);
const end = node.nodeValue.indexOf(this.syntaxEnd);
if (end === -1)
return;
if (end + this.syntaxLength !== node.nodeValue.length) {
const split = node.splitText(end + this.syntaxLength);
tasks.push(this.add(split, container, context, rewrites));
}
tasks.push(this.bind(node, container, 'text', node.nodeValue, node, context, rewrites));
return Promise.all(tasks);
}
else if (node.nodeType === EN) {
const attributes = node.attributes;
const inherit = attributes['inherit'];
if (inherit) {
// await window.customElements.whenDefined((node as any).localName);
// await (node as any).whenReady();
if (!node.ready) {
await new Promise((resolve) => node.addEventListener('ready', resolve));
}
await this.bind(inherit, container, inherit.name, inherit.value, inherit.ownerElement, context, rewrites);
}
const each = attributes['each'];
if (each)
await this.bind(each, container, each.name, each.value, each.ownerElement, context, rewrites);
if (!each && !inherit) {
let child = node.firstChild;
if (child) {
const tasks = [];
do {
tasks.push(this.add(child, container, context, rewrites));
} while (child = child.nextSibling);
if (tasks.length)
await Promise.all(tasks);
}
}
if (attributes.length) {
const tasks = [];
for (const attribute of attributes) {
if (attribute.name !== 'each' && attribute.name !== 'inherit' && this.syntaxMatch.test(attribute.value)) {
tasks.push(this.bind(attribute, container, attribute.name, attribute.value, attribute.ownerElement, context, rewrites));
}
}
if (tasks.length)
await Promise.all(tasks);
}
}
}
}
var Css = new class Css {
#data = new Map();
#style = document.createElement('style');
#support = !window.CSS || !window.CSS.supports || !window.CSS.supports('(--t: black)');
constructor() {
this.#style.appendChild(document.createTextNode(':not(:defined){visibility:hidden;}'));
this.#style.setAttribute('title', 'oxe');
document.head.appendChild(this.#style);
}
scope(name, text) {
return text
.replace(/\t|\n\s*/g, '')
// .replace(/(^\s*|}\s*|,\s*)(\.?[a-zA-Z_-]+)/g, `$1${name} $2`)
.replace(/:host/g, name);
}
transform(text = '') {
if (!this.#support) {
const matches = text.match(/--\w+(?:-+\w+)*:\s*.*?;/g) || [];
for (let i = 0; i < matches.length; i++) {
const match = matches[i];
const rule = match.match(/(--\w+(?:-+\w+)*):\s*(.*?);/);
const pattern = new RegExp('var\\(' + rule[1] + '\\)', 'g');
text = text.replace(rule[0], '');
text = text.replace(pattern, rule[2]);
}
}
return text;
}
detach(name) {
const item = this.#data.get(name);
if (!item)
return;
item.count--;
if (item.count === 1) {
this.#data.delete(name);
this.#style.removeChild(item.node);
}
}
attach(name, text) {
let item = this.#data.get(name);
if (item) {
item.count++;
}
else {
item = { count: 1, node: this.node(name, text) };
this.#data.set(name, item);
this.#style.appendChild(item.node);
}
}
node(name, text) {
return document.createTextNode(this.scope(name, this.transform(text)));
}
};
class Component extends HTMLElement {
static attributes;
static get observedAttributes() { return this.attributes; }
static set observedAttributes(attributes) { this.attributes = attributes; }
#root;
#binder;
// #template: any;
#flag = false;
#ready = false;
#name = this.nodeName.toLowerCase();
// this overwrites extends methods
// adopted: () => void;
// rendered: () => void;
// connected: () => void;
// disconnected: () => void;
// attributed: (name: string, from: string, to: string) => void;
#adopted;
#rendered;
#connected;
#disconnected;
#attributed;
#readyEvent = new Event('ready');
#afterRenderEvent = new Event('afterrender');
#beforeRenderEvent = new Event('beforerender');
#afterConnectedEvent = new Event('afterconnected');
#beforeConnectedEvent = new Event('beforeconnected');
// #css: string = typeof (this as any).css === 'string' ? (this as any).css : '';
// #html: string = typeof (this as any).html === 'string' ? (this as any).html : '';
// #data: object = typeof (this as any).data === 'object' ? (this as any).data : {};
// #adopt: boolean = typeof (this as any).adopt === 'boolean' ? (this as any).adopt : false;
// #shadow: boolean = typeof (this as any).shadow === 'boolean' ? (this as any).shadow : false;
css = '';
html = '';
data = {};
adopt = false;
shadow = false;
get root() { return this.#root; }
get ready() { return this.#ready; }
get binder() { return this.#binder; }
constructor() {
super();
this.#binder = new Binder();
this.#adopted = this.adopted;
this.#rendered = this.rendered;
this.#connected = this.connected;
this.#attributed = this.attributed;
this.#disconnected = this.disconnected;
if (this.shadow && 'attachShadow' in document.body) {
this.#root = this.attachShadow({ mode: 'open' });
}
else if (this.shadow && 'createShadowRoot' in document.body) {
this.#root = this.createShadowRoot();
}
else {
this.#root = this;
}
// this.#template = document.createElement('template');
// this.#template.innerHTML = this.html;
}
async #observe(path, type) {
const parents = this.#binder.pathBinders.get(path);
if (parents) {
// console.log('path:',path);
const parentTasks = [];
for (const binder of parents) {
if (!binder)
continue;
parentTasks.push(binder[type]());
}
await Promise.all(parentTasks);
}
for (const [key, children] of this.#binder.pathBinders) {
if (!children)
continue;
if (key.startsWith(`${path}.`)) {
// console.log('key:', key);
for (const binder of children) {
if (!binder)
continue;
binder[type]();
}
}
}
}
;
async #render() {
const tasks = [];
this.data = observer(typeof this.data === 'function' ? await this.data() : this.data, this.#observe.bind(this));
if (this.adopt) {
let child = this.firstChild;
while (child) {
tasks.push(this.#binder.add(child, this, this.data));
// this.#binder.add(child, this, this.data);
child = child.nextSibling;
}
}
const template = document.createElement('template');
template.innerHTML = this.html;
if (!this.shadow ||
!('attachShadow' in document.body) &&
!('createShadowRoot' in document.body)) {
const templateSlots = template.content.querySelectorAll('slot[name]');
const defaultSlot = template.content.querySelector('slot:not([name])');
for (let i = 0; i < templateSlots.length; i++) {
const templateSlot = templateSlots[i];
const name = templateSlot.getAttribute('name');
const instanceSlot = this.querySelector('[slot="' + name + '"]');
if (instanceSlot)
templateSlot.parentNode.replaceChild(instanceSlot, templateSlot);
else
templateSlot.parentNode.removeChild(templateSlot);
}
if (this.children.length) {
while (this.firstChild) {
if (defaultSlot)
defaultSlot.parentNode.insertBefore(this.firstChild, defaultSlot);
else
this.removeChild(this.firstChild);
}
}
if (defaultSlot)
defaultSlot.parentNode.removeChild(defaultSlot);
}
let child = template.content.firstChild;
while (child) {
tasks.push(this.#binder.add(child, this, this.data));
// this.#binder.add(child, this, this.data);
child = child.nextSibling;
}
this.#root.appendChild(template.content);
await Promise.all(tasks);
}
// async whenReady () {
// if (!this.#ready) {
// return new Promise(resolve => this.addEventListener('afterrender', resolve));
// }
// }
async attributeChangedCallback(name, from, to) {