@firsthandjs/dom 0.7.1 → 0.9.0
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/dist/chunk-26MSEGYG.js +1 -0
- package/dist/chunk-NNHZJMZS.js +1 -0
- package/dist/chunk-TZRPGQ3R.js +1 -0
- package/dist/claim.d.ts +76 -0
- package/dist/claim.d.ts.map +1 -0
- package/dist/component.d.ts +36 -1
- package/dist/component.d.ts.map +1 -1
- package/dist/dev-chunk-5QV3XALZ.js +45 -0
- package/dist/{dev-chunk-2INCAQ6G.js → dev-chunk-HCAUM4JK.js} +54 -277
- package/dist/dev-chunk-HTXIHAW7.js +501 -0
- package/dist/dev.d.ts +34 -0
- package/dist/dev.d.ts.map +1 -1
- package/dist/dev.prod.d.ts +9 -0
- package/dist/dev.prod.d.ts.map +1 -1
- package/dist/events.d.ts.map +1 -1
- package/dist/hydrate.d.ts +91 -0
- package/dist/hydrate.d.ts.map +1 -0
- package/dist/hydrate.dev.js +153 -0
- package/dist/hydrate.js +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.dev.js +19 -29
- package/dist/index.js +1 -1
- package/dist/insert.d.ts +112 -2
- package/dist/insert.d.ts.map +1 -1
- package/dist/internal.d.ts +4 -3
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.dev.js +46 -8
- package/dist/internal.js +1 -1
- package/dist/list.d.ts.map +1 -1
- package/dist/props.d.ts.map +1 -1
- package/dist/render.d.ts +9 -0
- package/dist/render.d.ts.map +1 -1
- package/dist/template.d.ts.map +1 -1
- package/package.json +7 -2
- package/dist/chunk-QZ5SESOU.js +0 -1
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
// packages/dom/src/dev.ts
|
|
2
|
+
function devWarn(message) {
|
|
3
|
+
console.warn(`[firsthand] ${message}`);
|
|
4
|
+
}
|
|
5
|
+
var seen = /* @__PURE__ */ new Set();
|
|
6
|
+
function devWarnOnce(key, message) {
|
|
7
|
+
if (!seen.has(key)) {
|
|
8
|
+
seen.add(key);
|
|
9
|
+
devWarn(message);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function devWarnRenderedObject(value) {
|
|
13
|
+
const cellish = "peek" in value && typeof value.peek === "function" && "value" in value;
|
|
14
|
+
devWarnOnce(
|
|
15
|
+
cellish ? "rendered-cell" : "rendered-object",
|
|
16
|
+
cellish ? 'A signal or computed was rendered directly, so the page shows "[object Object]". Read it instead: {count.value} rather than {count}.' : `An object was rendered as text, so the page shows "${String(value)}". Render a string, a number or a node.`
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
function hook() {
|
|
20
|
+
const installed = globalThis.__FIRSTHAND_DEVTOOLS__;
|
|
21
|
+
return installed !== void 0 && installed.attached ? installed : void 0;
|
|
22
|
+
}
|
|
23
|
+
function devPart(node, property) {
|
|
24
|
+
hook()?.part(node, property);
|
|
25
|
+
}
|
|
26
|
+
function devComponent(owner, name) {
|
|
27
|
+
hook()?.component(owner, name);
|
|
28
|
+
}
|
|
29
|
+
function label(value, kind, name) {
|
|
30
|
+
if (typeof value === "object" && value !== null) {
|
|
31
|
+
hook()?.label(value, kind, name);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
var QUIET = 20;
|
|
36
|
+
function devRan(store2) {
|
|
37
|
+
if (store2.busy === true) {
|
|
38
|
+
store2.busy = false;
|
|
39
|
+
store2.quiet = 0;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const quiet = (store2.quiet ?? 0) + 1;
|
|
43
|
+
store2.quiet = quiet;
|
|
44
|
+
if (quiet !== QUIET) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
devWarnOnce(
|
|
48
|
+
`quiet-run:${store2.name}`,
|
|
49
|
+
`<${store2.name}> ran ${String(QUIET)} times and wrote nothing.
|
|
50
|
+
Something it reads in a statement changes more often than what it shows. Move that read into the markup, where it is a part of its own, or move the derivation into a computed in the setup.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
function devHandedNewFunction(store2, value) {
|
|
54
|
+
if (typeof value !== "function") {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
devWarnOnce(
|
|
58
|
+
`new-function:${store2.name}`,
|
|
59
|
+
`<${store2.name}> hands a child a new function on every run, so that child runs again whenever this one does.
|
|
60
|
+
If the handler does not depend on the run, define it in the setup.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
function devHydrationMismatch(node, html) {
|
|
64
|
+
const template = document.createElement("template");
|
|
65
|
+
template.innerHTML = html;
|
|
66
|
+
const expected = template.content.firstElementChild;
|
|
67
|
+
if (expected === null) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const attribute of expected.attributes) {
|
|
71
|
+
const found = node.getAttribute(attribute.name);
|
|
72
|
+
if (found !== attribute.value) {
|
|
73
|
+
devWarn(
|
|
74
|
+
`Hydration found <${node.tagName.toLowerCase()} ${attribute.name}="${found ?? ""}"> where this render describes "${attribute.value}". The markup is kept as the server sent it, because static markup is never written again. Render the same thing on both sides, or render this part in the browser only.`
|
|
75
|
+
);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// packages/dom/src/claim.ts
|
|
82
|
+
var hydration = { current: null };
|
|
83
|
+
function first(node) {
|
|
84
|
+
const child = node.firstChild;
|
|
85
|
+
const claimer = hydration.current;
|
|
86
|
+
return claimer === null ? child : claimer.step(child);
|
|
87
|
+
}
|
|
88
|
+
function next(node) {
|
|
89
|
+
const sibling = node.nextSibling;
|
|
90
|
+
const claimer = hydration.current;
|
|
91
|
+
return claimer === null ? sibling : claimer.step(sibling);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// packages/dom/src/insert.ts
|
|
95
|
+
import {
|
|
96
|
+
bind,
|
|
97
|
+
createOwner,
|
|
98
|
+
signal,
|
|
99
|
+
disposeOwner,
|
|
100
|
+
getOwner,
|
|
101
|
+
onCleanup,
|
|
102
|
+
runWithOwner,
|
|
103
|
+
setOwner
|
|
104
|
+
} from "@firsthandjs/core";
|
|
105
|
+
var discarding = 0;
|
|
106
|
+
function discard(body) {
|
|
107
|
+
discarding++;
|
|
108
|
+
try {
|
|
109
|
+
body();
|
|
110
|
+
} finally {
|
|
111
|
+
discarding--;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
var TEXT_NODE = 3;
|
|
115
|
+
function isNode(value) {
|
|
116
|
+
return typeof value.nodeType === "number";
|
|
117
|
+
}
|
|
118
|
+
var PART = /* @__PURE__ */ Symbol("firsthand.part");
|
|
119
|
+
var FLAT = /* @__PURE__ */ Symbol("firsthand.flat");
|
|
120
|
+
function part(thunk) {
|
|
121
|
+
return { [PART]: true, anchor: document.createTextNode(""), thunk, owner: getOwner() };
|
|
122
|
+
}
|
|
123
|
+
function isDynamicChild(value) {
|
|
124
|
+
return PART in value;
|
|
125
|
+
}
|
|
126
|
+
function store(name = "") {
|
|
127
|
+
return { slots: [], owner: getOwner(), generation: 0, name };
|
|
128
|
+
}
|
|
129
|
+
function wrote(store2) {
|
|
130
|
+
store2.busy = true;
|
|
131
|
+
}
|
|
132
|
+
function site(store2, index) {
|
|
133
|
+
const existing = store2.slots[index];
|
|
134
|
+
if (existing !== void 0) {
|
|
135
|
+
existing.seen = store2.generation;
|
|
136
|
+
return existing;
|
|
137
|
+
}
|
|
138
|
+
const slot = { seen: store2.generation };
|
|
139
|
+
store2.slots[index] = slot;
|
|
140
|
+
return slot;
|
|
141
|
+
}
|
|
142
|
+
function open(store2, slot) {
|
|
143
|
+
const owner = createOwner(store2.owner);
|
|
144
|
+
slot.owner = owner;
|
|
145
|
+
return setOwner(owner);
|
|
146
|
+
}
|
|
147
|
+
function close(previous) {
|
|
148
|
+
setOwner(previous);
|
|
149
|
+
}
|
|
150
|
+
function ran(store2, value) {
|
|
151
|
+
devRan(store2);
|
|
152
|
+
const generation = store2.generation;
|
|
153
|
+
const slots = store2.slots;
|
|
154
|
+
for (let i = 0; i < slots.length; i++) {
|
|
155
|
+
const slot = slots[i];
|
|
156
|
+
if (slot === void 0 || slot.seen === generation) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (slot.owner !== void 0) {
|
|
160
|
+
disposeOwner(slot.owner);
|
|
161
|
+
}
|
|
162
|
+
slots[i] = void 0;
|
|
163
|
+
}
|
|
164
|
+
store2.generation = generation + 1;
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
function cell(store2, index, value) {
|
|
168
|
+
const slot = site(store2, index);
|
|
169
|
+
const existing = slot.cell;
|
|
170
|
+
if (existing === void 0) {
|
|
171
|
+
const made = signal(value);
|
|
172
|
+
slot.cell = made;
|
|
173
|
+
return made;
|
|
174
|
+
}
|
|
175
|
+
if (existing.peek() !== value) {
|
|
176
|
+
devHandedNewFunction(store2, value);
|
|
177
|
+
wrote(store2);
|
|
178
|
+
existing.value = value;
|
|
179
|
+
}
|
|
180
|
+
return existing;
|
|
181
|
+
}
|
|
182
|
+
function writeChild(store2, index, parent, marker, value) {
|
|
183
|
+
const slot = site(store2, index);
|
|
184
|
+
if (slot.node === void 0 && hydration.current !== null) {
|
|
185
|
+
const claimed = hydration.current.claim(parent, marker);
|
|
186
|
+
if (claimed !== null) {
|
|
187
|
+
if (typeof value === "object" && value !== null && isDynamicChild(value)) {
|
|
188
|
+
slot.last = value;
|
|
189
|
+
hydration.current.keep(slot, parent, value.anchor, claimed);
|
|
190
|
+
runWithOwner(value.owner, () => {
|
|
191
|
+
insert(parent, value.thunk, value.anchor);
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
slot.node = claimed.nodes;
|
|
196
|
+
slot.text = claimed.text;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const type = typeof value;
|
|
200
|
+
if (type === "string" || type === "number") {
|
|
201
|
+
const text = String(value);
|
|
202
|
+
if (text === slot.text) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
wrote(store2);
|
|
206
|
+
slot.text = text;
|
|
207
|
+
slot.node = applyChild(parent, marker, slot.node ?? null, text);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
slot.text = void 0;
|
|
211
|
+
if (value === slot.last) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
wrote(store2);
|
|
215
|
+
slot.last = value;
|
|
216
|
+
slot.node = applyChild(parent, marker, slot.node ?? null, value);
|
|
217
|
+
}
|
|
218
|
+
function insert(parent, value, marker = null, seed) {
|
|
219
|
+
if (typeof value !== "function") {
|
|
220
|
+
applyChild(parent, marker, null, value);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
let current = null;
|
|
224
|
+
let written;
|
|
225
|
+
const claimed = seed !== void 0 ? seed : hydration.current === null ? null : hydration.current.claim(parent, marker);
|
|
226
|
+
let adopting = claimed !== null;
|
|
227
|
+
if (claimed !== null) {
|
|
228
|
+
current = claimed.nodes;
|
|
229
|
+
written = claimed.text;
|
|
230
|
+
}
|
|
231
|
+
const body = () => {
|
|
232
|
+
const next2 = value();
|
|
233
|
+
const type = typeof next2;
|
|
234
|
+
if (type === "string" || type === "number") {
|
|
235
|
+
const text = String(next2);
|
|
236
|
+
if (text !== written) {
|
|
237
|
+
written = text;
|
|
238
|
+
current = applyChild(parent, marker, current, text);
|
|
239
|
+
}
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
written = void 0;
|
|
243
|
+
if (type === "function") {
|
|
244
|
+
if (adopting) {
|
|
245
|
+
current = null;
|
|
246
|
+
insert(parent, next2, marker, claimed);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
current = clear(current);
|
|
250
|
+
insert(parent, next2, marker, null);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
current = applyChild(parent, marker, current, next2);
|
|
254
|
+
};
|
|
255
|
+
if (claimed === null) {
|
|
256
|
+
bind(body);
|
|
257
|
+
} else {
|
|
258
|
+
hydration.current.within(claimed.region, () => {
|
|
259
|
+
bind(body);
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
adopting = false;
|
|
263
|
+
onCleanup(() => {
|
|
264
|
+
current = discarding > 0 ? null : clear(current);
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
function applyChild(parent, marker, current, value) {
|
|
268
|
+
const type = typeof value;
|
|
269
|
+
if (value == null || type === "boolean") {
|
|
270
|
+
devPart(parent, "text");
|
|
271
|
+
return clearIn(parent, marker, current);
|
|
272
|
+
}
|
|
273
|
+
if (type === "string" || type === "number") {
|
|
274
|
+
devPart(parent, "text");
|
|
275
|
+
const text = String(value);
|
|
276
|
+
if (current !== null && !Array.isArray(current) && current.nodeType === TEXT_NODE) {
|
|
277
|
+
current.data = text;
|
|
278
|
+
return current;
|
|
279
|
+
}
|
|
280
|
+
return single(parent, marker, current, document.createTextNode(text));
|
|
281
|
+
}
|
|
282
|
+
if (type === "function") {
|
|
283
|
+
return applyChild(parent, marker, current, value());
|
|
284
|
+
}
|
|
285
|
+
if (Array.isArray(value)) {
|
|
286
|
+
if (FLAT in value) {
|
|
287
|
+
const rows = value;
|
|
288
|
+
if (rows.length === 0) {
|
|
289
|
+
return clearIn(parent, marker, current);
|
|
290
|
+
}
|
|
291
|
+
reconcile(
|
|
292
|
+
parent,
|
|
293
|
+
marker,
|
|
294
|
+
current === null ? [] : Array.isArray(current) ? current : [current],
|
|
295
|
+
rows
|
|
296
|
+
);
|
|
297
|
+
return rows;
|
|
298
|
+
}
|
|
299
|
+
const next2 = [];
|
|
300
|
+
const dynamic = [];
|
|
301
|
+
flatten(value, next2, dynamic);
|
|
302
|
+
if (next2.length === 0) {
|
|
303
|
+
return clearIn(parent, marker, current);
|
|
304
|
+
}
|
|
305
|
+
reconcile(
|
|
306
|
+
parent,
|
|
307
|
+
marker,
|
|
308
|
+
current === null ? [] : Array.isArray(current) ? current : [current],
|
|
309
|
+
next2
|
|
310
|
+
);
|
|
311
|
+
for (let i = 0; i < dynamic.length; i++) {
|
|
312
|
+
const child = dynamic[i];
|
|
313
|
+
runWithOwner(child.owner ?? getOwner(), () => {
|
|
314
|
+
insert(parent, child.thunk, child.anchor);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return next2;
|
|
318
|
+
}
|
|
319
|
+
if (isDynamicChild(value)) {
|
|
320
|
+
return applyChild(parent, marker, current, [value]);
|
|
321
|
+
}
|
|
322
|
+
if (isNode(value)) {
|
|
323
|
+
return single(parent, marker, current, value);
|
|
324
|
+
}
|
|
325
|
+
devWarnRenderedObject(value);
|
|
326
|
+
return applyChild(parent, marker, current, String(value));
|
|
327
|
+
}
|
|
328
|
+
function flatten(value, out, dynamic) {
|
|
329
|
+
for (let i = 0; i < value.length; i++) {
|
|
330
|
+
let item = value[i];
|
|
331
|
+
if (typeof item === "object" && item !== null && isDynamicChild(item)) {
|
|
332
|
+
out.push(item.anchor);
|
|
333
|
+
dynamic.push(item);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
while (typeof item === "function") {
|
|
337
|
+
item = item();
|
|
338
|
+
}
|
|
339
|
+
if (item == null || typeof item === "boolean") {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (Array.isArray(item)) {
|
|
343
|
+
flatten(item, out, dynamic);
|
|
344
|
+
} else if (typeof item === "object" && isNode(item)) {
|
|
345
|
+
out.push(item);
|
|
346
|
+
} else {
|
|
347
|
+
out.push(document.createTextNode(String(item)));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
function detach(node) {
|
|
352
|
+
const owner = node.parentNode;
|
|
353
|
+
if (owner !== null) {
|
|
354
|
+
owner.removeChild(node);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
function clearIn(parent, marker, current) {
|
|
358
|
+
if (marker === null && Array.isArray(current) && current.length > 1 && parent.childNodes.length === current.length) {
|
|
359
|
+
parent.textContent = "";
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
return clear(current);
|
|
363
|
+
}
|
|
364
|
+
function clear(current) {
|
|
365
|
+
if (current !== null) {
|
|
366
|
+
if (Array.isArray(current)) {
|
|
367
|
+
for (let i = 0; i < current.length; i++) {
|
|
368
|
+
detach(current[i]);
|
|
369
|
+
}
|
|
370
|
+
} else {
|
|
371
|
+
detach(current);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return null;
|
|
375
|
+
}
|
|
376
|
+
function single(parent, marker, current, node) {
|
|
377
|
+
if (current === node) {
|
|
378
|
+
return node;
|
|
379
|
+
}
|
|
380
|
+
if (current !== null && !Array.isArray(current)) {
|
|
381
|
+
parent.replaceChild(node, current);
|
|
382
|
+
return node;
|
|
383
|
+
}
|
|
384
|
+
clear(current);
|
|
385
|
+
parent.insertBefore(node, marker);
|
|
386
|
+
return node;
|
|
387
|
+
}
|
|
388
|
+
function reconcile(parent, marker, a, b) {
|
|
389
|
+
let aStart = 0;
|
|
390
|
+
let bStart = 0;
|
|
391
|
+
let aEnd = a.length - 1;
|
|
392
|
+
let bEnd = b.length - 1;
|
|
393
|
+
while (aStart <= aEnd && bStart <= bEnd && a[aStart] === b[bStart]) {
|
|
394
|
+
aStart++;
|
|
395
|
+
bStart++;
|
|
396
|
+
}
|
|
397
|
+
while (aStart <= aEnd && bStart <= bEnd && a[aEnd] === b[bEnd]) {
|
|
398
|
+
aEnd--;
|
|
399
|
+
bEnd--;
|
|
400
|
+
}
|
|
401
|
+
const after = bEnd + 1 < b.length ? b[bEnd + 1] : marker;
|
|
402
|
+
if (aStart > aEnd) {
|
|
403
|
+
for (let i = bStart; i <= bEnd; i++) {
|
|
404
|
+
parent.insertBefore(b[i], after);
|
|
405
|
+
}
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (bStart > bEnd) {
|
|
409
|
+
for (let i = aStart; i <= aEnd; i++) {
|
|
410
|
+
parent.removeChild(a[i]);
|
|
411
|
+
}
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const oldIndex = /* @__PURE__ */ new Map();
|
|
415
|
+
for (let i = aStart; i <= aEnd; i++) {
|
|
416
|
+
oldIndex.set(a[i], i);
|
|
417
|
+
}
|
|
418
|
+
const middle = bEnd - bStart + 1;
|
|
419
|
+
const sources = new Int32Array(middle).fill(-1);
|
|
420
|
+
for (let j = 0; j < middle; j++) {
|
|
421
|
+
const node = b[bStart + j];
|
|
422
|
+
const found = oldIndex.get(node);
|
|
423
|
+
if (found !== void 0) {
|
|
424
|
+
sources[j] = found;
|
|
425
|
+
oldIndex.delete(node);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
for (const node of oldIndex.keys()) {
|
|
429
|
+
parent.removeChild(node);
|
|
430
|
+
}
|
|
431
|
+
const keep = longestIncreasing(sources);
|
|
432
|
+
let k = keep.length - 1;
|
|
433
|
+
let anchor = after;
|
|
434
|
+
for (let j = middle - 1; j >= 0; j--) {
|
|
435
|
+
const node = b[bStart + j];
|
|
436
|
+
if (k >= 0 && keep[k] === j) {
|
|
437
|
+
k--;
|
|
438
|
+
} else {
|
|
439
|
+
parent.insertBefore(node, anchor);
|
|
440
|
+
}
|
|
441
|
+
anchor = node;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function longestIncreasing(sources) {
|
|
445
|
+
const length = sources.length;
|
|
446
|
+
const predecessor = new Int32Array(length).fill(-1);
|
|
447
|
+
const tails = [];
|
|
448
|
+
for (let i = 0; i < length; i++) {
|
|
449
|
+
const value = sources[i];
|
|
450
|
+
if (value === -1) {
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
let low = 0;
|
|
454
|
+
let high = tails.length;
|
|
455
|
+
while (low < high) {
|
|
456
|
+
const mid = low + high >> 1;
|
|
457
|
+
if (sources[tails[mid]] < value) {
|
|
458
|
+
low = mid + 1;
|
|
459
|
+
} else {
|
|
460
|
+
high = mid;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (low > 0) {
|
|
464
|
+
predecessor[i] = tails[low - 1];
|
|
465
|
+
}
|
|
466
|
+
tails[low] = i;
|
|
467
|
+
}
|
|
468
|
+
const result = [];
|
|
469
|
+
let cursor = tails.length > 0 ? tails[tails.length - 1] : -1;
|
|
470
|
+
while (cursor !== -1) {
|
|
471
|
+
result.push(cursor);
|
|
472
|
+
cursor = predecessor[cursor];
|
|
473
|
+
}
|
|
474
|
+
return result.reverse();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
export {
|
|
478
|
+
devWarn,
|
|
479
|
+
devWarnOnce,
|
|
480
|
+
devPart,
|
|
481
|
+
devComponent,
|
|
482
|
+
label,
|
|
483
|
+
devHydrationMismatch,
|
|
484
|
+
hydration,
|
|
485
|
+
first,
|
|
486
|
+
next,
|
|
487
|
+
discard,
|
|
488
|
+
FLAT,
|
|
489
|
+
part,
|
|
490
|
+
store,
|
|
491
|
+
wrote,
|
|
492
|
+
site,
|
|
493
|
+
open,
|
|
494
|
+
close,
|
|
495
|
+
ran,
|
|
496
|
+
cell,
|
|
497
|
+
writeChild,
|
|
498
|
+
insert,
|
|
499
|
+
applyChild,
|
|
500
|
+
reconcile
|
|
501
|
+
};
|
package/dist/dev.d.ts
CHANGED
|
@@ -38,4 +38,38 @@ export declare function devComponent(owner: object, name: string): void;
|
|
|
38
38
|
* emits no call to it at all.
|
|
39
39
|
*/
|
|
40
40
|
export declare function label<T>(value: T, kind: string, name: string): T;
|
|
41
|
+
/**
|
|
42
|
+
* Reports a render function that keeps running and keeps changing nothing.
|
|
43
|
+
*
|
|
44
|
+
* Nothing is optimised here and nothing is hidden: this is bookkeeping, said
|
|
45
|
+
* out loud. A run that produces exactly what is already on screen did work for
|
|
46
|
+
* no one, and the reason is always the same — it reads something in a
|
|
47
|
+
* statement that moves more often than what it shows.
|
|
48
|
+
*/
|
|
49
|
+
export declare function devRan(store: {
|
|
50
|
+
name: string;
|
|
51
|
+
busy?: boolean;
|
|
52
|
+
quiet?: number;
|
|
53
|
+
}): void;
|
|
54
|
+
/**
|
|
55
|
+
* Reports a child that is handed a new function on every run.
|
|
56
|
+
*
|
|
57
|
+
* A function made in a run is never equal to the one before it, so the child
|
|
58
|
+
* it is given to runs again every time its parent does — the `useCallback`
|
|
59
|
+
* problem, named rather than papered over.
|
|
60
|
+
*/
|
|
61
|
+
export declare function devHandedNewFunction(store: {
|
|
62
|
+
name: string;
|
|
63
|
+
}, value: unknown): void;
|
|
64
|
+
/**
|
|
65
|
+
* Reports markup a server sent that is not the markup this run describes.
|
|
66
|
+
*
|
|
67
|
+
* Only the element's own attributes are compared, and only in development:
|
|
68
|
+
* what is inside it may already have been hydrated, and parsing the template
|
|
69
|
+
* to compare more is the cost hydration exists to avoid. That is enough to
|
|
70
|
+
* name the mistake, which is always the same mistake — a view that renders
|
|
71
|
+
* one thing on a server and another in a browser, from props or state that
|
|
72
|
+
* differ between them.
|
|
73
|
+
*/
|
|
74
|
+
export declare function devHydrationMismatch(node: Element, html: string): void;
|
|
41
75
|
//# sourceMappingURL=dev.d.ts.map
|
package/dist/dev.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAID,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAWzD;AAkBD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAKhE"}
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../src/dev.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAID,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAK9D;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAWzD;AAkBD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,CAKhE;AAKD;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkBpF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI,CAUlF;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAoBtE"}
|
package/dist/dev.prod.d.ts
CHANGED
|
@@ -5,4 +5,13 @@ export declare function devWarnRenderedObject(_value: object): void;
|
|
|
5
5
|
export declare function devPart(_node: object, _property: string): void;
|
|
6
6
|
export declare function devComponent(_owner: object, _name: string): void;
|
|
7
7
|
export declare function label<T>(value: T, _kind: string, _name: string): T;
|
|
8
|
+
export declare function devRan(_store: {
|
|
9
|
+
name: string;
|
|
10
|
+
busy?: boolean;
|
|
11
|
+
quiet?: number;
|
|
12
|
+
}): void;
|
|
13
|
+
export declare function devHandedNewFunction(_store: {
|
|
14
|
+
name: string;
|
|
15
|
+
}, _value: unknown): void;
|
|
16
|
+
export declare function devHydrationMismatch(_node: Element, _html: string): void;
|
|
8
17
|
//# sourceMappingURL=dev.prod.d.ts.map
|
package/dist/dev.prod.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.prod.d.ts","sourceRoot":"","sources":["../src/dev.prod.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAElE"}
|
|
1
|
+
{"version":3,"file":"dev.prod.d.ts","sourceRoot":"","sources":["../src/dev.prod.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAE3E,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAE9D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEhE;AAED,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,CAElE;AAED,wBAAgB,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAErF;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI,CAEpF;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAExE"}
|
package/dist/events.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkEH;;;;;;GAMG;AACH,wBAAgB,EAAE,CAChB,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAC/B,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,GACvC,IAAI,
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAkEH;;;;;;GAMG;AACH,wBAAgB,EAAE,CAChB,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,EAC/B,OAAO,CAAC,EAAE,uBAAuB,GAAG,IAAI,GACvC,IAAI,CA2BN;AAED,yEAAyE;AACzE,wBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED,kEAAkE;AAClE,wBAAgB,eAAe,IAAI,IAAI,CAKtC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adopting server-rendered markup instead of building it.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* import { hydrate } from '@firsthandjs/dom/hydrate';
|
|
6
|
+
*
|
|
7
|
+
* hydrate(() => <App />, document.getElementById('app')!);
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Its own entry point, not part of `@firsthandjs/dom`, for one reason: an
|
|
11
|
+
* application without a server must not carry this. Nothing in the render
|
|
12
|
+
* path imports this module — `template` and `insert` read the holder in
|
|
13
|
+
* `claim.ts`, which stays empty — so a bundle that never imports it never
|
|
14
|
+
* contains it.
|
|
15
|
+
*
|
|
16
|
+
* A hydrating render runs exactly the same compiled code as a fresh one. What
|
|
17
|
+
* changes is where nodes come from: `template()` hands back a node the server
|
|
18
|
+
* already sent rather than a clone, and a dynamic child finds its content in
|
|
19
|
+
* place rather than creating it. Nothing else in the render path knows.
|
|
20
|
+
*
|
|
21
|
+
* The only thing that makes that possible is knowing which nodes belong to
|
|
22
|
+
* which dynamic child, and the server says so: it wraps every dynamic child in
|
|
23
|
+
* `<!--[-->` … marker, where the marker is the same comment the browser's own
|
|
24
|
+
* template has at that position. Two consequences follow, and they are the
|
|
25
|
+
* whole design:
|
|
26
|
+
*
|
|
27
|
+
* - **Navigation still works.** `firstChild.nextSibling` would walk into the
|
|
28
|
+
* content of a dynamic child, because the content has a length the template
|
|
29
|
+
* does not. Compiled with `hydratable`, navigation goes through `first` and
|
|
30
|
+
* `next` instead, which step over a whole region in one move — landing on
|
|
31
|
+
* the marker, which is the node the template has there.
|
|
32
|
+
* - **Text does not run together.** The parser would read `Hello, ` and a
|
|
33
|
+
* dynamic `Ada` as one text node. The opening comment sits between them, so
|
|
34
|
+
* the part gets a text node of its own to write to.
|
|
35
|
+
*
|
|
36
|
+
* The comments are in the server's markup only. A build without SSR emits
|
|
37
|
+
* neither them nor the navigation helpers, and pays nothing at all.
|
|
38
|
+
*/
|
|
39
|
+
import type { Dispose } from '@firsthandjs/core';
|
|
40
|
+
import { type Claimed, type Region } from './claim.js';
|
|
41
|
+
import type { View } from './component.js';
|
|
42
|
+
/**
|
|
43
|
+
* Takes over markup a server already sent.
|
|
44
|
+
*
|
|
45
|
+
* The same call as `render`, against the same view, with the same result — a
|
|
46
|
+
* live application and a disposer. What it does not do is build anything that
|
|
47
|
+
* is already there: every element is adopted, every text node is kept, and the
|
|
48
|
+
* only writes are the listeners and the properties markup cannot express.
|
|
49
|
+
*
|
|
50
|
+
* The view has to be the view the server rendered — same props, same state,
|
|
51
|
+
* same answer. Where it is not:
|
|
52
|
+
*
|
|
53
|
+
* - A **dynamic** value that disagrees is written, as it would be on any other
|
|
54
|
+
* change. The page ends up right; the work hydration saved is spent.
|
|
55
|
+
* - A **different element** is not adopted. The browser builds its own and the
|
|
56
|
+
* part it belongs to puts it in place of what was sent.
|
|
57
|
+
* - **Static markup** that disagrees is kept as the server wrote it, because
|
|
58
|
+
* static markup is the markup nothing ever writes again. Development says so
|
|
59
|
+
* by name; production cannot, and a view that renders two different things
|
|
60
|
+
* from the same props is a bug this cannot repair.
|
|
61
|
+
*/
|
|
62
|
+
export declare function hydrate(view: () => View, container?: ParentNode): Dispose;
|
|
63
|
+
/**
|
|
64
|
+
* Runs `body` against server markup already in `container`.
|
|
65
|
+
*
|
|
66
|
+
* Nested calls are counted rather than nested: an island inside an island is
|
|
67
|
+
* one hydration as far as the markup is concerned, and the outer one finishes
|
|
68
|
+
* the sweep.
|
|
69
|
+
*/
|
|
70
|
+
export declare function hydrateWith<T>(container: Node, body: () => T): T;
|
|
71
|
+
/**
|
|
72
|
+
* The next node this region has not handed out yet, if it is a `tag`.
|
|
73
|
+
*
|
|
74
|
+
* A node that is not what was asked for is left where it is rather than
|
|
75
|
+
* consumed: the caller builds its own, and the part it belongs to replaces
|
|
76
|
+
* what is there. Nothing is adopted on a guess.
|
|
77
|
+
*/
|
|
78
|
+
export declare function adopt(tag: string): Node | null;
|
|
79
|
+
/**
|
|
80
|
+
* Finds the region belonging to the next dynamic child of `parent`.
|
|
81
|
+
*
|
|
82
|
+
* Dynamic children are claimed in document order, which is the order the
|
|
83
|
+
* compiled code reaches them in, so this is a cursor and never a search.
|
|
84
|
+
* `marker` is what navigation landed on, and it has to be where the region
|
|
85
|
+
* ends: if it is not, the server and the client disagree about this subtree
|
|
86
|
+
* and the caller renders it from nothing instead.
|
|
87
|
+
*/
|
|
88
|
+
export declare function claimRegion(parent: Node, marker: Node | null): Claimed | null;
|
|
89
|
+
/** Makes `region` the one nodes are adopted from while `body` runs. */
|
|
90
|
+
export declare function within<T>(region: Region, body: () => T): T;
|
|
91
|
+
//# sourceMappingURL=hydrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hydrate.d.ts","sourceRoot":"","sources":["../src/hydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAa,KAAK,OAAO,EAAe,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AA0B3C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,IAAI,EAAE,SAAS,GAAE,UAA0B,GAAG,OAAO,CAIxF;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAgBhE;AAaD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAW9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,IAAI,CAqD7E;AAED,uEAAuE;AACvE,wBAAgB,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAQ1D"}
|