Last active
September 3, 2021 23:09
-
-
Save Patrolin/9f86e8dea825f6bd432f03fb1b732429 to your computer and use it in GitHub Desktop.
qPact.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const QPACT_VERSION = '4.6'; | |
function qDescribe(input) { | |
return `${ | |
input != null ? `<${input.constructor.name}>` : '' | |
}${input}`; | |
} | |
// Q | |
function q(input) { | |
if(input == null) return null; | |
switch(input.constructor){ | |
case String: | |
var template = document.createElement('template'); | |
if(!input) return template.content; | |
template.innerHTML = input; | |
var children = template.content.childNodes; | |
return children.length > 1 ? [...children] : children[0]; | |
case Array: | |
return input.map((e) => q(e)); | |
case RegExp: | |
return document.q(input); | |
default: | |
if(input instanceof Node) return input; | |
throw TypeError( | |
`input expected type (RegExp | string | Node | Array | null), found ${ | |
qDescribe(input) | |
}` | |
); | |
} | |
} | |
Node.prototype.Q = function (input) { | |
var first; | |
while ((first = this.firstChild) !== null) | |
this.removeChild(first); | |
this.q(input); | |
}; | |
Node.prototype.q = function (input) { | |
if(input == null) return null; | |
switch(input.constructor){ | |
case RegExp: | |
return input.global | |
? [...this.querySelectorAll(input.source, input)] | |
: this.querySelector(input.source, input); | |
default: | |
var x = q(input); | |
if(x instanceof Node) | |
this.appendChild(x); | |
else if(x.constructor === Array) | |
for(var e of x) | |
this.appendChild(e); | |
return x; | |
} | |
}; | |
Array.prototype.q = function (input) { | |
if (!(input instanceof RegExp)) | |
throw TypeError(`input expected type RegExp, found ${qDescribe(input)}`); | |
return input.global | |
? [...this.querySelectorAll(input.source, input)] | |
: this.querySelector(input.source, input); | |
} | |
Array.prototype.querySelector = function (source, input) { | |
var x = null; | |
for(var e of this){ | |
if(e instanceof Node){ | |
if(e.matches(source)) | |
x = e; | |
} | |
else if(e.constructor === Array) | |
x = e.querySelector(source, input); | |
else | |
throw TypeError(`e expected type (Node | Array), found ${qDescribe(x)}`); | |
if(x != null) break; | |
} | |
return x; | |
} | |
Array.prototype.querySelectorAll = function (source, input) { | |
var x = []; | |
for(var e of this){ | |
if(e instanceof Node){ | |
if(e.matches(source)) | |
x.push(e); | |
} | |
else if(e.constructor === Array) | |
x.push(e.querySelectorAll(source, input)); | |
else | |
throw TypeError(`e expected type (Node | Array), found ${qDescribe(x)}`); | |
} | |
return x; | |
} | |
Text.prototype.matches = function () { | |
return false; | |
} | |
// Pact | |
var pactComponents = {}; | |
function defineComponent(name, Class) { | |
try { | |
document.querySelector(name); | |
} catch (e) { | |
throw SyntaxError(`${name} is not a valid css selector`); | |
} | |
Class.events = getEvents(Class); | |
pactComponents[name] = Class; | |
} | |
function getEvents(Class, prefix='on'){ | |
var ClassEvents = new Set(); | |
var Current = Class; | |
for(; Current.name; Current = Object.getPrototypeOf(Current)) { | |
var Prototype = Current.prototype; | |
for (var key of Reflect.ownKeys(Prototype)) { | |
var descriptor = Object.getOwnPropertyDescriptor(Prototype, key); | |
if ( | |
Reflect.has(descriptor, 'value') | |
&& (key.constructor === String) | |
&& key.startsWith(prefix) | |
) | |
ClassEvents.add(key.slice(prefix.length)); | |
} | |
} | |
return ClassEvents; | |
} | |
function* pactWalk(e) { | |
// yield elements bottom-up | |
for(var c of e.children || []) | |
for(var d of pactWalk(c)) | |
yield d; | |
yield e; | |
} | |
function pactInit(root){ | |
var tags = Object.keys(pactComponents).reverse(); | |
for(var e of pactWalk(root)){ | |
if(!e.c) { | |
var tag = tags.find(t => | |
(e.matches && t.constructor === String) ? e.matches(t) : e === t | |
); | |
var Class = tag ? pactComponents[tag] : Component; | |
Class.init(e); | |
} | |
if(!e.c.loaded) { | |
e.c.load && e.c.load(); | |
e.c.loaded = true; | |
} | |
} | |
} | |
function pactUpdate(mutationRecords){ | |
for(var mr of mutationRecords){ | |
for(var e of mr.addedNodes) | |
pactInit(e); | |
for(var f of mr.removedNodes) | |
if(f.c){ | |
f.c.unload && f.c.unload(); | |
f.c.loaded = false; | |
} | |
} | |
} | |
document.addEventListener('DOMContentLoaded', () => { | |
pactInit(document.documentElement); | |
var observer = new MutationObserver(pactUpdate); | |
observer.observe(document.documentElement, { | |
subtree: true, | |
childList: true, /* includes Text changes because js is stupid */ | |
}); | |
}); | |
// Components | |
class Component { | |
static init(e){ | |
var self = new this; | |
self.e = e; | |
e.c = self; | |
for (var k of this.events) | |
e.addEventListener(k, self[`on${k}`].bind(self)); | |
} | |
alter(){ | |
this.e.dispatchEvent( | |
new CustomEvent('alter', { | |
bubbles: true, | |
cancelable: true, | |
}) | |
); | |
} | |
get disabled(){ | |
return this.e.hasAttribute('disabled'); | |
} | |
set disabled(value){ | |
if(value) | |
this.e.setAttribute('disabled', ''); | |
else | |
this.e.removeAttribute('disabled'); | |
for(var e of this.e.children) | |
e.c.disabled = value; | |
} | |
} | |
defineComponent('*', Component); | |
class InputComponent extends Component { | |
oninput(ev){ | |
this.alter(); | |
} | |
} | |
defineComponent('input', InputComponent); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment