Загрузить файлы в «/»

This commit is contained in:
svsptech 2026-05-18 16:07:00 +05:00
parent 83b36f56df
commit 6b5296f533

523
dom.js Normal file
View 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;
}