@panyam/tsappkit 0.6.0 → 0.6.2

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/index.d.mts CHANGED
@@ -888,6 +888,18 @@ interface MountOptions<El, C> {
888
888
  defer?: (island: IslandSpec, el: El, mount: () => void) => void;
889
889
  /** Gets what the factory built for each island mounted later: through `defer`, or from a lazy entry. */
890
890
  onLateMount?: (component: C, island: IslandSpec) => void;
891
+ /**
892
+ * Gets every island that mounts, eager or late, with its slot, as it
893
+ * mounts (before onLateMount for a late one). IslandPage's debug overlay
894
+ * uses it to show when each island arrived.
895
+ */
896
+ onMount?: (component: C, island: IslandSpec, el: El) => void;
897
+ /**
898
+ * Gets every island that won't mount, with a short reason ("not in the
899
+ * registry", "no slot", "failed to load", "no factory", "factory threw"),
900
+ * alongside the message `log` gets.
901
+ */
902
+ onSkip?: (island: IslandSpec, reason: string) => void;
891
903
  }
892
904
  /**
893
905
  * Mounts every island in `spec` into the element `findSlot` gives for its
@@ -929,6 +941,10 @@ declare function mountIslands<Ctx, El, C, B>(spec: PageSpec, registry: Registry<
929
941
  * setupDependencies and activate. A deferred island mustn't be something
930
942
  * another island or the page needs at startup: nothing waits for it.
931
943
  *
944
+ * With `?islands` in the URL (or showIslandOverlay overridden), each slot is
945
+ * outlined and labelled with its island's name, load strategy and state; see
946
+ * IslandOverlay.
947
+ *
932
948
  * A page with no readable `#page-spec` mounts nothing and warns. Subclasses
933
949
  * that override initializeSpecificComponents call super and add to what it
934
950
  * returns.
@@ -942,6 +958,12 @@ declare abstract class IslandPage<Ctx, Ext extends object = {}> extends BasePage
942
958
  */
943
959
  protected readExtension(raw: Record<string, unknown>): Ext;
944
960
  protected initializeSpecificComponents(): LCMComponent[];
961
+ /**
962
+ * Whether to draw the island overlay. The default is true when the URL has
963
+ * an `islands` query parameter (`/game?islands`); a subclass can tie it to
964
+ * its own debug setting instead.
965
+ */
966
+ protected showIslandOverlay(): boolean;
945
967
  /**
946
968
  * Waits for `strategy` and then calls `mount`. Defaults to scheduleMount
947
969
  * against the window; a subclass or test can replace how the waiting is done.
@@ -949,6 +971,30 @@ declare abstract class IslandPage<Ctx, Ext extends object = {}> extends BasePage
949
971
  protected scheduleLoad(strategy: LoadStrategy, el: HTMLElement, mount: () => void): void;
950
972
  }
951
973
 
974
+ /**
975
+ * Outlines each island's slot and labels it with its name, slot, load
976
+ * strategy and state: `hero · top · eager · mounted 212 ms`,
977
+ * `below · bottom · visible · waiting`, or `ghost · foot · eager · not in the
978
+ * registry`. Times are from navigation start (performance.now()).
979
+ *
980
+ * IslandPage turns it on with `?islands` in the URL (see
981
+ * showIslandOverlay). It's a development aid: while it's on, every labelled
982
+ * slot is `position: relative`, which can move an island's absolutely
983
+ * positioned content.
984
+ */
985
+ declare class IslandOverlay {
986
+ private readonly doc;
987
+ private readonly now;
988
+ constructor(doc?: Document, now?: () => number);
989
+ /** The island has a slot and hasn't mounted yet. */
990
+ waiting(island: IslandSpec, el: HTMLElement): void;
991
+ /** The island has just mounted. */
992
+ mounted(island: IslandSpec, el: HTMLElement): void;
993
+ /** The island won't mount, for `reason`. */
994
+ failed(island: IslandSpec, el: HTMLElement, reason: string): void;
995
+ private label;
996
+ }
997
+
952
998
  /**
953
999
  * Common DOM utility functions for consistent behavior across components
954
1000
  */
@@ -1052,4 +1098,4 @@ declare class KeyboardShortcutManager {
1052
1098
  getCurrentArgs(): string;
1053
1099
  }
1054
1100
 
