45 lines
3.8 KiB
JavaScript
45 lines
3.8 KiB
JavaScript
// A minimal DOM for mounting the workspace viewer and inspector in Node: elements with
|
|
// children, attributes, listeners (fire() dispatches), dialogs and media methods.
|
|
export function mountedDom() {
|
|
class Node {
|
|
constructor(tag) {this.tagName=tag;this.children=[];this.dataset={};this.attributes={};this.listeners=new Map();this._text='';this.classList={add(){},remove(){}};}
|
|
get textContent(){return this._text+this.children.map(c=>c.textContent).join('');}
|
|
set textContent(value){this._text=String(value);this.children=[];}
|
|
get childNodes(){return this.children;}
|
|
get isConnected(){return this.root || !!this.parent?.isConnected;}
|
|
// As in the DOM, a string argument becomes a text node.
|
|
static wrap(node){if(typeof node!=='string')return node;const text=new Node('#text');text.textContent=node;return text;}
|
|
append(...nodes){for(const node of nodes.map(Node.wrap)){node.parent=this;this.children.push(node);}}
|
|
insertBefore(node,before){node.parent=this;const index=this.children.indexOf(before);this.children.splice(index<0?this.children.length:index,0,node);return node;}
|
|
prepend(...nodes){nodes=nodes.map(Node.wrap);for(const node of nodes)node.parent=this;this.children.unshift(...nodes);}
|
|
replaceChildren(...nodes){for(const child of this.children)child.parent=null;this.children=[];this._text='';this.append(...nodes);}
|
|
remove(){if(this.parent)this.parent.children=this.parent.children.filter(c=>c!==this);this.parent=null;}
|
|
setAttribute(key,value){this.attributes[key]=String(value);}
|
|
getAttribute(key){return this.attributes[key]??null;}
|
|
removeAttribute(key){delete this.attributes[key];}
|
|
addEventListener(type,callback,options={}){const list=this.listeners.get(type)||[];list.push({callback,signal:options.signal});this.listeners.set(type,list);}
|
|
removeEventListener(type,callback){this.listeners.set(type,(this.listeners.get(type)||[]).filter(e=>e.callback!==callback));}
|
|
fire(type,event={}){for(const e of [...(this.listeners.get(type)||[])])if(!e.signal?.aborted)e.callback({target:this,preventDefault(){},...event});}
|
|
focus(){this.focused=true;}
|
|
click(){this.fire('click');}
|
|
getClientRects(){return this.isConnected&&!this.layoutHidden?[{}]:[];}
|
|
showModal(){this.open=true;}
|
|
close(){this.open=false;this.fire('close');}
|
|
contains(node){return node===this||this.children.some(c=>c.contains(node));}
|
|
closest(selector){if(selector==='section'&&this.tagName==='section')return this;if(selector==='[data-send-role]'&&this.dataset.sendRole)return this;if(selector==='[data-copy-role]'&&this.dataset.copyRole)return this;return this.parent?.closest(selector)||null;}
|
|
querySelector(selector){return walk(this,n=>n.tagName===selector)[0]||null;}
|
|
querySelectorAll(selector){return walk(this,n=>n.tagName===selector);}
|
|
pause(){this.paused=true;}
|
|
load(){this.loaded=true;}
|
|
play(){this.played=true;return Promise.resolve();}
|
|
}
|
|
const walk=(node,predicate)=>[...(predicate(node)?[node]:[]),...node.children.flatMap(child=>walk(child,predicate))];
|
|
const oldDocument=globalThis.document,oldWindow=globalThis.window;
|
|
const container=new Node('main');container.root=true;
|
|
const doc=new Node('document');doc.createElement=tag=>new Node(tag);doc.createElementNS=(_,tag)=>new Node(tag);
|
|
doc.querySelectorAll=()=>walk(container,n=>n.tagName==='dialog'&&n.open);doc.activeElement=new Node('button');doc.activeElement.root=true;
|
|
const win=new Node('window');win.location={href:'http://localhost/prompt_manager/workspace'};win.getSelection=()=>({isCollapsed:true});
|
|
globalThis.document=doc;globalThis.window=win;
|
|
return {container,doc,win,walk,Node,restore(){globalThis.document=oldDocument;globalThis.window=oldWindow;}};
|
|
}
|