@solidjs/web 2.0.0-beta.25 → 2.0.0-beta.27

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.
Files changed (43) hide show
  1. package/dist/dev.cjs +14 -9
  2. package/dist/dev.js +14 -10
  3. package/dist/server.cjs +41 -78
  4. package/dist/server.js +42 -80
  5. package/dist/web.cjs +14 -9
  6. package/dist/web.js +14 -10
  7. package/frames/dist/client.cjs +180 -224
  8. package/frames/dist/client.dev.cjs +1456 -0
  9. package/frames/dist/client.dev.js +1444 -0
  10. package/frames/dist/client.js +181 -225
  11. package/frames/dist/server.cjs +139 -123
  12. package/frames/dist/server.js +139 -123
  13. package/package.json +24 -4
  14. package/serialization/dist/serialization.cjs +6 -3
  15. package/serialization/dist/serialization.js +6 -3
  16. package/serialization/types/index.d.ts +7 -1
  17. package/serialization/types-cjs/index.d.cts +7 -1
  18. package/server-functions/dist/client.cjs +18 -5
  19. package/server-functions/dist/client.js +16 -6
  20. package/server-functions/dist/server.cjs +164 -8
  21. package/server-functions/dist/server.js +158 -9
  22. package/types/client.d.ts +3 -3
  23. package/types/frames/client.d.ts +1 -1
  24. package/types/frames/frame-client.d.ts +34 -28
  25. package/types/frames/serializer.d.ts +7 -1
  26. package/types/jsx.d.ts +1 -1
  27. package/types/response.d.ts +10 -0
  28. package/types/serializer.d.ts +7 -1
  29. package/types/server-functions/client.d.ts +3 -0
  30. package/types/server-functions/flash.d.ts +38 -0
  31. package/types/server-functions/server.d.ts +79 -4
  32. package/types/server-functions/shared.d.ts +35 -0
  33. package/types-cjs/client.d.cts +3 -3
  34. package/types-cjs/frames/client.d.cts +1 -1
  35. package/types-cjs/frames/frame-client.d.cts +34 -28
  36. package/types-cjs/frames/serializer.d.cts +7 -1
  37. package/types-cjs/jsx.d.cts +1 -1
  38. package/types-cjs/response.d.cts +10 -0
  39. package/types-cjs/serializer.d.cts +7 -1
  40. package/types-cjs/server-functions/client.d.cts +3 -0
  41. package/types-cjs/server-functions/flash.d.cts +38 -0
  42. package/types-cjs/server-functions/server.d.cts +79 -4
  43. package/types-cjs/server-functions/shared.d.cts +35 -0
@@ -1,4 +1,5 @@
1
- import { getOwner, onCleanup, runWithOwner, sharedConfig, createOwner } from 'solid-js';
1
+ import { getOwner, onCleanup, createMemo, runWithOwner, createLoadingBoundary, sharedConfig, createOwner } from 'solid-js';
2
+ import { insert } from '@solidjs/web';
2
3
  import { configureServerFunctionsClient } from '@solidjs/web/server-functions/client';
3
4
  import { fromCrossJSON, Feature } from 'seroval';
4
5
  import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
@@ -64,22 +65,6 @@ function chunkToRecords(chunk) {
64
65
  args: chunk.args
65
66
  }
66
67
  };
67
- case "template":
68
- return {
69
- [`tpl:${chunk.key}`]: {
70
- kind: "template",
71
- html: chunk.html,
72
- fields: chunk.fields
73
- }
74
- };
75
- case "block":
76
- return {
77
- [`seg:${chunk.key}`]: {
78
- kind: "block",
79
- template: `tpl:${chunk.template}`,
80
- values: chunk.values
81
- }
82
- };
83
68
  case "complete":
