@lightspeed-js/core 0.4.8 → 0.4.9

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/compile.js CHANGED
@@ -1359,6 +1359,13 @@ class ForBlock {
1359
1359
  async reconcile(items) {
1360
1360
  const keys = this.keysFor(items);
1361
1361
  const next = new Map;
1362
+ const keep = new Set(keys);
1363
+ this.rows.forEach((row, key) => {
1364
+ if (!keep.has(key)) {
1365
+ this.rows.delete(key);
1366
+ this.renderer.destroyRow(this, row);
1367
+ }
1368
+ });
1362
1369
  for (let i = 0;i < items.length; i++) {
1363
1370
  const key = keys[i];
1364
1371
  const existing = this.rows.get(key);
@@ -1374,7 +1381,6 @@ class ForBlock {
1374
1381
  next.set(key, row);
1375
1382
  }
1376
1383
  }
1377
- this.rows.forEach((row) => this.renderer.destroyRow(this, row));
1378
1384
  this.rows = next;
1379
1385
  this.order = keys;
1380
1386
  this.position();
@@ -1388,17 +1394,30 @@ class ForBlock {
1388
1394
  const row = this.rows.get(this.order[i]);
1389
1395
  if (!row)
1390
1396
  continue;
1397
+ const nestedByAnchor = row.topLevelBlocks();
1391
1398
  for (let j = row.nodes.length - 1;j >= 0; j--) {
1392
1399
  const node = row.nodes[j];
1393
1400
  if (node.nextSibling !== reference || node.parentNode !== parent) {
1394
1401
  parent.insertBefore(node, reference);
1395
1402
  }
1396
1403
  reference = node;
1404
+ const nested = nestedByAnchor.get(node);
1405
+ if (nested)
1406
+ reference = nested.place();
1397
1407
  }
1398
1408
  }
1399
1409
  if (parent.isConnected)
1400
1410
  this.renderer.connected?.();
1401
1411
  }
1412
+ place() {
1413
+ this.position();
1414
+ for (const key of this.order) {
1415
+ const row = this.rows.get(key);
1416
+ if (row?.nodes.length)
1417
+ return row.nodes[0];
1418
+ }
1419
+ return this.anchor;
1420
+ }
1402
1421
  destroy() {
1403
1422
  this.pendingItems = null;
1404
1423
  this.rows.forEach((row) => this.renderer.destroyRow(this, row));
package/dist/index.js CHANGED
@@ -1103,6 +1103,22 @@ class Row {
1103
1103
  set item(value) {
1104
1104
  this.locals[this.locals.length - 1] = value;
1105
1105
  }
1106
+ topLevelBlocks() {
1107
+ const byAnchor = new Map;
1108
+ for (const block of this.blocks) {
1109
+ if (this.nodes.includes(block.anchor))
1110
+ byAnchor.set(block.anchor, block);
1111
+ }
1112
+ return byAnchor;
1113
+ }
1114
+ disownNestedRows() {
1115
+ const owned = new Set;
1116
+ for (const block of this.topLevelBlocks().values()) {
1117
+ block.rows.forEach((row) => row.nodes.forEach((node) => owned.add(node)));
1118
+ }
1119
+ if (owned.size)
1120
+ this.nodes = this.nodes.filter((node) => !owned.has(node));
1121
+ }
1106
1122
  }
1107
1123
  var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1108
1124
  var REFUSED_TAGS = new Set([
@@ -1266,6 +1282,13 @@ class ForBlock {
1266
1282
  async reconcile(items) {
1267
1283
  const keys = this.keysFor(items);
1268
1284
  const next = new Map;
1285
+ const keep = new Set(keys);
1286
+ this.rows.forEach((row, key) => {
1287
+ if (!keep.has(key)) {
1288
+ this.rows.delete(key);
1289
+ this.renderer.destroyRow(this, row);
1290
+ }
1291
+ });
1269
1292
  for (let i = 0;i < items.length; i++) {
1270
1293
  const key = keys[i];
1271
1294
  const existing = this.rows.get(key);
@@ -1281,7 +1304,6 @@ class ForBlock {
1281
1304
  next.set(key, row);
1282
1305
  }
1283
1306
  }
1284
- this.rows.forEach((row) => this.renderer.destroyRow(this, row));
1285
1307
  this.rows = next;
1286
1308
  this.order = keys;
1287
1309
  this.position();
@@ -1295,17 +1317,30 @@ class ForBlock {
1295
1317
  const row = this.rows.get(this.order[i]);
1296
1318
  if (!row)
1297
1319
  continue;
1320
+ const nestedByAnchor = row.topLevelBlocks();
1298
1321
  for (let j = row.nodes.length - 1;j >= 0; j--) {
1299
1322
  const node = row.nodes[j];
1300
1323
  if (node.nextSibling !== reference || node.parentNode !== parent) {
1301
1324
  parent.insertBefore(node, reference);
1302
1325
  }
1303
1326
  reference = node;
1327
+ const nested = nestedByAnchor.get(node);
1328
+ if (nested)
1329
+ reference = nested.place();
1304
1330
  }
1305
1331
  }
1306
1332
  if (parent.isConnected)
1307
1333
  this.renderer.connected?.();
1308
1334
  }
1335
+ place() {
1336
+ this.position();
1337
+ for (const key of this.order) {
1338
+ const row = this.rows.get(key);
1339
+ if (row?.nodes.length)
1340
+ return row.nodes[0];
1341
+ }
1342
+ return this.anchor;
1343
+ }
1309
1344
  destroy() {
1310
1345
  this.pendingItems = null;
1311
1346
  this.rows.forEach((row) => this.renderer.destroyRow(this, row));
@@ -1944,6 +1979,7 @@ class Mounter {
1944
1979
  if (!this.adoptNodes(templateNodes, content, rowContext, markers)) {
1945
1980
  return false;
1946
1981
  }
1982
+ row.disownNestedRows();
1947
1983
  for (const marker of markers) {
1948
1984
  this.mountMarker(marker, block.owner, rowContext).then((child) => {
1949
1985
  if (child)
@@ -2007,6 +2043,7 @@ class Mounter {
2007
2043
  row.instances.forEach((child) => this.bindings.updateTree(child));
2008
2044
  await Promise.all(row.blocks.map((nested) => nested.settled()));
2009
2045
  row.nodes = Array.from(fragment.childNodes);
2046
+ row.disownNestedRows();
2010
2047
  return row;
2011
2048
  }
2012
2049
  connected() {
@@ -2942,6 +2979,9 @@ class LightspeedRuntime {
2942
2979
  seedState(values) {
2943
2980
  this.cells.seed(values);
2944
2981
  }
2982
+ get signals() {
2983
+ return createSignalAPI(this.globalSignals);
2984
+ }
2945
2985
  shared(key, init, options) {
2946
2986
  const cell = this.cells.get(key, init, options);
2947
2987
  return {
@@ -2993,11 +3033,16 @@ class LightspeedRuntime {
2993
3033
  }
2994
3034
  async mount(componentId, target, props = {}, slotHtml) {
2995
3035
  const container = this.resolveTarget(target);
2996
- if (!container)
3036
+ if (!container) {
3037
+ warn(`mount(${componentId}): no element matches ${String(target)}`);
2997
3038
  return null;
3039
+ }
3040
+ componentId = componentId.toLowerCase();
2998
3041
  const instance = await this.mounter.mountRoot(componentId, container, props, this.parseSlotHtml(slotHtml));
2999
- if (!instance)
3042
+ if (!instance) {
3043
+ warn(`mount(${componentId}): no component with that id is registered. Known: ${[...new Set([...this.componentRegistry.getAllIds(), ...this.loaders.keys()])].join(", ") || "(none)"}`);
3000
3044
  return null;
3045
+ }
3001
3046
  const handle = {
3002
3047
  id: `root#${++this.rootCounter}`,
3003
3048
  componentId,
package/dist/ssg.js CHANGED
@@ -1122,6 +1122,22 @@ class Row {
1122
1122
  set item(value) {
1123
1123
  this.locals[this.locals.length - 1] = value;
1124
1124
  }
1125
+ topLevelBlocks() {
1126
+ const byAnchor = new Map;
1127
+ for (const block of this.blocks) {
1128
+ if (this.nodes.includes(block.anchor))
1129
+ byAnchor.set(block.anchor, block);
1130
+ }
1131
+ return byAnchor;
1132
+ }
1133
+ disownNestedRows() {
1134
+ const owned = new Set;
1135
+ for (const block of this.topLevelBlocks().values()) {
1136
+ block.rows.forEach((row) => row.nodes.forEach((node) => owned.add(node)));
1137
+ }
1138
+ if (owned.size)
1139
+ this.nodes = this.nodes.filter((node) => !owned.has(node));
1140
+ }
1125
1141
  }
1126
1142
  var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
1127
1143
  var REFUSED_TAGS = new Set([
@@ -1285,6 +1301,13 @@ class ForBlock {
1285
1301
  async reconcile(items) {
1286
1302
  const keys = this.keysFor(items);
1287
1303
  const next = new Map;
1304
+ const keep = new Set(keys);
1305
+ this.rows.forEach((row, key) => {
1306
+ if (!keep.has(key)) {
1307
+ this.rows.delete(key);
1308
+ this.renderer.destroyRow(this, row);
1309
+ }
1310
+ });
1288
1311
  for (let i = 0;i < items.length; i++) {
1289
1312
  const key = keys[i];
1290
1313
  const existing = this.rows.get(key);
@@ -1300,7 +1323,6 @@ class ForBlock {
1300
1323
  next.set(key, row);
1301
1324
  }
1302
1325
  }
1303
- this.rows.forEach((row) => this.renderer.destroyRow(this, row));
1304
1326
  this.rows = next;
1305
1327
  this.order = keys;
1306
1328
  this.position();
@@ -1314,17 +1336,30 @@ class ForBlock {
1314
1336
  const row = this.rows.get(this.order[i]);
1315
1337
  if (!row)
1316
1338
  continue;
1339
+ const nestedByAnchor = row.topLevelBlocks();
1317
1340
  for (let j = row.nodes.length - 1;j >= 0; j--) {
1318
1341
  const node = row.nodes[j];
1319
1342
  if (node.nextSibling !== reference || node.parentNode !== parent) {
1320
1343
  parent.insertBefore(node, reference);
1321
1344
  }
1322
1345
  reference = node;
1346
+ const nested = nestedByAnchor.get(node);
1347
+ if (nested)
1348
+ reference = nested.place();
1323
1349
  }
1324
1350
  }
1325
1351
  if (parent.isConnected)
1326
1352
  this.renderer.connected?.();
1327
1353
  }
1354
+ place() {
1355
+ this.position();
1356
+ for (const key of this.order) {
1357
+ const row = this.rows.get(key);
1358
+ if (row?.nodes.length)
1359
+ return row.nodes[0];
1360
+ }
1361
+ return this.anchor;
1362
+ }
1328
1363
  destroy() {
1329
1364
  this.pendingItems = null;
1330
1365
  this.rows.forEach((row) => this.renderer.destroyRow(this, row));
@@ -1967,6 +2002,7 @@ class Mounter {
1967
2002
  if (!this.adoptNodes(templateNodes, content, rowContext, markers)) {
1968
2003
  return false;
1969
2004
  }
2005
+ row.disownNestedRows();
1970
2006
  for (const marker of markers) {
1971
2007
  this.mountMarker(marker, block.owner, rowContext).then((child) => {
1972
2008
  if (child)
@@ -2030,6 +2066,7 @@ class Mounter {
2030
2066
  row.instances.forEach((child) => this.bindings.updateTree(child));
2031
2067
  await Promise.all(row.blocks.map((nested) => nested.settled()));
2032
2068
  row.nodes = Array.from(fragment.childNodes);
2069
+ row.disownNestedRows();
2033
2070
  return row;
2034
2071
  }
2035
2072
  connected() {
@@ -3050,6 +3087,9 @@ class LightspeedRuntime {
3050
3087
  seedState(values) {
3051
3088
  this.cells.seed(values);
3052
3089
  }
3090
+ get signals() {
3091
+ return createSignalAPI(this.globalSignals);
3092
+ }
3053
3093
  shared(key, init, options) {
3054
3094
  const cell = this.cells.get(key, init, options);
3055
3095
  return {
@@ -3101,11 +3141,16 @@ class LightspeedRuntime {
3101
3141
  }
3102
3142
  async mount(componentId, target, props = {}, slotHtml) {
3103
3143
  const container = this.resolveTarget(target);
3104
- if (!container)
3144
+ if (!container) {
3145
+ warn(`mount(${componentId}): no element matches ${String(target)}`);
3105
3146
  return null;
3147
+ }
3148
+ componentId = componentId.toLowerCase();
3106
3149
  const instance = await this.mounter.mountRoot(componentId, container, props, this.parseSlotHtml(slotHtml));
3107
- if (!instance)
3150
+ if (!instance) {
3151
+ warn(`mount(${componentId}): no component with that id is registered. Known: ${[...new Set([...this.componentRegistry.getAllIds(), ...this.loaders.keys()])].join(", ") || "(none)"}`);
3108
3152
  return null;
3153
+ }
3109
3154
  const handle = {
3110
3155
  id: `root#${++this.rootCounter}`,
3111
3156
  componentId,
@@ -1,3 +1,4 @@
1
+ import { type SignalAPI } from "./signals/index.js";
1
2
  import { ComponentInstance, type ComponentDefinition } from "./components/index.js";
2
3
  import type { MarkerRange } from "./dom/Mounter.js";
3
4
  /**
@@ -185,6 +186,11 @@ export declare class LightspeedRuntime {
185
186
  * Unscoped, so a subscription here lives until its returned unsubscribe is
186
187
  * called. Component logic gets the instance-bound facade instead.
187
188
  */
189
+ /**
190
+ * The page-wide signals, for code outside any component: a host app
191
+ * listening for what its islands emit, or emitting to them.
192
+ */
193
+ get signals(): SignalAPI;
188
194
  shared(key: string, init?: any, options?: any): {
189
195
  key: string;
190
196
  value: any;
@@ -30,6 +30,15 @@ export declare class Row implements BindingHolder {
30
30
  addBlock(block: ForBlock): void;
31
31
  get item(): any;
32
32
  set item(value: any);
33
+ /** Nested blocks whose anchor sits among this row's own nodes, by anchor. */
34
+ topLevelBlocks(): Map<Node, ForBlock>;
35
+ /**
36
+ * Drop the nodes that belong to rows of a nested block at this row's top
37
+ * level - a `$for` in a `<template>` branch, say. That block adds and
38
+ * removes its rows on its own; if this row still listed them, positioning
39
+ * this row would put a removed row back before the nested anchor.
40
+ */
41
+ disownNestedRows(): void;
33
42
  }
34
43
  export interface RowRenderer {
35
44
  createRow(block: ForBlock, item: any): Promise<Row>;
@@ -140,5 +149,7 @@ export declare class ForBlock {
140
149
  * moves are proportionate for the list sizes this targets.
141
150
  */
142
151
  private position;
152
+ /** Put the rows in place and say where the block now starts. */
153
+ place(): Node;
143
154
  destroy(): void;
144
155
  }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/vite.js CHANGED
@@ -553,6 +553,22 @@ class Row {
553
553
  set item(value) {
554
554
  this.locals[this.locals.length - 1] = value;
555
555
  }
556
+ topLevelBlocks() {
557
+ const byAnchor = new Map;
558
+ for (const block of this.blocks) {
559
+ if (this.nodes.includes(block.anchor))
560
+ byAnchor.set(block.anchor, block);
561
+ }
562
+ return byAnchor;
563
+ }
564
+ disownNestedRows() {
565
+ const owned = new Set;
566
+ for (const block of this.topLevelBlocks().values()) {
567
+ block.rows.forEach((row) => row.nodes.forEach((node) => owned.add(node)));
568
+ }
569
+ if (owned.size)
570
+ this.nodes = this.nodes.filter((node) => !owned.has(node));
571
+ }
556
572
  }
557
573
  var TAG_NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
558
574
  var REFUSED_TAGS = new Set([
@@ -716,6 +732,13 @@ class ForBlock {
716
732
  async reconcile(items) {
717
733
  const keys = this.keysFor(items);
718
734
  const next = new Map;
735
+ const keep = new Set(keys);
736
+ this.rows.forEach((row, key) => {
737
+ if (!keep.has(key)) {
738
+ this.rows.delete(key);
739
+ this.renderer.destroyRow(this, row);
740
+ }
741
+ });
719
742
  for (let i = 0;i < items.length; i++) {
720
743
  const key = keys[i];
721
744
  const existing = this.rows.get(key);
@@ -731,7 +754,6 @@ class ForBlock {
731
754
  next.set(key, row);
732
755
  }
733
756
  }
734
- this.rows.forEach((row) => this.renderer.destroyRow(this, row));
735
757
  this.rows = next;
736
758
  this.order = keys;
737
759
  this.position();
@@ -745,17 +767,30 @@ class ForBlock {
745
767
  const row = this.rows.get(this.order[i]);
746
768
  if (!row)
747
769
  continue;
770
+ const nestedByAnchor = row.topLevelBlocks();
748
771
  for (let j = row.nodes.length - 1;j >= 0; j--) {
749
772
  const node = row.nodes[j];
750
773
  if (node.nextSibling !== reference || node.parentNode !== parent) {
751
774
  parent.insertBefore(node, reference);
752
775
  }
753
776
  reference = node;
777
+ const nested = nestedByAnchor.get(node);
778
+ if (nested)
779
+ reference = nested.place();
754
780
  }
755
781
  }
756
782
  if (parent.isConnected)
757
783
  this.renderer.connected?.();
758
784
  }
785
+ place() {
786
+ this.position();
787
+ for (const key of this.order) {
788
+ const row = this.rows.get(key);
789
+ if (row?.nodes.length)
790
+ return row.nodes[0];
791
+ }
792
+ return this.anchor;
793
+ }
759
794
  destroy() {
760
795
  this.pendingItems = null;
761
796
  this.rows.forEach((row) => this.renderer.destroyRow(this, row));
@@ -1398,6 +1433,7 @@ class Mounter {
1398
1433
  if (!this.adoptNodes(templateNodes, content, rowContext, markers)) {
1399
1434
  return false;
1400
1435
  }
1436
+ row.disownNestedRows();
1401
1437
  for (const marker of markers) {
1402
1438
  this.mountMarker(marker, block.owner, rowContext).then((child) => {
1403
1439
  if (child)
@@ -1461,6 +1497,7 @@ class Mounter {
1461
1497
  row.instances.forEach((child) => this.bindings.updateTree(child));
1462
1498
  await Promise.all(row.blocks.map((nested) => nested.settled()));
1463
1499
  row.nodes = Array.from(fragment.childNodes);
1500
+ row.disownNestedRows();
1464
1501
  return row;
1465
1502
  }
1466
1503
  connected() {
@@ -4441,6 +4478,9 @@ class LightspeedRuntime {
4441
4478
  seedState(values) {
4442
4479
  this.cells.seed(values);
4443
4480
  }
4481
+ get signals() {
4482
+ return createSignalAPI(this.globalSignals);
4483
+ }
4444
4484
  shared(key, init, options) {
4445
4485
  const cell = this.cells.get(key, init, options);
4446
4486
  return {
@@ -4492,11 +4532,16 @@ class LightspeedRuntime {
4492
4532
  }
4493
4533
  async mount(componentId, target, props = {}, slotHtml) {
4494
4534
  const container = this.resolveTarget(target);
4495
- if (!container)
4535
+ if (!container) {
4536
+ warn(`mount(${componentId}): no element matches ${String(target)}`);
4496
4537
  return null;
4538
+ }
4539
+ componentId = componentId.toLowerCase();
4497
4540
  const instance = await this.mounter.mountRoot(componentId, container, props, this.parseSlotHtml(slotHtml));
4498
- if (!instance)
4541
+ if (!instance) {
4542
+ warn(`mount(${componentId}): no component with that id is registered. Known: ${[...new Set([...this.componentRegistry.getAllIds(), ...this.loaders.keys()])].join(", ") || "(none)"}`);
4499
4543
  return null;
4544
+ }
4500
4545
  const handle = {
4501
4546
  id: `root#${++this.rootCounter}`,
4502
4547
  componentId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightspeed-js/core",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "Lightspeed: a compile-first framework for islands of reactivity in static HTML.",
5
5
  "keywords": [
6
6
  "lightspeed",