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 +1 -1
- package/packages/reactivity/README.md +1 -51
- package/packages/reactivity/base.js +0 -2
- package/packages/reactivity/index.js +39 -55
- package/packages/reactivity/tests/effects.test.js +5 -5
- package/packages/reactivity/tests/values.test.js +1 -14
- package/packages/realdom/base.js +21 -15
- package/packages/realdom/dynamic.js +79 -123
- package/packages/realdom/index.js +85 -14
- package/packages/router/realdom-adapter.js +3 -1
- package/tools/test-reporter.js +15 -1
- package/packages/realdom/operators.js +0 -87
package/package.json
CHANGED
|
@@ -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, {
|
|
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
|
|
@@ -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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 =
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
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 } =
|
|
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 } =
|
|
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.
|
|
166
|
-
r.
|
|
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.
|
|
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('
|
|
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.
|
|
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.
|
|
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 {
|
|
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, () => {
|
package/packages/realdom/base.js
CHANGED
|
@@ -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
|
-
|
|
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,
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
74
|
-
if (!
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
83
|
-
if (!entries.has(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
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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 =
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
109
|
+
const conditions = [];
|
|
110
|
+
const node = createSlot(() =>
|
|
111
|
+
conditions.find(d => unwrapFn(d.cond))?.value
|
|
112
|
+
);
|
|
113
|
+
|
|
143
114
|
node.elif = (cond, ...args) => {
|
|
144
|
-
|
|
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
|
-
|
|
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
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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) =>
|
|
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[':'](
|
package/tools/test-reporter.js
CHANGED
|
@@ -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
|
-
})
|
|
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
|
-
}));
|