@panyam/tsappkit 0.2.0 → 0.6.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.
package/dist/index.mjs CHANGED
@@ -255,9 +255,9 @@ var BaseComponent = class {
255
255
  /**
256
256
  * Log messages with component identification
257
257
  */
258
- log(message, data = null) {
258
+ log(message2, data = null) {
259
259
  if (this.debugMode) {
260
- console.log(`[${this.componentId}] ${message}`, data);
260
+ console.log(`[${this.componentId}] ${message2}`, data);
261
261
  }
262
262
  }
263
263
  /**
@@ -435,16 +435,16 @@ var LifecycleController = class {
435
435
  /**
436
436
  * Log message if debug logging is enabled
437
437
  */
438
- log(message, ...args) {
438
+ log(message2, ...args) {
439
439
  if (this.config.enableDebugLogging) {
440
- console.log(`[LifecycleController] ${message}`, ...args);
440
+ console.log(`[LifecycleController] ${message2}`, ...args);
441
441
  }
442
442
  }
443
443
  /**
444
444
  * Log error message
445
445
  */
446
- logError(message, error) {
447
- console.error(`[LifecycleController] ${message}`, error);
446
+ logError(message2, error) {
447
+ console.error(`[LifecycleController] ${message2}`, error);
448
448
  }
449
449
  /**
450
450
  * Get current status of all components
@@ -843,7 +843,7 @@ var _ToastManager = class _ToastManager {
843
843
  * @param type Toast type for styling
844
844
  * @param duration Duration in ms (default: 4000)
845
845
  */
846
- showToast(title, message, type = "info", duration = 4e3) {
846
+ showToast(title, message2, type = "info", duration = 4e3) {
847
847
  if (!this.container || !this.template) return "";
848
848
  const id = `toast-${Date.now()}-${this.counter++}`;
849
849
  const toast = this.template.cloneNode(true);
@@ -852,7 +852,7 @@ var _ToastManager = class _ToastManager {
852
852
  const titleElement = toast.querySelector(".toast-title");
853
853
  const messageElement = toast.querySelector(".toast-message");
854
854
  if (titleElement) titleElement.textContent = title;
855
- if (messageElement) messageElement.textContent = message;
855
+ if (messageElement) messageElement.textContent = message2;
856
856
  const iconContainer = toast.querySelector(".flex-shrink-0");
857
857
  if (iconContainer) {
858
858
  iconContainer.innerHTML = "";
@@ -957,7 +957,7 @@ var SplashScreen = class {
957
957
  /**
958
958
  * Update the splash screen message (if it hasn't been dismissed yet)
959
959
  */
960
- static updateMessage(title, message) {
960
+ static updateMessage(title, message2) {
961
961
  if (this.dismissed) {
962
962
  return;
963
963
  }
@@ -971,10 +971,10 @@ var SplashScreen = class {
971
971
  titleEl.textContent = title;
972
972
  }
973
973
  }
974
- if (message) {
974
+ if (message2) {
975
975
  const messageEl = splash.querySelector("[data-splash-message]");
976
976
  if (messageEl) {
977
- messageEl.textContent = message;
977
+ messageEl.textContent = message2;
978
978
  }
979
979
  }
980
980
  }
@@ -1089,8 +1089,8 @@ var BasePage = class extends BaseComponent {
1089
1089
  /**
1090
1090
  * Show a toast notification
1091
1091
  */
1092
- showToast(title, message, type = "info", duration) {
1093
- this.toastManager?.showToast(title, message, type, duration);
1092
+ showToast(title, message2, type = "info", duration) {
1093
+ this.toastManager?.showToast(title, message2, type, duration);
1094
1094
  }
1095
1095
  /**
1096
1096
  * Show a modal dialog
@@ -1371,13 +1371,68 @@ var MobileBottomDrawer = class extends BaseComponent {
1371
1371
  }
1372
1372
  };
1373
1373
 
1374
+ // src/page/load.ts
1375
+ function parseLoad(load) {
1376
+ switch (load ?? "") {
1377
+ case "":
1378
+ case "eager":
1379
+ return { kind: "eager" };
1380
+ case "idle":
1381
+ return { kind: "idle" };
1382
+ case "visible":
1383
+ return { kind: "visible" };
1384
+ }
1385
+ const query = load.startsWith("media:") ? load.slice("media:".length) : "";
1386
+ return query.trim() ? { kind: "media", query } : null;
1387
+ }
1388
+ function scheduleMount(strategy, el, mount, env = window) {
1389
+ switch (strategy.kind) {
1390
+ case "eager":
1391
+ mount();
1392
+ return;
1393
+ case "idle":
1394
+ if (env.requestIdleCallback) env.requestIdleCallback(mount);
1395
+ else env.setTimeout(mount, 1);
1396
+ return;
1397
+ case "visible": {
1398
+ let done = false;
1399
+ const observer = new env.IntersectionObserver((entries) => {
1400
+ if (done || !entries.some((e) => e.isIntersecting)) return;
1401
+ done = true;
1402
+ observer.disconnect();
1403
+ mount();
1404
+ });
1405
+ observer.observe(el);
1406
+ return;
1407
+ }
1408
+ case "media": {
1409
+ const mq = env.matchMedia(strategy.query);
1410
+ if (mq.matches) {
1411
+ mount();
1412
+ return;
1413
+ }
1414
+ const onChange = (e) => {
1415
+ if (!e.matches) return;
1416
+ mq.removeEventListener("change", onChange);
1417
+ mount();
1418
+ };
1419
+ mq.addEventListener("change", onChange);
1420
+ return;
1421
+ }
1422
+ }
1423
+ }
1424
+
1374
1425
  // src/page/mount.ts
1375
- function mountIslands(spec, registry, findSlot, context, bus, log) {
1426
+ var LAZY = /* @__PURE__ */ Symbol("lazy island");
1427
+ function lazy(load) {
1428
+ return { [LAZY]: load };
1429
+ }
1430
+ function mountIslands(spec, registry, findSlot, context, bus, log, options = {}) {
1376
1431
  const out = [];
1377
1432
  let ctx;
1378
1433
  for (const island of spec.islands) {
1379
- const factory = Object.prototype.hasOwnProperty.call(registry, island.name) ? registry[island.name] : void 0;
1380
- if (!factory) {
1434
+ const entry = Object.prototype.hasOwnProperty.call(registry, island.name) ? registry[island.name] : void 0;
1435
+ if (!entry) {
1381
1436
  log(`page spec: no island called "${island.name}" in this page's registry`);
1382
1437
  continue;
1383
1438
  }
@@ -1386,15 +1441,53 @@ function mountIslands(spec, registry, findSlot, context, bus, log) {
1386
1441
  log(`page spec: island "${island.name}" wants slot "${island.slot}", which isn't on the page`);
1387
1442
  continue;
1388
1443
  }
1389
- try {
1390
- ctx ?? (ctx = context());
1391
- out.push(factory(el, island, ctx, bus));
1392
- } catch (err) {
1393
- log(`page spec: island "${island.name}" failed to mount: ${err instanceof Error ? err.message : String(err)}`);
1444
+ const build = (factory) => {
1445
+ try {
1446
+ ctx ?? (ctx = context());
1447
+ return factory(el, island, ctx, bus);
1448
+ } catch (err) {
1449
+ log(`page spec: island "${island.name}" failed to mount: ${message(err)}`);
1450
+ return void 0;
1451
+ }
1452
+ };
1453
+ const mountLate = () => {
1454
+ const late = (factory) => {
1455
+ const c2 = build(factory);
1456
+ if (c2 !== void 0) options.onLateMount?.(c2, island);
1457
+ };
1458
+ if (typeof entry === "function") {
1459
+ late(entry);
1460
+ return;
1461
+ }
1462
+ new Promise((resolve) => resolve(entry[LAZY]())).then(
1463
+ (m) => {
1464
+ const factory = typeof m === "function" ? m : m?.default;
1465
+ if (typeof factory === "function") late(factory);
1466
+ else log(`page spec: island "${island.name}" loaded, but its module has no factory (a default export or the function itself)`);
1467
+ },
1468
+ (err) => log(`page spec: island "${island.name}" failed to load: ${message(err)}`)
1469
+ );
1470
+ };
1471
+ const strategy = parseLoad(island.load);
1472
+ if (strategy === null) {
1473
+ log(`page spec: island "${island.name}" has load "${island.load}", which isn't eager, idle, visible or media:<query>; mounting it now`);
1474
+ }
1475
+ if (options.defer && strategy !== null && strategy.kind !== "eager") {
1476
+ options.defer(island, el, mountLate);
1477
+ continue;
1394
1478
  }
1479
+ if (typeof entry !== "function") {
1480
+ mountLate();
1481
+ continue;
1482
+ }
1483
+ const c = build(entry);
1484
+ if (c !== void 0) out.push(c);
1395
1485
  }
1396
1486
  return out;
1397
1487
  }
1488
+ function message(err) {
1489
+ return err instanceof Error ? err.message : String(err);
1490
+ }
1398
1491
 
1399
1492
  // src/page/spec.ts
1400
1493
  var SPEC_ELEMENT_ID = "page-spec";
@@ -1415,7 +1508,8 @@ function readSpec(text, extend) {
1415
1508
  name: is.name,
1416
1509
  slot: is.slot,
1417
1510
  ...typeof is.presentation === "string" && { presentation: is.presentation },
1418
- config: isObject(is.config) ? is.config : {}
1511
+ config: isObject(is.config) ? is.config : {},
1512
+ ...typeof is.load === "string" && { load: is.load }
1419
1513
  });
1420
1514
  }
1421
1515
  const spec = { layout: raw.layout, islands };
@@ -1453,9 +1547,22 @@ var IslandPage = class extends BasePage {
1453
1547
  (slot) => document.querySelector(`[data-slot="${slot}"]`),
1454
1548
  () => this.makeContext(spec),
1455
1549
  this.eventBus,
1456
- (message) => console.warn(message)
1550
+ (message2) => console.warn(message2),
1551
+ {
1552
+ defer: (island, el, mount) => this.scheduleLoad(parseLoad(island.load) ?? { kind: "eager" }, el, mount),
1553
+ onLateMount: (component, island) => {
1554
+ 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)}`));
1555
+ }
1556
+ }
1457
1557
  );
1458
1558
  }
1559
+ /**
1560
+ * Waits for `strategy` and then calls `mount`. Defaults to scheduleMount
1561
+ * against the window; a subclass or test can replace how the waiting is done.
1562
+ */
1563
+ scheduleLoad(strategy, el, mount) {
1564
+ scheduleMount(strategy, el, mount);
1565
+ }
1459
1566
  };
1460
1567
 
1461
1568
  // src/DOMUtils.ts
@@ -1800,6 +1907,6 @@ var KeyboardShortcutManager = class {
1800
1907
  }
1801
1908
  };
1802
1909
 
1803
- export { BaseComponent, BasePage, ComponentEventTypes, EventBus, IslandPage, KeyboardShortcutManager, KeyboardState, LifecycleController, LifecycleEventTypes, MobileBottomDrawer, Modal, SPEC_ELEMENT_ID, SplashScreen, TemplateLoader, ThemeManager, ToastManager, hasModifierKeys, isInInputContext, mountIslands, readSpec, shouldIgnoreShortcut };
1910
+ export { BaseComponent, BasePage, ComponentEventTypes, EventBus, IslandPage, KeyboardShortcutManager, KeyboardState, LifecycleController, LifecycleEventTypes, MobileBottomDrawer, Modal, SPEC_ELEMENT_ID, SplashScreen, TemplateLoader, ThemeManager, ToastManager, hasModifierKeys, isInInputContext, lazy, mountIslands, parseLoad, readSpec, scheduleMount, shouldIgnoreShortcut };
1804
1911
  //# sourceMappingURL=index.mjs.map
1805
1912
  //# sourceMappingURL=index.mjs.map