sprae 12.2.5 → 12.3.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/sprae.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import store from "./store.js";
2
- import { batch, computed, effect, signal, untracked } from './signal.js';
3
- import sprae, { use, directive, modifier, start, throttle, debounce, _off, _state, _on, _dispose } from './core.js';
2
+ import { batch, computed, effect, signal, untracked } from './core.js';
3
+ import * as signals from './signal.js';
4
+ import sprae, { use, decorate, directive, modifier, parse, throttle, debounce, _off, _state, _on, _dispose, _add, call } from './core.js';
5
+ import pkg from './package.json' with { type: 'json' };
4
6
 
5
7
  import _if from "./directive/if.js";
6
8
  import _else from "./directive/else.js";
@@ -12,55 +14,64 @@ import _value from "./directive/value.js";
12
14
  import _ref from "./directive/ref.js";
13
15
  import _scope from "./directive/scope.js";
14
16
  import _each from "./directive/each.js";
15
- import _default from "./directive/default.js";
17
+ import _default from "./directive/_.js";
16
18
  import _spread from "./directive/spread.js";
19
+ import _event from "./directive/event.js";
20
+ import _seq from "./directive/sequence.js";
17
21
 
18
22
 
19
23
  Object.assign(directive, {
20
- // :x="x"
21
- '*': _default,
22
-
23
- // FIXME
24
- // 'on*': _on,
25
-
26
- // :="{a,b,c}"
24
+ _: (el, state, expr, name) => (name.startsWith('on') ? _event : _default)(el, state, expr, name),
27
25
  '': _spread,
28
-
29
- // :class="[a, b, c]"
30
26
  class: _class,
31
-
32
- // :text="..."
33
27
  text: _text,
34
-
35
- // :style="..."
36
28
  style: _style,
37
-
38
- // :fx="..."
39
29
  fx: _fx,
40
-
41
- // :value - 2 way binding like x-model
42
30
  value: _value,
43
-
44
- // :ref="..."
45
31
  ref: _ref,
46
-
47
- // :scope creates variables scope for a subtree
48
32
  scope: _scope,
49
-
50
33
  if: _if,
51
34
  else: _else,
52
-
53
- // :each="v,k in src"
54
35
  each: _each
55
36
  })
56
37
 