84
69
  return {
85
70
  ":complete": true
@@ -127,7 +112,7 @@ function createFrameHost(options = {}) {
127
112
  pending.delete(id);
128
113
  const records = {};
129
114
  let version;
130
- for (const chunk of buffered) {
115
+ for (const chunk of buffered.chunks) {
131
116
  if (chunk.type === "data") {
132
117
  options.applyData && options.applyData(chunk);
133
118
  continue;
@@ -159,12 +144,20 @@ function createFrameHost(options = {}) {
159
144
  for (const frame of set) deliver(frame, chunk);
160
145
  return;
161
146
  }
162
- const buffered = pending.get(chunk.id) ?? [];
163
- const maxVersion = buffered.reduce((m, c) => Math.max(m, c.version), chunk.version);
164
- if (chunk.version < maxVersion) return;
165
- const kept = buffered.filter(c => c.version >= chunk.version);
166
- kept.push(chunk);
167
- pending.set(chunk.id, kept);
147
+ const buffered = pending.get(chunk.id);
148
+ if (!buffered) {
149
+ pending.set(chunk.id, {
150
+ version: chunk.version,
151
+ chunks: [chunk]
152
+ });
153
+ return;
154
+ }
155
+ if (chunk.version < buffered.version) return;
156
+ if (chunk.version > buffered.version) {
157
+ buffered.version = chunk.version;
158
+ buffered.chunks.length = 0;
159
+ }
160
+ buffered.chunks.push(chunk);
168
161
  },
169
162
  get(id) {
170
163
  const set = frames.get(id);
@@ -223,7 +216,7 @@ class FrameImpl {
223
216
  if (options.host && options.id !== undefined) {
224
217
  options.host.register(options.id, this);
225
218
  }
226
- if (options.adopt) this.#syncSlots();
219
+ if (options.adopt && this.#version === undefined) this.#syncSlots();
227
220
  }
228
221
  #parent() {
229
222
  return this.#element ?? this.#start.parentNode;
@@ -273,7 +266,7 @@ class FrameImpl {
273
266
  this.#revealed.clear();
274
267
  this.#fallbackShown.clear();
275
268
  for (const key of Object.keys(this.#store)) {
276
- if (key.startsWith("seg:") || key.startsWith("tpl:") || key === ":error") {
269
+ if (key.startsWith("seg:") || key === ":error") {
277
270
  delete this.#store[key];
278
271
  }
279
272
  }
@@ -342,10 +335,14 @@ class FrameImpl {
342
335
  if (record !== undefined) return record;
343
336
  return this.#options.resolveSlotRecord?.(occurrence);
344
337
  }
345
- #syncSlots() {
338
+ #removeSlotRecord(occurrence) {
339
+ const key = `slot:${occurrence}`;
340
+ if (key in this.#store) delete this.#store[key];else this.#options.removeSlotRecord?.(occurrence);
341
+ }
342
+ #syncSlots(root) {
346
343
  if (!this.#slots && !this.#options.resolveSlot) return;
347
344
  const found = new Map();
348
- this.#collectSlots(found);
345
+ if (root) collectSlots(root, found);else this.#collectSlots(found);
349
346
  for (const [occurrence, start] of found) {
350
347
  const callback = this.#resolveSlot(propOf(occurrence));
351
348
  if (!callback) continue;
@@ -358,11 +355,12 @@ class FrameImpl {
358
355
  this.#runSlotCleanups(occurrence);
359
356
  }
360
357
  if (!this.#mountedSlots.has(occurrence)) {
358
+ if (this.#options.adopt) this.#discoverRegions(occurrence, start);
361
359
  const nodes = this.#invokeSlot(occurrence, callback, record, start, this.#options.adopt);
362
360
  if (nodes) this.#replaceRange(occurrence, start, nodes);
363
361
  this.#slotNodes.set(occurrence, nodes);
364
362
  this.#mountedSlots.add(occurrence);
365
- this.#discoverRegions(occurrence, start);
363
+ if (!this.#options.adopt || nodes) this.#discoverRegions(occurrence, start);
366
364
  this.#bindRegions(occurrence);
367
365
  } else if (record !== this.#slotArgs.get(occurrence)) {
368
366
  if (this.#refArgsUnchanged(occurrence, record)) {
@@ -375,8 +373,10 @@ class FrameImpl {
375
373
  this.#bindRegions(occurrence);
376
374
  }
377
375
  }
378
- for (const occurrence of [...this.#mountedSlots]) {
379
- if (!found.has(occurrence)) this.#unmountSlot(occurrence);
376
+ if (!root) {
377
+ for (const occurrence of [...this.#mountedSlots]) {
378
+ if (!found.has(occurrence)) this.#unmountSlot(occurrence);
379
+ }
380
380
  }
381
381
  }
382
382
  #invokeSlot(occurrence, callback, record, start, adopted) {
@@ -389,6 +389,13 @@ class FrameImpl {
389
389
  existing: start ? rangeInterior(start, slotEnd(occurrence)) : []
390
390
  };
391
391
  const props = record && record.kind === "slot" ? this.#resolveArgs(occurrence, record.args) : {};
392
+ const regions = this.#slotRegions.get(occurrence);
393
+ if (regions) {
394
+ for (const entry of regions.values()) {
395
+ const argKey = entry.childId.slice(entry.childId.lastIndexOf(".") + 1);
396
+ if (!(argKey in props)) props[argKey] = entry.element;
397
+ }
398
+ }
392
399
  const content = callback(props, ctx);
393
400
  this.#slotArgs.set(occurrence, record);
394
401
  if (cleanups.length) this.#slotCleanups.set(occurrence, cleanups);
@@ -411,7 +418,7 @@ class FrameImpl {
411
418
  this.#slotNodes.delete(key);
412
419
  this.#slotArgs.delete(key);
413
420
  this.#slotResolvedRefs.delete(key);
414
- delete this.#store[`slot:${key}`];
421
+ this.#removeSlotRecord(key);
415
422
  this.#runSlotCleanups(key);
416
423
  const regions = this.#slotRegions.get(key);
417
424
  if (regions) {
@@ -445,29 +452,17 @@ class FrameImpl {
445
452
  cache[key] = resolved;
446
453
  props[key] = resolved;
447
454
  } else if (isFrameRef(value)) {
448
- const fragment = document.createDocumentFragment();
449
455
  let entry = regions.get(value.$frame);
450
456
  if (!entry) {
451
- const start = document.createComment(`frame:${value.$frame}:start`);
452
- const end = document.createComment(`frame:${value.$frame}:end`);
457
+ const element = makeFrameElement(value.$frame);
453
458
  entry = {
454
459
  childId: value.$frame,
455
- start,
456
- end,
460
+ element,
457
461
  frame: undefined
458
462
  };
459
463
  regions.set(value.$frame, entry);
460
- fragment.append(start, end);
461
- } else {
462
- let n = entry.start;
463
- while (n) {
464
- const next = n.nextSibling;
465
- fragment.append(n);
466
- if (n === entry.end) break;
467
- n = next;
468
- }
469
464
  }
470
- props[key] = fragment;
465
+ props[key] = entry.element;
471
466
  } else {
472
467
  props[key] = value;
473
468
  }
@@ -481,49 +476,12 @@ class FrameImpl {
481
476
  regions = new Map();
482
477
  this.#slotRegions.set(slotKey, regions);
483
478
  }
484
- const doc = start.ownerDocument;
485
479
  const endData = slotEnd(slotKey);
486
- const comments = [];
487
480
  let n = start.nextSibling;
488
481
  while (n && !(n.nodeType === COMMENT_NODE && n.data === endData)) {
489
- if (n.nodeType === COMMENT_NODE) comments.push(n);else if (n.nodeType === 1) {
490
- const walker = doc.createTreeWalker(n, 128 );
491
- let c;
492
- while (c = walker.nextNode()) comments.push(c);
493
- }
482
+ collectRegionElements(n, regions);
494
483
  n = n.nextSibling;
495
484
  }
496
- let depth = 0;
497
- let openId = null;
498
- let openStart = null;
499
- for (const c of comments) {
500
- const data = c.data;
501
- if (!data.startsWith("frame:")) continue;
502
- if (data.endsWith(":start")) {
503
- const childId = data.slice(6, -6);
504
- if (!childId) continue;
505
- if (depth === 0) {
506
- openId = childId;
507
- openStart = c;
508
- }
509
- depth++;
510
- } else if (data.endsWith(":end")) {
511
- const childId = data.slice(6, -4);
512
- if (!childId) continue;
513
- if (depth > 0) {
514
- depth--;
515
- if (depth === 0 && childId === openId && !regions.has(openId)) {
516
- regions.set(openId, {
517
- childId: openId,
518
- start: openStart,
519
- end: c,
520
- frame: undefined,
521
- adopt: true
522
- });
523
- }
524
- }
525
- }
526
- }
527
485
  }
528
486
  #refArgsUnchanged(occurrence, record) {
529
487
  const old = this.#slotArgs.get(occurrence);
@@ -567,15 +525,16 @@ class FrameImpl {
567
525
  const regions = this.#slotRegions.get(slotKey);
568
526
  if (!regions) return;
569
527
  for (const entry of regions.values()) {
570
- if (!entry.frame && entry.start.parentNode) {
571
- entry.frame = new FrameImpl(null, entry.start, entry.end, {
528
+ if (!entry.frame) {
529
+ entry.frame = new FrameImpl(entry.element, null, null, {
572
530
  id: entry.childId,
573
531
  host: this.#options.host,
574
532
  adopt: entry.adopt,
575
533
  claimScope: this.#options.claimScope ?? this.#options.id,
576
534
  ownerScope: this.#options.ownerScope,
577
535
  resolveSlot: prop => this.#resolveSlot(prop),
578
- resolveSlotRecord: occurrence => this.#resolveSlotRecord(occurrence)
536
+ resolveSlotRecord: occurrence => this.#resolveSlotRecord(occurrence),
537
+ removeSlotRecord: occurrence => this.#removeSlotRecord(occurrence)
579
538
  });
580
539
  }
581
540
  }
@@ -590,12 +549,7 @@ class FrameImpl {
590
549
  n = afterRange(n, id);
591
550
  continue;
592
551
  }
593
- const regionId = frameRegionStartId(n);
594
- if (regionId !== null) {
595
- n = afterFrameRegion(n, regionId);
596
- continue;
597
- }
598
- if (n.nodeType === ELEMENT_NODE) collectSlots(n, found);
552
+ if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n, found);
599
553
  n = n.nextSibling;
600
554
  }
601
555
  }
@@ -617,6 +571,7 @@ class FrameImpl {
617
571
  if (this.#disposed) return;
618
572
  this.#disposed = true;
619
573
  for (const key of [...this.#slotCleanups.keys()]) this.#runSlotCleanups(key);
574
+ for (const key of this.#mountedSlots) this.#removeSlotRecord(key);
620
575
  for (const regions of this.#slotRegions.values()) {
621
576
  for (const {
622
577
  frame
@@ -662,13 +617,7 @@ class FrameImpl {
662
617
  }
663
618
  #segmentReady(name) {
664
619
  const content = this.#store[`seg:${name}`];
665
- if (!content) return false;
666
- if (content.kind === "block") {
667
- const template = this.#store[content.template];
668
- if (!template || template.kind !== "template") return false;
669
- } else if (content.kind !== "html") {
670
- return false;
671
- }
620
+ if (!content || content.kind !== "html") return false;
672
621
  if (!this.#store[`seg:${name}:reveal`]) return false;
673
622
  const assets = this.#store[`seg:${name}:assets`];
674
623
  if (assets && assets.styles) {
@@ -695,6 +644,23 @@ class FrameImpl {
695
644
  parent.removeChild(n);
696
645
  n = next;
697
646
  }
647
+ if (this.#options.reveal) {
648
+ const fallbackFrag = tpl.content.cloneNode(true);
649
+ this.#claimTree(fallbackFrag);
650
+ this.#options.reveal({
651
+ before: closing,
652
+ fallback: [...fallbackFrag.childNodes],
653
+ content: () => {
654
+ const materialized = this.#materialize(content);
655
+ this.#syncSlots(materialized);
656
+ this.#claimTree(materialized);
657
+ return materialized;
658
+ }
659
+ });
660
+ tpl.remove();
661
+ this.#revealed.add(name);
662
+ return;
663
+ }
698
664
  const materialized = this.#materialize(content);
699
665
  this.#claimTree(materialized);
700
666
  parent.insertBefore(materialized, closing);
@@ -713,70 +679,37 @@ class FrameImpl {
713
679
  return true;
714
680
  }
715
681
  #materialize(record) {
716
- if (record.kind === "html") return parseFragment(record.value);
717
- if (record.kind === "block") {
718
- const template = this.#store[record.template];
719
- if (!template || template.kind !== "template") return parseFragment("");
720
- return materializeBlock(template, record.values);
721
- }
722
- return parseFragment("");
682
+ return record.kind === "html" ? parseFragment(record.value) : parseFragment("");
723
683
  }
724
684
  }
725
- function materializeBlock(template, values) {
726
- const fragment = parseFragment(template.html);
727
- for (let i = 0; i < template.fields.length; i++) {
728
- const marker = `field:${template.fields[i]}`;
729
- const value = values[i] == null ? "" : String(values[i]);
730
- let hole = findComment(fragment, marker);
731
- while (hole) {
732
- hole.replaceWith(document.createTextNode(value));
733
- hole = findComment(fragment, marker);
734
- }
735
- }
736
- return fragment;
737
- }
738
685
  function createFrame(boundary, options) {
739
686
  return new FrameImpl(boundary, null, null, options);
740
687
  }
741
- function adoptFrameRange(start, end, options) {
742
- return new FrameImpl(null, start, end, {
743
- ...options,
744
- adopt: true
745
- });
746
- }
747
- const FRAME = Symbol.for("dom-expressions.frame");
748
- function createFrameInsertable(options) {
749
- let frame = null;
750
- let start = null;
751
- let end = null;
688
+ const FRAME_TAG = "dx-frame";
689
+ const FRAME_ID_ATTR = "data-fid";
690
+ function createFrameElement(options) {
691
+ const el = makeFrameElement(options.id);
692
+ const frame = new FrameImpl(el, null, null, options);
752
693
  return {
694
+ element: el,
753
695
  get frame() {
754
696
  return frame;
755
697
  },
756
698
  dispose() {
757
- if (!frame) return;
758
699
  frame.dispose();
759
- frame = null;
760
- const parent = start.parentNode;
761
- if (!parent) return;
762
- let n = start;
763
- while (n) {
764
- const next = n.nextSibling;
765
- parent.removeChild(n);
766
- if (n === end) break;
767
- n = next;
768
- }
769
- },
770
- [FRAME](parent, marker) {
771
- if (frame) return;
772
- start = document.createComment("frame:start");
773
- end = document.createComment("frame:end");
774
- parent.insertBefore(start, marker);
775
- parent.insertBefore(end, marker);
776
- frame = new FrameImpl(null, start, end, options);
700
+ el.remove();
777
701
  }
778
702
  };
779
703
  }
704
+ function makeFrameElement(id) {
705
+ const el = document.createElement(FRAME_TAG);
706
+ el.style.display = "contents";
707
+ if (id !== undefined) el.setAttribute(FRAME_ID_ATTR, id);
708
+ return el;
709
+ }
710
+ function isFrameElement(node) {
711
+ return node.nodeType === ELEMENT_NODE && node.hasAttribute(FRAME_ID_ATTR);
712
+ }
780
713
  function segmentName(key) {
781
714
  const m = /^seg:([^:]+)$/.exec(key);
782
715
  return m ? m[1] : null;
@@ -875,29 +808,25 @@ function collectSlots(root, out) {
875
808
  n = afterRange(n, id);
876
809
  continue;
877
810
  }
878
- const regionId = frameRegionStartId(n);
879
- if (regionId !== null) {
880
- n = afterFrameRegion(n, regionId);
881
- continue;
882
- }
883
- if (n.nodeType === ELEMENT_NODE) collectSlots(n, out);
811
+ if (n.nodeType === ELEMENT_NODE && !isFrameElement(n)) collectSlots(n, out);
884
812
  n = n.nextSibling;
885
813
  }
886
814
  }
887
- const FRAME_REGION_START = /^frame:(.+):start$/;
888
- function frameRegionStartId(node) {
889
- if (node.nodeType !== COMMENT_NODE) return null;
890
- const m = FRAME_REGION_START.exec(node.data);
891
- return m ? m[1] : null;
892
- }
893
- function afterFrameRegion(start, id) {
894
- const end = `frame:${id}:end`;
895
- let n = start.nextSibling;
896
- while (n) {
897
- if (n.nodeType === COMMENT_NODE && n.data === end) return n.nextSibling;
898
- n = n.nextSibling;
815
+ function collectRegionElements(node, regions) {
816
+ if (node.nodeType !== ELEMENT_NODE) return;
817
+ if (isFrameElement(node)) {
818
+ const childId = node.getAttribute(FRAME_ID_ATTR);
819
+ if (childId && !regions.has(childId)) {
820
+ regions.set(childId, {
821
+ childId,
822
+ element: node,
823
+ frame: undefined,
824
+ adopt: true
825
+ });
826
+ }
827
+ return;
899
828
  }
900
- return null;
829
+ for (let c = node.firstChild; c; c = c.nextSibling) collectRegionElements(c, regions);
901
830
  }
902
831
  function argsEquivalent(a, b) {
903
832
  if (a === b) return true;
@@ -925,18 +854,6 @@ function rangeInterior(start, endData) {
925
854
  }
926
855
  return nodes;
927
856
  }
928
- function findComment(root, data) {
929
- const children = root.childNodes;
930
- for (let i = 0; i < children.length; i++) {
931
- const node = children[i];
932
- if (node.nodeType === COMMENT_NODE && node.data === data) return node;
933
- if (node.nodeType === ELEMENT_NODE) {
934
- const found = findComment(node, data);
935
- if (found) return found;
936
- }
937
- }
938
- return null;
939
- }
940
857
  function slotStartId(node) {
941
858
  if (node.nodeType !== COMMENT_NODE) return null;
942
859
  const m = SLOT_START.exec(node.data);
@@ -952,12 +869,18 @@ function compatible(a, b) {
952
869
  if (a.nodeType === ELEMENT_NODE) return a.nodeName === b.nodeName;
953
870
  return a.nodeType === TEXT_NODE || a.nodeType === COMMENT_NODE;
954
871
  }
872
+ function preservesOpen(el) {
873
+ const t = el.tagName;
874
+ return t === "DETAILS" || t === "DIALOG";
875
+ }
955
876
  function morphAttributes(oldEl, newEl, claim) {
956
877
  let reclaim = false;
957
878
  let changed = false;
879
+ const keepOpen = preservesOpen(oldEl);
958
880
  const oldAttrs = oldEl.attributes;
959
881
  for (let i = oldAttrs.length - 1; i >= 0; i--) {
960
882
  const name = oldAttrs[i].name;
883
+ if (keepOpen && name === "open") continue;
961
884
  if (!newEl.hasAttribute(name)) {
962
885
  oldEl.removeAttribute(name);
963
886
  changed = true;
@@ -967,6 +890,7 @@ function morphAttributes(oldEl, newEl, claim) {
967
890
  const newAttrs = newEl.attributes;
968
891
  for (let i = 0; i < newAttrs.length; i++) {
969
892
  const attr = newAttrs[i];
893
+ if (keepOpen && attr.name === "open") continue;
970
894
  if (oldEl.getAttribute(attr.name) !== attr.value) {
971
895
  oldEl.setAttribute(attr.name, attr.value);
972
896
  changed = true;
@@ -977,6 +901,7 @@ function morphAttributes(oldEl, newEl, claim) {
977
901
  }
978
902
  function morphNode(oldNode, newNode, claim) {
979
903
  if (oldNode.nodeType === ELEMENT_NODE) {
904
+ if (oldNode.hasAttribute("data-preserve")) return;
980
905
  morphAttributes(oldNode, newNode, claim);
981
906
  reconcileChildren(oldNode, newNode, null, null, claim);
982
907
  } else if (oldNode.data !== newNode.data) {
@@ -1161,15 +1086,15 @@ class ChunkReader {
1161
1086
  }
1162
1087
  }
1163
1088
  async next() {
1164
- if (this.buffer.length === 0) {
1089
+ while (this.buffer.length < 12) {
1165
1090
  if (this.done) {
1166
- return {
1091
+ if (this.buffer.length === 0) return {
1167
1092
  done: true,
1168
1093
  value: undefined
1169
1094
  };
1095
+ throw new Error("Malformed server function stream.");
1170
1096
  }
1171
1097
  await this.readChunk();
1172
- return await this.next();
1173
1098
  }
1174
1099
  const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
1175
1100
  const bytes = Number.parseInt(head, 16);
@@ -1307,15 +1232,18 @@ function normalizeSlotContent(value) {
1307
1232
  if (value == null || typeof value === "boolean") return document.createTextNode("");
1308
1233
  return value instanceof Node ? value : document.createTextNode(String(value));
1309
1234
  }
1235
+ function gatherClaims(el, registry) {
1236
+ if (el.hasAttribute("_hk")) registry.set(el.getAttribute("_hk"), el);
1237
+ if (el.hasAttribute(FRAME_ID_ATTR)) return;
1238
+ for (let c = el.firstElementChild; c; c = c.nextElementSibling) gatherClaims(c, registry);
1239
+ }
1310
1240
  function claimRender(prefix, existing, render) {
1311
1241
  const sc = sharedConfig;
1312
1242
  if (!sc.getNextContextId) return render();
1313
1243
  const registry = new Map();
1314
1244
  for (const n of existing) {
1315
- const el = n;
1316
- if (el.nodeType !== 1) continue;
1317
- if (el.hasAttribute("_hk")) registry.set(el.getAttribute("_hk"), el);
1318
- el.querySelectorAll("*[_hk]").forEach(child => registry.set(child.getAttribute("_hk"), child));
1245
+ if (n.nodeType !== 1) continue;
1246
+ gatherClaims(n, registry);
1319
1247
  }
1320
1248
  if (!registry.size) return render();
1321
1249
  const prevRegistry = sc.registry;
@@ -1357,50 +1285,80 @@ function slotsFor(props) {
1357
1285
  }
1358
1286
  });
1359
1287
  }
1288
+ function revealSeam(owner) {
1289
+ return seam => runWithOwner(owner, () => insert(seam.before.parentNode, createLoadingBoundary(() => seam.content(), () => seam.fallback), seam.before));
1290
+ }
1360
1291
  function boundaryComponent(host, id) {
1361
1292
  return props => {
1362
1293
  const owner = getOwner();
1363
- const insertable = createFrameInsertable({
1294
+ const {
1295
+ element,
1296
+ dispose
1297
+ } = createFrameElement({
1364
1298
  host,
1365
1299
  id,
1366
1300
  slots: slotsFor(props),
1367
- ownerScope: fn => runWithOwner(owner, fn)
1301
+ ownerScope: fn => runWithOwner(owner, fn),
1302
+ reveal: revealSeam(owner)
1368
1303
  });
1369
- onCleanup(() => insertable.dispose());
1370
- return insertable;
1304
+ onCleanup(dispose);
1305
+ return element;
1371
1306
  };
1372
1307
  }
1373
1308
  const claimedBoundaries = new Set();
1374
1309
  let boundaryIndex = null;
1375
- function findBoundaryRange(id) {
1310
+ const isBoundaryId = id => !id.includes(".");
1311
+ function indexBoundaries(root) {
1312
+ root.querySelectorAll(`[${FRAME_ID_ATTR}]`).forEach(el => {
1313
+ const key = el.getAttribute(FRAME_ID_ATTR);
1314
+ if (key && isBoundaryId(key) && !boundaryIndex.has(key)) boundaryIndex.set(key, el);
1315
+ });
1316
+ }
1317
+ function findBoundaryElement(id) {
1376
1318
  if (!boundaryIndex) {
1377
1319
  boundaryIndex = new Map();
1378
- if (typeof document !== "undefined" && document.body) {
1379
- const walker = document.createTreeWalker(document.body, 128 );
1380
- const opens = {};
1381
- let c;
1382
- while (c = walker.nextNode()) {
1383
- const d = c.data;
1384
- if (!d.startsWith("frame:")) continue;
1385
- if (d.endsWith(":start")) {
1386
- const key = d.slice(6, -6);
1387
- if (key && !(key in opens)) opens[key] = c;
1388
- } else if (d.endsWith(":end")) {
1389
- const key = d.slice(6, -4);
1390
- if (key && opens[key] && !boundaryIndex.has(key)) {
1391
- boundaryIndex.set(key, [opens[key], c]);
1392
- }
1393
- }
1394
- }
1395
- }
1320
+ if (typeof document !== "undefined" && document.body) indexBoundaries(document.body);
1396
1321
  }
1397
1322
  return boundaryIndex.get(id);
1398
1323
  }
1324
+ const boundaryWaiters = new Map();
1325
+ function documentStreaming() {
1326
+ const hy = globalThis._$HY;
1327
+ return !!hy && !hy.done;
1328
+ }
1329
+ function installRevealHook() {
1330
+ const hy = globalThis._$HY;
1331
+ if (!hy || hy.$sc) return;
1332
+ hy.$sc = true;
1333
+ const prev = hy.fe;
1334
+ hy.fe = (fragmentId, parent) => {
1335
+ if (prev) prev(fragmentId, parent);
1336
+ if (!boundaryIndex) return;
1337
+ const root = parent || (typeof document !== "undefined" ? document.body : null);
1338
+ if (!root) return;
1339
+ indexBoundaries(root);
1340
+ for (const [id, notify] of boundaryWaiters) {
1341
+ const el = boundaryIndex.get(id);
1342
+ if (!el) continue;
1343
+ boundaryWaiters.delete(id);
1344
+ notify(el);
1345
+ }
1346
+ };
1347
+ }
1399
1348
  function documentBoundary(host, id, props) {
1400
- const range = !claimedBoundaries.has(id) && findBoundaryRange(id);
1401
- if (!range) return boundaryComponent(host, id)(props);
1349
+ const claimed = claimedBoundaries.has(id);
1350
+ const el = !claimed ? findBoundaryElement(id) : undefined;
1351
+ if (el) return adoptBoundary(host, id, el, props);
1352
+ if (!claimed && !boundaryWaiters.has(id) && documentStreaming()) {
1353
+ const owner = getOwner();
1354
+ const arrival = new Promise(resolve => boundaryWaiters.set(id, resolve));
1355
+ onCleanup(() => boundaryWaiters.delete(id));
1356
+ return createMemo(() => arrival.then(node => runWithOwner(owner, () => adoptBoundary(host, id, node, props))));
1357
+ }
1358
+ return boundaryComponent(host, id)(props);
1359
+ }
1360
+ function adoptBoundary(host, id, el, props) {
1402
1361
  claimedBoundaries.add(id);
1403
- const [start, end] = range;
1404
1362
  const hy = globalThis._$HY;
1405
1363
  if (hy && hy.r) {
1406
1364
  const slotPrefix = `sc:slot:${id}:`;
@@ -1429,19 +1387,16 @@ function documentBoundary(host, id, props) {
1429
1387
  }
1430
1388
  }
1431
1389
  const owner = getOwner();
1432
- const frame = adoptFrameRange(start, end, {
1390
+ const frame = createFrame(el, {
1391
+ adopt: true,
1433
1392
  host,
1434
1393
  id,
1435
1394
  slots: slotsFor(props),
1436
- ownerScope: fn => runWithOwner(owner, fn)
1395
+ ownerScope: fn => runWithOwner(owner, fn),
1396
+ reveal: revealSeam(owner)
1437
1397
  });
1438
1398
  onCleanup(() => frame.dispose());
1439
- const nodes = [];
1440
- for (let n = start; n; n = n.nextSibling) {
1441
- nodes.push(n);
1442
- if (n === end) break;
1443
- }
1444
- return nodes;
1399
+ return el;
1445
1400
  }
1446
1401
  function installServerComponents(host = getFrameHost()) {
1447
1402
  const g = globalThis;
@@ -1454,6 +1409,7 @@ function installServerComponents(host = getFrameHost()) {
1454
1409
  };
1455
1410
  }
1456
1411
  g._$SC.impl = (id, props) => documentBoundary(host, id, props);
1412
+ installRevealHook();
1457
1413
  configureServerFunctionsClient({
1458
1414
  responseHandler: createServerComponentHandler({
1459
1415
  host,
@@ -1464,11 +1420,11 @@ function installServerComponents(host = getFrameHost()) {
1464
1420
  intercept: ({
1465
1421
  id
1466
1422
  }) => {
1467
- if (claimedBoundaries.has(id) || !findBoundaryRange(id)) return undefined;
1423
+ if (claimedBoundaries.has(id) || !findBoundaryElement(id)) return undefined;
1468
1424
  return g._$SC.r(id);
1469
1425
  }
1470
1426
  })
1471
1427
  });
1472
1428
  }
1473
1429
 
1474
- export { FRAME_APPLIED_EVENT, FRAME_STREAM_HEADER, applyFrameResponse, createFrame, createFrameHost, createFrameInsertable, createJSONDataTable, createServerComponentHandler, getFrameHost, installServerComponents, isFrameStreamResponse };
1430
+ export { FRAME_APPLIED_EVENT, FRAME_STREAM_HEADER, applyFrameResponse, createFrame, createFrameElement, createFrameHost, createJSONDataTable, createServerComponentHandler, getFrameHost, installServerComponents, isFrameStreamResponse };