Загрузить файлы в «/»
This commit is contained in:
parent
83b36f56df
commit
6b5296f533
523
dom.js
Normal file
523
dom.js
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
const EVENT_ALIASES = {
|
||||||
|
// Keyboard
|
||||||
|
keyDown: 'keydown',
|
||||||
|
keyUp: 'keyup',
|
||||||
|
keyPress: 'keypress',
|
||||||
|
|
||||||
|
// Mouse
|
||||||
|
mouseEnter: 'mouseenter',
|
||||||
|
mouseLeave: 'mouseleave',
|
||||||
|
mouseDown: 'mousedown',
|
||||||
|
mouseUp: 'mouseup',
|
||||||
|
mouseMove: 'mousemove',
|
||||||
|
mouseOver: 'mouseover',
|
||||||
|
mouseOut: 'mouseout',
|
||||||
|
|
||||||
|
// Touch
|
||||||
|
touchStart: 'touchstart',
|
||||||
|
touchEnd: 'touchend',
|
||||||
|
touchMove: 'touchmove',
|
||||||
|
touchCancel: 'touchcancel',
|
||||||
|
|
||||||
|
// Focus
|
||||||
|
focusIn: 'focusin',
|
||||||
|
focusOut: 'focusout',
|
||||||
|
|
||||||
|
// Animation / Transition
|
||||||
|
animationStart: 'animationstart',
|
||||||
|
animationEnd: 'animationend',
|
||||||
|
animationIteration: 'animationiteration',
|
||||||
|
transitionEnd: 'transitionend',
|
||||||
|
|
||||||
|
// Drag
|
||||||
|
dragStart: 'dragstart',
|
||||||
|
dragEnd: 'dragend',
|
||||||
|
dragEnter: 'dragenter',
|
||||||
|
dragLeave: 'dragleave',
|
||||||
|
dragOver: 'dragover',
|
||||||
|
|
||||||
|
// Wheel
|
||||||
|
wheelStart: 'wheelstart',
|
||||||
|
wheelEnd: 'wheelend',
|
||||||
|
|
||||||
|
// Composition
|
||||||
|
compositionStart: 'compositionstart',
|
||||||
|
compositionEnd: 'compositionend',
|
||||||
|
compositionUpdate: 'compositionupdate',
|
||||||
|
|
||||||
|
// Pointer
|
||||||
|
pointerDown: 'pointerdown',
|
||||||
|
pointerUp: 'pointerup',
|
||||||
|
pointerMove: 'pointermove',
|
||||||
|
pointerEnter: 'pointerenter',
|
||||||
|
pointerLeave: 'pointerleave',
|
||||||
|
pointerOver: 'pointerover',
|
||||||
|
pointerOut: 'pointerout',
|
||||||
|
pointerCancel: 'pointercancel',
|
||||||
|
|
||||||
|
// Context menu
|
||||||
|
contextMenu: 'contextmenu',
|
||||||
|
|
||||||
|
// Form
|
||||||
|
formData: 'formdata',
|
||||||
|
formChange: 'formchange',
|
||||||
|
formInput: 'forminput',
|
||||||
|
};
|
||||||
|
|
||||||
|
function createSignal(initialValue) {
|
||||||
|
let value = initialValue;
|
||||||
|
const listeners = new Set();
|
||||||
|
|
||||||
|
const signalId = `signal_${Math.random().toString(36).slice(2, 7)}`;
|
||||||
|
let childCounter = 0;
|
||||||
|
|
||||||
|
function signal(newValue) {
|
||||||
|
if (arguments.length === 0) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
const oldValue = value;
|
||||||
|
value = newValue;
|
||||||
|
if (oldValue !== newValue) {
|
||||||
|
listeners.forEach(fn => fn(value, oldValue));
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
signal.id = signalId;
|
||||||
|
|
||||||
|
signal.on = function(fn) {
|
||||||
|
listeners.add(fn);
|
||||||
|
//fn(value, value);
|
||||||
|
return () => listeners.delete(fn);
|
||||||
|
};
|
||||||
|
|
||||||
|
signal.map = function(fn, mapId) {
|
||||||
|
childCounter++;
|
||||||
|
const derivedId = mapId || `${signalId}_map_${childCounter}`;
|
||||||
|
const derived = createSignal(fn(value), derivedId);
|
||||||
|
|
||||||
|
const unsubscribe = signal.on(newVal => derived(fn(newVal)));
|
||||||
|
|
||||||
|
derived._parentUnsubscribe = unsubscribe;
|
||||||
|
derived._parent = signal;
|
||||||
|
|
||||||
|
return derived;
|
||||||
|
};
|
||||||
|
|
||||||
|
signal.destroy = function() {
|
||||||
|
if (signal._parentUnsubscribe) {
|
||||||
|
signal._parentUnsubscribe();
|
||||||
|
}
|
||||||
|
listeners.clear();
|
||||||
|
signal._parent = null;
|
||||||
|
signal._parentUnsubscribe = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
signal.chain = function() {
|
||||||
|
const chain = [signalId];
|
||||||
|
let current = signal;
|
||||||
|
while (current._parent) {
|
||||||
|
chain.unshift(current._parent.id);
|
||||||
|
current = current._parent;
|
||||||
|
}
|
||||||
|
return chain.join(' → ');
|
||||||
|
};
|
||||||
|
|
||||||
|
signal.listenerCount = function() {
|
||||||
|
return listeners.size;
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.defineProperty(signal, 'value', {
|
||||||
|
get: () => signal(),
|
||||||
|
set: (v) => signal(v)
|
||||||
|
});
|
||||||
|
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.dom = new Proxy({
|
||||||
|
signal: createSignal,
|
||||||
|
}, {
|
||||||
|
get(target, prop) {
|
||||||
|
if (prop in target) return target[prop];
|
||||||
|
const realTag = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||||
|
return (id) => {
|
||||||
|
const el = document.createElement(realTag);
|
||||||
|
if (id !== undefined && typeof id === 'string') {
|
||||||
|
el.id = id;
|
||||||
|
}
|
||||||
|
return createBuilder(el);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function createBuilder(el) {
|
||||||
|
|
||||||
|
const isSignal = (val) => typeof val === 'function' && typeof val.on === 'function';
|
||||||
|
|
||||||
|
const builder = {
|
||||||
|
element: el,
|
||||||
|
_proxy: null,
|
||||||
|
_dom_instr: [
|
||||||
|
'id', 'cls', 'atr', 'css', 'pcss', 'mcss',
|
||||||
|
'text', 'child' //'html'
|
||||||
|
],
|
||||||
|
_condition_instr: [
|
||||||
|
'if', 'elif', 'else', 'endif'
|
||||||
|
],
|
||||||
|
_each_instr: [
|
||||||
|
'ueach', 'oeach'
|
||||||
|
],
|
||||||
|
_adding_signals: [],
|
||||||
|
_subscribed_signals: new Set(),
|
||||||
|
_unsubscribe_fns: [],
|
||||||
|
|
||||||
|
stack: [[]],
|
||||||
|
|
||||||
|
get current_stack() {
|
||||||
|
return this.stack[this.stack.length - 1];
|
||||||
|
},
|
||||||
|
|
||||||
|
push_instruction(instruction, type, ...value){
|
||||||
|
this.current_stack.push([instruction, type, ...value]);
|
||||||
|
return this._proxy;
|
||||||
|
},
|
||||||
|
|
||||||
|
processValue(val) {
|
||||||
|
if (isSignal(val)) {
|
||||||
|
if(!this._adding_signals.includes(val.id)) {
|
||||||
|
//this.stack[0].push(['atr', 'value', val.id]);
|
||||||
|
this._adding_signals.push(val.id);
|
||||||
|
}
|
||||||
|
return ['signal', val];
|
||||||
|
}
|
||||||
|
return ['value', val];
|
||||||
|
},
|
||||||
|
|
||||||
|
handle_instruction(prop, ...args){
|
||||||
|
if (args.length === 1) {
|
||||||
|
const [type, value] = this.processValue(args[0]);
|
||||||
|
return this.push_instruction(prop, type, value);
|
||||||
|
} else {
|
||||||
|
const hvalarr = args.flatMap(val => this.processValue(val));
|
||||||
|
return this.push_instruction(prop, 'varr', [...hvalarr]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handle_condition(prop, ...args){
|
||||||
|
if(prop === 'if'){
|
||||||
|
this.stack.push([]);
|
||||||
|
return this.handle_instruction(prop, ...args);
|
||||||
|
} else if(['elif', 'else', 'endif'].includes(prop)){
|
||||||
|
const condition_branch = this.current_stack;
|
||||||
|
const condition_instr = this.current_stack[0];
|
||||||
|
this.stack.pop();
|
||||||
|
const cproxy = this.handle_instruction(condition_instr[0], condition_instr[2] ?? null, condition_branch.slice(1));
|
||||||
|
if(prop === 'endif') return cproxy;
|
||||||
|
cproxy.stack.push([]);
|
||||||
|
return cproxy.handle_instruction(prop, ...args);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handle_each(prop, ...args){
|
||||||
|
var arr = isSignal(args[0]) ? args[0].value : args[0];
|
||||||
|
if(Array.isArray(arr)){
|
||||||
|
const map = [];
|
||||||
|
for(var i = 0; i < arr.length; i++){
|
||||||
|
map.push(dom.signal(arr[i]));
|
||||||
|
}
|
||||||
|
return this.handle_instruction(prop, args[0], map, null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update_each(inst){
|
||||||
|
const data = inst[2][1].value;
|
||||||
|
const map = inst[2][3];
|
||||||
|
const list = inst[2][5];
|
||||||
|
|
||||||
|
while (list.children.length > data.length) {
|
||||||
|
list.removeChild(list.lastChild);
|
||||||
|
map.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (list.children.length < data.length) {
|
||||||
|
const value = dom.signal("text");
|
||||||
|
list.appendChild(dom.li().text(value).done());
|
||||||
|
map.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(var i = 0; i < data.length; i++){
|
||||||
|
if(map[i].value !== data[i]) {
|
||||||
|
map[i](data[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inst[2][3] = map;
|
||||||
|
},
|
||||||
|
|
||||||
|
on(event, handler) {
|
||||||
|
el.addEventListener(event, handler);
|
||||||
|
return this._proxy;
|
||||||
|
},
|
||||||
|
|
||||||
|
atrs(attrs) {
|
||||||
|
Object.entries(attrs).forEach(([k, v]) => this.handle_instruction('atr', k, v));
|
||||||
|
return this._proxy;
|
||||||
|
},
|
||||||
|
|
||||||
|
children(arr) {
|
||||||
|
arr.forEach((el) => this.handle_instruction('child', el))
|
||||||
|
return this._proxy;
|
||||||
|
},
|
||||||
|
|
||||||
|
unpack(val) {
|
||||||
|
const processValue = (type, value) => {
|
||||||
|
return type === 'signal' ? value() : value;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (val[1] === 'value') {
|
||||||
|
return [val[2]];
|
||||||
|
} else if (val[1] === 'signal') {
|
||||||
|
return [processValue('signal', val[2])];
|
||||||
|
} else if (val[1] === 'varr') {
|
||||||
|
let res = [];
|
||||||
|
for (let i = 0; i < val[2].length; i += 2) {
|
||||||
|
const type = val[2][i];
|
||||||
|
const value = val[2][i + 1];
|
||||||
|
res.push(processValue(type, value));
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
render(stack, clear = true){
|
||||||
|
var isLastConditionTrue = false;
|
||||||
|
if(clear) el.innerHTML = '';
|
||||||
|
stack.forEach((inst) => {
|
||||||
|
const val = this.unpack(inst);
|
||||||
|
//console.log(inst[0], val);
|
||||||
|
switch (inst[0]) {
|
||||||
|
case 'id':
|
||||||
|
el.id = val[0];
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
el.innerText = val[0];
|
||||||
|
break;
|
||||||
|
/*case 'html':
|
||||||
|
el.innerHTML = val[0];
|
||||||
|
break;*/
|
||||||
|
case 'cls':
|
||||||
|
el.classList.add(...String(val[0]).split(' ').filter(Boolean));
|
||||||
|
break;
|
||||||
|
case 'atr':
|
||||||
|
el.setAttribute(val[0], val[1]);
|
||||||
|
break;
|
||||||
|
case 'css':
|
||||||
|
el.style.cssText = (el.style.cssText || '') + String(val[0]);
|
||||||
|
break;
|
||||||
|
case 'pcss':
|
||||||
|
case 'mcss':
|
||||||
|
const type = inst[0] === 'pcss' ? 'pcss' : 'mcss';
|
||||||
|
const uniqueCls = `${type}-${Math.random().toString(36).slice(2, 9)}`;
|
||||||
|
el.classList.add(uniqueCls);
|
||||||
|
const existingClasses = el.getAttribute(type) || '';
|
||||||
|
el.setAttribute(type, existingClasses ? `${existingClasses},${uniqueCls}` : uniqueCls);
|
||||||
|
const newStyle = document.createElement('style');
|
||||||
|
newStyle.textContent = type === 'pcss'
|
||||||
|
? `.${uniqueCls}${val[0]} { ${val[1]} }`
|
||||||
|
: `@media ${val[0]} {.${uniqueCls}{ ${val[1]} }}`;
|
||||||
|
newStyle.setAttribute(type, uniqueCls);
|
||||||
|
document.head.appendChild(newStyle);
|
||||||
|
break;
|
||||||
|
case 'child':
|
||||||
|
if(val[0] instanceof HTMLElement) el.appendChild(val[0]);
|
||||||
|
else el.appendChild(val[0].done());
|
||||||
|
break;
|
||||||
|
case 'if':
|
||||||
|
if(val[0]){
|
||||||
|
this.render(val[1], false);
|
||||||
|
isLastConditionTrue = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'elif':
|
||||||
|
if(val[0] && !isLastConditionTrue){
|
||||||
|
this.render(val[1], false);
|
||||||
|
isLastConditionTrue = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'else':
|
||||||
|
if(!isLastConditionTrue){
|
||||||
|
this.render(val[1], false);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'oeach':
|
||||||
|
case 'ueach':
|
||||||
|
const listel = inst[0] === 'oeach' ?
|
||||||
|
document.createElement('ol') :
|
||||||
|
document.createElement('ul');
|
||||||
|
val[1].forEach((value) => {
|
||||||
|
const li = dom.li().text(value).done();
|
||||||
|
listel.appendChild(li);
|
||||||
|
});
|
||||||
|
|
||||||
|
inst[2][5] = listel;
|
||||||
|
el.appendChild(listel);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('Unknown instruction', inst)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
subscribeToSignals() {
|
||||||
|
const subscribe = (val) => {
|
||||||
|
if (val[1] === 'signal') {
|
||||||
|
const signal = val[2];
|
||||||
|
if (!this._subscribed_signals.has(signal)) {
|
||||||
|
this._subscribed_signals.add(signal);
|
||||||
|
var unsubscribe = null;
|
||||||
|
if(['if', 'elif', 'else'].includes(val[0])){
|
||||||
|
unsubscribe = signal.on(() => this.render(this.stack[0]));
|
||||||
|
} else if(this._each_instr.includes(val[0])){
|
||||||
|
unsubscribe = signal.on(() => this.update_each(val[3]));
|
||||||
|
} else {
|
||||||
|
unsubscribe = signal.on(() => this.findLastAndCallInstruction(this.stack[0], val[0]));
|
||||||
|
}
|
||||||
|
this._unsubscribe_fns.push(unsubscribe);
|
||||||
|
}
|
||||||
|
} else if (val[1] === 'varr') {
|
||||||
|
for (let i = 0; i < val[2].length; i += 2) {
|
||||||
|
const type = val[2][i];
|
||||||
|
const value = val[2][i + 1];
|
||||||
|
if (type === 'signal') {
|
||||||
|
subscribe([val[0], 'signal', value, val]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.stack[0].forEach(inst => subscribe(inst));
|
||||||
|
},
|
||||||
|
|
||||||
|
findLastAndCallInstruction(stack, instructionType) {
|
||||||
|
let foundInstructions = [];
|
||||||
|
|
||||||
|
function search(instructions, depth) {
|
||||||
|
const foundAtThisLevel = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < instructions.length; i++) {
|
||||||
|
const inst = instructions[i];
|
||||||
|
const type = inst[0];
|
||||||
|
|
||||||
|
if (type === instructionType) {
|
||||||
|
foundAtThisLevel.push({ instruction: inst, depth: depth, index: i });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'if' || type === 'elif' || type === 'else') {
|
||||||
|
const valArr = inst[2];
|
||||||
|
let conditionTrue = false;
|
||||||
|
|
||||||
|
if (type === 'else') {
|
||||||
|
let anyPreviousTrue = false;
|
||||||
|
for (let j = i - 1; j >= 0; j--) {
|
||||||
|
const prev = instructions[j];
|
||||||
|
if (prev[0] === 'if' || prev[0] === 'elif') {
|
||||||
|
const prevCondition = evaluateCondition(prev);
|
||||||
|
if (prevCondition) {
|
||||||
|
anyPreviousTrue = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (prev[0] === 'if') break;
|
||||||
|
}
|
||||||
|
conditionTrue = !anyPreviousTrue;
|
||||||
|
} else {
|
||||||
|
conditionTrue = evaluateCondition(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conditionTrue && valArr[3]) {
|
||||||
|
search(valArr[3], depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foundInstructions.push(...foundAtThisLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateCondition(inst) {
|
||||||
|
const valArr = inst[2];
|
||||||
|
if (valArr[0] === 'signal' && typeof valArr[1] === 'function') {
|
||||||
|
return valArr[1]();
|
||||||
|
}
|
||||||
|
return valArr[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
search(stack, 0);
|
||||||
|
|
||||||
|
foundInstructions.sort((a, b) => {
|
||||||
|
if (a.depth !== b.depth) return b.depth - a.depth;
|
||||||
|
return b.index - a.index;
|
||||||
|
});
|
||||||
|
|
||||||
|
const maxDepth = foundInstructions.length > 0 ? foundInstructions[0].depth : -1;
|
||||||
|
const result = foundInstructions
|
||||||
|
.filter(item => item.depth === maxDepth)
|
||||||
|
.sort((a, b) => a.index - b.index)
|
||||||
|
.map(item => item.instruction);
|
||||||
|
|
||||||
|
if (result.length > 0) {
|
||||||
|
result.forEach((inst) => {
|
||||||
|
const clearType = inst[0] === 'pcss' ? 'pcss' : 'mcss';
|
||||||
|
const classesToRemove = (el.getAttribute(clearType) || '').split(',').filter(Boolean);
|
||||||
|
|
||||||
|
classesToRemove.forEach(cls => {
|
||||||
|
el.classList.remove(cls);
|
||||||
|
const styleToRemove = document.querySelector(`style[${clearType}="${cls}"]`);
|
||||||
|
if (styleToRemove) styleToRemove.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
el.removeAttribute(clearType);
|
||||||
|
})
|
||||||
|
this.render(result, false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
done() {
|
||||||
|
this.subscribeToSignals();
|
||||||
|
this.render(this.stack[0]);
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxy = new Proxy(builder, {
|
||||||
|
get(target, prop) {
|
||||||
|
if (prop in target) return target[prop];
|
||||||
|
if (target._dom_instr.includes(prop)) {
|
||||||
|
return (...args) => {
|
||||||
|
return target.handle_instruction(prop, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (target._condition_instr.includes(prop)) {
|
||||||
|
return (...args) => {
|
||||||
|
return target.handle_condition(prop, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (target._each_instr.includes(prop)) {
|
||||||
|
return (...args) => {
|
||||||
|
return target.handle_each(prop, ...args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (typeof prop === 'symbol') return undefined;
|
||||||
|
if (EVENT_ALIASES[prop]) {
|
||||||
|
const eventName = EVENT_ALIASES[prop];
|
||||||
|
return (handler) => target.on(eventName, handler);
|
||||||
|
}
|
||||||
|
const event = prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
||||||
|
return (handler) => target.on(event, handler);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
builder._proxy = proxy;
|
||||||
|
|
||||||
|
return proxy;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user