1055
- export { BaseComponent, BasePage, type Component, type ComponentEventType, ComponentEventTypes, type ComponentLifecycleEvent, EventBus, type EventHandler, type EventSubscriber, type IslandFactory, IslandPage, type IslandSpec, KeyboardShortcutManager, KeyboardState, type LCMComponent, type LCMComponentConfig, type LCMComponentEvent, type LazyIsland, LifecycleController, type LifecycleEventType, LifecycleEventTypes, type LoadEnv, type LoadStrategy, MobileBottomDrawer, Modal, type MountOptions, type PageSpec, type Registry, SPEC_ELEMENT_ID, type ShortcutConfig, type ShortcutManagerConfig, type SpecExtension, SplashScreen, TemplateLoader, ThemeManager, ToastManager, hasModifierKeys, isInInputContext, lazy, mountIslands, parseLoad, readSpec, scheduleMount, shouldIgnoreShortcut };
1101
+ export { BaseComponent, BasePage, type Component, type ComponentEventType, ComponentEventTypes, type ComponentLifecycleEvent, EventBus, type EventHandler, type EventSubscriber, type IslandFactory, IslandOverlay, IslandPage, type IslandSpec, KeyboardShortcutManager, KeyboardState, type LCMComponent, type LCMComponentConfig, type LCMComponentEvent, type LazyIsland, LifecycleController, type LifecycleEventType, LifecycleEventTypes, type LoadEnv, type LoadStrategy, MobileBottomDrawer, Modal, type MountOptions, type PageSpec, type Registry, SPEC_ELEMENT_ID, type ShortcutConfig, type ShortcutManagerConfig, type SpecExtension, SplashScreen, TemplateLoader, ThemeManager, ToastManager, hasModifierKeys, isInInputContext, lazy, mountIslands, parseLoad, readSpec, scheduleMount, shouldIgnoreShortcut };
package/dist/index.d.ts CHANGED
@@ -888,6 +888,18 @@ interface MountOptions<El, C> {
888
888
  defer?: (island: IslandSpec, el: El, mount: () => void) => void;
889
889
  /** Gets what the factory built for each island mounted later: through `defer`, or from a lazy entry. */
890
890
  onLateMount?: (component: C, island: IslandSpec) => void;
891
+ /**
892
+ * Gets every island that mounts, eager or late, with its slot, as it
893
+ * mounts (before onLateMount for a late one). IslandPage's debug overlay
894
+ * uses it to show when each island arrived.
895
+ */
896
+ onMount?: (component: C, island: IslandSpec, el: El) => void;
897
+ /**
898
+ * Gets every island that won't mount, with a short reason ("not in the
899
+ * registry", "no slot", "failed to load", "no factory", "factory threw"),
900
+ * alongside the message `log` gets.
901
+ */
902
+ onSkip?: (island: IslandSpec, reason: string) => void;
891
903
  }
892
904
  /**
893
905
  * Mounts every island in `spec` into the element `findSlot` gives for its
@@ -929,6 +941,10 @@ declare function mountIslands<Ctx, El, C, B>(spec: PageSpec, registry: Registry<
929
941
  * setupDependencies and activate. A deferred island mustn't be something
930
942
  * another island or the page needs at startup: nothing waits for it.
931
943
  *
944
+ * With `?islands` in the URL (or showIslandOverlay overridden), each slot is
945
+ * outlined and labelled with its island's name, load strategy and state; see
946
+ * IslandOverlay.
947
+ *
932
948
  * A page with no readable `#page-spec` mounts nothing and warns. Subclasses
933
949
  * that override initializeSpecificComponents call super and add to what it
934
950
  * returns.
@@ -942,6 +958,12 @@ declare abstract class IslandPage<Ctx, Ext extends object = {}> extends BasePage
942
958
  */
943
959
  protected readExtension(raw: Record<string, unknown>): Ext;
944
960
  protected initializeSpecificComponents(): LCMComponent[];
