sprae 11.0.7 → 11.1.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 +52 -17
- package/directive/aria.js +4 -7
- package/directive/class.js +6 -7
- package/directive/data.js +2 -7
- package/directive/default.js +32 -34
- package/directive/each.js +41 -55
- package/directive/fx.js +2 -4
- package/directive/if.js +8 -6
- package/directive/ref.js +8 -5
- package/directive/style.js +6 -8
- package/directive/text.js +5 -10
- package/directive/value.js +33 -30
- package/directive/with.js +2 -8
- package/dist/sprae.js +138 -185
- package/dist/sprae.js.map +4 -4
- package/dist/sprae.min.js +3 -3
- package/dist/sprae.min.js.map +3 -3
- package/dist/sprae.umd.js +169 -213
- package/dist/sprae.umd.js.map +4 -4
- package/dist/sprae.umd.min.js +3 -3
- package/dist/sprae.umd.min.js.map +3 -3
- package/package.json +1 -1
- package/readme.md +13 -11
- package/signal.js +5 -16
- package/store.js +3 -3
package/core.js
CHANGED
|
@@ -3,15 +3,31 @@ import store, { _signals } from './store.js';
|
|
|
3
3
|
|
|
4
4
|
// polyfill
|
|
5
5
|
const _dispose = (Symbol.dispose ||= Symbol("dispose"));
|
|
6
|
-
export const _state = Symbol("state")
|
|
7
|
-
export const _on = Symbol('on')
|
|
8
|
-
export const _off = Symbol('off')
|
|
9
6
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
export const _state = Symbol("state"), _on = Symbol('on'), _off = Symbol('off')
|
|
8
|
+
|
|
9
|
+
// registered directives
|
|
10
|
+
const directive = {}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Register a directive with a parsed expression and evaluator.
|
|
14
|
+
* @param {string} name - The name of the directive.
|
|
15
|
+
* @param {(el: Element, state: Object, attrValue: string, attrName: string) => (value: any) => void} create - A function to create the directive.
|
|
16
|
+
* @param {(expr: string) => (state: Object) => any} [p=parse] - Create evaluator from expression string.
|
|
17
|
+
*/
|
|
18
|
+
export const dir = (name, create, p = parse) => directive[name] = (el, expr, state, name, update, evaluate) => (
|
|
19
|
+
evaluate = p(expr),
|
|
20
|
+
update = create(el, state, expr, name, evaluate),
|
|
21
|
+
() => update(evaluate(state))
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Applies directives to an HTML element and manages its reactive state.
|
|
26
|
+
*
|
|
27
|
+
* @param {Element} el - The target HTML element to apply directives to.
|
|
28
|
+
* @param {Object} [values] - Initial values to populate the element's reactive state.
|
|
29
|
+
* @returns {Object} The reactive state object associated with the element.
|
|
30
|
+
*/
|
|
15
31
|
export default function sprae(el, values) {
|
|
16
32
|
// text nodes, comments etc
|
|
17
33
|
if (!el?.childNodes) return
|
|
@@ -47,17 +63,16 @@ export default function sprae(el, values) {
|
|
|
47
63
|
|
|
48
64
|
// init generic-name attributes second
|
|
49
65
|
for (let i = 0; i < el.attributes?.length;) {
|
|
50
|
-
let attr = el.attributes[i];
|
|
66
|
+
let attr = el.attributes[i], update;
|
|
51
67
|
|
|
52
68
|
if (attr.name[0] === ':') {
|
|
53
69
|
el.removeAttribute(attr.name);
|
|
54
70
|
|
|
55
71
|
// multiple attributes like :id:for=""
|
|
56
72
|
for (let name of attr.name.slice(1).split(':')) {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
fx.push(update)
|
|
60
|
-
offs.push(effect(update))
|
|
73
|
+
update = (directive[name] || directive.default)(el, attr.value, state, name)
|
|
74
|
+
|
|
75
|
+
fx.push(update), offs.push(effect(update)) // save & start effect
|
|
61
76
|
|
|
62
77
|
// stop after :each, :if, :with?
|
|
63
78
|
if (_state in el) return
|
|
@@ -70,9 +85,17 @@ export default function sprae(el, values) {
|
|
|
70
85
|
}
|
|
71
86
|
|
|
72
87
|
|
|
73
|
-
// parse expression into evaluator fn
|
|
74
88
|
const memo = {};
|
|
75
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Parses an expression into an evaluator function, caching the result for reuse.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} expr - The expression to parse and compile into a function.
|
|
93
|
+
* @param {string} dir - The directive associated with the expression (used for error reporting).
|
|
94
|
+
* @returns {Function} The compiled evaluator function for the expression.
|
|
95
|
+
*/
|
|
96
|
+
export const parse = (expr, dir) => {
|
|
97
|
+
let fn
|
|
98
|
+
|
|
76
99
|
if (fn = memo[expr = expr.trim()]) return fn
|
|
77
100
|
|
|
78
101
|
// static-time errors
|
|
@@ -83,11 +106,23 @@ export const parse = (expr, dir, fn) => {
|
|
|
83
106
|
return memo[expr] = fn
|
|
84
107
|
}
|
|
85
108
|
|
|
86
|
-
|
|
109
|
+
/**
|
|
110
|
+
* Branded sprae error with context about the directive and expression
|
|
111
|
+
*
|
|
112
|
+
* @param {Error} e - The original error object to enhance.
|
|
113
|
+
* @param {string} dir - The directive where the error occurred.
|
|
114
|
+
* @param {string} [expr=''] - The expression associated with the error, if any.
|
|
115
|
+
* @throws {Error} The enhanced error object with a formatted message.
|
|
116
|
+
*/
|
|
87
117
|
export const err = (e, dir, expr = '') => {
|
|
88
|
-
throw Object.assign(e, { message: `∴ ${e.message}\n\n${dir}${expr ? `="${expr}"\n\n` : ""}`, expr })
|
|
118
|
+
throw Object.assign(e, { message: `∴ ${e.message}\n\n${dir || ''}${expr ? `="${expr}"\n\n` : ""}`, expr })
|
|
89
119
|
}
|
|
90
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Compiles an expression into an evaluator function.
|
|
123
|
+
*
|
|
124
|
+
* @type {(expr: string) => Function}
|
|
125
|
+
*/
|
|
91
126
|
export let compile
|
|
92
127
|
|
|
93
128
|
// configure signals/compile
|
package/directive/aria.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dir } from "../core.js";
|
|
2
2
|
import { attr, dashcase } from './default.js'
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
return () => update(evaluate(state))
|
|
9
|
-
}
|
|
4
|
+
dir('aria', (el) => value => {
|
|
5
|
+
for (let key in value) attr(el, 'aria-' + dashcase(key), value[key] == null ? null : value[key] + '')
|
|
6
|
+
})
|
package/directive/class.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dir } from "../core.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let v = evaluate(state);
|
|
3
|
+
dir('class', (el, cur) => (
|
|
4
|
+
cur = new Set,
|
|
5
|
+
v => {
|
|
7
6
|
let clsx = new Set;
|
|
8
7
|
if (v) {
|
|
9
8
|
if (typeof v === "string") v.split(' ').map(cls => clsx.add(cls));
|
|
@@ -12,5 +11,5 @@ directive.class = (el, evaluate, state) => {
|
|
|
12
11
|
}
|
|
13
12
|
for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls); else el.classList.remove(cls);
|
|
14
13
|
for (let cls of cur = clsx) el.classList.add(cls)
|
|
15
|
-
}
|
|
16
|
-
|
|
14
|
+
})
|
|
15
|
+
)
|
package/directive/data.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dir } from "../core.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
return () => {
|
|
5
|
-
let value = evaluate(state)
|
|
6
|
-
for (let key in value) el.dataset[key] = value[key];
|
|
7
|
-
}
|
|
8
|
-
}
|
|
3
|
+
dir('data', el => value => {for (let key in value) el.dataset[key] = value[key];})
|
package/directive/default.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
// generic property directive
|
|
2
|
+
import { dir, err } from "../core.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
directive.default = (target, evaluate, state, name) => {
|
|
4
|
+
dir('default', (target, state, expr, name) => {
|
|
5
5
|
// simple prop
|
|
6
|
-
if (!name.startsWith('on'))
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
6
|
+
if (!name.startsWith('on'))
|
|
7
|
+
return name ?
|
|
8
|
+
value => attr(target, name, value) :
|
|
9
|
+
value => { for (let key in value) attr(target, dashcase(key), value[key]) };
|
|
11
10
|
|
|
12
11
|
// bind event to a target
|
|
13
12
|
// NOTE: if you decide to remove chain of events, thing again - that's unique feature of sprae, don't diminish your own value.
|
|
@@ -15,30 +14,13 @@ directive.default = (target, evaluate, state, name) => {
|
|
|
15
14
|
const ctxs = name.split('..').map(e => {
|
|
16
15
|
let ctx = { evt: '', target, test: () => true };
|
|
17
16
|
ctx.evt = (e.startsWith('on') ? e.slice(2) : e).replace(/\.(\w+)?-?([-\w]+)?/g,
|
|
18
|
-
(
|
|
17
|
+
(_, mod, param = '') => (ctx.test = mods[mod]?.(ctx, ...param.split('-')) || ctx.test, '')
|
|
19
18
|
);
|
|
20
19
|
return ctx;
|
|
21
20
|
});
|
|
22
21
|
|
|
23
|
-
// single event
|
|
24
|
-
if (ctxs.length == 1) return () => addListener(evaluate(state), ctxs[0])
|
|
25
|
-
|
|
26
|
-
// events cycler
|
|
27
|
-
let startFn, nextFn, off, idx = 0
|
|
28
|
-
const nextListener = (fn) => {
|
|
29
|
-
off = addListener((e) => (
|
|
30
|
-
off(), nextFn = fn?.(e), (idx = ++idx % ctxs.length) ? nextListener(nextFn) : (startFn && nextListener(startFn))
|
|
31
|
-
), ctxs[idx]);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return () => (
|
|
35
|
-
startFn = evaluate(state),
|
|
36
|
-
!off && nextListener(startFn),
|
|
37
|
-
() => startFn = null // nil startFn to autodispose chain
|
|
38
|
-
)
|
|
39
|
-
|
|
40
22
|
// add listener with the context
|
|
41
|
-
|
|
23
|
+
const addListener = (fn, { evt, target, test, defer, stop, prevent, immediate, ...opts }) => {
|
|
42
24
|
if (defer) fn = defer(fn)
|
|
43
25
|
|
|
44
26
|
const cb = (e) => {
|
|
@@ -51,7 +33,23 @@ directive.default = (target, evaluate, state, name) => {
|
|
|
51
33
|
return () => target.removeEventListener(evt, cb, opts)
|
|
52
34
|
};
|
|
53
35
|
|
|
54
|
-
|
|
36
|
+
// single event
|
|
37
|
+
if (ctxs.length == 1) return v => addListener(v, ctxs[0])
|
|
38
|
+
|
|
39
|
+
// events cycler
|
|
40
|
+
let startFn, nextFn, off, idx = 0
|
|
41
|
+
const nextListener = (fn) => {
|
|
42
|
+
off = addListener((e) => (
|
|
43
|
+
off(), nextFn = fn?.(e), (idx = ++idx % ctxs.length) ? nextListener(nextFn) : (startFn && nextListener(startFn))
|
|
44
|
+
), ctxs[idx]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return value => (
|
|
48
|
+
startFn = value,
|
|
49
|
+
!off && nextListener(startFn),
|
|
50
|
+
() => startFn = null // nil startFn to autodispose chain
|
|
51
|
+
)
|
|
52
|
+
})
|
|
55
53
|
|
|
56
54
|
// event modifiers
|
|
57
55
|
const mods = {
|
|
@@ -117,12 +115,6 @@ const keys = {
|
|
|
117
115
|
char: (e) => /^\S$/.test(e.key),
|
|
118
116
|
};
|
|
119
117
|
|
|
120
|
-
// set attr
|
|
121
|
-
export const attr = (el, name, v) => {
|
|
122
|
-
if (v == null || v === false) el.removeAttribute(name);
|
|
123
|
-
else el.setAttribute(name, v === true ? "" : typeof v === "number" || typeof v === "string" ? v : "");
|
|
124
|
-
}
|
|
125
|
-
|
|
126
118
|
// create delayed fns
|
|
127
119
|
const throttle = (fn, limit) => {
|
|
128
120
|
let pause, planned,
|
|
@@ -152,6 +144,12 @@ const debounce = (fn, wait) => {
|
|
|
152
144
|
};
|
|
153
145
|
};
|
|
154
146
|
|
|
147
|
+
// set attr
|
|
148
|
+
export const attr = (el, name, v) => {
|
|
149
|
+
if (v == null || v === false) el.removeAttribute(name);
|
|
150
|
+
else el.setAttribute(name, v === true ? "" : typeof v === "number" || typeof v === "string" ? v : "");
|
|
151
|
+
}
|
|
152
|
+
|
|
155
153
|
export const dashcase = (str) => {
|
|
156
154
|
return str.replace(/[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g, (match, i) => (i ? '-' : '') + match.toLowerCase());
|
|
157
155
|
}
|
package/directive/each.js
CHANGED
|
@@ -1,53 +1,37 @@
|
|
|
1
|
-
import sprae, { _state,
|
|
1
|
+
import sprae, { _state, dir, frag, parse } from "../core.js";
|
|
2
2
|
import store, { _change, _signals } from "../store.js";
|
|
3
|
-
import {
|
|
3
|
+
import { effect } from '../signal.js';
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const holder = (document.createTextNode(""));
|
|
9
|
-
tpl.replaceWith(holder);
|
|
10
|
-
tpl[_state] = null // mark as fake-spraed, to preserve :-attribs for template
|
|
6
|
+
dir('each', (tpl, state, expr) => {
|
|
7
|
+
const [itemVar, idxVar = "$"] = expr.split(/\s+in\s+/)[0].split(/\s*,\s*/);
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
// we need :if to be able to replace holder instead of tpl for :if :each case
|
|
10
|
+
const holder = document.createTextNode("");
|
|
11
|
+
tpl.replaceWith(holder);
|
|
12
|
+
tpl[_state] = null // mark as fake-spraed, to preserve :-attribs for template
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
keys = null
|
|
18
|
-
let items = evaluate(state)
|
|
19
|
-
if (typeof items === "number") items = Array.from({ length: items }, (_, i) => i + 1)
|
|
20
|
-
if (items?.constructor === Object) keys = Object.keys(items), items = Object.values(items)
|
|
21
|
-
return items || []
|
|
22
|
-
})
|
|
14
|
+
// we re-create items any time new items are produced
|
|
15
|
+
let cur, keys, items, prevl = 0
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
untracked(() => {
|
|
27
|
-
let i = 0, newItems = items.value, newl = newItems.length
|
|
17
|
+
const update = () => {
|
|
18
|
+
let i = 0, newItems = items, newl = newItems.length
|
|
28
19
|
|
|
29
20
|
// plain array update, not store (signal with array) - updates full list
|
|
30
|
-
if (cur && !
|
|
31
|
-
for (let s of cur[_signals] || [])
|
|
21
|
+
if (cur && !cur[_change]) {
|
|
22
|
+
for (let s of cur[_signals] || []) s[Symbol.dispose]()
|
|
32
23
|
cur = null, prevl = 0
|
|
33
24
|
}
|
|
34
25
|
|
|
35
26
|
// delete
|
|
36
|
-
if (newl < prevl)
|
|
37
|
-
|
|
38
|
-
}
|
|
27
|
+
if (newl < prevl) cur.length = newl
|
|
28
|
+
|
|
39
29
|
// update, append, init
|
|
40
30
|
else {
|
|
41
31
|
// init
|
|
42
|
-
if (!cur)
|
|
43
|
-
cur = newItems
|
|
44
|
-
}
|
|
32
|
+
if (!cur) cur = newItems
|
|
45
33
|
// update
|
|
46
|
-
else
|
|
47
|
-
for (; i < prevl; i++) {
|
|
48
|
-
cur[i] = newItems[i]
|
|
49
|
-
}
|
|
50
|
-
}
|
|
34
|
+
else while (i < prevl) cur[i] = newItems[i++]
|
|
51
35
|
|
|
52
36
|
// append
|
|
53
37
|
for (; i < newl; i++) {
|
|
@@ -70,24 +54,26 @@ directive.each = (tpl, [itemVar, idxVar, evaluate], state) => {
|
|
|
70
54
|
}
|
|
71
55
|
|
|
72
56
|
prevl = newl
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return value => {
|
|
60
|
+
keys = null
|
|
61
|
+
if (typeof value === "number") items = Array.from({ length: value }, (_, i) => i + 1)
|
|
62
|
+
else if (value?.constructor === Object) keys = Object.keys(value), items = Object.values(value)
|
|
63
|
+
else items = value || []
|
|
64
|
+
|
|
65
|
+
// whenever list changes, we rebind internal change effect
|
|
66
|
+
let planned = 0
|
|
67
|
+
return effect(() => {
|
|
68
|
+
// subscribe to items change (.length) - we do it every time (not just in update) since preact unsubscribes unused signals
|
|
69
|
+
items[_change]?.value
|
|
70
|
+
|
|
71
|
+
// make first render immediately, debounce subsequent renders
|
|
72
|
+
if (!planned++) update(), queueMicrotask(() => (planned > 1 && update(), planned = 0));
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
// redefine evaluator to take second part of expression
|
|
78
|
+
expr => parse(expr.split(/\s+in\s+/)[1])
|
|
79
|
+
)
|
package/directive/fx.js
CHANGED
package/directive/if.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import sprae, {
|
|
1
|
+
import sprae, { dir, _state, _on, _off, frag } from "../core.js";
|
|
2
2
|
|
|
3
3
|
// :if is interchangeable with :each depending on order, :if :each or :each :if have different meanings
|
|
4
4
|
// as for :if :with - :if must init first, since it is lazy, to avoid initializing component ahead of time by :with
|
|
5
5
|
// we consider :with={x} :if={x} case insignificant
|
|
6
6
|
const _prevIf = Symbol("if");
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
dir('if', (el, state) => {
|
|
9
|
+
const holder = document.createTextNode('')
|
|
10
|
+
|
|
8
11
|
let next = el.nextElementSibling,
|
|
9
|
-
holder = document.createTextNode(''),
|
|
10
12
|
curEl, ifEl, elseEl;
|
|
11
13
|
|
|
12
14
|
el.replaceWith(holder)
|
|
@@ -20,8 +22,8 @@ directive.if = (el, evaluate, state) => {
|
|
|
20
22
|
if (!next.hasAttribute(":if")) next.remove(), elseEl = next.content ? frag(next) : next, elseEl[_state] = null
|
|
21
23
|
}
|
|
22
24
|
|
|
23
|
-
return () => {
|
|
24
|
-
const newEl =
|
|
25
|
+
return (value) => {
|
|
26
|
+
const newEl = value ? ifEl : el[_prevIf] ? null : elseEl;
|
|
25
27
|
if (next) next[_prevIf] = newEl === ifEl
|
|
26
28
|
if (curEl != newEl) {
|
|
27
29
|
// disable effects on child elements when element is not matched
|
|
@@ -34,4 +36,4 @@ directive.if = (el, evaluate, state) => {
|
|
|
34
36
|
}
|
|
35
37
|
}
|
|
36
38
|
};
|
|
37
|
-
}
|
|
39
|
+
})
|
package/directive/ref.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dir, parse } from "../core.js";
|
|
2
|
+
import { setter, ensure } from "./value.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
dir('ref', (el, state, expr, _, ev) => (
|
|
5
|
+
ensure(state, expr),
|
|
6
|
+
ev(state) == null ?
|
|
7
|
+
(setter(expr)(state, el), _ => _) :
|
|
8
|
+
v => v.call(null, el)
|
|
9
|
+
))
|
package/directive/style.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dir } from "../core.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
return () => {
|
|
7
|
-
let v = evaluate(state);
|
|
3
|
+
dir('style', (el, initStyle) => (
|
|
4
|
+
initStyle = el.getAttribute("style"),
|
|
5
|
+
v => {
|
|
8
6
|
if (typeof v === "string") el.setAttribute("style", initStyle + (initStyle.endsWith(';') ? '' : '; ') + v);
|
|
9
7
|
else {
|
|
10
8
|
if (initStyle) el.setAttribute("style", initStyle);
|
|
11
9
|
for (let k in v) k[0] == '-' ? (el.style.setProperty(k, v[k])) : el.style[k] = v[k]
|
|
12
10
|
}
|
|
13
|
-
}
|
|
14
|
-
|
|
11
|
+
})
|
|
12
|
+
)
|
package/directive/text.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dir, frag } from "../core.js";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
directive.text = (el, evaluate, state) => {
|
|
3
|
+
dir('text', el => (
|
|
5
4
|
// <template :text="a"/> or previously initialized template
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
let value = evaluate(state);
|
|
10
|
-
el.textContent = value == null ? "" : value;
|
|
11
|
-
};
|
|
12
|
-
};
|
|
5
|
+
el.content && el.replaceWith(el = frag(el).childNodes[0]),
|
|
6
|
+
value => el.textContent = value == null ? "" : value
|
|
7
|
+
))
|
package/directive/value.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import sprae from "../core.js";
|
|
2
|
-
import {
|
|
2
|
+
import { dir, parse } from "../core.js";
|
|
3
3
|
import { attr } from './default.js';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
|
|
6
|
+
dir('value', (el, state, expr) => {
|
|
7
7
|
const update =
|
|
8
8
|
(el.type === "text" || el.type === "") ?
|
|
9
9
|
(value) => el.setAttribute("value", (el.value = value == null ? "" : value)) :
|
|
@@ -29,36 +29,39 @@ directive.value = (el, [getValue, setValue], state) => {
|
|
|
29
29
|
} :
|
|
30
30
|
(value) => (el.value = value);
|
|
31
31
|
|
|
32
|
+
// make sure value exists in state
|
|
33
|
+
ensure(state, expr)
|
|
34
|
+
|
|
32
35
|
// bind ui back to value
|
|
33
|
-
|
|
36
|
+
try {
|
|
37
|
+
const set = setter(expr)
|
|
38
|
+
const handleChange = el.type === 'checkbox' ? () => set(state, el.checked) :
|
|
39
|
+
el.type === 'select-multiple' ? () => set(state, [...el.selectedOptions].map(o => o.value)) :
|
|
40
|
+
() => set(state, el.selectedIndex < 0 ? null : el.value)
|
|
34
41
|
|
|
35
|
-
|
|
42
|
+
el.oninput = el.onchange = handleChange; // hope user doesn't redefine these manually via `.oninput = somethingElse` - it saves 5 loc vs addEventListener
|
|
36
43
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
44
|
+
if (el.type?.startsWith('select')) {
|
|
45
|
+
// select element also must observe any added/removed options or changed values (outside of sprae)
|
|
46
|
+
new MutationObserver(handleChange).observe(el, { childList: true, subtree: true, attributes: true });
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
// select options must be initialized before calling an update
|
|
49
|
+
sprae(el, state)
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
44
52
|
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
};
|
|
53
|
+
return update
|
|
54
|
+
})
|
|
49
55
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
catch (e) { }
|
|
63
|
-
return evaluate
|
|
64
|
-
}
|
|
56
|
+
// create expression setter, reflecting value back to state
|
|
57
|
+
export const setter = (expr, set = parse(`${expr}=__`)) => (
|
|
58
|
+
// FIXME: if there's a simpler way to set value in justin?
|
|
59
|
+
(state, value) => (
|
|
60
|
+
state.__ = value,
|
|
61
|
+
set(state, value),
|
|
62
|
+
delete state.__
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
// make sure state contains first element of path, eg. `a` from `a.b[c]`
|
|
67
|
+
export const ensure = (state, expr, name = expr.match(/^\w+(?=\s*(?:\.|\[|$))/)) => name && (state[name[0]] ||= null)
|
package/directive/with.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import sprae, {
|
|
1
|
+
import sprae, { dir } from "../core.js";
|
|
2
2
|
import store, { _signals } from '../store.js';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
let state
|
|
6
|
-
return () => {
|
|
7
|
-
let values = evaluate(rootState);
|
|
8
|
-
sprae(el, state ? values : state = store(values, rootState))
|
|
9
|
-
}
|
|
10
|
-
};
|
|
4
|
+
dir('with', (el, rootState, state) => (state=null, values => sprae(el, state ? values : state = store(values, rootState))))
|