sprae 11.5.1 → 11.5.3

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 CHANGED
@@ -1,5 +1,5 @@
1
- import { use, effect } from "./signal.js";
2
- import { store, _signals } from './store.js';
1
+ import { use, effect, untracked } from "./signal.js";
2
+ import { store } from './store.js';
3
3
 
4
4
  // polyfill
5
5
  export const _dispose = (Symbol.dispose ||= Symbol("dispose"));
@@ -16,8 +16,8 @@ export const directive = {}
16
16
  * @param {(expr: string) => (state: Object) => any} [p=parse] - Create evaluator from expression string.
17
17
  */
18
18
  export const dir = (name, create, p = parse) => directive[name] = (el, expr, state, name, update, evaluate) => (
19
+ update = create(el, state, expr, name),
19
20
  evaluate = p(expr),
20
- update = create(el, state, expr, name, evaluate),
21
21
  () => update(evaluate(state))
22
22
  )
23
23
 
@@ -33,9 +33,9 @@ export const sprae = (el=document.body, values) => {
33
33
  if (el[_state]) return Object.assign(el[_state], values)
34
34
 
35
35
  // take over existing state instead of creating a clone
36
- let state = store(values || {}), offs = [], fx = [],
36
+ let state = store(values || {}), offs = [], fx = []
37
37
 
38
- init = (el, attrs = el.attributes) => {
38
+ let init = (el, attrs = el.attributes) => {
39
39
  // we iterate live collection (subsprae can init args)
40
40
  if (attrs) for (let i = 0; i < attrs.length;) {
41
41
  let { name, value } = attrs[i], update, dir
@@ -49,7 +49,9 @@ export const sprae = (el=document.body, values) => {
49
49
  update = (directive[dir] || directive.default)(el, value, state, dir)
50
50
 
51
51
  // save & start effect
52
- fx.push(update), offs.push(effect(update))
52
+ fx.push(update)
53
+ // FIXME: since effect can have async start, we can just use el[_on]
54
+ offs.push(effect(update))
53
55
 
54
56
  // stop after :each, :if, :with etc.
55
57
  if (el[_state] === null) return
@@ -96,11 +98,15 @@ sprae.use = s => (
96
98
  export const parse = (expr, dir, fn) => {
97
99
  if (fn = memo[expr = expr.trim()]) return fn
98
100
 
99
- // static-time errors
101
+ // static time errors
100
102
  try { fn = compile(expr) }
101
103
  catch (e) { err(e, dir, expr) }
102
104
 
103
- return memo[expr] = fn
105
+ // run time errors
106
+ return memo[expr] = s => {
107
+ try { return fn(s) }
108
+ catch(e) { err(e, dir, expr) }
109
+ }
104
110
  }
105
111
  const memo = {};
106
112
 
@@ -1,4 +1,4 @@
1
- import { dir } from "../core.js";
1
+ import { dir, directive } from "../core.js";
2
2
 
3
3
  dir('class', (el, cur) => (
4
4
  cur = new Set,
@@ -13,3 +13,5 @@ dir('class', (el, cur) => (
13
13
  for (let cls of cur = clsx) el.classList.add(cls)
14
14
  })
15
15
  )
16
+
17
+ directive.className = directive.class // JSX alias
package/directive/each.js CHANGED
@@ -35,10 +35,14 @@ dir('each', (tpl, state, expr) => {
35
35
  for (; i < newl; i++) {
36
36
  cur[i] = newItems[i]
37
37
  let idx = i,
38
+ // FIXME: inherited state is cheaper in terms of memory and faster in terms of performance
39
+ // compared to cloning all parent signals and creating a proxy
40
+ // FIXME: besides try to avoid _signals access: we can optimize store then not checking for _signals key
38
41
  scope = store({
39
42
  [itemVar]: cur[_signals]?.[idx] || cur[idx],
40
43
  [idxVar]: keys ? keys[idx] : idx
41
44
  }, state),
45
+
42
46
  el = tpl.content ? frag(tpl) : tpl.cloneNode(true);
43
47
 
44
48
  holder.before(el.content || el);
package/directive/ref.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import { dir, parse } from "../core.js";
2
- import { setter, ensure } from "./value.js";
2
+ import { untracked } from "../signal.js";
3
+ import { setter } from "../store.js";
3
4
 
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)
5
+ dir('ref', (el, state, expr) => (
6
+ // FIXME: ideally we don't use untracked here, but ev may have internal refs that will subscribe root effect
7
+ untracked(() => typeof parse(expr)(state) == 'function') ?
8
+ v => v.call(null, el) :
9
+ (setter(expr)(state, el), _ => _)
9
10
  ))
@@ -1,5 +1,7 @@
1
- import sprae from "../core.js";
2
- import { dir, parse } from "../core.js";
1
+ import sprae, { parse } from "../core.js";
2
+ import { dir } from "../core.js";
3
+ import { untracked } from "../signal.js";
4
+ import { setter } from "../store.js";
3
5
  import { attr } from './default.js';
4
6
 
5
7
 
@@ -29,9 +31,6 @@ dir('value', (el, state, expr) => {
29
31
  } :
30
32
  (value) => (el.value = value);
31
33
 
32
- // make sure value exists in state
33
- ensure(state, expr)
34
-
35
34
  // bind back to value, but some values can be not bindable, eg. `:value="7"`
36
35
  try {
37
36
  const set = setter(expr)
@@ -48,20 +47,10 @@ dir('value', (el, state, expr) => {
48
47
  // select options must be initialized before calling an update
49
48
  sprae(el, state)
50
49
  }
50
+
51
+ // initial state value
52
+ untracked(()=>parse(expr)(state)) ?? handleChange()
51
53
  } catch {}
52
54
 
53
55
  return update
54
56
  })
55
-
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,4 +1,10 @@
1
1
  import sprae, { dir } from "../core.js";
2
+ import { untracked } from "../signal.js";
2
3
  import store, { _signals } from '../store.js';
3
4
 
4
- dir('with', (el, rootState, state) => (state=null, values => sprae(el, state ? values : state = store(values, rootState))))
5
+ dir('with', (el, rootState, state) => (
6
+ state=null,
7
+ values => //untracked(() => (
8
+ sprae(el, state ? values : state = store(values, rootState))
9
+ //))
10
+ ))
@@ -64,60 +64,64 @@ var init_signal = __esm({
64
64
  });
65
65
 
66
66
  // store.js
67
- var _signals, _change, store, list, mut, set, store_default;
67
+ var _signals, _change, _stash, store, list, mut, set, setter, store_default;
68
68
  var init_store = __esm({
69
69
  "store.js"() {
70
70
  init_signal();
71
+ init_core();
71
72
  _signals = Symbol("signals");
72
73
  _change = Symbol("change");
74
+ _stash = "__";
73
75
  store = (values, parent) => {
74
76
  if (!values) return values;
75
77
  if (values[_signals] || values[Symbol.toStringTag]) return values;
76
78
  if (values.constructor !== Object) return Array.isArray(values) ? list(values) : values;
77
- let signals = { ...parent?.[_signals] }, _len = signal(Object.values(values).length), state = new Proxy(signals, {
78
- get: (_, key) => key === _change ? _len : key === _signals ? signals : signals[key]?.valueOf(),
79
- set: (_, key, v, s) => (s = signals[key], set(signals, key, v), s ?? ++_len.value, 1),
79
+ let signals = Object.create(parent?.[_signals] || {}), _len = signal(Object.keys(values).length), stash;
80
+ let state = new Proxy(signals, {
81
+ get: (_, k) => k === _change ? _len : k === _signals ? signals : k === _stash ? stash : k in signals ? signals[k]?.valueOf() : globalThis[k],
82
+ set: (_, k, v, s) => k === _stash ? (stash = v, 1) : (s = k in signals, set(signals, k, v), s || ++_len.value),
80
83
  // bump length for new signal
81
- deleteProperty: (_, key) => (signals[key] && (signals[key][Symbol.dispose]?.(), delete signals[key], _len.value--), 1),
84
+ deleteProperty: (_, k) => (signals[k] && (signals[k][Symbol.dispose]?.(), delete signals[k], _len.value--), 1),
82
85
  // subscribe to length when object is spread
83
- ownKeys: () => (_len.value, Reflect.ownKeys(signals))
84
- }), descs = Object.getOwnPropertyDescriptors(values), desc;
85
- for (let key in values) {
86
- if ((desc = descs[key])?.get)
87
- (signals[key] = computed(desc.get.bind(state)))._set = desc.set?.bind(state);
86
+ ownKeys: () => (_len.value, Reflect.ownKeys(signals)),
87
+ has: (_) => true
88
+ // sandbox prevents writing to global
89
+ }), descs = Object.getOwnPropertyDescriptors(values);
90
+ for (let k in values) {
91
+ if (descs[k]?.get)
92
+ (signals[k] = computed(descs[k].get.bind(state)))._set = descs[k].set?.bind(state);
88
93
  else
89
- signals[key] = null, set(signals, key, values[key]);
94
+ signals[k] = null, set(signals, k, values[k]);
90
95
  }
91
96
  return state;
92
97
  };
93
98
  list = (values) => {
94
99
  let lastProp, _len = signal(values.length), signals = Array(values.length).fill(), state = new Proxy(signals, {
95
- get(_, key) {
96
- if (typeof key === "symbol") return key === _change ? _len : key === _signals ? signals : signals[key];
97
- if (key === "length") return mut.includes(lastProp) ? _len.peek() : _len.value;
98
- lastProp = key;
99
- if (signals[key]) return signals[key].valueOf();
100
- if (key < signals.length) return (signals[key] = signal(store(values[key]))).value;
100
+ get(_, k) {
101
+ if (typeof k === "symbol") return k === _change ? _len : k === _signals ? signals : signals[k];
102
+ if (k === "length") return mut.includes(lastProp) ? _len.peek() : _len.value;
103
+ lastProp = k;
104
+ return (signals[k] ?? (signals[k] = signal(store(values[k])))).valueOf();
101
105
  },
102
- set(_, key, v) {
103
- if (key === "length") {
106
+ set(_, k, v) {
107
+ if (k === "length") {
104
108
  for (let i = v; i < signals.length; i++) delete state[i];
105
109
  _len.value = signals.length = v;
106
110
  } else {
107
- set(signals, key, v);
108
- if (key >= _len.peek()) _len.value = signals.length = +key + 1;
111
+ set(signals, k, v);
112
+ if (k >= _len.peek()) _len.value = signals.length = +k + 1;
109
113
  }
110
114
  return 1;
111
115
  },
112
- deleteProperty: (_, key) => (signals[key]?.[Symbol.dispose]?.(), delete signals[key], 1)
116
+ deleteProperty: (_, k) => (signals[k]?.[Symbol.dispose]?.(), delete signals[k], 1)
113
117
  });
114
118
  return state;
115
119
  };
116
120
  mut = ["push", "pop", "shift", "unshift", "splice"];
117
- set = (signals, key, v) => {
118
- let s = signals[key], cur;
119
- if (key[0] === "_") signals[key] = v;
120
- else if (!s) signals[key] = s = v?.peek ? v : signal(store(v));
121
+ set = (signals, k, v) => {
122
+ let s = signals[k], cur;
123
+ if (k[0] === "_") signals[k] = v;
124
+ else if (!s) signals[k] = s = v?.peek ? v : signal(store(v));
121
125
  else if (v === (cur = s.peek())) ;
122
126
  else if (s._set) s._set(v);
123
127
  else if (Array.isArray(v) && Array.isArray(cur)) {
@@ -128,6 +132,8 @@ var init_store = __esm({
128
132
  else s.value = v;
129
133
  } else s.value = store(v);
130
134
  };
135
+ setter = (expr, set2 = parse(`${expr}=${_stash}`)) => (state, value) => (state[_stash] = value, // save value to stash
136
+ set2(state));
131
137
  store_default = store;
132
138
  }
133
139
  });
@@ -143,17 +149,19 @@ var init_core = __esm({
143
149
  _on = Symbol("on");
144
150
  _off = Symbol("off");
145
151
  directive = {};
146
- dir = (name, create, p = parse) => directive[name] = (el, expr, state, name2, update, evaluate) => (evaluate = p(expr), update = create(el, state, expr, name2, evaluate), () => update(evaluate(state)));
152
+ dir = (name, create, p = parse) => directive[name] = (el, expr, state, name2, update, evaluate) => (update = create(el, state, expr, name2), evaluate = p(expr), () => update(evaluate(state)));
147
153
  sprae = (el = document.body, values) => {
148
154
  if (el[_state]) return Object.assign(el[_state], values);
149
- let state = store(values || {}), offs = [], fx = [], init = (el2, attrs = el2.attributes) => {
155
+ let state = store(values || {}), offs = [], fx = [];
156
+ let init = (el2, attrs = el2.attributes) => {
150
157
  if (attrs) for (let i = 0; i < attrs.length; ) {
151
158
  let { name, value } = attrs[i], update, dir2;
152
159
  if (name.startsWith(prefix)) {
153
160
  el2.removeAttribute(name);
154
161
  for (dir2 of name.slice(prefix.length).split(":")) {
155
162
  update = (directive[dir2] || directive.default)(el2, value, state, dir2);
156
- fx.push(update), offs.push(effect(update));
163
+ fx.push(update);
164
+ offs.push(effect(update));
157
165
  if (el2[_state] === null) return;
158
166
  }
159
167
  } else i++;
@@ -177,7 +185,13 @@ var init_core = __esm({
177
185
  } catch (e) {
178
186
  err(e, dir2, expr);
179
187
  }
180
- return memo[expr] = fn;
188
+ return memo[expr] = (s) => {
189
+ try {
190
+ return fn(s);
191
+ } catch (e) {
192
+ err(e, dir2, expr);
193
+ }
194
+ };
181
195
  };
182
196
  memo = {};
183
197
  err = (e, dir2 = "", expr = "") => {
@@ -300,6 +314,82 @@ var init_each = __esm({
300
314
  }
301
315
  });
302
316
 
317
+ // directive/ref.js
318
+ var init_ref = __esm({
319
+ "directive/ref.js"() {
320
+ init_core();
321
+ init_signal();
322
+ init_store();
323
+ dir("ref", (el, state, expr) => (
324
+ // FIXME: ideally we don't use untracked here, but ev may have internal refs that will subscribe root effect
325
+ untracked(() => typeof parse(expr)(state) == "function") ? (v) => v.call(null, el) : (setter(expr)(state, el), (_) => _)
326
+ ));
327
+ }
328
+ });
329
+
330
+ // directive/with.js
331
+ var init_with = __esm({
332
+ "directive/with.js"() {
333
+ init_core();
334
+ init_signal();
335
+ init_store();
336
+ dir("with", (el, rootState, state) => (state = null, (values) => (
337
+ //untracked(() => (
338
+ core_default(el, state ? values : state = store_default(values, rootState))
339
+ )));
340
+ }
341
+ });
342
+
343
+ // directive/text.js
344
+ var init_text = __esm({
345
+ "directive/text.js"() {
346
+ init_core();
347
+ dir("text", (el) => (
348
+ // <template :text="a"/> or previously initialized template
349
+ (el.content && el.replaceWith(el = frag(el).childNodes[0]), (value) => el.textContent = value == null ? "" : value)
350
+ ));
351
+ }
352
+ });
353
+
354
+ // directive/class.js
355
+ var init_class = __esm({
356
+ "directive/class.js"() {
357
+ init_core();
358
+ dir(
359
+ "class",
360
+ (el, cur) => (cur = /* @__PURE__ */ new Set(), (v) => {
361
+ let clsx = /* @__PURE__ */ new Set();
362
+ if (v) {
363
+ if (typeof v === "string") v.split(" ").map((cls) => clsx.add(cls));
364
+ else if (Array.isArray(v)) v.map((v2) => v2 && clsx.add(v2));
365
+ else Object.entries(v).map(([k, v2]) => v2 && clsx.add(k));
366
+ }
367
+ for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls);
368
+ else el.classList.remove(cls);
369
+ for (let cls of cur = clsx) el.classList.add(cls);
370
+ })
371
+ );
372
+ directive.className = directive.class;
373
+ }
374
+ });
375
+
376
+ // directive/style.js
377
+ var init_style = __esm({
378
+ "directive/style.js"() {
379
+ init_core();
380
+ dir(
381
+ "style",
382
+ (el, initStyle) => (initStyle = el.getAttribute("style"), (v) => {
383
+ if (typeof v === "string") el.setAttribute("style", initStyle + (initStyle.endsWith(";") ? "" : "; ") + v);
384
+ else {
385
+ if (initStyle) el.setAttribute("style", initStyle);
386
+ for (let k in v) k[0] == "-" ? el.style.setProperty(k, v[k]) : el.style[k] = v[k];
387
+ }
388
+ })
389
+ );
390
+ }
391
+ });
392
+
303
393
  // directive/default.js
304
394
  var mods, keys, throttle, debounce, attr, dashcase;
305
395
  var init_default = __esm({
@@ -449,11 +539,12 @@ var init_default = __esm({
449
539
  });
450
540
 
451
541
  // directive/value.js
452
- var setter, ensure;
453
542
  var init_value = __esm({
454
543
  "directive/value.js"() {
455
544
  init_core();
456
545
  init_core();
546
+ init_signal();
547
+ init_store();
457
548
  init_default();
458
549
  dir("value", (el, state, expr) => {
459
550
  const update = el.type === "text" || el.type === "" ? (value) => el.setAttribute("value", el.value = value == null ? "" : value) : el.tagName === "TEXTAREA" || el.type === "text" || el.type === "" ? (value, from, to) => (
@@ -467,7 +558,6 @@ var init_value = __esm({
467
558
  for (let o of el.options) o.removeAttribute("selected");
468
559
  for (let v of value) el.querySelector(`[value="${v}"]`).setAttribute("selected", "");
469
560
  } : (value) => el.value = value;
470
- ensure(state, expr);
471
561
  try {
472
562
  const set2 = setter(expr);
473
563
  const handleChange = el.type === "checkbox" ? () => set2(state, el.checked) : el.type === "select-multiple" ? () => set2(state, [...el.selectedOptions].map((o) => o.value)) : () => set2(state, el.selectedIndex < 0 ? null : el.value);
@@ -476,85 +566,11 @@ var init_value = __esm({
476
566
  new MutationObserver(handleChange).observe(el, { childList: true, subtree: true, attributes: true });
477
567
  core_default(el, state);
478
568
  }
569
+ untracked(() => parse(expr)(state)) ?? handleChange();
479
570
  } catch {
480
571
  }
481
572
  return update;
482
573
  });
483
- setter = (expr, set2 = parse(`${expr}=__`)) => (
484
- // FIXME: if there's a simpler way to set value in justin?
485
- (state, value) => (state.__ = value, set2(state, value), delete state.__)
486
- );
487
- ensure = (state, expr, name = expr.match(/^\w+(?=\s*(?:\.|\[|$))/)) => {
488
- var _a;
489
- return name && (state[_a = name[0]] ?? (state[_a] = null));
490
- };
491
- }
492
- });
493
-
494
- // directive/ref.js
495
- var init_ref = __esm({
496
- "directive/ref.js"() {
497
- init_core();
498
- init_value();
499
- dir("ref", (el, state, expr, _, ev) => (ensure(state, expr), ev(state) == null ? (setter(expr)(state, el), (_2) => _2) : (v) => v.call(null, el)));
500
- }
501
- });
502
-
503
- // directive/with.js
504
- var init_with = __esm({
505
- "directive/with.js"() {
506
- init_core();
507
- init_store();
508
- dir("with", (el, rootState, state) => (state = null, (values) => core_default(el, state ? values : state = store_default(values, rootState))));
509
- }
510
- });
511
-
512
- // directive/text.js
513
- var init_text = __esm({
514
- "directive/text.js"() {
515
- init_core();
516
- dir("text", (el) => (
517
- // <template :text="a"/> or previously initialized template
518
- (el.content && el.replaceWith(el = frag(el).childNodes[0]), (value) => el.textContent = value == null ? "" : value)
519
- ));
520
- }
521
- });
522
-
523
- // directive/class.js
524
- var init_class = __esm({
525
- "directive/class.js"() {
526
- init_core();
527
- dir(
528
- "class",
529
- (el, cur) => (cur = /* @__PURE__ */ new Set(), (v) => {
530
- let clsx = /* @__PURE__ */ new Set();
531
- if (v) {
532
- if (typeof v === "string") v.split(" ").map((cls) => clsx.add(cls));
533
- else if (Array.isArray(v)) v.map((v2) => v2 && clsx.add(v2));
534
- else Object.entries(v).map(([k, v2]) => v2 && clsx.add(k));
535
- }
536
- for (let cls of cur) if (clsx.has(cls)) clsx.delete(cls);
537
- else el.classList.remove(cls);
538
- for (let cls of cur = clsx) el.classList.add(cls);
539
- })
540
- );
541
- }
542
- });
543
-
544
- // directive/style.js
545
- var init_style = __esm({
546
- "directive/style.js"() {
547
- init_core();
548
- dir(
549
- "style",
550
- (el, initStyle) => (initStyle = el.getAttribute("style"), (v) => {
551
- if (typeof v === "string") el.setAttribute("style", initStyle + (initStyle.endsWith(";") ? "" : "; ") + v);
552
- else {
553
- if (initStyle) el.setAttribute("style", initStyle);
554
- for (let k in v) k[0] == "-" ? el.style.setProperty(k, v[k]) : el.style[k] = v[k];
555
- }
556
- })
557
- );
558
574
  }
559
575
  });
560
576
 
@@ -590,7 +606,19 @@ var init_data = __esm({
590
606
  // sprae.js
591
607
  var sprae_exports = {};
592
608
  __export(sprae_exports, {
593
- default: () => sprae_default
609
+ _change: () => _change,
610
+ _signals: () => _signals,
611
+ _stash: () => _stash,
612
+ batch: () => batch,
613
+ computed: () => computed,
614
+ default: () => sprae_default,
615
+ effect: () => effect,
616
+ list: () => list,
617
+ setter: () => setter,
618
+ signal: () => signal,
619
+ store: () => store,
620
+ untracked: () => untracked,
621
+ use: () => use
594
622
  });
595
623
  var sprae_default;
596
624
  var init_sprae = __esm({
@@ -608,6 +636,8 @@ var init_sprae = __esm({
608
636
  init_default();
609
637
  init_aria();
610
638
  init_data();
639
+ init_store();
640
+ init_signal();
611
641
  core_default.use({ compile: (expr) => core_default.constructor(`with (arguments[0]) { return ${expr} };`) });
612
642
  sprae_default = core_default;
613
643
  }