961
+ /**
962
+ * Whether to draw the island overlay. The default is true when the URL has
963
+ * an `islands` query parameter (`/game?islands`); a subclass can tie it to
964
+ * its own debug setting instead.
965
+ */
966
+ protected showIslandOverlay(): boolean;
945
967
  /**
946
968
  * Waits for `strategy` and then calls `mount`. Defaults to scheduleMount
947
969
  * against the window; a subclass or test can replace how the waiting is done.
@@ -949,6 +971,30 @@ declare abstract class IslandPage<Ctx, Ext extends object = {}> extends BasePage
949
971
  protected scheduleLoad(strategy: LoadStrategy, el: HTMLElement, mount: () => void): void;
950
972
  }
951
973
 
974
+ /**
975
+ * Outlines each island's slot and labels it with its name, slot, load
976
+ * strategy and state: `hero · top · eager · mounted 212 ms`,
977
+ * `below · bottom · visible · waiting`, or `ghost · foot · eager · not in the
978
+ * registry`. Times are from navigation start (performance.now()).
979
+ *
980
+ * IslandPage turns it on with `?islands` in the URL (see
981
+ * showIslandOverlay). It's a development aid: while it's on, every labelled
982
+ * slot is `position: relative`, which can move an island's absolutely
983
+ * positioned content.
984
+ */
985
+ declare class IslandOverlay {
986
+ private readonly doc;
987
+ private readonly now;
988
+ constructor(doc?: Document, now?: () => number);
989
+ /** The island has a slot and hasn't mounted yet. */
990
+ waiting(island: IslandSpec, el: HTMLElement): void;
991
+ /** The island has just mounted. */
992
+ mounted(island: IslandSpec, el: HTMLElement): void;
993
+ /** The island won't mount, for `reason`. */
994
+ failed(island: IslandSpec, el: HTMLElement, reason: string): void;
995
+ private label;
996
+ }
997
+
952
998
  /**
953
999
  * Common DOM utility functions for consistent behavior across components
954
1000
  */
@@ -1052,4 +1098,4 @@ declare class KeyboardShortcutManager {
1052
1098
  getCurrentArgs(): string;
1053
1099
  }
1054
1100
 
1055
- export { BaseComponent, BasePage, type Component, type ComponentEventType, ComponentEventTypes, type ComponentLifecycleEvent, EventBus, type EventHandler, type EventSubscriber, type IslandFactory, IslandPage, type IslandSpec, KeyboardShortcutManager, KeyboardState, type LCMComponent, type LCMComponentConfig, type LCMComponentEvent, type LazyIsland, LifecycleController, type LifecycleEventType, LifecycleEventTypes, type LoadEnv, type LoadStrategy, MobileBottomDrawer, Modal, type MountOptions, type PageSpec, type Registry, SPEC_ELEMENT_ID, type ShortcutConfig, type ShortcutManagerConfig, type SpecExtension, SplashScreen, TemplateLoader, ThemeManager, ToastManager, hasModifierKeys, isInInputContext, lazy, mountIslands, parseLoad, readSpec, scheduleMount, shouldIgnoreShortcut };
1101
+ export { BaseComponent, BasePage, type Component, type ComponentEventType, ComponentEventTypes, type ComponentLifecycleEvent, EventBus, type EventHandler, type EventSubscriber, type IslandFactory, IslandOverlay, IslandPage, type IslandSpec, KeyboardShortcutManager, KeyboardState, type LCMComponent, type LCMComponentConfig, type LCMComponentEvent, type LazyIsland, LifecycleController, type LifecycleEventType, LifecycleEventTypes, type LoadEnv, type LoadStrategy, MobileBottomDrawer, Modal, type MountOptions, type PageSpec, type Registry, SPEC_ELEMENT_ID, type ShortcutConfig, type ShortcutManagerConfig, type SpecExtension, SplashScreen, TemplateLoader, ThemeManager, ToastManager, hasModifierKeys, isInInputContext, lazy, mountIslands, parseLoad, readSpec, scheduleMount, shouldIgnoreShortcut };
package/dist/index.js CHANGED
@@ -1431,16 +1431,20 @@ function lazy(load) {
1431
1431
  }
