webdetta 0.1.226 → 0.1.228

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webdetta",
3
- "version": "0.1.226",
3
+ "version": "0.1.228",
4
4
  "author": "Fedot Kriutchenko <fodyadev@gmail.com>",
5
5
  "description": "",
6
6
  "license": "MIT",
@@ -51,24 +51,6 @@ val(1); // logs "Value: 1"
51
51
  val(2); // logs "Value: 2"
52
52
  ```
53
53
 
54
- ### `r.untrack(func)`
55
-
56
- ```javascript
57
- const a = r.val(2);
58
- const b = r.val(3);
59
-
60
- r.effect(() => {
61
- const aVal = a();
62
- r.untrack(() => {
63
- const result = aVal * b();
64
- console.log('Result:', result);
65
- });
66
- });
67
-
68
- a(4); // run outer effect
69
- b(5); // does not run outer effect (b was only read inside untrack)
70
- ```
71
-
72
54
  ### Effect cleanup (return from handler)
73
55
 
74
56
  ```javascript
@@ -80,7 +62,7 @@ r.effect(() => {
80
62
 
81
63
  ## Computed values
82
64
 
83
- ### `r.computed(func, { type, initial, resolvePromises }?)`
65
+ ### `r.computed(func, { initial }?)`
84
66
 
85
67
  ```javascript
86
68
  const a = r.val(2);
@@ -90,38 +72,6 @@ const sum = r.computed(() => a() + b());
90
72
  sum(); // 5
91
73
  ```
92
74
 
93
- ```javascript
94
- const source = r.val(1);
95
- const doubled = r.computed(
96
- () => source() * 2,
97
- { type: r.dval, initial: 0 }, // custom signal type
98
- );
99
- ```
100
-
101
- ```javascript
102
- const url = r.val('/api/data');
103
- const data = r.computed(() => {
104
- const currentUrl = url();
105
- return fetch(currentUrl).then(r => r.json()); // resolves Promise by default
106
- });
107
-
108
- r.effect(() => {
109
- console.log(data()); // parsed response after Promise resolves
110
- });
111
- ```
112
-
113
- ```javascript
114
- const source = r.val(1);
115
- const promise = r.computed(
116
- () => Promise.resolve(source() * 3),
117
- { resolvePromises: false }, // store Promise as-is
118
- );
119
-
120
- r.effect(() => {
121
- console.log(promise()); // Promise { 3 }
122
- });
123
- ```
124
-
125
75
  ### `r.store(target)`
126
76
 
127
77
  ```javascript
@@ -84,8 +84,6 @@ export class Effect {
84
84
  run() {
85
85
  if (this.destroyed) return;
86
86
 
87
-
88
-
89
87
  this.cleanup();
90
88
 
91
89
  let err;
@@ -39,6 +39,28 @@ r.dval = value => {
39
39
  return signal.accessor;
40
40
  }
41
41
 
42
+ // Effect
43
+
44
+ r.effect = (handler, {
45
+ track = true,
46
+ attach = true,
47
+ writes = undefined,
48
+ run = true,
49
+ onError,
50
+ } = {}) => {
51
+ assertSyncFunction('effect `handler`', handler);
52
+ const parent = currentEffect();
53
+ const effect = new Effect({
54
+ parent: attach ? parent : null,
55
+ handler,
56
+ errorHandler: onError,
57
+ tracking: track,
58
+ readonly: writes !== undefined ? !writes : parent?.readonly,
59
+ });
60
+ if (run) effect.run();
61
+ return effect;
62
+ }
63
+
42
64
  // Derived
43
65
 
44
66
  r.computed = (func, { initial }={}) => {
@@ -48,13 +70,10 @@ r.computed = (func, { initial }={}) => {
48
70
  get() { return value; },
49
71
  set(v) { value = v; this.trigger(); return value; }
50
72
  });
51
- const effect = new Effect({
52
- parent: currentEffect(),
53
- handler: () => { value = func(); signal.trigger(); },
54
- tracking: true,
55
- readonly: true
56
- });
57
- effect.run();
73
+ r.effect(() => {
74
+ value = func();
75
+ signal.trigger();
76
+ }, { track: true, writes: false });
58
77
  return signal.accessor;
59
78
  }
60
79
 
@@ -80,26 +99,20 @@ r.resource = (source, func, { initial }) => {
80
99
  }
81
100
  }
82
101
 
83
- const effect = new Effect({
84
- parent: currentEffect(),
85
- handler: () => {
86
- const boundFunc = func.bind(this, source());
87
- r.untrack(() => {
88
- const res = resolveResource(effect, boundFunc, resourceHandlers);
89
- return res.destroy
90
- });
91
- },
92
- tracking: true,
93
- readonly: true
94
- });
95
- effect.run();
102
+ const effect = r.effect(() => {
103
+ const boundFunc = func.bind(this, source());
104
+ r.effect(() => {
105
+ const res = resolveResource(effect, boundFunc, resourceHandlers);
106
+ return res.destroy
107
+ }, { track: false });
108
+ }, { track: true, writes: false });
96
109
 
97
110
  return Object.assign(value, { error, loading });
98
111
  }
99
112
 
100
113
  // Stores
101
114
 
102
- const Store = ({ target, updateTarget=false }) => {
115
+ const createStore = ({ target, updateTarget=false }) => {
103
116
  const refs = {};
104
117
  const ref = key => refs[key] ??= new Signal({
105
118
  get() { return currentTarget[key]; },
@@ -120,54 +133,25 @@ const Store = ({ target, updateTarget=false }) => {
120
133
  return { ref }
121
134
  }
122
135
  r.store = (target, { updateTarget }={}) => {
123
- const { ref } = Store({ target, updateTarget });
136
+ const { ref } = createStore({ target, updateTarget });
124
137
  return new Proxy({}, {
125
138
  get(_, key) { return ref(key).accessor(); },
126
139
  set(_, key, val) { return ref(key).accessor(val); }
127
140
  });
128
141
  }
129
142
  r.proxy = (target, { updateTarget }={}) => {
130
- const { ref } = Store({ target, updateTarget });
143
+ const { ref } = createStore({ target, updateTarget });
131
144
  return new Proxy({}, {
132
145
  get(_, key) { return ref(key).accessor; }
133
146
  });
134
147
  }
135
148
 
136
- // Effects
137
-
138
- r.effect = (handler, { onError, readonly }={}) => {
139
- assertSyncFunction('r.effect `handler`', handler);
140
- const effect = new Effect({
141
- parent: currentEffect(),
142
- handler,
143
- errorHandler: onError,
144
- tracking: true,
145
- readonly: readonly
146
- });
147
- effect.run();
148
- return effect;
149
- };
150
- r.untrack = (handler, { onError, readonly }={}) => {
151
- assertSyncFunction('r.untrack `handler`', handler);
152
- const effect = new Effect({
153
- parent: currentEffect(),
154
- handler,
155
- errorHandler: onError,
156
- tracking: false,
157
- readonly,
158
- });
159
- effect.run();
160
- return effect;
161
- };
162
-
163
149
  // Utils
164
150
 
165
- r.subtle = {};
166
- r.subtle.effectRoot = handler => currentEffect.run(null, r.untrack, handler);
167
- r.subtle.onCleanup = handler => {
168
- assertSyncFunction('r.cleanup `handler`', handler);
151
+ r.onCleanup = handler => {
152
+ assertSyncFunction('r.onCleanup `handler`', handler);
169
153
  const effect = currentEffect();
170
- if (!effect) throw new Error('r.cleanup cannot be executed outside r.effect');
154
+ if (!effect) throw new Error('r.onCleanup cannot be executed outside r.effect');
171
155
  (effect.oncleanup ??= []).push(handler);
172
156
  }
173
157
 
@@ -337,7 +337,7 @@ describe('effect', () => {
337
337
  });
338
338
  });
339
339
 
340
- describe('untrack', () => {
340
+ describe('effect untracked', () => {
341
341
  it('no subscribe', () => {
342
342
  const left = r.val(2);
343
343
  const right = r.val(3);
@@ -347,9 +347,9 @@ describe('untrack', () => {
347
347
  r.effect(() => {
348
348
  outerRunCount++;
349
349
  const leftValue = left();
350
- r.untrack(() => {
350
+ r.effect(() => {
351
351
  seen.push(leftValue * right());
352
- });
352
+ }, { track: false });
353
353
  });
354
354
 
355
355
  assertEquals(outerRunCount, 1);
@@ -374,11 +374,11 @@ describe('untrack', () => {
374
374
  const reset = () => log.length = 0;
375
375
  r.effect(() => {
376
376
  const active = branch();
377
- r.untrack(() => {
377
+ r.effect(() => {
378
378
  const value = active === 'left' ? left() : right();
379
379
  log.push(`${active}:${value}`);
380
380
  return () => log.push(`clean:${active}:${value}`);
381
- });
381
+ }, { track: false });
382
382
  });
383
383
 
384
384
  assertEquals(log, ['left:1']);
@@ -1,5 +1,5 @@
1
1
  import { describe, it } from 'jsr:@std/testing/bdd';
2
- import { assert, assertEquals, assertThrows } from 'jsr:@std/assert';
2
+ import { assertEquals, assertThrows } from 'jsr:@std/assert';
3
3
  import { r } from '../index.js';
4
4
 
5
5
  const watchValues = read => {
@@ -187,19 +187,6 @@ describe('computed', () => {
187
187
  );
188
188
  });
189
189
 
190
- it('returns raw promise without resolving', () => {
191
- const source = r.val(1);
192
- const value = r.computed(() => Promise.resolve(source() * 3));
193
-
194
- const firstPromise = value();
195
- assert(firstPromise instanceof Promise);
196
-
197
- source(2);
198
-
199
- const secondPromise = value();
200
- assert(secondPromise instanceof Promise);
201
- assert(secondPromise !== firstPromise);
202
- });
203
190
  });
204
191
 
205
192
  const describeStore = (name, create, read, write) => describe(name, () => {
@@ -1,6 +1,5 @@
1
1
  import { Builder } from '../builder/index.js';
2
2
  import { unwrapFn, templateCallToArray } from '../common/utils.js';
3
- import { domAppendTrigger, domRemoveTrigger } from './dynamic.js';
4
3
  import { r } from '../reactivity/index.js';
5
4
 
6
5
  export const toString = (...args) => {
@@ -11,8 +10,9 @@ export const toString = (...args) => {
11
10
 
12
11
  const isFragment = node => node.nodeType === 11;
13
12
  export const processItem = (item, processOperator, processNode, flattenFragments=false) => {
14
- if (item === undefined || item === null) {}
15
- else if (Array.isArray(item)) {
13
+ if (item === false || item === undefined || item === null) {
14
+
15
+ } else if (Array.isArray(item)) {
16
16
  for (const d of item) processItem(d, processOperator, processNode, flattenFragments);
17
17
  } else if (Operator.isOperator(item)) {
18
18
  processOperator(item);
@@ -26,7 +26,7 @@ export const processItem = (item, processOperator, processNode, flattenFragments
26
26
  }
27
27
  }
28
28
 
29
- export const Element = (tag, ns) => (...args) => {
29
+ export const Element = (ns, tag, ...args) => {
30
30
  const node = (
31
31
  tag === '!' ? document.createComment('') :
32
32
  tag === ':' ? document.createDocumentFragment() :
@@ -45,10 +45,15 @@ Element.from = arg => {
45
45
  return document.createTextNode(arg);
46
46
  }
47
47
 
48
+ const elementHooks = new WeakMap();
49
+ Element.registerHooks = (node, hooks) => elementHooks.set(node, hooks);
50
+
48
51
  const performAppend = (node, method, item) => {
49
- const dom = Element.from(item);
50
- node[method](dom);
51
- domAppendTrigger(dom);
52
+ const itemNode = Element.from(item);
53
+ const hooks = elementHooks.get(itemNode);
54
+ hooks?.beforeAppend?.();
55
+ node[method](itemNode);
56
+ hooks?.afterAppend?.();
52
57
  }
53
58
  Element.append = (node, item) => {
54
59
  processItem(item,
@@ -62,17 +67,18 @@ Element.appendBefore = (node, sibling) => performAppend(node, 'before', sibling)
62
67
  Element.appendAfter = (node, sibling) => performAppend(node, 'after', sibling);
63
68
 
64
69
  Element.remove = (node) => {
65
- domRemoveTrigger(node);
70
+ const hooks = elementHooks.get(node);
71
+ hooks?.beforeRemove?.();
66
72
  node.remove();
73
+ hooks?.afterRemove?.();
67
74
  }
68
75
 
69
76
  export const Operator = (...funcs) => Builder((tasks, node) => {
70
- for (const {names, args} of tasks)
71
- for (const func of funcs)
72
- func(node, names, args);
77
+ for (const {names, args} of tasks) {
78
+ for (const func of funcs) {
79
+ r.effect(() => func(node, names, args));
80
+ }
81
+ }
73
82
  });
74
83
  Operator.isOperator = Builder.isBuilder;
75
- Operator.apply = (node, operator) => Builder.launch(operator, node);
76
- Operator.extend = (operator, { get }) => new Proxy(operator, {
77
- get: (_, key) => get(_, key) ?? operator[key]
78
- });
84
+ Operator.apply = (node, operator) => Builder.launch(operator, node);
@@ -1,31 +1,11 @@
1
1
  import { isIterable, isObject, unwrapFn } from '../common/utils.js';
2
+ import { once } from '../execution/index.js';
2
3
  import { r } from '../reactivity/index.js';
3
4
  import { Element, Operator, processItem } from './base.js';
4
5
 
5
- const domHandlers = () => {
6
- const map = new WeakMap();
7
- const on = (dom, f) => {
8
- let list = map.get(dom);
9
- if (!list) map.set(dom, list = []);
10
- list.push(f);
11
- }
12
- const trigger = (dom) => {
13
- const list = map.get(dom);
14
- if (!list) return;
15
- for (const f of list) f();
16
- }
17
- return [on, trigger];
18
- }
19
-
20
- export const [onDomAppend, domAppendTrigger] = domHandlers();
21
- export const [onDomRemove, domRemoveTrigger] = domHandlers();
22
-
23
6
  const listItemKey = (d, i) => {
24
7
  if (typeof d == 'number' || typeof d == 'string') return d;
25
- if (d) {
26
- if ('key' in d) return d.key;
27
- if ('id' in d) return d.id;
28
- }
8
+ if (d && Object.hasOwn(d, 'id')) return d.id;
29
9
  return i;
30
10
  };
31
11
  const listItemsToEntries = (items, keyFn) => new Map(
@@ -33,138 +13,114 @@ const listItemsToEntries = (items, keyFn) => new Map(
33
13
  : isIterable(items) ? Array.from(items.entries())
34
14
  : isObject(items) ? Object.entries(items)
35
15
  : null
36
- )
37
- export const createList = (itemsFn, renderItem, keyFn = listItemKey) => {
38
- const root = document.createTextNode('');
39
-
40
- const elements = new Map();
41
- const effects = new Map();
42
-
43
- const connect = (k, v, i, items) => {
44
- let dom;
45
- const effect = r.subtle.effectRoot(() => {
46
- dom = renderItem(v, i, items, k);
47
- });
48
- effects.set(k, effect);
49
- elements.set(k, dom);
50
- return dom;
51
- };
52
- const disconnect = (k) => {
53
- effects.get(k)?.destroy();
54
- effects.delete(k);
55
- const el = elements.get(k);
56
- if (el) Element.remove(el);
57
- elements.delete(k);
16
+ );
17
+
18
+ const createContainer = (content) => {
19
+ let startNode;
20
+
21
+ const nodes = [], operators = [];
22
+ const contentEffect = r.effect(() => {
23
+ const items = unwrapFn(content);
24
+ processItem(items, o => operators.push(o), c => nodes.push(c), true);
25
+ if (startNode) appendAfter(startNode);
26
+ return () => {
27
+ for (const child of nodes) Element.remove(child);
28
+ nodes.length = 0;
29
+ operators.length = 0;
30
+ }
31
+ }, { writes: false, attach: false, track: true, run: false });
32
+
33
+ const operatorsEffect = r.effect(() => {
34
+ for (const o of operators) Operator.apply(startNode.parentNode, o);
35
+ }, { writes: false, attach: false, track: true, run: false });
36
+
37
+ const initContent = once(contentEffect.run.bind(contentEffect));
38
+ const appendAfter = (newStartNode) => {
39
+ initContent();
40
+ const parentChanged = startNode?.parentNode != newStartNode.parentNode;
41
+ let lastNode = startNode = newStartNode;
42
+ if (parentChanged) operatorsEffect.run();
43
+ for (const node of nodes) {
44
+ if (node !== lastNode.nextSibling) Element.appendAfter(lastNode, node);
45
+ lastNode = node;
46
+ }
47
+ return lastNode;
58
48
  };
59
49
 
60
- const attached = r.dval(false);
50
+ const remove = () => {
51
+ contentEffect.cleanup();
52
+ operatorsEffect.cleanup();
53
+ }
54
+ const destroy = () => {
55
+ contentEffect.destroy();
56
+ operatorsEffect.destroy();
57
+ }
58
+
59
+ return { nodes, appendAfter, remove, destroy };
60
+ }
61
61
 
62
- r.effect(() => {
63
- if (!attached()) {
64
- for (const k of elements.keys()) disconnect(k);
65
- return;
62
+ export const createList = (itemsFn, renderItem, keyFn = listItemKey) => {
63
+ const root = document.createTextNode('');
64
+ const containers = new Map();
65
+ Element.registerHooks(root, {
66
+ afterAppend: () => effect.run(),
67
+ beforeRemove: () => {
68
+ for (const c of containers.values()) c.remove();
69
+ containers.clear();
66
70
  }
71
+ });
67
72
 
73
+ const effect = r.effect(() => {
68
74
  const items = unwrapFn(itemsFn);
69
75
  const entries = listItemsToEntries(items, keyFn);
70
76
 
71
77
  let last = root, i = 0;
72
78
  for (const [k, v] of entries) {
73
- let el = elements.get(k);
74
- if (!el) el = connect(k, v, i, items);
75
- if (el !== last.nextSibling) {
76
- Element.appendAfter(last, el);
77
- }
78
- last = el;
79
+ let container = containers.get(k);
80
+ if (!container) containers.set(k, container = createContainer(
81
+ () => renderItem(v, i, items, k)
82
+ ));
83
+ last = container.appendAfter(last);
79
84
  i++;
80
85
  }
81
86
 
82
- for (const k of elements.keys()) {
83
- if (!entries.has(k)) disconnect(k);
87
+ for (const [k, c] of containers) {
88
+ if (!entries.has(k)) {
89
+ c.remove();
90
+ containers.delete(k);
91
+ }
84
92
  }
85
- });
86
-
87
- onDomAppend(root, () => attached(true));
88
- onDomRemove(root, () => attached(false));
93
+ }, { run: false });
89
94
 
90
95
  return root;
91
96
  };
92
97
 
93
98
  export const createSlot = (content) => {
94
- const node = document.createTextNode('');
95
- const attached = r.dval(false);
96
-
97
- let controller;
98
- let nodes = [];
99
- const append = (content) => {
100
- if (!content) return;
101
- nodes = [];
102
-
103
- controller = r.subtle.effectRoot(() => {
104
- let last = node;
105
- processItem(content,
106
- op => {
107
- Operator.apply(node.parentNode, op);
108
- },
109
- child => {
110
- Element.appendAfter(last, child);
111
- nodes.push(last = child);
112
- },
113
- true
114
- );
115
- });
116
- };
117
-
118
- const remove = () => {
119
- if (!nodes) return;
120
- for (const child of nodes) Element.remove(child);
121
- nodes = null;
122
- controller?.destroy();
123
- controller = null;
124
- }
125
-
126
- r.effect(() => {
127
- remove();
128
- if (!attached()) return;
129
- append(content());
99
+ const root = document.createTextNode('');
100
+ const container = createContainer(content);
101
+ Element.registerHooks(root, {
102
+ afterAppend: () => container.appendAfter(root),
103
+ beforeRemove: () => container.remove()
130
104
  });
131
-
132
- onDomAppend(node, () => attached(true));
133
- onDomRemove(node, () => attached(false));
134
-
135
- return node;
105
+ return root;
136
106
  }
137
107
 
138
108
  export const createIf = () => {
139
- const conditions = r.val([]);
140
- const content = r.computed(() => conditions().find(d => unwrapFn(d.cond))?.args);
141
-
142
- const node = createSlot(content);
109
+ const conditions = [];
110
+ const node = createSlot(() =>
111
+ conditions.find(d => unwrapFn(d.cond))?.value
112
+ );
113
+
143
114
  node.elif = (cond, ...args) => {
144
- const list = conditions();
145
- list.push({ cond, args });
146
- conditions(list);
115
+ conditions.push({ cond, value: args });
147
116
  return node;
148
117
  }
149
118
  node.else = (...args) => {
150
- node.elif(true, ...args);
119
+ conditions.push({ cond: true, value: args });
151
120
  delete node.elif;
152
121
  delete node.else;
153
122
  return node;
154
123
  }
155
124
 
156
125
  return node;
157
- }
158
-
159
- export const createDynamic = (argFn, renderFn) => {
160
- const content = r.val();
161
- let controller;
162
- r.effect(() => {
163
- const arg = argFn();
164
- controller?.destroy();
165
- controller = r.subtle.effectRoot(() => {
166
- content(renderFn(arg));
167
- });
168
- });
169
- return createSlot(content);
170
126
  }
@@ -1,23 +1,94 @@
1
- import * as operators from './operators.js';
2
1
  import { Element } from './base.js';
3
2
  import { kebab } from '../common/dom.js';
3
+ import { unwrapFn } from '../common/utils.js';
4
4
  import { Context } from '../context/sync.js';
5
5
  import { cached } from '../execution/index.js';
6
+ import { Operator, toString } from './base.js';
7
+ import { createIf, createList, createSlot } from './dynamic.js';
6
8
 
7
- const NS = {
8
- svg: 'http://www.w3.org/2000/svg',
9
- math: 'http://www.w3.org/1998/Math/MathML'
9
+ const api = {};
10
+
11
+ api.ref = Operator((node, _, args) => {
12
+ for (const func of args) func(node);
13
+ });
14
+
15
+ api.parse = (...args) => {
16
+ const div = document.createElement('div');
17
+ div.innerHTML = toString(...args);
18
+ return Array.from(div.children);
10
19
  };
11
20
 
12
- const tag = key => key.length ? key[0].toLowerCase() + kebab(key.slice(1)) : '';
13
- const namespace = ns => {
14
- return new Proxy({}, {
15
- get: cached((_, key) => (
16
- key == 'ns' && ns == null ? key => namespace(NS[key.toLowerCase()])
17
- : key in operators ? operators[key]
18
- : Element(tag(key), ns)
19
- ), (_, key) => key)
20
- });
21
- }
21
+ api.append = (node, ...args) => Element.append(node, args);
22
+ api.remove = Element.remove;
23
+
24
+ api.if = (cond, ...args) => createIf().elif(cond, args);
25
+
26
+ api.list = createList;
27
+ api.slot = createSlot;
28
+
29
+ api.attr = Operator((node, names, args) => {
30
+ const value = toString(...args);
31
+ for (const name of names) node.setAttribute(name, value);
32
+ return () => {
33
+ for (const name of names) node.removeAttribute(name);
34
+ };
35
+ });
36
+
37
+ api.on = Operator((node, names, args) => {
38
+ let handlers = [], options;
39
+ for (const arg of args) {
40
+ if (typeof arg == 'function') handlers.push(arg);
41
+ else options = arg;
42
+ }
43
+
44
+ const target = options?.target ?? node;
45
+ for (const e of names) for (const h of handlers) {
46
+ target.addEventListener(e, h, options);
47
+ }
48
+ return () => {
49
+ for (const e of names) for (const h of handlers) {
50
+ target.removeEventListener(e, h);
51
+ }
52
+ options = handlers = null;
53
+ };
54
+ });
55
+
56
+ api.class = Operator((node, names, args) => {
57
+ const value = Boolean(unwrapFn(args[0]));
58
+ if (!value) return;
59
+ node.classList.add(...names.map(kebab));
60
+ return () => {
61
+ node.classList.remove(...names.map(kebab));
62
+ };
63
+ });
64
+
65
+ api.style = Operator((node, names, args) => {
66
+ const value = toString(...args);
67
+ for (const name of names) {
68
+ node.style.setProperty(kebab(name), value);
69
+ }
70
+ return () => {
71
+ for (const name of names) node.style.removeProperty(kebab(name));
72
+ };
73
+ });
74
+
75
+ api.prop = Operator((node, names, args) => {
76
+ const value = unwrapFn(args[0]);
77
+ for (const name of names) node[name] = value;
78
+ return () => {
79
+ for (const name of names) delete node[name];
80
+ };
81
+ });
82
+
83
+ const namespace = ns => new Proxy(api, {
84
+ get: cached((target, key) => (
85
+ key[0] >= 'A' && key[0] <= 'Z'
86
+ ? Element.bind(null, ns, key[0].toLowerCase() + kebab(key.slice(1)))
87
+ : target[key]
88
+ ), (_, key) => key + ns)
89
+ });
90
+ api.NS_SVG = namespace('http://www.w3.org/2000/svg');
91
+ api.NS_MATH = namespace('http://www.w3.org/1998/Math/MathML');
92
+
22
93
  export const el = namespace(null);
23
94
  export { Context };
@@ -28,7 +28,9 @@ const RouterRealdom = ({
28
28
  loadedRoutes(loaded);
29
29
  currentRoute(key);
30
30
  }
31
- router.listen((...a) => r.untrack(listener.bind(null, ...a)));
31
+ router.listen((...a) => {
32
+ r.effect(listener.bind(null, ...a), { tracking: false })
33
+ });
32
34
  router.attach();
33
35
 
34
36
  const container = el[':'](
@@ -48,13 +48,27 @@ const nodes = lines.nodes.map((line, i) => {
48
48
  if (curr < prev) return null;
49
49
  }
50
50
 
51
+ return { pkg, file, parts };
52
+ }).filter(Boolean).filter((node, _, all) => {
53
+ const { pkg, file, parts } = node;
54
+ if (parts.length !== 2) return true;
55
+ const suiteName = parts[0];
56
+ const hasChildren = all.some(other =>
57
+ other !== node &&
58
+ other.pkg === pkg &&
59
+ other.file === file &&
60
+ other.parts.length > 2 &&
61
+ other.parts[0] === suiteName
62
+ );
63
+ return !hasChildren;
64
+ }).map(({ pkg, file, parts }) => {
51
65
  return [
52
66
  brightCyan('webdetta/' + pkg),
53
67
  cyan(file),
54
68
  ...parts.slice(0, -2),
55
69
  [nodeResult(parts.at(-1)), parts.at(-2)].filter(Boolean).join(' ')
56
70
  ];
57
- }).filter(Boolean);
71
+ });
58
72
 
59
73
  const group = (arr) => {
60
74
  const res = {};
@@ -1,87 +0,0 @@
1
- import { kebab } from '../common/dom.js';
2
- import { unwrapFn } from '../common/utils.js';
3
- import { r } from '../reactivity/index.js';
4
- import { Element, Operator, toString } from './base.js';
5
- import { createDynamic, createIf, createList, createSlot, onDomAppend, onDomRemove } from './dynamic.js';
6
-
7
- export const ref = Operator((node, _, args) => {
8
- for (const func of args) func(node);
9
- });
10
-
11
- export const parse = (...args) => {
12
- const div = document.createElement('div');
13
- div.innerHTML = toString(...args);
14
- return Array.from(div.children);
15
- };
16
-
17
- export const append = (node, ...args) => Element.append(node, args);
18
- export const remove = (node) => Element.remove(node);
19
-
20
- const if_ = (cond, ...args) => createIf().elif(cond, args);
21
- export { if_ as if };
22
-
23
- export const list = (items, render, keyFn) => createList(items, render, keyFn);
24
- export const slot = (func) => createSlot(func);
25
- export const dynamic = (argFn, renderFn) => createDynamic(argFn, renderFn);
26
-
27
- export const lifecycle = Operator((node, names, args) => {
28
- for (const name of names) {
29
- if (name == 'append') for (const f of args) onDomAppend(node, f);
30
- if (name == 'remove') for (const f of args) onDomRemove(node, f);
31
- }
32
- });
33
-
34
- export const attr = Operator((node, names, args) => r.effect(() => {
35
- const value = toString(...args);
36
- for (const name of names) node.setAttribute(name, value);
37
- return () => {
38
- for (const name of names) node.removeAttribute(name);
39
- };
40
- }));
41
-
42
- export const on = Operator((node, names, args) => r.effect(() => {
43
- let handlers = [], options;
44
- for (const arg of args) {
45
- if (typeof arg == 'function') handlers.push(arg);
46
- else options = arg;
47
- }
48
-
49
- const target = options?.target ?? node;
50
- for (const e of names) for (const h of handlers) {
51
- target.addEventListener(e, h, options);
52
- }
53
- return () => {
54
- for (const e of names) for (const h of handlers) {
55
- target.removeEventListener(e, h);
56
- }
57
- options = handlers = null;
58
- };
59
- }));
60
-
61
- const class_ = Operator((node, names, args) => r.effect(() => {
62
- const value = Boolean(unwrapFn(args[0]));
63
- if (!value) return;
64
- node.classList.add(...names.map(kebab));
65
- return () => {
66
- node.classList.remove(...names.map(kebab));
67
- };
68
- }));
69
- export { class_ as class };
70
-
71
- export const style = Operator((node, names, args) => r.effect(() => {
72
- const value = toString(...args);
73
- for (const name of names) {
74
- node.style.setProperty(kebab(name), value);
75
- }
76
- return () => {
77
- for (const name of names) node.style.removeProperty(kebab(name));
78
- };
79
- }));
80
-
81
- export const prop = Operator((node, names, args) => r.effect(() => {
82
- const value = unwrapFn(args[0]);
83
- for (const name of names) node[name] = value;
84
- return () => {
85
- for (const name of names) delete node[name];
86
- };
87
- }));