tannijs 0.1.0 → 0.1.1
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/{src/reactivity.ts → dist/chunk-GQWZEFRY.js} +50 -86
- package/dist/index.d.ts +11 -0
- package/dist/index.js +16 -0
- package/dist/internals.d.ts +9 -0
- package/{src/dom.ts → dist/internals.js} +48 -88
- package/package.json +16 -9
- package/src/dom.test.ts +0 -101
- package/src/index.ts +0 -10
- package/src/internals.ts +0 -3
- package/src/reactivity.test.ts +0 -135
|
@@ -1,150 +1,113 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
deps: Set<Source>;
|
|
9
|
-
cleanups: CleanupFn[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Source {
|
|
13
|
-
subscribers: Set<Computation>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
class Signal<T> implements Source {
|
|
17
|
-
public readonly subscribers = new Set<Computation>();
|
|
18
|
-
|
|
19
|
-
public constructor(private value: T) {}
|
|
20
|
-
|
|
21
|
-
public read(): T {
|
|
1
|
+
// src/reactivity.ts
|
|
2
|
+
var Signal = class {
|
|
3
|
+
constructor(value) {
|
|
4
|
+
this.value = value;
|
|
5
|
+
}
|
|
6
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
7
|
+
read() {
|
|
22
8
|
trackDependency(this);
|
|
23
9
|
return this.value;
|
|
24
10
|
}
|
|
25
|
-
|
|
26
|
-
public peek(): T {
|
|
11
|
+
peek() {
|
|
27
12
|
return this.value;
|
|
28
13
|
}
|
|
29
|
-
|
|
30
|
-
public write(next: T): T {
|
|
14
|
+
write(next) {
|
|
31
15
|
if (Object.is(this.value, next)) {
|
|
32
16
|
return this.value;
|
|
33
17
|
}
|
|
34
|
-
|
|
35
18
|
this.value = next;
|
|
36
19
|
notifySubscribers(this.subscribers);
|
|
37
20
|
return this.value;
|
|
38
21
|
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
public readonly deps = new Set<Source>();
|
|
44
|
-
public cleanups: CleanupFn[] = [];
|
|
45
|
-
public value!: T;
|
|
46
|
-
|
|
47
|
-
public constructor(private readonly fn: () => T) {
|
|
22
|
+
};
|
|
23
|
+
var Memo = class {
|
|
24
|
+
constructor(fn) {
|
|
25
|
+
this.fn = fn;
|
|
48
26
|
this.execute();
|
|
49
27
|
}
|
|
50
|
-
|
|
51
|
-
|
|
28
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
29
|
+
deps = /* @__PURE__ */ new Set();
|
|
30
|
+
cleanups = [];
|
|
31
|
+
value;
|
|
32
|
+
read() {
|
|
52
33
|
trackDependency(this);
|
|
53
34
|
return this.value;
|
|
54
35
|
}
|
|
55
|
-
|
|
56
|
-
public execute(): void {
|
|
36
|
+
execute() {
|
|
57
37
|
cleanupComputation(this);
|
|
58
38
|
const previous = currentComputation;
|
|
59
39
|
currentComputation = this;
|
|
60
|
-
|
|
61
|
-
let nextValue!: T;
|
|
40
|
+
let nextValue;
|
|
62
41
|
try {
|
|
63
42
|
nextValue = this.fn();
|
|
64
43
|
} finally {
|
|
65
44
|
currentComputation = previous;
|
|
66
45
|
}
|
|
67
|
-
|
|
68
46
|
if (!Object.is(nextValue, this.value)) {
|
|
69
47
|
this.value = nextValue;
|
|
70
48
|
notifySubscribers(this.subscribers);
|
|
71
49
|
}
|
|
72
50
|
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
function trackDependency(source: Source): void {
|
|
51
|
+
};
|
|
52
|
+
var pendingComputations = /* @__PURE__ */ new Set();
|
|
53
|
+
var batchDepth = 0;
|
|
54
|
+
var currentComputation = null;
|
|
55
|
+
function trackDependency(source) {
|
|
80
56
|
if (!currentComputation) {
|
|
81
57
|
return;
|
|
82
58
|
}
|
|
83
|
-
|
|
84
59
|
source.subscribers.add(currentComputation);
|
|
85
60
|
currentComputation.deps.add(source);
|
|
86
61
|
}
|
|
87
|
-
|
|
88
|
-
function cleanupComputation(computation: Computation): void {
|
|
62
|
+
function cleanupComputation(computation) {
|
|
89
63
|
for (const source of computation.deps) {
|
|
90
64
|
source.subscribers.delete(computation);
|
|
91
65
|
}
|
|
92
66
|
computation.deps.clear();
|
|
93
|
-
|
|
94
67
|
const cleanups = computation.cleanups;
|
|
95
68
|
computation.cleanups = [];
|
|
96
69
|
for (const cleanup of cleanups) {
|
|
97
70
|
cleanup();
|
|
98
71
|
}
|
|
99
72
|
}
|
|
100
|
-
|
|
101
|
-
function runComputation(computation: Computation): void {
|
|
73
|
+
function runComputation(computation) {
|
|
102
74
|
if (batchDepth > 0) {
|
|
103
75
|
pendingComputations.add(computation);
|
|
104
76
|
return;
|
|
105
77
|
}
|
|
106
|
-
|
|
107
78
|
computation.execute();
|
|
108
79
|
}
|
|
109
|
-
|
|
110
|
-
function notifySubscribers(subscribers: Set<Computation>): void {
|
|
80
|
+
function notifySubscribers(subscribers) {
|
|
111
81
|
const queue = Array.from(subscribers);
|
|
112
82
|
for (const subscriber of queue) {
|
|
113
83
|
runComputation(subscriber);
|
|
114
84
|
}
|
|
115
85
|
}
|
|
116
|
-
|
|
117
|
-
function flushPending(): void {
|
|
86
|
+
function flushPending() {
|
|
118
87
|
if (pendingComputations.size === 0) {
|
|
119
88
|
return;
|
|
120
89
|
}
|
|
121
|
-
|
|
122
90
|
const queue = Array.from(pendingComputations);
|
|
123
91
|
pendingComputations.clear();
|
|
124
92
|
for (const computation of queue) {
|
|
125
93
|
computation.execute();
|
|
126
94
|
}
|
|
127
|
-
|
|
128
95
|
if (pendingComputations.size > 0) {
|
|
129
96
|
flushPending();
|
|
130
97
|
}
|
|
131
98
|
}
|
|
132
|
-
|
|
133
|
-
export function createSignal<T>(initialValue: T): [Accessor<T>, Setter<T>] {
|
|
99
|
+
function createSignal(initialValue) {
|
|
134
100
|
const signal = new Signal(initialValue);
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
const nextValue = typeof value === 'function' ? (value as (prev: T) => T)(signal.peek()) : value;
|
|
101
|
+
const accessor = () => signal.read();
|
|
102
|
+
const setter = (value) => {
|
|
103
|
+
const nextValue = typeof value === "function" ? value(signal.peek()) : value;
|
|
139
104
|
return signal.write(nextValue);
|
|
140
105
|
};
|
|
141
|
-
|
|
142
106
|
return [accessor, setter];
|
|
143
107
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
deps: new Set<Source>(),
|
|
108
|
+
function createEffect(fn) {
|
|
109
|
+
const effect = {
|
|
110
|
+
deps: /* @__PURE__ */ new Set(),
|
|
148
111
|
cleanups: [],
|
|
149
112
|
execute() {
|
|
150
113
|
cleanupComputation(effect);
|
|
@@ -155,18 +118,15 @@ export function createEffect(fn: () => void): void {
|
|
|
155
118
|
} finally {
|
|
156
119
|
currentComputation = previous;
|
|
157
120
|
}
|
|
158
|
-
}
|
|
121
|
+
}
|
|
159
122
|
};
|
|
160
|
-
|
|
161
123
|
effect.execute();
|
|
162
124
|
}
|
|
163
|
-
|
|
164
|
-
export function createMemo<T>(fn: () => T): Accessor<T> {
|
|
125
|
+
function createMemo(fn) {
|
|
165
126
|
const memo = new Memo(fn);
|
|
166
127
|
return () => memo.read();
|
|
167
128
|
}
|
|
168
|
-
|
|
169
|
-
export function batch<T>(fn: () => T): T {
|
|
129
|
+
function batch(fn) {
|
|
170
130
|
batchDepth += 1;
|
|
171
131
|
try {
|
|
172
132
|
return fn();
|
|
@@ -177,8 +137,7 @@ export function batch<T>(fn: () => T): T {
|
|
|
177
137
|
}
|
|
178
138
|
}
|
|
179
139
|
}
|
|
180
|
-
|
|
181
|
-
export function untrack<T>(fn: () => T): T {
|
|
140
|
+
function untrack(fn) {
|
|
182
141
|
const previous = currentComputation;
|
|
183
142
|
currentComputation = null;
|
|
184
143
|
try {
|
|
@@ -187,13 +146,18 @@ export function untrack<T>(fn: () => T): T {
|
|
|
187
146
|
currentComputation = previous;
|
|
188
147
|
}
|
|
189
148
|
}
|
|
190
|
-
|
|
191
|
-
export function onCleanup(fn: CleanupFn): void {
|
|
149
|
+
function onCleanup(fn) {
|
|
192
150
|
if (!currentComputation) {
|
|
193
|
-
throw new Error(
|
|
151
|
+
throw new Error("onCleanup must be called inside a tracked computation.");
|
|
194
152
|
}
|
|
195
|
-
|
|
196
153
|
currentComputation.cleanups.push(fn);
|
|
197
154
|
}
|
|
198
155
|
|
|
199
|
-
export
|
|
156
|
+
export {
|
|
157
|
+
createSignal,
|
|
158
|
+
createEffect,
|
|
159
|
+
createMemo,
|
|
160
|
+
batch,
|
|
161
|
+
untrack,
|
|
162
|
+
onCleanup
|
|
163
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Accessor<T> = () => T;
|
|
2
|
+
type Setter<T> = (value: T | ((prev: T) => T)) => T;
|
|
3
|
+
type CleanupFn = () => void;
|
|
4
|
+
declare function createSignal<T>(initialValue: T): [Accessor<T>, Setter<T>];
|
|
5
|
+
declare function createEffect(fn: () => void): void;
|
|
6
|
+
declare function createMemo<T>(fn: () => T): Accessor<T>;
|
|
7
|
+
declare function batch<T>(fn: () => T): T;
|
|
8
|
+
declare function untrack<T>(fn: () => T): T;
|
|
9
|
+
declare function onCleanup(fn: CleanupFn): void;
|
|
10
|
+
|
|
11
|
+
export { type Accessor, type Setter, batch, createEffect, createMemo, createSignal, onCleanup, untrack };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { createEffect } from './index.js';
|
|
2
|
+
|
|
3
|
+
type InsertValue = Node | string | number | boolean | null | undefined | InsertValue[] | (() => InsertValue);
|
|
4
|
+
declare function template(html: string): Node;
|
|
5
|
+
declare function insert(parent: Node, value: InsertValue, marker?: Node | null): void;
|
|
6
|
+
declare function spread(element: Element, props: Record<string, unknown>): void;
|
|
7
|
+
declare function delegateEvents(eventNames: string[]): void;
|
|
8
|
+
|
|
9
|
+
export { type InsertValue, delegateEvents, insert, spread, template };
|
|
@@ -1,90 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
| (() => InsertValue);
|
|
12
|
-
|
|
13
|
-
type EventHandler = (event: Event) => void;
|
|
14
|
-
|
|
15
|
-
const delegatedEvents = new Set<string>();
|
|
16
|
-
const listeningEvents = new Set<string>();
|
|
17
|
-
const directListeners = new WeakMap<Element, Map<string, EventHandler>>();
|
|
18
|
-
|
|
19
|
-
export function template(html: string): Node {
|
|
20
|
-
const tpl = document.createElement('template');
|
|
1
|
+
import {
|
|
2
|
+
createEffect
|
|
3
|
+
} from "./chunk-GQWZEFRY.js";
|
|
4
|
+
|
|
5
|
+
// src/dom.ts
|
|
6
|
+
var delegatedEvents = /* @__PURE__ */ new Set();
|
|
7
|
+
var listeningEvents = /* @__PURE__ */ new Set();
|
|
8
|
+
var directListeners = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
function template(html) {
|
|
10
|
+
const tpl = document.createElement("template");
|
|
21
11
|
tpl.innerHTML = html.trim();
|
|
22
|
-
|
|
23
12
|
if (tpl.content.childNodes.length === 1) {
|
|
24
|
-
return tpl.content.firstChild
|
|
13
|
+
return tpl.content.firstChild;
|
|
25
14
|
}
|
|
26
|
-
|
|
27
15
|
return tpl.content;
|
|
28
16
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
let currentNodes: Node[] = [];
|
|
17
|
+
function insert(parent, value, marker = null) {
|
|
18
|
+
if (typeof value === "function") {
|
|
19
|
+
let currentNodes = [];
|
|
33
20
|
createEffect(() => {
|
|
34
|
-
const resolved =
|
|
35
|
-
const
|
|
36
|
-
currentNodes = replaceNodes(parent, currentNodes,
|
|
21
|
+
const resolved = value();
|
|
22
|
+
const nextNodes2 = normalizeNodes(resolved);
|
|
23
|
+
currentNodes = replaceNodes(parent, currentNodes, nextNodes2, marker);
|
|
37
24
|
});
|
|
38
25
|
return;
|
|
39
26
|
}
|
|
40
|
-
|
|
41
27
|
const nextNodes = normalizeNodes(value);
|
|
42
28
|
replaceNodes(parent, [], nextNodes, marker);
|
|
43
29
|
}
|
|
44
|
-
|
|
45
|
-
export function spread(element: Element, props: Record<string, unknown>): void {
|
|
30
|
+
function spread(element, props) {
|
|
46
31
|
for (const [key, value] of Object.entries(props)) {
|
|
47
|
-
if (key ===
|
|
48
|
-
insert(element, value
|
|
32
|
+
if (key === "children") {
|
|
33
|
+
insert(element, value);
|
|
49
34
|
continue;
|
|
50
35
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
Object.assign((element as HTMLElement).style, value as Record<string, string>);
|
|
36
|
+
if (key === "style" && value && typeof value === "object") {
|
|
37
|
+
Object.assign(element.style, value);
|
|
54
38
|
continue;
|
|
55
39
|
}
|
|
56
|
-
|
|
57
40
|
const eventName = toEventName(key);
|
|
58
41
|
if (eventName) {
|
|
59
42
|
applyEventHandler(element, eventName, value);
|
|
60
43
|
continue;
|
|
61
44
|
}
|
|
62
|
-
|
|
63
45
|
applyProperty(element, key, value);
|
|
64
46
|
}
|
|
65
47
|
}
|
|
66
|
-
|
|
67
|
-
export function delegateEvents(eventNames: string[]): void {
|
|
48
|
+
function delegateEvents(eventNames) {
|
|
68
49
|
for (const eventName of eventNames) {
|
|
69
50
|
const normalized = eventName.toLowerCase();
|
|
70
51
|
delegatedEvents.add(normalized);
|
|
71
|
-
|
|
72
52
|
if (listeningEvents.has(normalized)) {
|
|
73
53
|
continue;
|
|
74
54
|
}
|
|
75
|
-
|
|
76
55
|
listeningEvents.add(normalized);
|
|
77
56
|
document.addEventListener(normalized, handleDelegatedEvent);
|
|
78
57
|
}
|
|
79
58
|
}
|
|
80
|
-
|
|
81
|
-
function handleDelegatedEvent(event: Event): void {
|
|
59
|
+
function handleDelegatedEvent(event) {
|
|
82
60
|
const type = event.type.toLowerCase();
|
|
83
|
-
let node
|
|
84
|
-
|
|
61
|
+
let node = event.target;
|
|
85
62
|
while (node && node !== document) {
|
|
86
63
|
if (node instanceof Element) {
|
|
87
|
-
const handlers =
|
|
64
|
+
const handlers = node.__tnDelegatedHandlers;
|
|
88
65
|
const handler = handlers?.[type];
|
|
89
66
|
if (handler) {
|
|
90
67
|
handler(event);
|
|
@@ -96,100 +73,83 @@ function handleDelegatedEvent(event: Event): void {
|
|
|
96
73
|
node = node.parentNode;
|
|
97
74
|
}
|
|
98
75
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (typeof value !== 'function') {
|
|
76
|
+
function applyEventHandler(element, eventName, value) {
|
|
77
|
+
if (typeof value !== "function") {
|
|
102
78
|
return;
|
|
103
79
|
}
|
|
104
|
-
|
|
105
|
-
const handler = value as EventHandler;
|
|
80
|
+
const handler = value;
|
|
106
81
|
if (delegatedEvents.has(eventName)) {
|
|
107
|
-
const delegatedElement = element
|
|
82
|
+
const delegatedElement = element;
|
|
108
83
|
delegatedElement.__tnDelegatedHandlers ??= {};
|
|
109
84
|
delegatedElement.__tnDelegatedHandlers[eventName] = handler;
|
|
110
85
|
return;
|
|
111
86
|
}
|
|
112
|
-
|
|
113
87
|
let listenersForElement = directListeners.get(element);
|
|
114
88
|
if (!listenersForElement) {
|
|
115
|
-
listenersForElement = new Map
|
|
89
|
+
listenersForElement = /* @__PURE__ */ new Map();
|
|
116
90
|
directListeners.set(element, listenersForElement);
|
|
117
91
|
}
|
|
118
|
-
|
|
119
92
|
const previous = listenersForElement.get(eventName);
|
|
120
93
|
if (previous) {
|
|
121
94
|
element.removeEventListener(eventName, previous);
|
|
122
95
|
}
|
|
123
|
-
|
|
124
96
|
listenersForElement.set(eventName, handler);
|
|
125
97
|
element.addEventListener(eventName, handler);
|
|
126
98
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const writableElement = element as unknown as Record<string, unknown>;
|
|
130
|
-
|
|
99
|
+
function applyProperty(element, key, value) {
|
|
100
|
+
const writableElement = element;
|
|
131
101
|
if (value === false || value == null) {
|
|
132
102
|
element.removeAttribute(key);
|
|
133
103
|
if (key in element) {
|
|
134
|
-
writableElement[key] =
|
|
104
|
+
writableElement[key] = "";
|
|
135
105
|
}
|
|
136
106
|
return;
|
|
137
107
|
}
|
|
138
|
-
|
|
139
|
-
if (key in element && !key.startsWith('aria-') && !key.startsWith('data-')) {
|
|
108
|
+
if (key in element && !key.startsWith("aria-") && !key.startsWith("data-")) {
|
|
140
109
|
writableElement[key] = value;
|
|
141
110
|
return;
|
|
142
111
|
}
|
|
143
|
-
|
|
144
112
|
element.setAttribute(key, String(value));
|
|
145
113
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (key.startsWith('on:')) {
|
|
114
|
+
function toEventName(key) {
|
|
115
|
+
if (key.startsWith("on:")) {
|
|
149
116
|
return key.slice(3).toLowerCase();
|
|
150
117
|
}
|
|
151
|
-
|
|
152
|
-
if (key.startsWith('@')) {
|
|
118
|
+
if (key.startsWith("@")) {
|
|
153
119
|
return key.slice(1).toLowerCase();
|
|
154
120
|
}
|
|
155
|
-
|
|
156
|
-
if (key.startsWith('on') && key.length > 2) {
|
|
121
|
+
if (key.startsWith("on") && key.length > 2) {
|
|
157
122
|
return key.slice(2).toLowerCase();
|
|
158
123
|
}
|
|
159
|
-
|
|
160
124
|
return null;
|
|
161
125
|
}
|
|
162
|
-
|
|
163
|
-
function normalizeNodes(value: InsertValue): Node[] {
|
|
126
|
+
function normalizeNodes(value) {
|
|
164
127
|
if (value == null || value === false || value === true) {
|
|
165
128
|
return [];
|
|
166
129
|
}
|
|
167
|
-
|
|
168
130
|
if (Array.isArray(value)) {
|
|
169
131
|
return value.flatMap((entry) => normalizeNodes(entry));
|
|
170
132
|
}
|
|
171
|
-
|
|
172
133
|
if (value instanceof Node) {
|
|
173
134
|
return [value];
|
|
174
135
|
}
|
|
175
|
-
|
|
176
136
|
return [document.createTextNode(String(value))];
|
|
177
137
|
}
|
|
178
|
-
|
|
179
|
-
function replaceNodes(parent: Node, currentNodes: Node[], nextNodes: Node[], marker: Node | null): Node[] {
|
|
138
|
+
function replaceNodes(parent, currentNodes, nextNodes, marker) {
|
|
180
139
|
for (const node of currentNodes) {
|
|
181
140
|
if (node.parentNode === parent) {
|
|
182
141
|
parent.removeChild(node);
|
|
183
142
|
}
|
|
184
143
|
}
|
|
185
|
-
|
|
186
144
|
for (const node of nextNodes) {
|
|
187
145
|
parent.insertBefore(node, marker);
|
|
188
146
|
}
|
|
189
|
-
|
|
190
147
|
return nextNodes;
|
|
191
148
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
149
|
+
export {
|
|
150
|
+
createEffect,
|
|
151
|
+
delegateEvents,
|
|
152
|
+
insert,
|
|
153
|
+
spread,
|
|
154
|
+
template
|
|
155
|
+
};
|
package/package.json
CHANGED
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tannijs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Signal-based reactive runtime for the Tanni framework",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Sebastijan Zindl",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"types": "./
|
|
11
|
-
"default": "./
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
12
|
},
|
|
13
13
|
"./internals": {
|
|
14
|
-
"types": "./
|
|
15
|
-
"default": "./
|
|
14
|
+
"types": "./dist/internals.d.ts",
|
|
15
|
+
"default": "./dist/internals.js"
|
|
16
16
|
}
|
|
17
17
|
},
|
|
18
|
-
"main": "./
|
|
19
|
-
"types": "./
|
|
18
|
+
"main": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
20
|
"files": [
|
|
21
|
-
"src",
|
|
22
21
|
"dist"
|
|
23
|
-
]
|
|
22
|
+
],
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"tsup": "^8.5.1",
|
|
25
|
+
"typescript": "^5.9.3"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup src/index.ts src/internals.ts --format esm --dts --clean --out-dir dist",
|
|
29
|
+
"dev": "tsup src/index.ts src/internals.ts --format esm --dts --out-dir dist --watch"
|
|
30
|
+
}
|
|
24
31
|
}
|
package/src/dom.test.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { createSignal } from './reactivity';
|
|
4
|
-
import { delegateEvents, insert, spread, template } from './dom';
|
|
5
|
-
|
|
6
|
-
describe('dom helpers', () => {
|
|
7
|
-
it('creates cloneable template nodes', () => {
|
|
8
|
-
const node = template('<button class="btn">Click</button>');
|
|
9
|
-
const clone = node.cloneNode(true) as HTMLElement;
|
|
10
|
-
|
|
11
|
-
expect(clone.tagName).toBe('BUTTON');
|
|
12
|
-
expect(clone.className).toBe('btn');
|
|
13
|
-
expect(clone.textContent).toBe('Click');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('inserts reactive text updates', () => {
|
|
17
|
-
const host = document.createElement('div');
|
|
18
|
-
const [count, setCount] = createSignal(0);
|
|
19
|
-
|
|
20
|
-
insert(host, () => `Count: ${count()}`);
|
|
21
|
-
expect(host.textContent).toBe('Count: 0');
|
|
22
|
-
|
|
23
|
-
setCount(2);
|
|
24
|
-
expect(host.textContent).toBe('Count: 2');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('replaces only content before marker in reactive inserts', () => {
|
|
28
|
-
const host = document.createElement('div');
|
|
29
|
-
const marker = document.createComment('marker');
|
|
30
|
-
host.append(document.createTextNode('prefix-'));
|
|
31
|
-
host.append(marker);
|
|
32
|
-
host.append(document.createTextNode('-suffix'));
|
|
33
|
-
|
|
34
|
-
const [value, setValue] = createSignal('one');
|
|
35
|
-
insert(host, () => value(), marker);
|
|
36
|
-
|
|
37
|
-
expect(host.textContent).toBe('prefix-one-suffix');
|
|
38
|
-
setValue('two');
|
|
39
|
-
expect(host.textContent).toBe('prefix-two-suffix');
|
|
40
|
-
expect(host.lastChild).not.toBe(marker);
|
|
41
|
-
expect(host.childNodes[1]?.nodeType).toBe(Node.TEXT_NODE);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('applies properties and delegated events via spread', () => {
|
|
45
|
-
delegateEvents(['click']);
|
|
46
|
-
|
|
47
|
-
const host = document.createElement('div');
|
|
48
|
-
const button = document.createElement('button');
|
|
49
|
-
const onClick = vi.fn();
|
|
50
|
-
|
|
51
|
-
spread(button, {
|
|
52
|
-
id: 'counter-btn',
|
|
53
|
-
'@click': onClick,
|
|
54
|
-
children: 'Tap',
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
host.append(button);
|
|
58
|
-
document.body.append(host);
|
|
59
|
-
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
60
|
-
|
|
61
|
-
expect(button.id).toBe('counter-btn');
|
|
62
|
-
expect(button.textContent).toBe('Tap');
|
|
63
|
-
expect(onClick).toHaveBeenCalledTimes(1);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('supports direct event listeners and replaces previous handlers', () => {
|
|
67
|
-
const button = document.createElement('button');
|
|
68
|
-
const first = vi.fn();
|
|
69
|
-
const second = vi.fn();
|
|
70
|
-
|
|
71
|
-
spread(button, { onMouseover: first });
|
|
72
|
-
button.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
|
|
73
|
-
|
|
74
|
-
spread(button, { onMouseover: second });
|
|
75
|
-
button.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
|
|
76
|
-
|
|
77
|
-
expect(first).toHaveBeenCalledTimes(1);
|
|
78
|
-
expect(second).toHaveBeenCalledTimes(1);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it('supports delegated bubbling and stopPropagation semantics', () => {
|
|
82
|
-
delegateEvents(['click']);
|
|
83
|
-
const host = document.createElement('div');
|
|
84
|
-
const parent = document.createElement('div');
|
|
85
|
-
const button = document.createElement('button');
|
|
86
|
-
const parentHandler = vi.fn();
|
|
87
|
-
const childHandler = vi.fn((event: Event) => event.stopPropagation());
|
|
88
|
-
|
|
89
|
-
spread(parent, { '@click': parentHandler });
|
|
90
|
-
spread(button, { '@click': childHandler });
|
|
91
|
-
|
|
92
|
-
parent.append(button);
|
|
93
|
-
host.append(parent);
|
|
94
|
-
document.body.append(host);
|
|
95
|
-
|
|
96
|
-
button.dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
|
97
|
-
|
|
98
|
-
expect(childHandler).toHaveBeenCalledTimes(1);
|
|
99
|
-
expect(parentHandler).toHaveBeenCalledTimes(0);
|
|
100
|
-
});
|
|
101
|
-
});
|
package/src/index.ts
DELETED
package/src/internals.ts
DELETED
package/src/reactivity.test.ts
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import { batch, createEffect, createMemo, createSignal, onCleanup, untrack } from './reactivity';
|
|
4
|
-
|
|
5
|
-
describe('reactivity core', () => {
|
|
6
|
-
it('tracks signal reads and reruns effects on updates', () => {
|
|
7
|
-
const [count, setCount] = createSignal(0);
|
|
8
|
-
const values: number[] = [];
|
|
9
|
-
|
|
10
|
-
createEffect(() => {
|
|
11
|
-
values.push(count());
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
setCount(1);
|
|
15
|
-
setCount((prev) => prev + 1);
|
|
16
|
-
|
|
17
|
-
expect(values).toEqual([0, 1, 2]);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('batches updates and runs effects once', () => {
|
|
21
|
-
const [count, setCount] = createSignal(0);
|
|
22
|
-
const spy = vi.fn();
|
|
23
|
-
|
|
24
|
-
createEffect(() => {
|
|
25
|
-
count();
|
|
26
|
-
spy();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
batch(() => {
|
|
30
|
-
setCount(1);
|
|
31
|
-
setCount(2);
|
|
32
|
-
setCount(3);
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
expect(spy).toHaveBeenCalledTimes(2);
|
|
36
|
-
expect(count()).toBe(3);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('supports memoized derived values', () => {
|
|
40
|
-
const [count, setCount] = createSignal(2);
|
|
41
|
-
const doubled = createMemo(() => count() * 2);
|
|
42
|
-
|
|
43
|
-
expect(doubled()).toBe(4);
|
|
44
|
-
setCount(5);
|
|
45
|
-
expect(doubled()).toBe(10);
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('recomputes memo only when dependencies change', () => {
|
|
49
|
-
const [count, setCount] = createSignal(1);
|
|
50
|
-
const compute = vi.fn(() => count() * 10);
|
|
51
|
-
const value = createMemo(compute);
|
|
52
|
-
const effectSpy = vi.fn();
|
|
53
|
-
|
|
54
|
-
createEffect(() => {
|
|
55
|
-
value();
|
|
56
|
-
effectSpy();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
expect(value()).toBe(10);
|
|
60
|
-
expect(compute).toHaveBeenCalledTimes(1);
|
|
61
|
-
expect(effectSpy).toHaveBeenCalledTimes(1);
|
|
62
|
-
|
|
63
|
-
setCount(1);
|
|
64
|
-
expect(compute).toHaveBeenCalledTimes(1);
|
|
65
|
-
expect(effectSpy).toHaveBeenCalledTimes(1);
|
|
66
|
-
|
|
67
|
-
setCount(2);
|
|
68
|
-
expect(value()).toBe(20);
|
|
69
|
-
expect(compute).toHaveBeenCalledTimes(2);
|
|
70
|
-
expect(effectSpy).toHaveBeenCalledTimes(2);
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('supports untrack and effect cleanups', () => {
|
|
74
|
-
const [count, setCount] = createSignal(0);
|
|
75
|
-
const sideEffect = vi.fn();
|
|
76
|
-
const cleanup = vi.fn();
|
|
77
|
-
|
|
78
|
-
createEffect(() => {
|
|
79
|
-
sideEffect(untrack(() => count()));
|
|
80
|
-
onCleanup(cleanup);
|
|
81
|
-
count();
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
setCount(1);
|
|
85
|
-
|
|
86
|
-
expect(sideEffect).toHaveBeenCalledTimes(2);
|
|
87
|
-
expect(cleanup).toHaveBeenCalledTimes(1);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('runs previous cleanup before the next effect pass', () => {
|
|
91
|
-
const [value, setValue] = createSignal('a');
|
|
92
|
-
const callOrder: string[] = [];
|
|
93
|
-
|
|
94
|
-
createEffect(() => {
|
|
95
|
-
const current = value();
|
|
96
|
-
callOrder.push(`effect:${current}`);
|
|
97
|
-
onCleanup(() => {
|
|
98
|
-
callOrder.push(`cleanup:${current}`);
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
setValue('b');
|
|
103
|
-
setValue('c');
|
|
104
|
-
|
|
105
|
-
expect(callOrder).toEqual([
|
|
106
|
-
'effect:a',
|
|
107
|
-
'cleanup:a',
|
|
108
|
-
'effect:b',
|
|
109
|
-
'cleanup:b',
|
|
110
|
-
'effect:c',
|
|
111
|
-
]);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('supports nested batching with a single downstream rerun', () => {
|
|
115
|
-
const [count, setCount] = createSignal(0);
|
|
116
|
-
const spy = vi.fn();
|
|
117
|
-
|
|
118
|
-
createEffect(() => {
|
|
119
|
-
count();
|
|
120
|
-
spy();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
batch(() => {
|
|
124
|
-
setCount(1);
|
|
125
|
-
batch(() => {
|
|
126
|
-
setCount(2);
|
|
127
|
-
setCount(3);
|
|
128
|
-
});
|
|
129
|
-
setCount(4);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
expect(count()).toBe(4);
|
|
133
|
-
expect(spy).toHaveBeenCalledTimes(2);
|
|
134
|
-
});
|
|
135
|
-
});
|