1432
1432
  function mountIslands(spec, registry, findSlot, context, bus, log, options = {}) {
1433
1433
  const out = [];
1434
+ const skip = (island, reason, message2) => {
1435
+ log(message2);
1436
+ options.onSkip?.(island, reason);
1437
+ };
1434
1438
  let ctx;
1435
1439
  for (const island of spec.islands) {
1436
1440
  const entry = Object.prototype.hasOwnProperty.call(registry, island.name) ? registry[island.name] : void 0;
1437
1441
  if (!entry) {
1438
- log(`page spec: no island called "${island.name}" in this page's registry`);
1442
+ skip(island, "not in the registry", `page spec: no island called "${island.name}" in this page's registry`);
1439
1443
  continue;
1440
1444
  }
1441
1445
  const el = findSlot(island.slot);
1442
1446
  if (el === null) {
1443
- log(`page spec: island "${island.name}" wants slot "${island.slot}", which isn't on the page`);
1447
+ skip(island, "no slot", `page spec: island "${island.name}" wants slot "${island.slot}", which isn't on the page`);
1444
1448
  continue;
1445
1449
  }
1446
1450
  const build = (factory) => {
@@ -1448,14 +1452,16 @@ function mountIslands(spec, registry, findSlot, context, bus, log, options = {})
1448
1452
  ctx ?? (ctx = context());
1449
1453
  return factory(el, island, ctx, bus);
1450
1454
  } catch (err) {
1451
- log(`page spec: island "${island.name}" failed to mount: ${message(err)}`);
1455
+ skip(island, "factory threw", `page spec: island "${island.name}" failed to mount: ${message(err)}`);
1452
1456
  return void 0;
1453
1457
  }
1454
1458
  };
1455
1459
  const mountLate = () => {
1456
1460
  const late = (factory) => {
1457
1461
  const c2 = build(factory);
1458
- if (c2 !== void 0) options.onLateMount?.(c2, island);
1462
+ if (c2 === void 0) return;
1463
+ options.onMount?.(c2, island, el);
1464
+ options.onLateMount?.(c2, island);
1459
1465
  };
1460
1466
  if (typeof entry === "function") {
1461
1467
  late(entry);
@@ -1465,9 +1471,9 @@ function mountIslands(spec, registry, findSlot, context, bus, log, options = {})
1465
1471
  (m) => {
1466
1472
  const factory = typeof m === "function" ? m : m?.default;
1467
1473
  if (typeof factory === "function") late(factory);
1468
- else log(`page spec: island "${island.name}" loaded, but its module has no factory (a default export or the function itself)`);
1474
+ else skip(island, "no factory", `page spec: island "${island.name}" loaded, but its module has no factory (a default export or the function itself)`);
1469
1475
  },
1470
- (err) => log(`page spec: island "${island.name}" failed to load: ${message(err)}`)
1476
+ (err) => skip(island, "failed to load", `page spec: island "${island.name}" failed to load: ${message(err)}`)
1471
1477
  );
1472
1478
  };
1473
1479
  const strategy = parseLoad(island.load);
@@ -1483,7 +1489,10 @@ function mountIslands(spec, registry, findSlot, context, bus, log, options = {})
1483
1489
  continue;
1484
1490
  }
1485
1491
  const c = build(entry);
1486
- if (c !== void 0) out.push(c);
1492
+ if (c !== void 0) {
1493
+ out.push(c);
1494
+ options.onMount?.(c, island, el);
1495
+ }
1487
1496
  }
1488
1497
  return out;
1489
1498
  }
@@ -1491,6 +1500,48 @@ function message(err) {
1491
1500
  return err instanceof Error ? err.message : String(err);
1492
1501
  }
1493
1502
 
