@devera_se/bedrockjs 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/README.md +519 -0
- package/package.json +65 -0
- package/src/component.d.ts +36 -0
- package/src/component.js +357 -0
- package/src/html.d.ts +12 -0
- package/src/html.js +148 -0
- package/src/index.d.ts +12 -0
- package/src/index.js +50 -0
- package/src/reactive.d.ts +20 -0
- package/src/reactive.js +277 -0
- package/src/render.d.ts +5 -0
- package/src/render.js +326 -0
- package/src/router.d.ts +52 -0
- package/src/router.js +356 -0
package/src/reactive.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reactive state management system
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Current watcher being tracked
|
|
6
|
+
let currentWatcher = null;
|
|
7
|
+
|
|
8
|
+
// Set of all active watchers
|
|
9
|
+
const watchers = new Set();
|
|
10
|
+
|
|
11
|
+
// Map of reactive objects to their dependency sets
|
|
12
|
+
const dependencyMap = new WeakMap();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create a reactive proxy that triggers updates on change
|
|
16
|
+
* @param {Object} target - Object to make reactive
|
|
17
|
+
* @returns {Proxy} - Reactive proxy
|
|
18
|
+
*/
|
|
19
|
+
export function reactive(target) {
|
|
20
|
+
if (typeof target !== 'object' || target === null) {
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Already a proxy
|
|
25
|
+
if (target.__isReactive) {
|
|
26
|
+
return target;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const deps = new Map(); // property -> Set of watchers
|
|
30
|
+
dependencyMap.set(target, deps);
|
|
31
|
+
|
|
32
|
+
const proxy = new Proxy(target, {
|
|
33
|
+
get(obj, prop) {
|
|
34
|
+
if (prop === '__isReactive') return true;
|
|
35
|
+
if (prop === '__target') return obj;
|
|
36
|
+
|
|
37
|
+
// Track dependency
|
|
38
|
+
if (currentWatcher) {
|
|
39
|
+
if (!deps.has(prop)) {
|
|
40
|
+
deps.set(prop, new Set());
|
|
41
|
+
}
|
|
42
|
+
deps.get(prop).add(currentWatcher);
|
|
43
|
+
currentWatcher.deps.add(deps.get(prop));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const value = obj[prop];
|
|
47
|
+
|
|
48
|
+
// Recursively make nested objects reactive
|
|
49
|
+
if (typeof value === 'object' && value !== null && !value.__isReactive) {
|
|
50
|
+
obj[prop] = reactive(value);
|
|
51
|
+
return obj[prop];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return value;
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
set(obj, prop, value) {
|
|
58
|
+
const oldValue = obj[prop];
|
|
59
|
+
|
|
60
|
+
// Make nested objects reactive
|
|
61
|
+
if (typeof value === 'object' && value !== null) {
|
|
62
|
+
value = reactive(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
obj[prop] = value;
|
|
66
|
+
|
|
67
|
+
// Trigger watchers if value changed
|
|
68
|
+
if (oldValue !== value && deps.has(prop)) {
|
|
69
|
+
const propDeps = deps.get(prop);
|
|
70
|
+
for (const watcher of propDeps) {
|
|
71
|
+
queueWatcher(watcher);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true;
|
|
76
|
+
},
|
|
77
|
+
|
|
78
|
+
deleteProperty(obj, prop) {
|
|
79
|
+
if (prop in obj) {
|
|
80
|
+
delete obj[prop];
|
|
81
|
+
if (deps.has(prop)) {
|
|
82
|
+
const propDeps = deps.get(prop);
|
|
83
|
+
for (const watcher of propDeps) {
|
|
84
|
+
queueWatcher(watcher);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
return proxy;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Watch for reactive changes and run callback
|
|
97
|
+
* @param {Function} fn - Function to run and track
|
|
98
|
+
* @param {Object} options - Options
|
|
99
|
+
* @returns {Function} - Stop watching function
|
|
100
|
+
*/
|
|
101
|
+
export function watch(fn, options = {}) {
|
|
102
|
+
const watcher = {
|
|
103
|
+
fn,
|
|
104
|
+
deps: new Set(),
|
|
105
|
+
active: true,
|
|
106
|
+
immediate: options.immediate !== false
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
watchers.add(watcher);
|
|
110
|
+
|
|
111
|
+
// Run immediately to collect dependencies
|
|
112
|
+
if (watcher.immediate) {
|
|
113
|
+
runWatcher(watcher);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Return cleanup function
|
|
117
|
+
return () => {
|
|
118
|
+
watcher.active = false;
|
|
119
|
+
watchers.delete(watcher);
|
|
120
|
+
cleanupWatcher(watcher);
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Run a watcher and track its dependencies
|
|
126
|
+
*/
|
|
127
|
+
function runWatcher(watcher) {
|
|
128
|
+
if (!watcher.active) return;
|
|
129
|
+
|
|
130
|
+
// Cleanup old dependencies
|
|
131
|
+
cleanupWatcher(watcher);
|
|
132
|
+
|
|
133
|
+
// Track new dependencies
|
|
134
|
+
const prevWatcher = currentWatcher;
|
|
135
|
+
currentWatcher = watcher;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
watcher.fn();
|
|
139
|
+
} finally {
|
|
140
|
+
currentWatcher = prevWatcher;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Clean up watcher dependencies
|
|
146
|
+
*/
|
|
147
|
+
function cleanupWatcher(watcher) {
|
|
148
|
+
for (const dep of watcher.deps) {
|
|
149
|
+
dep.delete(watcher);
|
|
150
|
+
}
|
|
151
|
+
watcher.deps.clear();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Batch updates using microtask queue
|
|
155
|
+
let pendingWatchers = new Set();
|
|
156
|
+
let isPending = false;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Queue a watcher to run in the next microtask
|
|
160
|
+
*/
|
|
161
|
+
function queueWatcher(watcher) {
|
|
162
|
+
if (!watcher.active) return;
|
|
163
|
+
|
|
164
|
+
pendingWatchers.add(watcher);
|
|
165
|
+
|
|
166
|
+
if (!isPending) {
|
|
167
|
+
isPending = true;
|
|
168
|
+
queueMicrotask(flushWatchers);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Flush all pending watchers
|
|
174
|
+
*/
|
|
175
|
+
function flushWatchers() {
|
|
176
|
+
const watchersToRun = [...pendingWatchers];
|
|
177
|
+
pendingWatchers.clear();
|
|
178
|
+
isPending = false;
|
|
179
|
+
|
|
180
|
+
for (const watcher of watchersToRun) {
|
|
181
|
+
runWatcher(watcher);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Create a computed value that caches and auto-updates
|
|
187
|
+
* @param {Function} getter - Getter function
|
|
188
|
+
* @returns {Object} - Object with .value property
|
|
189
|
+
*/
|
|
190
|
+
export function computed(getter) {
|
|
191
|
+
let cachedValue;
|
|
192
|
+
let dirty = true;
|
|
193
|
+
|
|
194
|
+
const watcher = {
|
|
195
|
+
fn: () => {
|
|
196
|
+
dirty = true;
|
|
197
|
+
// Trigger any watchers watching this computed
|
|
198
|
+
if (computedDeps.size > 0) {
|
|
199
|
+
for (const dep of computedDeps) {
|
|
200
|
+
queueWatcher(dep);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
deps: new Set(),
|
|
205
|
+
active: true,
|
|
206
|
+
immediate: false
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
watchers.add(watcher);
|
|
210
|
+
const computedDeps = new Set();
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
get value() {
|
|
214
|
+
// Track dependency on this computed
|
|
215
|
+
if (currentWatcher) {
|
|
216
|
+
computedDeps.add(currentWatcher);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (dirty) {
|
|
220
|
+
const prevWatcher = currentWatcher;
|
|
221
|
+
currentWatcher = watcher;
|
|
222
|
+
cleanupWatcher(watcher);
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
cachedValue = getter();
|
|
226
|
+
} finally {
|
|
227
|
+
currentWatcher = prevWatcher;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
dirty = false;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return cachedValue;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
stop() {
|
|
237
|
+
watcher.active = false;
|
|
238
|
+
watchers.delete(watcher);
|
|
239
|
+
cleanupWatcher(watcher);
|
|
240
|
+
computedDeps.clear();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create a simple signal (reactive value)
|
|
247
|
+
* @param {any} initialValue - Initial value
|
|
248
|
+
* @returns {[Function, Function]} - [getter, setter]
|
|
249
|
+
*/
|
|
250
|
+
export function signal(initialValue) {
|
|
251
|
+
const state = reactive({ value: initialValue });
|
|
252
|
+
|
|
253
|
+
const get = () => state.value;
|
|
254
|
+
const set = (newValue) => {
|
|
255
|
+
state.value = newValue;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
return [get, set];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Batch multiple updates into a single flush
|
|
263
|
+
* @param {Function} fn - Function containing updates
|
|
264
|
+
*/
|
|
265
|
+
export function batch(fn) {
|
|
266
|
+
const prevPending = isPending;
|
|
267
|
+
isPending = true;
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
fn();
|
|
271
|
+
} finally {
|
|
272
|
+
if (!prevPending) {
|
|
273
|
+
isPending = false;
|
|
274
|
+
queueMicrotask(flushWatchers);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
package/src/render.d.ts
ADDED
package/src/render.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOM rendering and patching engine
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { isTemplateResult } from './html.js';
|
|
6
|
+
|
|
7
|
+
// Store instance state keyed by container
|
|
8
|
+
const instanceMap = new WeakMap();
|
|
9
|
+
|
|
10
|
+
// Unique key for tracking array items
|
|
11
|
+
const KEY_SYMBOL = Symbol('bedrock-key');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Render a template result into a container
|
|
15
|
+
* @param {TemplateResult} result - The template to render
|
|
16
|
+
* @param {Element} container - The container element
|
|
17
|
+
*/
|
|
18
|
+
export function render(result, container) {
|
|
19
|
+
let instance = instanceMap.get(container);
|
|
20
|
+
|
|
21
|
+
if (!instance) {
|
|
22
|
+
// First render - create new instance
|
|
23
|
+
instance = createInstance(result, container);
|
|
24
|
+
instanceMap.set(container, instance);
|
|
25
|
+
} else if (instance.strings === result.strings) {
|
|
26
|
+
// Same template - update values only
|
|
27
|
+
updateInstance(instance, result.values);
|
|
28
|
+
} else {
|
|
29
|
+
// Different template - replace entirely
|
|
30
|
+
container.innerHTML = '';
|
|
31
|
+
instance = createInstance(result, container);
|
|
32
|
+
instanceMap.set(container, instance);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Create a new template instance
|
|
38
|
+
*/
|
|
39
|
+
function createInstance(result, container) {
|
|
40
|
+
const template = result.getTemplate();
|
|
41
|
+
const fragment = template.element.content.cloneNode(true);
|
|
42
|
+
|
|
43
|
+
// Resolve node paths to actual nodes in the fragment
|
|
44
|
+
const parts = template.parts.map((part) => {
|
|
45
|
+
if (!part) return null;
|
|
46
|
+
const node = getNodeByPath(fragment, part.path);
|
|
47
|
+
return { ...part, node, value: undefined };
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Apply initial values before adding to DOM
|
|
51
|
+
for (let i = 0; i < result.values.length; i++) {
|
|
52
|
+
if (parts[i]) {
|
|
53
|
+
applyValue(parts[i], result.values[i]);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
container.appendChild(fragment);
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
strings: result.strings,
|
|
61
|
+
parts,
|
|
62
|
+
container
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Update an existing instance with new values
|
|
68
|
+
*/
|
|
69
|
+
function updateInstance(instance, values) {
|
|
70
|
+
for (let i = 0; i < values.length; i++) {
|
|
71
|
+
const part = instance.parts[i];
|
|
72
|
+
if (part && part.value !== values[i]) {
|
|
73
|
+
applyValue(part, values[i]);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Apply a value to a part
|
|
80
|
+
*/
|
|
81
|
+
function applyValue(part, value) {
|
|
82
|
+
const oldValue = part.value;
|
|
83
|
+
part.value = value;
|
|
84
|
+
|
|
85
|
+
switch (part.type) {
|
|
86
|
+
case 'attribute':
|
|
87
|
+
applyAttribute(part.node, part.name, value);
|
|
88
|
+
break;
|
|
89
|
+
case 'property':
|
|
90
|
+
part.node[part.name] = value;
|
|
91
|
+
break;
|
|
92
|
+
case 'event':
|
|
93
|
+
applyEvent(part, value, oldValue);
|
|
94
|
+
break;
|
|
95
|
+
case 'node':
|
|
96
|
+
applyNode(part, value, oldValue);
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Apply an attribute value
|
|
103
|
+
*/
|
|
104
|
+
function applyAttribute(node, name, value) {
|
|
105
|
+
if (value === null || value === undefined || value === false) {
|
|
106
|
+
node.removeAttribute(name);
|
|
107
|
+
} else if (value === true) {
|
|
108
|
+
node.setAttribute(name, '');
|
|
109
|
+
} else {
|
|
110
|
+
node.setAttribute(name, String(value));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Apply an event handler
|
|
116
|
+
*/
|
|
117
|
+
function applyEvent(part, value, oldValue) {
|
|
118
|
+
if (oldValue) {
|
|
119
|
+
part.node.removeEventListener(part.name, oldValue);
|
|
120
|
+
}
|
|
121
|
+
if (value) {
|
|
122
|
+
part.node.addEventListener(part.name, value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Apply a node value (text, template, or array)
|
|
128
|
+
*/
|
|
129
|
+
function applyNode(part, value, oldValue) {
|
|
130
|
+
const node = part.node;
|
|
131
|
+
|
|
132
|
+
if (value === null || value === undefined) {
|
|
133
|
+
clearNodePart(part);
|
|
134
|
+
} else if (isTemplateResult(value)) {
|
|
135
|
+
applyTemplateNode(part, value);
|
|
136
|
+
} else if (Array.isArray(value)) {
|
|
137
|
+
applyArrayNode(part, value);
|
|
138
|
+
} else {
|
|
139
|
+
// Primitive value - render as text
|
|
140
|
+
clearNodePart(part);
|
|
141
|
+
const textNode = document.createTextNode(String(value));
|
|
142
|
+
node.parentNode.insertBefore(textNode, node);
|
|
143
|
+
part.nodes = [textNode];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Clear any rendered content for a node part
|
|
149
|
+
*/
|
|
150
|
+
function clearNodePart(part) {
|
|
151
|
+
if (part.nodes) {
|
|
152
|
+
part.nodes.forEach(n => n.remove());
|
|
153
|
+
part.nodes = null;
|
|
154
|
+
}
|
|
155
|
+
if (part.templateInstance) {
|
|
156
|
+
part.templateInstance = null;
|
|
157
|
+
}
|
|
158
|
+
if (part.arrayItems) {
|
|
159
|
+
part.arrayItems.forEach(item => item.nodes.forEach(n => n.remove()));
|
|
160
|
+
part.arrayItems = null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Apply a template result to a node part
|
|
166
|
+
*/
|
|
167
|
+
function applyTemplateNode(part, value) {
|
|
168
|
+
const marker = part.node;
|
|
169
|
+
|
|
170
|
+
// Check if we have an existing template instance with same structure
|
|
171
|
+
if (part.templateInstance && part.templateInstance.strings === value.strings) {
|
|
172
|
+
// Update existing template
|
|
173
|
+
updateInstance(part.templateInstance, value.values);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Clear any existing content
|
|
178
|
+
clearNodePart(part);
|
|
179
|
+
|
|
180
|
+
// Create new template instance
|
|
181
|
+
const template = value.getTemplate();
|
|
182
|
+
const fragment = template.element.content.cloneNode(true);
|
|
183
|
+
|
|
184
|
+
const parts = template.parts.map((p) => {
|
|
185
|
+
if (!p) return null;
|
|
186
|
+
const n = getNodeByPath(fragment, p.path);
|
|
187
|
+
return { ...p, node: n, value: undefined };
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Apply values
|
|
191
|
+
for (let i = 0; i < value.values.length; i++) {
|
|
192
|
+
if (parts[i]) {
|
|
193
|
+
applyValue(parts[i], value.values[i]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Track nodes being inserted
|
|
198
|
+
const nodes = Array.from(fragment.childNodes);
|
|
199
|
+
marker.parentNode.insertBefore(fragment, marker);
|
|
200
|
+
|
|
201
|
+
part.nodes = nodes;
|
|
202
|
+
part.templateInstance = { strings: value.strings, parts };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Apply an array of values/templates to a node part
|
|
207
|
+
*/
|
|
208
|
+
function applyArrayNode(part, values) {
|
|
209
|
+
const marker = part.node;
|
|
210
|
+
const parent = marker.parentNode;
|
|
211
|
+
const oldItems = part.arrayItems || [];
|
|
212
|
+
|
|
213
|
+
// Build map of old items by key
|
|
214
|
+
const oldItemsByKey = new Map();
|
|
215
|
+
for (const item of oldItems) {
|
|
216
|
+
if (item.key !== undefined) {
|
|
217
|
+
oldItemsByKey.set(item.key, item);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const newItems = [];
|
|
222
|
+
|
|
223
|
+
// Process each new value
|
|
224
|
+
for (let i = 0; i < values.length; i++) {
|
|
225
|
+
const value = values[i];
|
|
226
|
+
const key = value && value[KEY_SYMBOL] !== undefined ? value[KEY_SYMBOL] : i;
|
|
227
|
+
|
|
228
|
+
let item = oldItemsByKey.get(key);
|
|
229
|
+
|
|
230
|
+
if (item) {
|
|
231
|
+
// Reuse existing item
|
|
232
|
+
oldItemsByKey.delete(key);
|
|
233
|
+
if (isTemplateResult(value)) {
|
|
234
|
+
if (item.instance && item.instance.strings === value.strings) {
|
|
235
|
+
updateInstance(item.instance, value.values);
|
|
236
|
+
} else {
|
|
237
|
+
// Different template - recreate
|
|
238
|
+
item.nodes.forEach(n => n.remove());
|
|
239
|
+
item = createArrayItem(value, key, marker, parent);
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
// Update text content
|
|
243
|
+
if (item.nodes[0]) {
|
|
244
|
+
item.nodes[0].textContent = String(value ?? '');
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// Create new item
|
|
249
|
+
item = createArrayItem(value, key, marker, parent);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
newItems.push(item);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Remove old items that are no longer present
|
|
256
|
+
for (const item of oldItemsByKey.values()) {
|
|
257
|
+
item.nodes.forEach(n => n.remove());
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Reorder items to be before the marker
|
|
261
|
+
for (const item of newItems) {
|
|
262
|
+
for (const itemNode of item.nodes) {
|
|
263
|
+
parent.insertBefore(itemNode, marker);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
part.arrayItems = newItems;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Create a new array item
|
|
272
|
+
*/
|
|
273
|
+
function createArrayItem(value, key, marker, parent) {
|
|
274
|
+
if (isTemplateResult(value)) {
|
|
275
|
+
const template = value.getTemplate();
|
|
276
|
+
const fragment = template.element.content.cloneNode(true);
|
|
277
|
+
|
|
278
|
+
const parts = template.parts.map((p) => {
|
|
279
|
+
if (!p) return null;
|
|
280
|
+
const n = getNodeByPath(fragment, p.path);
|
|
281
|
+
return { ...p, node: n, value: undefined };
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
for (let i = 0; i < value.values.length; i++) {
|
|
285
|
+
if (parts[i]) {
|
|
286
|
+
applyValue(parts[i], value.values[i]);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const nodes = Array.from(fragment.childNodes);
|
|
291
|
+
parent.insertBefore(fragment, marker);
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
key,
|
|
295
|
+
nodes,
|
|
296
|
+
instance: { strings: value.strings, parts }
|
|
297
|
+
};
|
|
298
|
+
} else {
|
|
299
|
+
const textNode = document.createTextNode(String(value ?? ''));
|
|
300
|
+
parent.insertBefore(textNode, marker);
|
|
301
|
+
return { key, nodes: [textNode], instance: null };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get a node by path from a root
|
|
307
|
+
*/
|
|
308
|
+
function getNodeByPath(root, path) {
|
|
309
|
+
let node = root;
|
|
310
|
+
for (const index of path) {
|
|
311
|
+
if (!node.childNodes) return null;
|
|
312
|
+
node = node.childNodes[index];
|
|
313
|
+
if (!node) return null;
|
|
314
|
+
}
|
|
315
|
+
return node;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Create a keyed item for array rendering
|
|
320
|
+
* @param {any} key - Unique key for this item
|
|
321
|
+
* @param {TemplateResult} template - The template result
|
|
322
|
+
*/
|
|
323
|
+
export function keyed(key, template) {
|
|
324
|
+
template[KEY_SYMBOL] = key;
|
|
325
|
+
return template;
|
|
326
|
+
}
|
package/src/router.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Component } from './component.js';
|
|
2
|
+
|
|
3
|
+
export interface RouteDefinition {
|
|
4
|
+
path: string;
|
|
5
|
+
component: string;
|
|
6
|
+
loader?: (params: Record<string, string>) => any | Promise<any>;
|
|
7
|
+
[key: string]: any;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface RouterOptions {
|
|
11
|
+
routes?: RouteDefinition[];
|
|
12
|
+
hash?: boolean;
|
|
13
|
+
base?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface NavigationOptions {
|
|
17
|
+
replace?: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class Router {
|
|
21
|
+
constructor(options?: RouterOptions);
|
|
22
|
+
|
|
23
|
+
start(): this;
|
|
24
|
+
stop(): void;
|
|
25
|
+
setOutlet(outlet: RouterOutlet | Element): void;
|
|
26
|
+
|
|
27
|
+
readonly currentPath: string;
|
|
28
|
+
navigate(path: string, options?: NavigationOptions): void;
|
|
29
|
+
|
|
30
|
+
addRoute(route: RouteDefinition): void;
|
|
31
|
+
removeRoute(path: string): void;
|
|
32
|
+
|
|
33
|
+
readonly routes: RouteDefinition[];
|
|
34
|
+
readonly useHash: boolean;
|
|
35
|
+
|
|
36
|
+
static instance: Router | null;
|
|
37
|
+
currentRoute?: RouteDefinition & { params?: Record<string, string> };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export class RouterOutlet extends Component {}
|
|
41
|
+
|
|
42
|
+
export class RouterLink extends Component {
|
|
43
|
+
to?: string;
|
|
44
|
+
replace: boolean;
|
|
45
|
+
readonly href: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createRouter(options?: RouterOptions): Router;
|
|
49
|
+
|
|
50
|
+
export function navigate(path: string, options?: NavigationOptions): void;
|
|
51
|
+
|
|
52
|
+
export function getParams(): Record<string, string>;
|