@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.
@@ -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"}
@@ -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
@@ -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"}
@@ -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,CAUN;AAED,yEAAyE;AACzE,wBAAgB,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAIrD;AAED,kEAAkE;AAClE,wBAAgB,eAAe,IAAI,IAAI,CAKtC"}
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"}