38
+
39
+ /**
40
+ * Directive initializer (with modifiers support)
41
+ * @type {(el: HTMLElement, name:string, value:string, state:Object) => Function}
42
+ * */
43
+ const dir = (target, name, expr, state) => {
44
+ let [dirName, ...mods] = name.split('.'), create = directive[dirName] || directive._
45
+
46
+ return () => {
47
+ let update = create(target, state, expr, name)
48
+
49
+ if (!update?.call) return update?.[_dispose]
50
+
51
+ // throttle prevents multiple updates within one tick as well as isolates stack for each update
52
+ let trigger = decorate(Object.assign(throttle(() => change.value++), { target }), mods),
53
+ change = signal(0), // signal authorized to trigger effect: 0 = init; >0 = trigger
54
+ count = 0, // called effect count
55
+ evaluate = update.eval ?? parse(expr).bind(target),
56
+ _out, out = () => (_out && call(_out), _out=null) // effect trigger and invoke may happen in the same tick, so it will be effect-within-effect call - we need to store output of evaluate to return from trigger effect
57
+
58
+ state = target[_state] ?? state
59
+
60
+ return effect(() => (
61
+ // if planned count is same as actual count - plan new update, else update right away
62
+ change.value == count ? (trigger()) : (count = change.value, _out = evaluate(state, update)),
63
+ out
64
+ ))
65
+ }
66
+ }
67
+
57
68
  Object.assign(modifier, {
58
69
  // timing
59
70
  debounce: (fn, _how = 250) => debounce(fn, (_how ||= 0, (fn) => setTimeout(fn, _how))),
60
71
  throttle: (fn, _how = 250) => throttle(fn, (_how ||= 0, (fn) => setTimeout(fn, _how))),
61
- tick: (fn) => (e) => queueMicrotask(() => fn(e)),
72
+ tick: (fn) => (e) => (queueMicrotask(() => fn(e))),
62
73
  raf: (fn) => (e) => requestAnimationFrame(() => fn(e)),
63
- once: (fn, _done, _fn) => Object.assign((e) => !_done && (_done = 1, fn(e)), { once: true }),
74
+ once: (fn, _done, _fn) => (_fn = (e) => !_done && (_done = 1, fn(e)), _fn.once = true, _fn),
64
75
 
65
76
  // target
66
77
  window: fn => (fn.target = fn.target.ownerDocument.defaultView, fn),
@@ -87,7 +98,8 @@ const keys = {
87
98
  ctrl: e => e.ctrlKey || e.key === "Control" || e.key === "Ctrl",
88
99
  shift: e => e.shiftKey || e.key === "Shift",
89
100
  alt: e => e.altKey || e.key === "Alt",
90
- meta: e => e.metaKey || e.key === "Meta" || e.key === "Command",
101
+ meta: e => e.metaKey || e.key === "Meta",
102
+ cmd: e => e.metaKey || e.key === "Command",
91
103
  arrow: e => e.key.startsWith("Arrow"),
92
104
  enter: e => e.key === "Enter",
93
105
  esc: e => e.key.startsWith("Esc"),
@@ -100,23 +112,54 @@ const keys = {
100
112
  };
101
113
 
102
114
  // augment modifiers with key testers
103
- for (let k in keys) modifier[k] = (fn, ...params) => (e) => keys[k](e) && params.every(k => keys[k]?.(e) ?? e.key === k) && fn(e)
115
+ for (let k in keys) modifier[k] = (fn, a, b) => (e) => keys[k](e) && (!a || keys[a]?.(e)) && (!b || keys[b]?.(e)) && fn(e)
116
+
104
117
 
105
118
  use({
106
- compile: expr => {
107
- return sprae.constructor(`with (arguments[0]) { ${expr} }`)
119
+ compile: expr => sprae.constructor(`with (arguments[0]) { ${expr} }`),
120
+ dir: (el, name, expr, state) => {
121
+ // sequences shortcut
122
+ if (name.includes('..')) return () => _seq(el, state, expr, name)[_dispose]
123
+ return name.split(':').reduce((prev, str) => {
124
+ let start = dir(el, str, expr, state)
125
+ return !prev ? start : (p, s) => (p = prev(), s = start(), () => { p(); s() })
126
+ }, null)
108
127
  },
109
-
110
- // signals
111
- signal, effect, computed, batch, untracked
128
+ ...signals
112
129
  })
113
130
 
131
+
114
132
  // expose for runtime config
115
133
  sprae.use = use
116
134
  sprae.store = store
117
135
  sprae.directive = directive
118
136
  sprae.modifier = modifier
119
- sprae.start = start
137
+ sprae.version = pkg.version;
138
+
139
+
140
+ /**
141
+ * Lifecycle hanger: spraes automatically any new nodes
142
+ */
143
+ const start = sprae.start = (root = document.body, values) => {
144
+ const state = store(values)
145
+ sprae(root, state);
146
+ const mo = new MutationObserver(mutations => {
147
+ for (const m of mutations) {
148
+ for (const el of m.addedNodes) {
149
+ // el can be spraed or removed by subsprae (like within :each/:if)
150
+ if (el.nodeType === 1 && el[_state] === undefined && root.contains(el)) {
151
+ // even if element has no spraeable attrs, some of its children can have
152
+ root[_add](el)
153
+ // sprae(el, state, root);
154
+ }
155
+ }
156
+ // for (const el of m.removedNodes) el[Symbol.dispose]?.()
157
+ }
158
+ });
159
+ mo.observe(root, { childList: true, subtree: true });
160
+ return state
161
+ }
162
+
120
163
 
121
164
  // version placeholder for bundler
122
165
  sprae.version = "[VI]{{inject}}[/VI]"
package/store.js CHANGED
@@ -43,7 +43,7 @@ export const store = (values, parent) => {
43
43
  },
44
44
 
45
45
  set: (_, k, v, _s) => {
46
- // console.group('SET', k, v, signals, k in signals)
46
+ // console.group('SET', k, v)
47
47
  if (k in signals) return set(signals, k, v), 1
48
48
 
49
49
  // turn off sandbox to check if parents have the prop - we don't want to create new prop in global scope
@@ -1,3 +0,0 @@
1
- import { attr, call } from "../core.js";
2
-
3
- export default (el, st, ex, name) => v => attr(el, name, call(v, el.getAttribute(name)))