1503
+ // src/page/overlay.ts
1504
+ var STYLE_ID = "island-overlay-style";
1505
+ var CSS = `
1506
+ [data-island-debug] { position: relative; outline: 2px dashed #1565c0; outline-offset: -2px; }
1507
+ [data-island-debug]::after {
1508
+ content: attr(data-island-debug); position: absolute; top: 0; right: 0; z-index: 2147483647;
1509
+ font: 11px/1.4 ui-monospace, monospace; padding: 1px 6px; background: #1565c0; color: #fff; pointer-events: none;
1510
+ }
1511
+ [data-island-state="mounted"] { outline-color: #2e7d32; }
1512
+ [data-island-state="mounted"]::after { background: #2e7d32; }
1513
+ [data-island-state="failed"] { outline-color: #c62828; }
1514
+ [data-island-state="failed"]::after { background: #c62828; }
1515
+ `;
1516
+ var IslandOverlay = class {
1517
+ constructor(doc = document, now = () => performance.now()) {
1518
+ this.doc = doc;
1519
+ this.now = now;
1520
+ if (!doc.getElementById(STYLE_ID)) {
1521
+ const style = doc.createElement("style");
1522
+ style.id = STYLE_ID;
1523
+ style.textContent = CSS;
1524
+ doc.head.appendChild(style);
1525
+ }
1526
+ }
1527
+ /** The island has a slot and hasn't mounted yet. */
1528
+ waiting(island, el) {
1529
+ this.label(island, el, "waiting", "waiting");
1530
+ }
1531
+ /** The island has just mounted. */
1532
+ mounted(island, el) {
1533
+ this.label(island, el, "mounted", `mounted ${Math.round(this.now())} ms`);
1534
+ }
1535
+ /** The island won't mount, for `reason`. */
1536
+ failed(island, el, reason) {
1537
+ this.label(island, el, "failed", reason);
1538
+ }
1539
+ label(island, el, state, text) {
1540
+ el.dataset.islandState = state;
1541
+ el.dataset.islandDebug = [island.name, island.slot, island.load || "eager", text].join(" \xB7 ");
1542
+ }
1543
+ };
1544
+
1494
1545
  // src/page/spec.ts
1495
1546
  var SPEC_ELEMENT_ID = "page-spec";
1496
1547
  var SLOT = /^[a-z][a-z0-9-]*$/;
@@ -1543,10 +1594,18 @@ var IslandPage = class extends BasePage {
1543
1594
  console.warn(`page spec: no readable #${SPEC_ELEMENT_ID} on this page, so nothing is mounted`);
1544
1595
  return [];
1545
1596
  }
1597
+ const findSlot = (slot) => document.querySelector(`[data-slot="${slot}"]`);
1598
+ const overlay = this.showIslandOverlay() ? new IslandOverlay() : void 0;
1599
+ if (overlay) {
1600
+ for (const island of spec.islands) {
1601
+ const el = findSlot(island.slot);
1602
+ if (el) overlay.waiting(island, el);
1603
+ }
1604
+ }
1546
1605
  return mountIslands(
1547
1606
  spec,
1548
1607
  this.registry(),
1549
- (slot) => document.querySelector(`[data-slot="${slot}"]`),
1608
+ findSlot,
1550
1609
  () => this.makeContext(spec),
1551
1610
  this.eventBus,
1552
1611
  (message2) => console.warn(message2),
@@ -1554,10 +1613,23 @@ var IslandPage = class extends BasePage {
1554
1613
  defer: (island, el, mount) => this.scheduleLoad(parseLoad(island.load) ?? { kind: "eager" }, el, mount),
1555
1614
  onLateMount: (component, island) => {
1556
1615
  new LifecycleController(this.eventBus, LifecycleController.DefaultConfig).initializeFromRoot(component).catch((err) => console.warn(`page spec: island "${island.name}" failed to start: ${err instanceof Error ? err.message : String(err)}`));
1557
- }
1616
+ },
1617
+ onMount: overlay && ((_c, island, el) => overlay.mounted(island, el)),
1618
+ onSkip: overlay && ((island, reason) => {
1619
+ const el = findSlot(island.slot);
1620
+ if (el) overlay.failed(island, el, reason);
1621
+ })
1558
1622
  }
1559
1623
  );
1560
1624
  }
1625
+ /**
1626
+ * Whether to draw the island overlay. The default is true when the URL has
1627
+ * an `islands` query parameter (`/game?islands`); a subclass can tie it to
1628
+ * its own debug setting instead.
1629
+ */
1630
+ showIslandOverlay() {
1631
+ return new URLSearchParams(window.location.search).has("islands");
1632
+ }
1561
1633
  /**
1562
1634
  * Waits for `strategy` and then calls `mount`. Defaults to scheduleMount
1563
1635
  * against the window; a subclass or test can replace how the waiting is done.
@@ -1913,6 +1985,7 @@ exports.BaseComponent = BaseComponent;
1913
1985
  exports.BasePage = BasePage;
1914
1986
  exports.ComponentEventTypes = ComponentEventTypes;
1915
1987
  exports.EventBus = EventBus;
1988
+ exports.IslandOverlay = IslandOverlay;
1916
1989
  exports.IslandPage = IslandPage;
1917
1990
  exports.KeyboardShortcutManager = KeyboardShortcutManager;
1918
1991
  exports.KeyboardState = KeyboardState;