mirror of
https://github.com/MercuryWorkshop/dreamlandjs.git
synced 2025-05-16 15:40:01 -04:00
chore: setup and run prettier
This commit is contained in:
parent
ffe095557e
commit
7a131a3e2e
17 changed files with 1418 additions and 1512 deletions
675
src/js.js
675
src/js.js
|
@ -1,17 +1,17 @@
|
|||
import { assert } from "./asserts";
|
||||
import { assert } from './asserts'
|
||||
|
||||
// enables a small terser optimization
|
||||
let document = self.document;
|
||||
let document = self.document
|
||||
|
||||
let Fragment = Symbol();
|
||||
let Fragment = Symbol()
|
||||
|
||||
// We add some extra properties into various objects throughout, better to use symbols and not interfere. this is just a tiny optimization
|
||||
let [USE_MAPFN, TARGET, PROXY, STEPS, LISTENERS, IF] = [, , , , , ,].fill().map(Symbol);
|
||||
|
||||
let [USE_MAPFN, TARGET, PROXY, STEPS, LISTENERS, IF] = [, , , , , ,]
|
||||
.fill()
|
||||
.map(Symbol)
|
||||
|
||||
// whether to return the true value from a stateful object or a "trap" containing the pointer
|
||||
let __use_trap = false;
|
||||
|
||||
let __use_trap = false
|
||||
|
||||
// Say you have some code like
|
||||
//// let state = stateful({
|
||||
|
@ -29,399 +29,418 @@ let __use_trap = false;
|
|||
// - the JSX factory h() is now passed the trap, which essentially contains a set of pointers pointing to the theoretical value of b
|
||||
// - with the setter on the stateful proxy, we can listen to any change in any of the nested layers and call whatever listeners registered
|
||||
// - the result is full intuitive reactivity with minimal overhead
|
||||
Object.defineProperty(window, "use", {
|
||||
get: () => {
|
||||
__use_trap = true;
|
||||
return (ptr, mapping, ...rest) => {
|
||||
if (ptr instanceof Array) return usestr(ptr, mapping, ...rest);
|
||||
assert(isDLPtr(ptr), "a value was passed into use() that was not part of a stateful context");
|
||||
__use_trap = false;
|
||||
if (mapping) ptr[USE_MAPFN] = mapping;
|
||||
return ptr;
|
||||
};
|
||||
}
|
||||
});
|
||||
Object.defineProperty(window, 'use', {
|
||||
get: () => {
|
||||
__use_trap = true
|
||||
return (ptr, mapping, ...rest) => {
|
||||
if (ptr instanceof Array) return usestr(ptr, mapping, ...rest)
|
||||
assert(
|
||||
isDLPtr(ptr),
|
||||
'a value was passed into use() that was not part of a stateful context'
|
||||
)
|
||||
__use_trap = false
|
||||
if (mapping) ptr[USE_MAPFN] = mapping
|
||||
return ptr
|
||||
}
|
||||
},
|
||||
})
|
||||
const usestr = (strings, ...values) => {
|
||||
__use_trap = false;
|
||||
__use_trap = false
|
||||
|
||||
let state = stateful({});
|
||||
const flattened_template = [];
|
||||
for (const i in strings) {
|
||||
flattened_template.push(strings[i]);
|
||||
if (values[i]) {
|
||||
const prop = values[i];
|
||||
let state = stateful({})
|
||||
const flattened_template = []
|
||||
for (const i in strings) {
|
||||
flattened_template.push(strings[i])
|
||||
if (values[i]) {
|
||||
const prop = values[i]
|
||||
|
||||
if (isDLPtr(prop)) {
|
||||
const current_i = flattened_template.length;
|
||||
let oldparsed;
|
||||
handle(prop, (val) => {
|
||||
flattened_template[current_i] = String(val);
|
||||
let parsed = flattened_template.join("");
|
||||
if (parsed != oldparsed)
|
||||
state.string = parsed
|
||||
oldparsed = parsed;
|
||||
});
|
||||
} else {
|
||||
flattened_template.push(String(prop));
|
||||
}
|
||||
if (isDLPtr(prop)) {
|
||||
const current_i = flattened_template.length
|
||||
let oldparsed
|
||||
handle(prop, (val) => {
|
||||
flattened_template[current_i] = String(val)
|
||||
let parsed = flattened_template.join('')
|
||||
if (parsed != oldparsed) state.string = parsed
|
||||
oldparsed = parsed
|
||||
})
|
||||
} else {
|
||||
flattened_template.push(String(prop))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.string = flattened_template.join("");
|
||||
state.string = flattened_template.join('')
|
||||
|
||||
return use(state.string);
|
||||
};
|
||||
Object.assign(window, { isDLPtr, h, stateful, handle, $if, Fragment });
|
||||
return use(state.string)
|
||||
}
|
||||
Object.assign(window, { isDLPtr, h, stateful, handle, $if, Fragment })
|
||||
|
||||
let TRAPS = new Map;
|
||||
let TRAPS = new Map()
|
||||
// This wraps the target in a proxy, doing 2 things:
|
||||
// - whenever a property is accessed, return a "trap" that catches and records accessors
|
||||
// - whenever a property is set, notify the subscribed listeners
|
||||
// This is what makes our "pass-by-reference" magic work
|
||||
function stateful(target, hook) {
|
||||
assert(isobj(target), "stateful() requires an object");
|
||||
target[LISTENERS] = [];
|
||||
target[TARGET] = target;
|
||||
let TOPRIMITIVE = Symbol.toPrimitive;
|
||||
assert(isobj(target), 'stateful() requires an object')
|
||||
target[LISTENERS] = []
|
||||
target[TARGET] = target
|
||||
let TOPRIMITIVE = Symbol.toPrimitive
|
||||
|
||||
let proxy = new Proxy(target, {
|
||||
get(target, property, proxy) {
|
||||
if (__use_trap) {
|
||||
let sym = Symbol();
|
||||
let trap = new Proxy({
|
||||
[TARGET]: target,
|
||||
[PROXY]: proxy,
|
||||
[STEPS]: [property],
|
||||
[TOPRIMITIVE]: _ => sym,
|
||||
}, {
|
||||
get(target, property) {
|
||||
if ([TARGET, PROXY, STEPS, USE_MAPFN, TOPRIMITIVE].includes(property)) return target[property];
|
||||
property = TRAPS.get(property) || property;
|
||||
target[STEPS].push(property);
|
||||
return trap;
|
||||
}
|
||||
});
|
||||
TRAPS.set(sym, trap);
|
||||
let proxy = new Proxy(target, {
|
||||
get(target, property, proxy) {
|
||||
if (__use_trap) {
|
||||
let sym = Symbol()
|
||||
let trap = new Proxy(
|
||||
{
|
||||
[TARGET]: target,
|
||||
[PROXY]: proxy,
|
||||
[STEPS]: [property],
|
||||
[TOPRIMITIVE]: (_) => sym,
|
||||
},
|
||||
{
|
||||
get(target, property) {
|
||||
if (
|
||||
[
|
||||
TARGET,
|
||||
PROXY,
|
||||
STEPS,
|
||||
USE_MAPFN,
|
||||
TOPRIMITIVE,
|
||||
].includes(property)
|
||||
)
|
||||
return target[property]
|
||||
property = TRAPS.get(property) || property
|
||||
target[STEPS].push(property)
|
||||
return trap
|
||||
},
|
||||
}
|
||||
)
|
||||
TRAPS.set(sym, trap)
|
||||
|
||||
return trap;
|
||||
}
|
||||
return Reflect.get(target, property, proxy);
|
||||
},
|
||||
set(target, property, val) {
|
||||
if (hook) hook(target, property, val);
|
||||
let trap = Reflect.set(target, property, val);
|
||||
for (let listener of target[LISTENERS]) {
|
||||
listener(target, property, val);
|
||||
}
|
||||
return trap;
|
||||
},
|
||||
});
|
||||
return trap
|
||||
}
|
||||
return Reflect.get(target, property, proxy)
|
||||
},
|
||||
set(target, property, val) {
|
||||
if (hook) hook(target, property, val)
|
||||
let trap = Reflect.set(target, property, val)
|
||||
for (let listener of target[LISTENERS]) {
|
||||
listener(target, property, val)
|
||||
}
|
||||
return trap
|
||||
},
|
||||
})
|
||||
|
||||
return proxy;
|
||||
return proxy
|
||||
}
|
||||
|
||||
let isobj = (o) => o instanceof Object;
|
||||
let isfn = (o) => typeof o === "function";
|
||||
let isobj = (o) => o instanceof Object
|
||||
let isfn = (o) => typeof o === 'function'
|
||||
function isDLPtr(arr) {
|
||||
return isobj(arr) && TARGET in arr
|
||||
return isobj(arr) && TARGET in arr
|
||||
}
|
||||
|
||||
function $if(condition, then, otherwise) {
|
||||
otherwise ??= document.createTextNode("");
|
||||
if (!isDLPtr(condition)) return condition ? then : otherwise;
|
||||
otherwise ??= document.createTextNode('')
|
||||
if (!isDLPtr(condition)) return condition ? then : otherwise
|
||||
|
||||
return { [IF]: condition, then, otherwise };
|
||||
return { [IF]: condition, then, otherwise }
|
||||
}
|
||||
|
||||
// This lets you subscribe to a stateful object
|
||||
function handle(ptr, callback) {
|
||||
assert(isDLPtr(ptr), "handle() requires a stateful object");
|
||||
assert(isfn(callback), "handle() requires a callback function");
|
||||
let step, resolvedSteps = [];
|
||||
assert(isDLPtr(ptr), 'handle() requires a stateful object')
|
||||
assert(isfn(callback), 'handle() requires a callback function')
|
||||
let step,
|
||||
resolvedSteps = []
|
||||
|
||||
function update() {
|
||||
let val = ptr[TARGET];
|
||||
for (step of resolvedSteps) {
|
||||
val = val[step];
|
||||
if (!isobj(val)) break;
|
||||
}
|
||||
|
||||
let mapfn = ptr[USE_MAPFN];
|
||||
if (mapfn) val = mapfn(val);
|
||||
callback(val);
|
||||
}
|
||||
|
||||
// inject ourselves into nested objects
|
||||
let curry = (target, i) => function subscription(tgt, prop, val) {
|
||||
if (prop === resolvedSteps[i] && target === tgt) {
|
||||
update();
|
||||
|
||||
if (isobj(val)) {
|
||||
let v = val[LISTENERS];
|
||||
if (v && !v.includes(subscription)) {
|
||||
v.push(curry(val[TARGET], i + 1));
|
||||
function update() {
|
||||
let val = ptr[TARGET]
|
||||
for (step of resolvedSteps) {
|
||||
val = val[step]
|
||||
if (!isobj(val)) break
|
||||
}
|
||||
}
|
||||
|
||||
let mapfn = ptr[USE_MAPFN]
|
||||
if (mapfn) val = mapfn(val)
|
||||
callback(val)
|
||||
}
|
||||
};
|
||||
|
||||
// inject ourselves into nested objects
|
||||
let curry = (target, i) =>
|
||||
function subscription(tgt, prop, val) {
|
||||
if (prop === resolvedSteps[i] && target === tgt) {
|
||||
update()
|
||||
|
||||
// imagine we have a `use(state.a[state.b])`
|
||||
// simply recursively resolve any of the intermediate steps until we get to the final value
|
||||
// this will "misfire" occassionaly with a scenario like state.a[state.b][state.c] and call the listener more than needed
|
||||
// it is up to the caller to not implode
|
||||
for (let i in ptr[STEPS]) {
|
||||
let step = ptr[STEPS][i];
|
||||
if (isobj(step) && step[TARGET]) {
|
||||
handle(step, val => {
|
||||
resolvedSteps[i] = val;
|
||||
update();
|
||||
});
|
||||
continue;
|
||||
if (isobj(val)) {
|
||||
let v = val[LISTENERS]
|
||||
if (v && !v.includes(subscription)) {
|
||||
v.push(curry(val[TARGET], i + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// imagine we have a `use(state.a[state.b])`
|
||||
// simply recursively resolve any of the intermediate steps until we get to the final value
|
||||
// this will "misfire" occassionaly with a scenario like state.a[state.b][state.c] and call the listener more than needed
|
||||
// it is up to the caller to not implode
|
||||
for (let i in ptr[STEPS]) {
|
||||
let step = ptr[STEPS][i]
|
||||
if (isobj(step) && step[TARGET]) {
|
||||
handle(step, (val) => {
|
||||
resolvedSteps[i] = val
|
||||
update()
|
||||
})
|
||||
continue
|
||||
}
|
||||
resolvedSteps[i] = step
|
||||
}
|
||||
resolvedSteps[i] = step;
|
||||
}
|
||||
|
||||
let sub = curry(ptr[TARGET], 0);
|
||||
ptr[TARGET][LISTENERS].push(sub);
|
||||
let sub = curry(ptr[TARGET], 0)
|
||||
ptr[TARGET][LISTENERS].push(sub)
|
||||
|
||||
sub(ptr[TARGET], resolvedSteps[0], ptr[TARGET][resolvedSteps[0]]);
|
||||
sub(ptr[TARGET], resolvedSteps[0], ptr[TARGET][resolvedSteps[0]])
|
||||
}
|
||||
|
||||
function JSXAddFixedWrapper(ptr, cb, $if) {
|
||||
let before, appended, first, flag;
|
||||
handle(ptr, val => {
|
||||
first = appended?.[0];
|
||||
if (first)
|
||||
before = first.previousSibling || (flag = first.parentNode);
|
||||
if (appended)
|
||||
appended.forEach(a => a.remove());
|
||||
let before, appended, first, flag
|
||||
handle(ptr, (val) => {
|
||||
first = appended?.[0]
|
||||
if (first) before = first.previousSibling || (flag = first.parentNode)
|
||||
if (appended) appended.forEach((a) => a.remove())
|
||||
|
||||
appended = JSXAddChild($if ? (val ? $if.then : $if.otherwise) : val, el => {
|
||||
if (before) {
|
||||
if (flag) {
|
||||
before.prepend(el)
|
||||
flag = null;
|
||||
}
|
||||
else before.after(el);
|
||||
before = el;
|
||||
}
|
||||
else cb(el)
|
||||
appended = JSXAddChild(
|
||||
$if ? (val ? $if.then : $if.otherwise) : val,
|
||||
(el) => {
|
||||
if (before) {
|
||||
if (flag) {
|
||||
before.prepend(el)
|
||||
flag = null
|
||||
} else before.after(el)
|
||||
before = el
|
||||
} else cb(el)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// returns a function that sets a reference
|
||||
// the currying is a small optimization
|
||||
let curryset = ptr => val => {
|
||||
let next = ptr[PROXY];
|
||||
let steps = ptr[STEPS];
|
||||
let i = 0;
|
||||
for (; i < steps.length - 1; i++) {
|
||||
next = next[steps[i]];
|
||||
if (!isobj(next)) return;
|
||||
}
|
||||
next[steps[i]] = val;
|
||||
let curryset = (ptr) => (val) => {
|
||||
let next = ptr[PROXY]
|
||||
let steps = ptr[STEPS]
|
||||
let i = 0
|
||||
for (; i < steps.length - 1; i++) {
|
||||
next = next[steps[i]]
|
||||
if (!isobj(next)) return
|
||||
}
|
||||
next[steps[i]] = val
|
||||
}
|
||||
|
||||
// Actual JSX factory. Responsible for creating the HTML elements and all of the *reactive* syntactic sugar
|
||||
function h(type, props, ...children) {
|
||||
if (type == Fragment) return children;
|
||||
if (typeof type == "function") {
|
||||
// functional components. create the stateful object
|
||||
let newthis = stateful(Object.create(type.prototype));
|
||||
if (type == Fragment) return children
|
||||
if (typeof type == 'function') {
|
||||
// functional components. create the stateful object
|
||||
let newthis = stateful(Object.create(type.prototype))
|
||||
|
||||
for (let name in props) {
|
||||
let ptr = props[name]
|
||||
if (name.startsWith('bind:')) {
|
||||
assert(
|
||||
isDLPtr(ptr),
|
||||
'bind: requires a reference pointer from use'
|
||||
)
|
||||
|
||||
let set = curryset(ptr)
|
||||
let propname = name.substring(5)
|
||||
if (propname == 'this') {
|
||||
set(newthis)
|
||||
} else {
|
||||
// component two way data binding!! (exact same behavior as svelte:bind)
|
||||
let isRecursive = false
|
||||
|
||||
handle(ptr, (value) => {
|
||||
if (isRecursive) {
|
||||
isRecursive = false
|
||||
return
|
||||
}
|
||||
isRecursive = true
|
||||
newthis[propname] = value
|
||||
})
|
||||
handle(use(newthis[propname]), (value) => {
|
||||
if (isRecursive) {
|
||||
isRecursive = false
|
||||
return
|
||||
}
|
||||
isRecursive = true
|
||||
set(value)
|
||||
})
|
||||
}
|
||||
delete props[name]
|
||||
}
|
||||
}
|
||||
Object.assign(newthis, props)
|
||||
|
||||
newthis.children = []
|
||||
for (let child of children) {
|
||||
JSXAddChild(child, newthis.children.push.bind(newthis.children))
|
||||
}
|
||||
|
||||
let elm = type.apply(newthis)
|
||||
elm.$ = newthis
|
||||
newthis.root = elm
|
||||
if (newthis.css) {
|
||||
let cl = elm.classList
|
||||
cl.add(newthis.css)
|
||||
cl.add('self')
|
||||
}
|
||||
elm.setAttribute('data-component', type.name)
|
||||
if (typeof newthis.mount === 'function') newthis.mount()
|
||||
return elm
|
||||
}
|
||||
|
||||
let xmlns = props?.xmlns
|
||||
let elm = xmlns
|
||||
? document.createElementNS(xmlns, type)
|
||||
: document.createElement(type)
|
||||
|
||||
for (let child of children) {
|
||||
let cond = child && !isDLPtr(child) && child[IF]
|
||||
let bappend = elm.append.bind(elm)
|
||||
if (cond) {
|
||||
JSXAddFixedWrapper(cond, bappend, child)
|
||||
} else JSXAddChild(child, bappend)
|
||||
}
|
||||
|
||||
if (!props) return elm
|
||||
|
||||
let useProp = (name, callback) => {
|
||||
if (!(name in props)) return
|
||||
let prop = props[name]
|
||||
callback(prop)
|
||||
delete props[name]
|
||||
}
|
||||
|
||||
for (let name in props) {
|
||||
let ptr = props[name];
|
||||
if (name.startsWith("bind:")) {
|
||||
assert(isDLPtr(ptr), "bind: requires a reference pointer from use");
|
||||
let ptr = props[name]
|
||||
if (name.startsWith('bind:')) {
|
||||
assert(isDLPtr(ptr), 'bind: requires a reference pointer from use')
|
||||
let propname = name.substring(5)
|
||||
|
||||
let set = curryset(ptr);
|
||||
let propname = name.substring(5);
|
||||
if (propname == "this") {
|
||||
set(newthis);
|
||||
} else {
|
||||
// component two way data binding!! (exact same behavior as svelte:bind)
|
||||
let isRecursive = false;
|
||||
|
||||
handle(ptr, value => {
|
||||
if (isRecursive) {
|
||||
isRecursive = false;
|
||||
return;
|
||||
// create the function to set the value of the pointer
|
||||
let set = curryset(ptr)
|
||||
if (propname == 'this') {
|
||||
set(elm)
|
||||
} else if (propname == 'value') {
|
||||
handle(ptr, (value) => (elm.value = value))
|
||||
elm.addEventListener('change', () => set(elm.value))
|
||||
} else if (propname == 'checked') {
|
||||
handle(ptr, (value) => (elm.checked = value))
|
||||
elm.addEventListener('click', () => set(elm.checked))
|
||||
}
|
||||
isRecursive = true;
|
||||
newthis[propname] = value
|
||||
});
|
||||
handle(use(newthis[propname]), value => {
|
||||
if (isRecursive) {
|
||||
isRecursive = false;
|
||||
return;
|
||||
}
|
||||
isRecursive = true;
|
||||
set(value);
|
||||
});
|
||||
delete props[name]
|
||||
}
|
||||
if (name == 'style' && isobj(ptr)) {
|
||||
for (let key in ptr) {
|
||||
let prop = ptr[key]
|
||||
if (isDLPtr(prop)) {
|
||||
handle(prop, (value) => (elm.style[key] = value))
|
||||
} else {
|
||||
elm.style[key] = prop
|
||||
}
|
||||
}
|
||||
delete props[name]
|
||||
}
|
||||
delete props[name];
|
||||
}
|
||||
}
|
||||
Object.assign(newthis, props);
|
||||
|
||||
newthis.children = [];
|
||||
for (let child of children) {
|
||||
JSXAddChild(child, newthis.children.push.bind(newthis.children));
|
||||
}
|
||||
|
||||
let elm = type.apply(newthis);
|
||||
elm.$ = newthis;
|
||||
newthis.root = elm;
|
||||
if (newthis.css) {
|
||||
let cl = elm.classList
|
||||
cl.add(newthis.css);
|
||||
cl.add("self");
|
||||
}
|
||||
elm.setAttribute("data-component", type.name);
|
||||
if (typeof newthis.mount === "function")
|
||||
newthis.mount();
|
||||
return elm;
|
||||
}
|
||||
useProp('class', (classlist) => {
|
||||
assert(
|
||||
typeof classlist === 'string' || classlist instanceof Array,
|
||||
'class must be a string or array'
|
||||
)
|
||||
if (typeof classlist === 'string') {
|
||||
elm.setAttribute('class', classlist)
|
||||
return
|
||||
}
|
||||
|
||||
if (isDLPtr(classlist)) {
|
||||
handle(classlist, (classname) =>
|
||||
elm.setAttribute('class', classname)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let xmlns = props?.xmlns;
|
||||
let elm = xmlns ? document.createElementNS(xmlns, type) : document.createElement(type);
|
||||
for (let name of classlist) {
|
||||
if (isDLPtr(name)) {
|
||||
let oldvalue = null
|
||||
handle(name, (value) => {
|
||||
if (typeof oldvalue === 'string') {
|
||||
elm.classList.remove(oldvalue)
|
||||
}
|
||||
elm.classList.add(value)
|
||||
oldvalue = value
|
||||
})
|
||||
} else {
|
||||
elm.classList.add(name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
for (let child of children) {
|
||||
let cond = child && !isDLPtr(child) && child[IF];
|
||||
let bappend = elm.append.bind(elm);
|
||||
if (cond) {
|
||||
JSXAddFixedWrapper(cond, bappend, child);
|
||||
} else
|
||||
JSXAddChild(child, bappend);
|
||||
}
|
||||
|
||||
if (!props) return elm;
|
||||
|
||||
let useProp = (name, callback) => {
|
||||
if (!(name in props)) return;
|
||||
let prop = props[name];
|
||||
callback(prop);
|
||||
delete props[name];
|
||||
}
|
||||
|
||||
for (let name in props) {
|
||||
let ptr = props[name];
|
||||
if (name.startsWith("bind:")) {
|
||||
assert(isDLPtr(ptr), "bind: requires a reference pointer from use");
|
||||
let propname = name.substring(5);
|
||||
|
||||
// create the function to set the value of the pointer
|
||||
let set = curryset(ptr);
|
||||
if (propname == "this") {
|
||||
set(elm);
|
||||
} else if (propname == "value") {
|
||||
handle(ptr, value => elm.value = value);
|
||||
elm.addEventListener("change", () => set(elm.value))
|
||||
} else if (propname == "checked") {
|
||||
handle(ptr, value => elm.checked = value);
|
||||
elm.addEventListener("click", () => set(elm.checked))
|
||||
}
|
||||
delete props[name];
|
||||
}
|
||||
if (name == "style" && isobj(ptr)) {
|
||||
for (let key in ptr) {
|
||||
let prop = ptr[key];
|
||||
// apply the non-reactive properties
|
||||
for (let name in props) {
|
||||
let prop = props[name]
|
||||
if (isDLPtr(prop)) {
|
||||
handle(prop, value => elm.style[key] = value);
|
||||
handle(prop, (val) => {
|
||||
JSXAddAttributes(elm, name, val)
|
||||
})
|
||||
} else {
|
||||
elm.style[key] = prop;
|
||||
JSXAddAttributes(elm, name, prop)
|
||||
}
|
||||
}
|
||||
delete props[name];
|
||||
}
|
||||
}
|
||||
|
||||
useProp("class", classlist => {
|
||||
assert(typeof classlist === "string" || classlist instanceof Array, "class must be a string or array");
|
||||
if (typeof classlist === "string") {
|
||||
elm.setAttribute("class", classlist);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDLPtr(classlist)) {
|
||||
handle(classlist, classname => elm.setAttribute("class", classname));
|
||||
return;
|
||||
}
|
||||
// hack to fix svgs
|
||||
if (xmlns) elm.innerHTML = elm.innerHTML
|
||||
|
||||
for (let name of classlist) {
|
||||
if (isDLPtr(name)) {
|
||||
let oldvalue = null;
|
||||
handle(name, value => {
|
||||
if (typeof oldvalue === "string") {
|
||||
elm.classList.remove(oldvalue);
|
||||
}
|
||||
elm.classList.add(value);
|
||||
oldvalue = value;
|
||||
});
|
||||
} else {
|
||||
elm.classList.add(name);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// apply the non-reactive properties
|
||||
for (let name in props) {
|
||||
let prop = props[name];
|
||||
if (isDLPtr(prop)) {
|
||||
handle(prop, (val) => {
|
||||
JSXAddAttributes(elm, name, val);
|
||||
});
|
||||
} else {
|
||||
JSXAddAttributes(elm, name, prop);
|
||||
}
|
||||
}
|
||||
|
||||
// hack to fix svgs
|
||||
if (xmlns)
|
||||
elm.innerHTML = elm.innerHTML
|
||||
|
||||
return elm;
|
||||
return elm
|
||||
}
|
||||
|
||||
// glue for nested children
|
||||
function JSXAddChild(child, cb) {
|
||||
let childchild, elms, node;
|
||||
if (isDLPtr(child)) {
|
||||
JSXAddFixedWrapper(child, cb);
|
||||
} else if (child instanceof Node) {
|
||||
cb(child);
|
||||
return [child];
|
||||
} else if (child instanceof Array) {
|
||||
elms = [];
|
||||
for (childchild of child) {
|
||||
elms = elms.concat(JSXAddChild(childchild, cb));
|
||||
let childchild, elms, node
|
||||
if (isDLPtr(child)) {
|
||||
JSXAddFixedWrapper(child, cb)
|
||||
} else if (child instanceof Node) {
|
||||
cb(child)
|
||||
return [child]
|
||||
} else if (child instanceof Array) {
|
||||
elms = []
|
||||
for (childchild of child) {
|
||||
elms = elms.concat(JSXAddChild(childchild, cb))
|
||||
}
|
||||
if (!elms[0]) elms = JSXAddChild('', cb)
|
||||
return elms
|
||||
} else {
|
||||
node = document.createTextNode(child)
|
||||
cb(node)
|
||||
return [node]
|
||||
}
|
||||
if (!elms[0]) elms = JSXAddChild("", cb);
|
||||
return elms;
|
||||
} else {
|
||||
node = document.createTextNode(child);
|
||||
cb(node);
|
||||
return [node];
|
||||
}
|
||||
}
|
||||
|
||||
// Where properties are assigned to elements, and where the *non-reactive* syntax sugar goes
|
||||
function JSXAddAttributes(elm, name, prop) {
|
||||
if (name.startsWith("on:")) {
|
||||
assert(typeof prop === "function", "on: requires a function");
|
||||
let names = name.substring(3);
|
||||
for (let name of names.split("$")) {
|
||||
elm.addEventListener(name, (...args) => {
|
||||
self.$el = elm;
|
||||
prop(...args);
|
||||
});
|
||||
if (name.startsWith('on:')) {
|
||||
assert(typeof prop === 'function', 'on: requires a function')
|
||||
let names = name.substring(3)
|
||||
for (let name of names.split('$')) {
|
||||
elm.addEventListener(name, (...args) => {
|
||||
self.$el = elm
|
||||
prop(...args)
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
elm.setAttribute(name, prop);
|
||||
elm.setAttribute(name, prop)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue