sprae 8.1.3 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core.js +127 -0
- package/directive/aria.js +10 -0
- package/directive/class.js +17 -0
- package/directive/data.js +10 -0
- package/directive/default.js +148 -0
- package/directive/each.js +64 -0
- package/directive/fx.js +6 -0
- package/directive/html.js +11 -0
- package/directive/if.js +40 -0
- package/directive/ref.js +10 -0
- package/directive/scope.js +11 -0
- package/directive/style.js +16 -0
- package/directive/text.js +12 -0
- package/directive/value.js +31 -0
- package/dist/sprae.js +633 -861
- package/dist/sprae.min.js +1 -1
- package/package.json +18 -13
- package/readme.md +373 -210
- package/sprae.js +14 -0
- package/dist/sprae.auto.js +0 -976
- package/dist/sprae.auto.min.js +0 -1
- package/src/core.js +0 -82
- package/src/directives.js +0 -447
- package/src/domdiff.js +0 -71
- package/src/index.js +0 -6
- package/src/state.proxy.js +0 -150
- package/src/state.signals-proxy.js +0 -151
- package/src/state.signals.js +0 -82
- package/src/util.js +0 -13
package/core.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import swapdom from 'swapdom'
|
|
2
|
+
import * as signals from 'ulive'
|
|
3
|
+
import justin from 'subscript/justin.js'
|
|
4
|
+
|
|
5
|
+
// polyfill
|
|
6
|
+
const _dispose = (Symbol.dispose ||= Symbol("dispose"));
|
|
7
|
+
|
|
8
|
+
// mark
|
|
9
|
+
const SPRAE = `∴`
|
|
10
|
+
|
|
11
|
+
// signals impl
|
|
12
|
+
export let { signal, effect, batch, computed, untracked } = signals;
|
|
13
|
+
|
|
14
|
+
// reserved directives - order matters!
|
|
15
|
+
export const directive = {};
|
|
16
|
+
|
|
17
|
+
// sprae element: apply directives
|
|
18
|
+
const memo = new WeakMap();
|
|
19
|
+
export default function sprae(container, values) {
|
|
20
|
+
if (!container.children) return // text nodes, comments etc
|
|
21
|
+
|
|
22
|
+
// repeated call can be caused by :each with new objects with old keys needs an update
|
|
23
|
+
if (memo.has(container)) {
|
|
24
|
+
const [state, effects] = memo.get(container)
|
|
25
|
+
// we rewrite signals instead of update, because user should have what he provided
|
|
26
|
+
// console.log(container, state, values)
|
|
27
|
+
for (let k in values) state[k] = values[k]
|
|
28
|
+
for (let fx of effects) fx()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// take over existing state instead of creating clone
|
|
32
|
+
const state = values || {};
|
|
33
|
+
const effects = [];
|
|
34
|
+
|
|
35
|
+
// init directives on element
|
|
36
|
+
const init = (el, parent = el.parentNode) => {
|
|
37
|
+
if (el.attributes) {
|
|
38
|
+
// init generic-name attributes second
|
|
39
|
+
for (let i = 0; i < el.attributes.length;) {
|
|
40
|
+
let attr = el.attributes[i];
|
|
41
|
+
|
|
42
|
+
if (attr.name[0] === ':') {
|
|
43
|
+
el.removeAttribute(attr.name);
|
|
44
|
+
|
|
45
|
+
// multiple attributes like :id:for=""
|
|
46
|
+
let names = attr.name.slice(1).split(':')
|
|
47
|
+
|
|
48
|
+
// NOTE: secondary directives don't stop flow nor extend state, so no need to check
|
|
49
|
+
for (let name of names) {
|
|
50
|
+
let update = (directive[name] || directive.default)(el, attr.value, state, name);
|
|
51
|
+
if (update) {
|
|
52
|
+
update[_dispose] = effect(update);
|
|
53
|
+
effects.push(update);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// stop if element was spraed by directive or skipped (detached) like in case of :if or :each
|
|
58
|
+
if (memo.has(el)) return;
|
|
59
|
+
if (el.parentNode !== parent) return false;
|
|
60
|
+
} else i++;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (let i = 0, child; child = el.children[i]; i++) {
|
|
65
|
+
// if element was removed from parent (skipped) - reduce index
|
|
66
|
+
if (init(child, el) === false) i--;
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
init(container);
|
|
71
|
+
|
|
72
|
+
// if element was spraed by :scope or :each instruction - skip
|
|
73
|
+
if (memo.has(container)) return state// memo.get(container)
|
|
74
|
+
|
|
75
|
+
// save
|
|
76
|
+
memo.set(container, [state, effects]);
|
|
77
|
+
container.classList?.add(SPRAE); // mark spraed element
|
|
78
|
+
|
|
79
|
+
// expose dispose
|
|
80
|
+
container[_dispose] = () => {
|
|
81
|
+
while (effects.length) effects.pop()[_dispose]();
|
|
82
|
+
container.classList.remove(SPRAE)
|
|
83
|
+
memo.delete(container);
|
|
84
|
+
let els = container.getElementsByClassName(SPRAE);
|
|
85
|
+
while (els.length) els[0][_dispose]?.()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return state;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// default compiler
|
|
92
|
+
const evalMemo = {};
|
|
93
|
+
|
|
94
|
+
export let compile = (expr, dir, evaluate) => {
|
|
95
|
+
if (evaluate = evalMemo[expr = expr.trim()]) return evaluate
|
|
96
|
+
|
|
97
|
+
// static-time errors
|
|
98
|
+
try {
|
|
99
|
+
// evaluate = new Function(`__scope`, `with (__scope) { return ${expr} };`);
|
|
100
|
+
evaluate = justin(expr);
|
|
101
|
+
}
|
|
102
|
+
catch (e) { throw Object.assign(e, { message: `${SPRAE} ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr }) }
|
|
103
|
+
|
|
104
|
+
// runtime errors
|
|
105
|
+
return evalMemo[expr] = evaluate
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// DOM swapper
|
|
109
|
+
export let swap = swapdom
|
|
110
|
+
|
|
111
|
+
// interpolate a$<b> fields from context
|
|
112
|
+
export const ipol = (v, state) => {
|
|
113
|
+
return v?.replace ? v.replace(/\$<([^>]+)>/g, (match, field) => state[field]?.valueOf?.() ?? '') : v
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// configure signals/compiler/differ
|
|
117
|
+
// it's more compact than using sprae.signal = signal etc.
|
|
118
|
+
sprae.use = s => {
|
|
119
|
+
s.signal && (
|
|
120
|
+
signal = s.signal,
|
|
121
|
+
effect = s.effect,
|
|
122
|
+
computed = s.computed,
|
|
123
|
+
batch = s.batch || (fn => fn()),
|
|
124
|
+
untracked = s.untracked || batch
|
|
125
|
+
);
|
|
126
|
+
s.swap && (swap = s.swap)
|
|
127
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { directive, compile } from "../core.js";
|
|
2
|
+
import { attr, dashcase } from './default.js'
|
|
3
|
+
|
|
4
|
+
directive['aria'] = (el, expr, state) => {
|
|
5
|
+
let evaluate = compile(expr, 'aria')
|
|
6
|
+
const update = (value) => {
|
|
7
|
+
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '');
|
|
8
|
+
}
|
|
9
|
+
return () => update(evaluate(state)?.valueOf())
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { directive, compile, ipol } from "../core.js";
|
|
2
|
+
|
|
3
|
+
directive.class = (el, expr, state) => {
|
|
4
|
+
let evaluate = compile(expr, 'class');
|
|
5
|
+
let cur = new Set
|
|
6
|
+
return () => {
|
|
7
|
+
let v = evaluate(state);
|
|
8
|
+
let clsx = new Set;
|
|
9
|
+
if (v) {
|
|
10
|
+
if (typeof v === "string") ipol(v?.valueOf?.(), state).split(' ').map(cls => clsx.add(cls));
|
|
11
|
+
else if (Array.isArray(v)) v.map(v => (v = ipol(v?.valueOf?.(), state)) && clsx.add(v));
|
|
12
|
+
else Object.entries(v).map(([k, v]) => v?.valueOf?.() && clsx.add(k));
|
|
13
|
+
}
|
|
14
|
+
for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls); else el.classList.remove(cls);
|
|
15
|
+
for (let cls of cur = clsx) el.classList.add(cls)
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { directive, compile, ipol } from "../core.js";
|
|
2
|
+
|
|
3
|
+
// set generic property directive
|
|
4
|
+
directive.default = (el, expr, state, name) => {
|
|
5
|
+
let evt = name.startsWith("on") && name.slice(2);
|
|
6
|
+
let evaluate = compile(expr, name);
|
|
7
|
+
|
|
8
|
+
if (evt) {
|
|
9
|
+
let off
|
|
10
|
+
return () => (
|
|
11
|
+
off?.(), // intermediate teardown
|
|
12
|
+
off = on(el, evt, evaluate(state))
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return () => {
|
|
17
|
+
let value = evaluate(state)?.valueOf();
|
|
18
|
+
if (name) attr(el, name, ipol(value, state))
|
|
19
|
+
else for (let key in value) attr(el, dashcase(key), ipol(value[key], state));
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
// bind event to a target
|
|
25
|
+
const on = (el, e, fn = () => { }) => {
|
|
26
|
+
const ctx = { evt: "", target: el, test: () => true };
|
|
27
|
+
|
|
28
|
+
// onevt.debounce-108 -> evt.debounce-108
|
|
29
|
+
ctx.evt = e.replace(
|
|
30
|
+
/\.(\w+)?-?([-\w]+)?/g,
|
|
31
|
+
(match, mod, param = "") => ((ctx.test = mods[mod]?.(ctx, ...param.split("-")) || ctx.test), ""),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// add listener applying the context
|
|
35
|
+
const { evt, target, test, defer, stop, prevent, ...opts } = ctx;
|
|
36
|
+
|
|
37
|
+
if (defer) fn = defer(fn);
|
|
38
|
+
|
|
39
|
+
const cb = (e) =>
|
|
40
|
+
test(e) && (stop && e.stopPropagation(), prevent && e.preventDefault(), fn.call(target, e));
|
|
41
|
+
|
|
42
|
+
target.addEventListener(evt, cb, opts);
|
|
43
|
+
|
|
44
|
+
// return off
|
|
45
|
+
return () => target.removeEventListener(evt, cb, opts);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// event modifiers
|
|
49
|
+
const mods = {
|
|
50
|
+
// actions
|
|
51
|
+
prevent(ctx) { ctx.prevent = true; },
|
|
52
|
+
stop(ctx) { ctx.stop = true; },
|
|
53
|
+
|
|
54
|
+
// options
|
|
55
|
+
once(ctx) { ctx.once = true; },
|
|
56
|
+
passive(ctx) { ctx.passive = true; },
|
|
57
|
+
capture(ctx) { ctx.capture = true; },
|
|
58
|
+
|
|
59
|
+
// target
|
|
60
|
+
window(ctx) { ctx.target = window; },
|
|
61
|
+
document(ctx) { ctx.target = document; },
|
|
62
|
+
|
|
63
|
+
throttle(ctx, limit) { ctx.defer = (fn) => throttle(fn, limit ? Number(limit) || 0 : 108); },
|
|
64
|
+
debounce(ctx, wait) { ctx.defer = (fn) => debounce(fn, wait ? Number(wait) || 0 : 108); },
|
|
65
|
+
|
|
66
|
+
// test
|
|
67
|
+
outside: (ctx) => (e) => {
|
|
68
|
+
let target = ctx.target;
|
|
69
|
+
if (target.contains(e.target)) return false;
|
|
70
|
+
if (e.target.isConnected === false) return false;
|
|
71
|
+
if (target.offsetWidth < 1 && target.offsetHeight < 1) return false;
|
|
72
|
+
return true;
|
|
73
|
+
},
|
|
74
|
+
self: (ctx) => (e) => e.target === ctx.target,
|
|
75
|
+
|
|
76
|
+
// keyboard
|
|
77
|
+
ctrl: (_, ...param) => (e) => keys.ctrl(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
|
|
78
|
+
shift: (_, ...param) => (e) => keys.shift(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
|
|
79
|
+
alt: (_, ...param) => (e) => keys.alt(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
|
|
80
|
+
meta: (_, ...param) => (e) => keys.meta(e) && param.every((p) => (keys[p] ? keys[p](e) : e.key === p)),
|
|
81
|
+
arrow: () => keys.arrow,
|
|
82
|
+
enter: () => keys.enter,
|
|
83
|
+
escape: () => keys.escape,
|
|
84
|
+
tab: () => keys.tab,
|
|
85
|
+
space: () => keys.space,
|
|
86
|
+
backspace: () => keys.backspace,
|
|
87
|
+
delete: () => keys.delete,
|
|
88
|
+
digit: () => keys.digit,
|
|
89
|
+
letter: () => keys.letter,
|
|
90
|
+
character: () => keys.character,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// key testers
|
|
94
|
+
const keys = {
|
|
95
|
+
ctrl: (e) => e.ctrlKey || e.key === "Control" || e.key === "Ctrl",
|
|
96
|
+
shift: (e) => e.shiftKey || e.key === "Shift",
|
|
97
|
+
alt: (e) => e.altKey || e.key === "Alt",
|
|
98
|
+
meta: (e) => e.metaKey || e.key === "Meta" || e.key === "Command",
|
|
99
|
+
arrow: (e) => e.key.startsWith("Arrow"),
|
|
100
|
+
enter: (e) => e.key === "Enter",
|
|
101
|
+
escape: (e) => e.key.startsWith("Esc"),
|
|
102
|
+
tab: (e) => e.key === "Tab",
|
|
103
|
+
space: (e) => e.key === " " || e.key === "Space" || e.key === " ",
|
|
104
|
+
backspace: (e) => e.key === "Backspace",
|
|
105
|
+
delete: (e) => e.key === "Delete",
|
|
106
|
+
digit: (e) => /^\d$/.test(e.key),
|
|
107
|
+
letter: (e) => /^[a-zA-Z]$/.test(e.key),
|
|
108
|
+
character: (e) => /^\S$/.test(e.key),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// set attr
|
|
112
|
+
export const attr = (el, name, v) => {
|
|
113
|
+
if (v == null || v === false) el.removeAttribute(name);
|
|
114
|
+
else el.setAttribute(name, v === true ? "" : typeof v === "number" || typeof v === "string" ? v : "");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// create delayed fns
|
|
118
|
+
const throttle = (fn, limit) => {
|
|
119
|
+
let pause, planned,
|
|
120
|
+
block = (e) => {
|
|
121
|
+
pause = true;
|
|
122
|
+
setTimeout(() => {
|
|
123
|
+
pause = false;
|
|
124
|
+
// if event happened during blocked time, it schedules call by the end
|
|
125
|
+
if (planned) return (planned = false), block(e), fn(e);
|
|
126
|
+
}, limit);
|
|
127
|
+
};
|
|
128
|
+
return (e) => {
|
|
129
|
+
if (pause) return (planned = true);
|
|
130
|
+
block(e);
|
|
131
|
+
return fn(e);
|
|
132
|
+
};
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const debounce = (fn, wait) => {
|
|
136
|
+
let timeout;
|
|
137
|
+
return (e) => {
|
|
138
|
+
clearTimeout(timeout);
|
|
139
|
+
timeout = setTimeout(() => {
|
|
140
|
+
timeout = null;
|
|
141
|
+
fn(e);
|
|
142
|
+
}, wait);
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const dashcase = (str) => {
|
|
147
|
+
return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match) => "-" + match.toLowerCase());
|
|
148
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import sprae, { directive, compile, swap } from "../core.js";
|
|
2
|
+
|
|
3
|
+
export const _each = Symbol(":each");
|
|
4
|
+
|
|
5
|
+
const keys = {}; // boxed primitives pool
|
|
6
|
+
|
|
7
|
+
// :each must init before :ref, :id or any others, since it defines scope
|
|
8
|
+
directive.each = (tpl, expr, state, name) => {
|
|
9
|
+
let [leftSide, itemsExpr] = expr.split(/\s+in\s+/);
|
|
10
|
+
let [itemVar, idxVar = "_$"] = leftSide.split(/\s*,\s*/);
|
|
11
|
+
|
|
12
|
+
// we need :if to be able to replace holder instead of tpl for :if :each case
|
|
13
|
+
const holder = (tpl[_each] = document.createTextNode(""));
|
|
14
|
+
tpl.replaceWith(holder);
|
|
15
|
+
|
|
16
|
+
const evaluate = compile(itemsExpr, name);
|
|
17
|
+
const memo = new WeakMap;
|
|
18
|
+
|
|
19
|
+
tpl.removeAttribute(':key')
|
|
20
|
+
|
|
21
|
+
let cur = [];
|
|
22
|
+
|
|
23
|
+
return () => {
|
|
24
|
+
// naive approach: whenever items change we replace full list
|
|
25
|
+
let items = evaluate(state)?.valueOf(), els = [];
|
|
26
|
+
if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i);
|
|
27
|
+
|
|
28
|
+
const count = new WeakSet
|
|
29
|
+
for (let idx in items) {
|
|
30
|
+
let item = items[idx]
|
|
31
|
+
// creating via prototype is faster in both creation time & reading time
|
|
32
|
+
let substate = Object.create(state, { [idxVar]: { value: idx } });
|
|
33
|
+
substate[itemVar] = item; // can be changed by subsequent updates, need to be writable
|
|
34
|
+
item = item.peek?.() ?? item; // unwrap signal
|
|
35
|
+
let key = item.key ?? item.id ?? item;
|
|
36
|
+
let el;
|
|
37
|
+
|
|
38
|
+
if (key == null) el = tpl.cloneNode(true)
|
|
39
|
+
else {
|
|
40
|
+
// make sure key is object
|
|
41
|
+
if (Object(key) !== key) key = (keys[key] ||= Object(key));
|
|
42
|
+
|
|
43
|
+
if (count.has(key)) {
|
|
44
|
+
console.warn('Duplicate key', key), el = tpl.cloneNode(true);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
console.log(key, count.has(key))
|
|
48
|
+
count.add(key);
|
|
49
|
+
el = memo.get(key) || memo.set(key, tpl.cloneNode(true)).get(key);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (el.content) el = el.content.cloneNode(true) // <template>
|
|
54
|
+
|
|
55
|
+
sprae(el, substate)
|
|
56
|
+
|
|
57
|
+
// document fragment
|
|
58
|
+
if (el.nodeType === 11) els.push(...el.childNodes);
|
|
59
|
+
else els.push(el);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
swap(holder.parentNode, cur, cur = els, holder);
|
|
63
|
+
};
|
|
64
|
+
};
|
package/directive/fx.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import sprae, { directive, compile } from "../core.js";
|
|
2
|
+
|
|
3
|
+
directive.html = (el, expr, state, name) => {
|
|
4
|
+
let evaluate = compile(expr, name), tpl = evaluate(state);
|
|
5
|
+
|
|
6
|
+
if (!tpl) return
|
|
7
|
+
|
|
8
|
+
let content = (tpl.content || tpl).cloneNode(true);
|
|
9
|
+
el.replaceChildren(content);
|
|
10
|
+
sprae(el, state);
|
|
11
|
+
};
|
package/directive/if.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import sprae, { compile, directive, swap } from "../core.js";
|
|
2
|
+
import { _each } from './each.js';
|
|
3
|
+
|
|
4
|
+
// :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
|
|
5
|
+
// as for :if :scope - :if must init first, since it is lazy, to avoid initializing component ahead of time by :scope
|
|
6
|
+
// we consider :scope={x} :if={x} case insignificant
|
|
7
|
+
const _prevIf = Symbol("if");
|
|
8
|
+
directive.if = (ifEl, expr, state, name) => {
|
|
9
|
+
let parent = ifEl.parentNode,
|
|
10
|
+
next = ifEl.nextElementSibling,
|
|
11
|
+
holder = document.createTextNode(''),
|
|
12
|
+
|
|
13
|
+
evaluate = compile(expr, name),
|
|
14
|
+
|
|
15
|
+
// actual replaceable els (takes <template>)
|
|
16
|
+
cur, ifs, elses, none = [];
|
|
17
|
+
|
|
18
|
+
ifEl.after(holder) // mark end of modifying section
|
|
19
|
+
|
|
20
|
+
if (ifEl.content) cur = none, ifEl.remove(), ifs = [...ifEl.content.childNodes]
|
|
21
|
+
else ifs = cur = [ifEl]
|
|
22
|
+
|
|
23
|
+
if (next?.hasAttribute(":else")) {
|
|
24
|
+
next.removeAttribute(":else");
|
|
25
|
+
// if next is :else :if - leave it for its own :if handler
|
|
26
|
+
if (next.hasAttribute(":if")) elses = none;
|
|
27
|
+
else next.remove(), elses = next.content ? [...next.content.childNodes] : [next];
|
|
28
|
+
} else elses = none
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
const newEls = evaluate(state)?.valueOf() ? ifs : ifEl[_prevIf] ? none : elses;
|
|
32
|
+
if (next) next[_prevIf] = newEls === ifs
|
|
33
|
+
if (cur != newEls) {
|
|
34
|
+
// :if :each
|
|
35
|
+
if (cur[0]?.[_each]) cur = [cur[0][_each]]
|
|
36
|
+
swap(parent, cur, cur = newEls, holder);
|
|
37
|
+
for (let el of cur) sprae(el, state);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
};
|
package/directive/ref.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { directive, ipol } from "../core.js";
|
|
2
|
+
|
|
3
|
+
// ref must be last within primaries, since that must be skipped by :each, but before secondaries
|
|
4
|
+
directive.ref = (el, expr, state) => {
|
|
5
|
+
let prev;
|
|
6
|
+
return () => {
|
|
7
|
+
if (prev) delete state[prev]
|
|
8
|
+
state[prev = ipol(expr, state)] = el;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import sprae, { directive, compile } from "../core.js";
|
|
2
|
+
|
|
3
|
+
// `:each` can redefine scope as `:each="a in {myScope}"`,
|
|
4
|
+
// same time per-item scope as `:each="..." :scope="{collapsed:true}"` is useful
|
|
5
|
+
directive.scope = (el, expr, rootState, name) => {
|
|
6
|
+
let evaluate = compile(expr, name);
|
|
7
|
+
// local state may contain signals that update, so we take them over
|
|
8
|
+
return () => {
|
|
9
|
+
sprae(el, { ...rootState, ...(evaluate(rootState)?.valueOf?.() || {}) });
|
|
10
|
+
}
|
|
11
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { directive, compile, ipol } from "../core.js";
|
|
2
|
+
|
|
3
|
+
directive.style = (el, expr, state) => {
|
|
4
|
+
let evaluate = compile(expr, 'style');
|
|
5
|
+
let initStyle = el.getAttribute("style") || "";
|
|
6
|
+
if (!initStyle.endsWith(";")) initStyle += "; ";
|
|
7
|
+
|
|
8
|
+
return () => {
|
|
9
|
+
let v = evaluate(state)?.valueOf();
|
|
10
|
+
if (typeof v === "string") el.setAttribute("style", initStyle + ipol(v, state));
|
|
11
|
+
else {
|
|
12
|
+
el.setAttribute("style", initStyle);
|
|
13
|
+
for (let k in v) el.style.setProperty(k, ipol(v[k], state));
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { directive, compile } from "../core.js";
|
|
2
|
+
|
|
3
|
+
// set text content
|
|
4
|
+
directive.text = (el, expr, state) => {
|
|
5
|
+
let evaluate = compile(expr, 'text');
|
|
6
|
+
if (el.content) el.replaceWith(el = document.createTextNode('')) // <template :text="abc"/>
|
|
7
|
+
|
|
8
|
+
return () => {
|
|
9
|
+
let value = evaluate(state)?.valueOf();
|
|
10
|
+
el.textContent = value == null ? "" : value;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { directive, compile } from "../core.js";
|
|
2
|
+
import { attr } from './default.js';
|
|
3
|
+
|
|
4
|
+
// connect expr to element value
|
|
5
|
+
directive.value = (el, expr, state) => {
|
|
6
|
+
let evaluate = compile(expr, 'value');
|
|
7
|
+
|
|
8
|
+
let from, to;
|
|
9
|
+
let update = el.type === "text" || el.type === ""
|
|
10
|
+
? (value) => el.setAttribute("value", (el.value = value == null ? "" : value))
|
|
11
|
+
: el.tagName === "TEXTAREA" || el.type === "text" || el.type === ""
|
|
12
|
+
? (value) =>
|
|
13
|
+
(
|
|
14
|
+
// we retain selection in input
|
|
15
|
+
(from = el.selectionStart),
|
|
16
|
+
(to = el.selectionEnd),
|
|
17
|
+
el.setAttribute("value", (el.value = value == null ? "" : value)),
|
|
18
|
+
from && el.setSelectionRange(from, to)
|
|
19
|
+
)
|
|
20
|
+
: el.type === "checkbox"
|
|
21
|
+
? (value) => (el.checked = value, attr(el, "checked", value))
|
|
22
|
+
: el.type === "select-one"
|
|
23
|
+
? (value) => {
|
|
24
|
+
for (let option in el.options) option.removeAttribute("selected");
|
|
25
|
+
el.value = value;
|
|
26
|
+
el.selectedOptions[0]?.setAttribute("selected", "");
|
|
27
|
+
}
|
|
28
|
+
: (value) => (el.value = value);
|
|
29
|
+
|
|
30
|
+
return () => (update(evaluate(state)?.valueOf?.()));
|
|
31
|
+
};
|