@solidjs/web 2.0.0-beta.29 → 2.0.0-beta.30

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.
@@ -102,12 +102,11 @@ function createFrameHost(options = {}) {
102
102
  const sibling = set.size ? set.values().next().value : undefined;
103
103
  set.add(frame);
104
104
  if (sibling) {
105
- if (sibling.version !== undefined) {
106
- frame.apply({
107
- version: sibling.version,
108
- r: sibling.store
109
- });
110
- }
105
+ frame.apply({
106
+ version: sibling.version ?? 0,
107
+ r: sibling.store
108
+ });
109
+ if (sibling.version === undefined) frame.rebase && frame.rebase();
111
110
  return;
112
111
  }
113
112
  const kept = retained.get(id);
@@ -117,6 +116,7 @@ function createFrameHost(options = {}) {
117
116
  version: kept.version,
118
117
  r: kept.records
119
118
  });
119
+ frame.rebase && frame.rebase();
120
120
  }
121
121
  const buffered = pending.get(id);
122
122
  if (buffered) {
@@ -205,6 +205,7 @@ class FrameImpl {
205
205
  #mountedSlots = new Set();
206
206
  #slotCleanups = new Map();
207
207
  #slotArgs = new Map();
208
+ #slotUpdaters = new Map();
208
209
  #slotRegions = new Map();
209
210
  #slotResolvedRefs = new Map();
210
211
  #slotNodes = new Map();
@@ -382,6 +383,21 @@ class FrameImpl {
382
383
  } else if (record !== this.#slotArgs.get(occurrence)) {
383
384
  if (this.#refArgsUnchanged(occurrence, record)) {
384
385
  this.#slotArgs.set(occurrence, record);
386
+ this.#reconcileRegions(occurrence, record);
387
+ continue;
388
+ }
389
+ const update = this.#slotUpdaters.get(occurrence);
390
+ if (update) {
391
+ const props = this.#resolveArgs(occurrence, record.args);
392
+ const regions = this.#slotRegions.get(occurrence);
393
+ if (regions) {
394
+ for (const [argKey, entry] of regions) {
395
+ if (!(argKey in props)) props[argKey] = entry.element;
396
+ }
397
+ }
398
+ this.#slotArgs.set(occurrence, record);
399
+ update(props);
400
+ this.#bindRegions(occurrence);
385
401
  continue;
386
402
  }
387
403
  const nodes = this.#invokeSlot(occurrence, callback, record, start);
@@ -397,6 +413,7 @@ class FrameImpl {
397
413
  }
398
414
  }
399
415
  #invokeSlot(occurrence, callback, record, start, adopted) {
416
+ this.#slotUpdaters.delete(occurrence);
400
417
  const cleanups = this.#slotCleanups.get(occurrence) ?? [];
401
418
  let existing = [];
402
419
  let end = null;
@@ -415,6 +432,7 @@ class FrameImpl {
415
432
  adopted: !!adopted,
416
433
  invoked: !!(record && record.kind === "slot"),
417
434
  onCleanup: fn => cleanups.push(fn),
435
+ onUpdate: fn => this.#slotUpdaters.set(occurrence, fn),
418
436
  existing,
419
437
  range: end ? {
420
438
  start,
@@ -424,8 +442,7 @@ class FrameImpl {
424
442
  const props = record && record.kind === "slot" ? this.#resolveArgs(occurrence, record.args) : {};
425
443
  const regions = this.#slotRegions.get(occurrence);
426
444
  if (regions) {
427
- for (const entry of regions.values()) {
428
- const argKey = entry.childId.slice(entry.childId.lastIndexOf(".") + 1);
445
+ for (const [argKey, entry] of regions) {
429
446
  if (!(argKey in props)) props[argKey] = entry.element;
430
447
  }
431
448
  }
@@ -451,6 +468,7 @@ class FrameImpl {
451
468
  this.#mountedSlots.delete(key);
452
469
  this.#slotNodes.delete(key);
453
470
  this.#slotArgs.delete(key);
471
+ this.#slotUpdaters.delete(key);
454
472
  this.#slotResolvedRefs.delete(key);
455
473
  this.#removeSlotRecord(key);
456
474
  this.#runSlotCleanups(key);
@@ -485,7 +503,7 @@ class FrameImpl {
485
503
  cache[key] = resolved;
486
504
  props[key] = resolved;
487
505
  } else if (isFrameRef(value)) {
488
- let entry = regions.get(value.$frame);
506
+ let entry = regions.get(key);
489
507
  if (!entry) {
490
508
  const element = makeFrameElement(value.$frame);
491
509
  entry = {
@@ -493,7 +511,9 @@ class FrameImpl {
493
511
  element,
494
512
  frame: undefined
495
513
  };
496
- regions.set(value.$frame, entry);
514
+ regions.set(key, entry);
515
+ } else if (entry.childId !== value.$frame) {
516
+ renameRegion(entry, value.$frame);
497
517
  }
498
518
  props[key] = entry.element;
499
519
  } else {
@@ -510,9 +530,10 @@ class FrameImpl {
510
530
  this.#slotRegions.set(slotKey, regions);
511
531
  }
512
532
  const endData = slotEnd(slotKey);
533
+ const prefix = `${this.#options.id}.`;
513
534
  let n = start.nextSibling;
514
535
  while (n && !(n.nodeType === COMMENT_NODE && n.data === endData)) {
515
- collectRegionElements(n, regions);
536
+ collectRegionElements(n, regions, prefix);
516
537
  n = n.nextSibling;
517
538
  }
518
539
  }
@@ -531,7 +552,7 @@ class FrameImpl {
531
552
  if (key in a) continue;
532
553
  const vb = b[key];
533
554
  if (!vb || typeof vb !== "object" || typeof vb.$frame !== "string") return false;
534
- if (!regions || !regions.has(vb.$frame)) return false;
555
+ if (!regions || !regions.has(key)) return false;
535
556
  }
536
557
  }
537
558
  const cache = this.#slotResolvedRefs.get(occurrence);
@@ -540,7 +561,7 @@ class FrameImpl {
540
561
  const vb = b[key];
541
562
  if (va === vb) continue;
542
563
  if (!va || !vb || typeof va !== "object" || typeof vb !== "object") return false;
543
- if (typeof va.$frame === "string" && va.$frame === vb.$frame) continue;
564
+ if (typeof va.$frame === "string" && typeof vb.$frame === "string") continue;
544
565
  if (typeof va.$ref === "string" && typeof vb.$ref === "string" && cache && key in cache) {
545
566
  const host = this.#options.host;
546
567
  const next = host ? host.resolve(vb, this.#options.id) : undefined;
@@ -554,6 +575,18 @@ class FrameImpl {
554
575
  }
555
576
  return true;
556
577
  }
578
+ #reconcileRegions(occurrence, record) {
579
+ const args = record && record.args;
580
+ if (!args) return;
581
+ const regions = this.#slotRegions.get(occurrence);
582
+ if (!regions) return;
583
+ for (const key of Object.keys(args)) {
584
+ const value = args[key];
585
+ if (!isFrameRef(value)) continue;
586
+ const entry = regions.get(key);
587
+ if (entry && entry.childId !== value.$frame) renameRegion(entry, value.$frame);
588
+ }
589
+ }
557
590
  #bindRegions(slotKey) {
558
591
  const regions = this.#slotRegions.get(slotKey);
559
592
  if (!regions) return;
@@ -595,6 +628,31 @@ class FrameImpl {
595
628
  records
596
629
  };
597
630
  }
631
+ rebind(id) {
632
+ if (this.#disposed || id === this.#options.id) return;
633
+ const {
634
+ host,
635
+ id: oldId
636
+ } = this.#options;
637
+ if (host && oldId !== undefined) host.unregister(oldId, this);
638
+ this.#options = {
639
+ ...this.#options,
640
+ id
641
+ };
642
+ if (this.#element && this.#element.nodeType === ELEMENT_NODE) {
643
+ this.#element.setAttribute(FRAME_ID_ATTR, id);
644
+ }
645
+ this.#version = undefined;
646
+ this.#revealed.clear();
647
+ this.#fallbackShown.clear();
648
+ for (const key of Object.keys(this.#store)) {
649
+ if (key.startsWith("seg:") || key === ":error") delete this.#store[key];
650
+ }
651
+ if (host) host.register(id, this);
652
+ }
653
+ rebase() {
654
+ this.#version = undefined;
655
+ }
598
656
  dispose() {
599
657
  if (this.#disposed) return;
600
658
  const {
@@ -626,6 +684,7 @@ class FrameImpl {
626
684
  const ranges = new Map();
627
685
  this.#collectSlots(ranges);
628
686
  reconcileChildren(parent, fragment, this.#start, this.#end, claim, ranges);
687
+ if (ranges.size) restoreDisplacedRanges(this.#firstContent(), this.#end, ranges);
629
688
  }
630
689
  }
631
690
  #clearContent() {
@@ -652,8 +711,8 @@ class FrameImpl {
652
711
  const assets = this.#store[`seg:${name}:assets`];
653
712
  if (assets && assets.styles) {
654
713
  let ready = true;
655
- for (const href of assets.styles) {
656
- if (!ensureStylesheet(href, this.#styleFlush)) ready = false;
714
+ for (const entry of assets.styles) {
715
+ if (!ensureStylesheet(entry, this.#styleFlush)) ready = false;
657
716
  }
658
717
  if (!ready) return false;
659
718
  }
@@ -769,11 +828,15 @@ function findHeadElement(selector, attr, value) {
769
828
  }
770
829
  return null;
771
830
  }
772
- function ensureStylesheet(href, onSettle) {
831
+ function ensureStylesheet(entry, onSettle) {
832
+ const href = typeof entry === "string" ? entry : entry.href;
773
833
  let link = findHeadElement('link[rel="stylesheet"]', "href", href);
774
834
  if (!link) {
775
835
  link = document.createElement("link");
776
836
  link.rel = "stylesheet";
837
+ if (typeof entry !== "string" && entry.attrs) {
838
+ for (const name in entry.attrs) link.setAttribute(name, entry.attrs[name]);
839
+ }
777
840
  link.href = href;
778
841
  const waiters = new Set();
779
842
  link._$frWaiters = waiters;
@@ -844,21 +907,28 @@ function collectSlots(n, end, out) {
844
907
  n = n.nextSibling;
845
908
  }
846
909
  }
847
- function collectRegionElements(node, regions) {
910
+ function collectRegionElements(node, regions, prefix) {
848
911
  if (node.nodeType !== ELEMENT_NODE) return;
849
912
  if (isFrameElement(node)) {
850
913
  const childId = node.getAttribute(FRAME_ID_ATTR);
851
- if (childId && !regions.has(childId)) {
852
- regions.set(childId, {
853
- childId,
854
- element: node,
855
- frame: undefined,
856
- adopt: true
857
- });
914
+ if (childId && childId.startsWith(prefix)) {
915
+ const argKey = childId.slice(childId.lastIndexOf(".") + 1);
916
+ if (!regions.has(argKey)) {
917
+ regions.set(argKey, {
918
+ childId,
919
+ element: node,
920
+ frame: undefined,
921
+ adopt: true
922
+ });
923
+ }
858
924
  }
859
925
  return;
860
926
  }
861
- for (let c = node.firstChild; c; c = c.nextSibling) collectRegionElements(c, regions);
927
+ for (let c = node.firstChild; c; c = c.nextSibling) collectRegionElements(c, regions, prefix);
928
+ }
929
+ function renameRegion(entry, childId) {
930
+ entry.childId = childId;
931
+ if (entry.frame) entry.frame.rebind(childId);else entry.element.setAttribute(FRAME_ID_ATTR, childId);
862
932
  }
863
933
  function argsEquivalent(a, b) {
864
934
  if (a === b) return true;
@@ -1063,6 +1133,22 @@ function reconcileChildren(parent, source, boundStart = null, boundEnd = null, c
1063
1133
  oldChild = next;
1064
1134
  }
1065
1135
  }
1136
+ function restoreDisplacedRanges(first, end, ranges) {
1137
+ const found = new Map();
1138
+ collectSlots(first, end, found);
1139
+ for (const [id, start] of found) {
1140
+ const displaced = ranges.get(id);
1141
+ if (!displaced || displaced === start) continue;
1142
+ ranges.delete(id);
1143
+ const parent = start.parentNode;
1144
+ if (displaced.nodeType === 11 ) {
1145
+ parent.insertBefore(displaced, start);
1146
+ } else {
1147
+ moveRangeBefore(parent, displaced, id, start);
1148
+ }
1149
+ stashRange(document.createDocumentFragment(), start, id);
1150
+ }
1151
+ }
1066
1152
  function stashRange(frag, start, id) {
1067
1153
  const end = slotEnd(id);
1068
1154
  let n = start;
@@ -1106,6 +1192,7 @@ async function applyFrameResponse(response, host, options = {}) {
1106
1192
  }
1107
1193
  const SERVER_COMPONENT = /*#__PURE__*/Symbol.for("dom-expressions.server-component");
1108
1194
  const SERVER_COMPONENT_ADDRESS = /*#__PURE__*/Symbol.for("dom-expressions.server-component-address");
1195
+ const COMPONENT_HANDOFF = /*#__PURE__*/Symbol.for("dom-expressions.component-handoff");
1109
1196
  let resolveServerComponent;
1110
1197
  function parseServerComponent(value, ctx) {
1111
1198
  return {
@@ -1160,18 +1247,48 @@ function createServerComponentHandler({
1160
1247
  codec = client.getServerFunctionsCodec
1161
1248
  }) {
1162
1249
  const byAddress = new Map();
1163
- const boundaryFor = address => {
1250
+ const forwards = new Map();
1251
+ const brand = (comp, fnId, frameId) => {
1252
+ const brandable = typeof comp === "function" || typeof comp === "object" && comp !== null;
1253
+ if (brandable && !comp[COMPONENT_HANDOFF]) {
1254
+ comp[COMPONENT_HANDOFF] = {
1255
+ fnId,
1256
+ frameId,
1257
+ take(prev) {
1258
+ const meta = prev !== null && (typeof prev === "function" || typeof prev === "object") && prev[COMPONENT_HANDOFF];
1259
+ if (!meta || meta.fnId !== fnId) return false;
1260
+ const root = meta.frameId;
1261
+ let cur = forwards.get(root) ?? root;
1262
+ if (cur !== root && !host.get(cur)) {
1263
+ forwards.delete(root);
1264
+ cur = root;
1265
+ }
1266
+ if (cur === frameId) return true;
1267
+ let frame = host.get(cur);
1268
+ if (!frame) return false;
1269
+ while (frame) {
1270
+ frame.rebind(frameId);
1271
+ frame = host.get(cur);
1272
+ }
1273
+ if (frameId === root) forwards.delete(root);else forwards.set(root, frameId);
1274
+ return true;
1275
+ }
1276
+ };
1277
+ }
1278
+ return comp;
1279
+ };
1280
+ const boundaryFor = (address, fnId) => {
1164
1281
  let entry = byAddress.get(address);
1165
1282
  if (!entry) {
1166
1283
  entry = {
1167
1284
  frameId: address,
1168
- component: component(address)
1285
+ component: brand(component(address), fnId, address)
1169
1286
  };
1170
1287
  byAddress.set(address, entry);
1171
1288
  }
1172
1289
  return entry;
1173
1290
  };
1174
- const resolveAddress = (id, address) => boundaryFor(address).component;
1291
+ const resolveAddress = (id, address) => boundaryFor(address, id).component;
1175
1292
  resolveServerComponent = resolveAddress;
1176
1293
  const versions = new Map();
1177
1294
  const bump = frameId => {
@@ -1186,7 +1303,7 @@ function createServerComponentHandler({
1186
1303
  const address = client.frameAddress(info.id, info.args);
1187
1304
  byAddress.set(address, {
1188
1305
  frameId: info.id,
1189
- component: hit
1306
+ component: brand(hit, info.id, info.id)
1190
1307
  });
1191
1308
  }
1192
1309
  return hit;
@@ -1200,11 +1317,11 @@ function createServerComponentHandler({
1200
1317
  if (adopted) {
1201
1318
  entry = {
1202
1319
  frameId: ctx.id,
1203
- component: adopted
1320
+ component: brand(adopted, ctx.id, ctx.id)
1204
1321
  };
1205
1322
  byAddress.set(address, entry);
1206
1323
  } else {
1207
- entry = boundaryFor(address);
1324
+ entry = boundaryFor(address, ctx.id);
1208
1325
  }
1209
1326
  }
1210
1327
  if (response.headers.has(client.SINGLE_FLIGHT_HEADER)) {
@@ -1229,7 +1346,7 @@ function createServerComponentHandler({
1229
1346
  if (!byAddress.has(address)) {
1230
1347
  byAddress.set(address, {
1231
1348
  frameId: functionId,
1232
- component,
1349
+ component: brand(component, functionId, functionId),
1233
1350
  address
1234
1351
  });
1235
1352
  }
@@ -1361,6 +1478,15 @@ function gatherClaims(el, registry) {
1361
1478
  if (el.hasAttribute(FRAME_ID_ATTR)) return;
1362
1479
  for (let c = el.firstElementChild; c; c = c.nextElementSibling) gatherClaims(c, registry);
1363
1480
  }
1481
+ function hasPendingFragment(existing) {
1482
+ for (const n of existing) {
1483
+ if (n.nodeType !== 1) continue;
1484
+ const el = n;
1485
+ if (el.tagName === "TEMPLATE" && el.id.startsWith("pl-")) return true;
1486
+ if (el.querySelector?.('template[id^="pl-"]')) return true;
1487
+ }
1488
+ return false;
1489
+ }
1364
1490
  function claimRender(prefix, existing, render) {
1365
1491
  const sc = solidJs.sharedConfig;
1366
1492
  if (!sc.getNextContextId) return render();
@@ -1369,10 +1495,11 @@ function claimRender(prefix, existing, render) {
1369
1495
  if (n.nodeType !== 1) continue;
1370
1496
  gatherClaims(n, registry);
1371
1497
  }
1372
- if (!registry.size) return render();
1498
+ if (!registry.size && !hasPendingFragment(existing)) return render();
1373
1499
  const prevRegistry = sc.registry;
1374
1500
  const prevHydrating = sc.hydrating;
1375
1501
  const prevClaimRoots = sc.claimRoots;
1502
+ if (prevRegistry) for (const key of registry.keys()) prevRegistry.delete(key);
1376
1503
  sc.registry = registry;
1377
1504
  sc.hydrating = true;
1378
1505
  sc.claimRoots = existing;
@@ -1386,6 +1513,19 @@ function claimRender(prefix, existing, render) {
1386
1513
  sc.claimRoots = prevClaimRoots;
1387
1514
  }
1388
1515
  }
1516
+ function liveSlotProps(initial, ctx) {
1517
+ const [args, setArgs] = solidJs.createSignal(initial);
1518
+ ctx.onUpdate(next => setArgs(() => next));
1519
+ return new Proxy({}, {
1520
+ get: (_, key) => args()[key],
1521
+ has: (_, key) => key in args(),
1522
+ ownKeys: () => Reflect.ownKeys(args()),
1523
+ getOwnPropertyDescriptor: (_, key) => key in args() ? {
1524
+ enumerable: true,
1525
+ configurable: true
1526
+ } : undefined
1527
+ });
1528
+ }
1389
1529
  function isReactiveContent(value) {
1390
1530
  if (typeof value === "function") return true;
1391
1531
  if (Array.isArray(value)) {
@@ -1416,7 +1556,10 @@ function slotsFor(props) {
1416
1556
  };
1417
1557
  const evaluate = () => {
1418
1558
  const v = props[prop];
1419
- return typeof v === "function" && ctx && ctx.invoked ? v(slotProps) : v;
1559
+ if (typeof v === "function" && ctx && ctx.invoked) {
1560
+ return v(ctx.onUpdate ? liveSlotProps(slotProps, ctx) : slotProps);
1561
+ }
1562
+ return v;
1420
1563
  };
1421
1564
  const adopted = !!(ctx && ctx.frame && ctx.adopted && ctx.existing && ctx.existing.length);
1422
1565
  const prefix = ctx && ctx.frame ? `sc-${ctx.frame}-${ctx.key}-` : "";