@lingxia/html 0.20.1 → 0.21.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/global.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { pageReady, getPage, subscribePage } from "./page.js";
2
- export { getHost, subscribeHost } from "@lingxia/bridge";
2
+ export { getHost, subscribeHost, holdLeaveGuard } from "@lingxia/bridge";
@@ -366,12 +366,12 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
366
366
  * whichever copy won the race to install the hook, and the other, which is the
367
367
  * one the framework hooks read, would answer with the boot value forever.
368
368
  */
369
- const fallbackStore$1 = {
369
+ const fallbackStore$2 = {
370
370
  value: BRIDGE_CONFIG.displayLanguage?.trim() || "en-US",
371
371
  listeners: /* @__PURE__ */ new Set()
372
372
  };
373
- function store$1() {
374
- if (typeof window === "undefined") return fallbackStore$1;
373
+ function store$2() {
374
+ if (typeof window === "undefined") return fallbackStore$2;
375
375
  if (!window.__lxDisplayLanguage) window.__lxDisplayLanguage = {
376
376
  value: BRIDGE_CONFIG.displayLanguage?.trim() || "en-US",
377
377
  listeners: /* @__PURE__ */ new Set()
@@ -406,7 +406,7 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
406
406
  */
407
407
  function stampDocumentLanguage() {
408
408
  if (typeof document !== "undefined" && document.documentElement) {
409
- const language = store$1().value;
409
+ const language = store$2().value;
410
410
  document.documentElement.lang = language;
411
411
  document.documentElement.dir = textDirection(language);
412
412
  }
@@ -425,7 +425,7 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
425
425
  */
426
426
  function applyDisplayLanguage(next, revision) {
427
427
  const normalized = typeof next === "string" ? next.trim() : "";
428
- const current = store$1();
428
+ const current = store$2();
429
429
  if (typeof revision === "number" && Number.isFinite(revision)) {
430
430
  if (revision <= (current.revision ?? 0)) return;
431
431
  current.revision = revision;
@@ -441,7 +441,7 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
441
441
  value: applyDisplayLanguage
442
442
  });
443
443
  function getDisplayLanguage() {
444
- return store$1().value;
444
+ return store$2().value;
445
445
  }
446
446
  /**
447
447
  * Subscribe to host display-language changes. Returns an unsubscribe.
@@ -452,7 +452,7 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
452
452
  * deliberately — it has no render loop to feed, so it delivers immediately.
453
453
  */
454
454
  function subscribeDisplayLanguage(listener) {
455
- const listeners = store$1().listeners;
455
+ const listeners = store$2().listeners;
456
456
  listeners.add(listener);
457
457
  return () => {
458
458
  listeners.delete(listener);
@@ -493,8 +493,8 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
493
493
  * language: the bridge module is present twice in a page, and the host must
494
494
  * reach the copy the framework hooks read.
495
495
  */
496
- function store() {
497
- if (typeof window === "undefined") return fallbackStore;
496
+ function store$1() {
497
+ if (typeof window === "undefined") return fallbackStore$1;
498
498
  if (!window.__lxSurfaceContext) {
499
499
  const config = window.__LX_BRIDGE_CFG;
500
500
  window.__lxSurfaceContext = {
@@ -505,7 +505,7 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
505
505
  }
506
506
  return window.__lxSurfaceContext;
507
507
  }
508
- const fallbackStore = {
508
+ const fallbackStore$1 = {
509
509
  value: UNREPORTED,
510
510
  revision: 0,
511
511
  listeners: /* @__PURE__ */ new Set()
@@ -532,7 +532,7 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
532
532
  */
533
533
  function applySurfaceContext(next, revision) {
534
534
  const context = parse(next);
535
- const current = store();
535
+ const current = store$1();
536
536
  if (!context) return;
537
537
  if (typeof revision === "number") {
538
538
  if (revision <= current.revision) return;
@@ -553,14 +553,14 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
553
553
  * than this API reports `compact` with a 0×0 viewport.
554
554
  */
555
555
  function getSurfaceContext() {
556
- return store().value;
556
+ return store$1().value;
557
557
  }
558
558
  /**
559
559
  * Follow the adaptive context. Change-only, like `subscribeDisplayLanguage`,
560
560
  * so it composes with `useSyncExternalStore`. Returns an unsubscribe.
561
561
  */
562
562
  function subscribeSurfaceContext(listener) {
563
- const listeners = store().listeners;
563
+ const listeners = store$1().listeners;
564
564
  listeners.add(listener);
565
565
  return () => {
566
566
  listeners.delete(listener);
@@ -613,6 +613,108 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
613
613
  };
614
614
  }
615
615
  //#endregion
616
+ //#region ../lingxia-bridge/dist/es2020/leave-guard.js
617
+ /**
618
+ * Guarding a page against being left with unsaved changes.
619
+ *
620
+ * While a page holds the guard, a user back or home (navigation bar buttons,
621
+ * Android/Harmony system back, the iOS/Harmony edge swipe) does not leave it.
622
+ * The host asks here instead, each holder answers, and when all agree the
623
+ * host carries out what the user asked for.
624
+ *
625
+ * Works without Logic: a View-only (`logic: false`) page owns its drafts, so
626
+ * the guard is held from the View.
627
+ *
628
+ * Its only import-time effect is installing the host hook (idempotent, shared
629
+ * across bundle copies). It reaches the host through the already-booted
630
+ * `window.LingXiaBridge` at call time and never imports the bridge module, so
631
+ * the plain-HTML global build can include it without booting a second bridge.
632
+ */
633
+ function newStore() {
634
+ return {
635
+ holders: [],
636
+ sent: false,
637
+ tail: Promise.resolve(),
638
+ asking: false
639
+ };
640
+ }
641
+ const fallbackStore = newStore();
642
+ function store() {
643
+ if (typeof window === "undefined") return fallbackStore;
644
+ const host = window;
645
+ if (!host.__lxLeaveGuard) host.__lxLeaveGuard = newStore();
646
+ return host.__lxLeaveGuard;
647
+ }
648
+ function syncGuard() {
649
+ const s = store();
650
+ s.tail = s.tail.then(() => {
651
+ const want = s.holders.length > 0;
652
+ if (want === s.sent) return void 0;
653
+ s.sent = want;
654
+ return callHost("navigation.setLeaveGuard", { enabled: want }).catch((error) => {
655
+ s.sent = !want;
656
+ console.warn("[lingxia] leave guard not applied; the page is not guarded", error);
657
+ });
658
+ });
659
+ }
660
+ /** Host entry point: the user tried to leave a guarded page. */
661
+ function dispatchLeaveRequest(reason) {
662
+ const s = store();
663
+ if (s.asking) return;
664
+ s.asking = true;
665
+ const request = { reason };
666
+ const holders = s.holders.slice().reverse();
667
+ let agreed = Promise.resolve(true);
668
+ holders.forEach((handler) => {
669
+ agreed = agreed.then((leave) => leave ? handler(request) : false);
670
+ });
671
+ agreed.then((leave) => leave === true, (error) => {
672
+ console.error("[lingxia] leave guard handler failed; staying on the page", error);
673
+ return false;
674
+ }).then((leave) => {
675
+ s.asking = false;
676
+ if (!leave) return void 0;
677
+ return callHost("navigation.leave", { reason }).catch((error) => {
678
+ console.warn("[lingxia] leaving the page failed", error);
679
+ });
680
+ });
681
+ }
682
+ if (typeof window !== "undefined" && !window.__lingxiaDispatchLeaveRequest) Object.defineProperty(window, "__lingxiaDispatchLeaveRequest", {
683
+ configurable: false,
684
+ enumerable: false,
685
+ value: dispatchLeaveRequest
686
+ });
687
+ /**
688
+ * Hold this page against user back and home until the returned function is
689
+ * called. Hold while there are unsaved changes; `onRequest` is asked when the
690
+ * user tries to leave and returns whether they may.
691
+ *
692
+ * Holds are independent: each part of a page holds for itself, the page stays
693
+ * guarded while any hold is live, and every holder must agree to leave. A
694
+ * reloaded document, or a page that has left the stack, starts unguarded.
695
+ */
696
+ function holdLeaveGuard(onRequest) {
697
+ const s = store();
698
+ const handler = (request) => onRequest(request);
699
+ s.holders.push(handler);
700
+ syncGuard();
701
+ return () => {
702
+ const index = s.holders.indexOf(handler);
703
+ if (index < 0) return;
704
+ s.holders.splice(index, 1);
705
+ syncGuard();
706
+ };
707
+ }
708
+ function callHost(route, input) {
709
+ const bridge = typeof window !== "undefined" ? window.LingXiaBridge : void 0;
710
+ if (!bridge) return Promise.reject(/* @__PURE__ */ new Error("LingXiaBridge is not available"));
711
+ try {
712
+ return bridge.invoke(route, input);
713
+ } catch (error) {
714
+ return Promise.reject(error);
715
+ }
716
+ }
717
+ //#endregion
616
718
  //#region ../lingxia-page-runtime/dist/shared/startup.js
617
719
  let pending;
618
720
  /** Mount with complete initial data; a delayed startup stays recoverable. */
@@ -727,6 +829,7 @@ ${ERROR_STYLES.replace(/\bbody\b/g, ".lx-page-fault")}`;
727
829
  //#endregion
728
830
  exports.getHost = getHost;
729
831
  exports.getPage = getPage;
832
+ exports.holdLeaveGuard = holdLeaveGuard;
730
833
  exports.pageReady = pageReady;
731
834
  exports.subscribeHost = subscribeHost;
732
835
  exports.subscribePage = subscribePage;
@@ -427,14 +427,14 @@ var LingXiaPage = (function (exports) {
427
427
  * whichever copy won the race to install the hook, and the other, which is the
428
428
  * one the framework hooks read, would answer with the boot value forever.
429
429
  */
430
- var fallbackStore$1 = {
430
+ var fallbackStore$2 = {
431
431
  value: ((_a = BRIDGE_CONFIG.displayLanguage) === null || _a === void 0 ? void 0 : _a.trim()) || "en-US",
432
432
  listeners: /* @__PURE__ */ new Set()
433
433
  };
434
- function store$1() {
434
+ function store$2() {
435
435
  var _a;
436
436
  if (typeof window === "undefined")
437
- return fallbackStore$1;
437
+ return fallbackStore$2;
438
438
  if (!window.__lxDisplayLanguage)
439
439
  window.__lxDisplayLanguage = {
440
440
  value: ((_a = BRIDGE_CONFIG.displayLanguage) === null || _a === void 0 ? void 0 : _a.trim()) || "en-US",
@@ -472,7 +472,7 @@ var LingXiaPage = (function (exports) {
472
472
  */
473
473
  function stampDocumentLanguage() {
474
474
  if (typeof document !== "undefined" && document.documentElement) {
475
- var language = store$1().value;
475
+ var language = store$2().value;
476
476
  document.documentElement.lang = language;
477
477
  document.documentElement.dir = textDirection(language);
478
478
  }
@@ -492,7 +492,7 @@ var LingXiaPage = (function (exports) {
492
492
  function applyDisplayLanguage(next, revision) {
493
493
  var _a;
494
494
  var normalized = typeof next === "string" ? next.trim() : "";
495
- var current = store$1();
495
+ var current = store$2();
496
496
  if (typeof revision === "number" && Number.isFinite(revision)) {
497
497
  if (revision <= ((_a = current.revision) !== null && _a !== void 0 ? _a : 0))
498
498
  return;
@@ -514,7 +514,7 @@ var LingXiaPage = (function (exports) {
514
514
  value: applyDisplayLanguage
515
515
  });
516
516
  function getDisplayLanguage() {
517
- return store$1().value;
517
+ return store$2().value;
518
518
  }
519
519
  /**
520
520
  * Subscribe to host display-language changes. Returns an unsubscribe.
@@ -525,7 +525,7 @@ var LingXiaPage = (function (exports) {
525
525
  * deliberately — it has no render loop to feed, so it delivers immediately.
526
526
  */
527
527
  function subscribeDisplayLanguage(listener) {
528
- var listeners = store$1().listeners;
528
+ var listeners = store$2().listeners;
529
529
  listeners.add(listener);
530
530
  return function () {
531
531
  listeners.delete(listener);
@@ -567,10 +567,10 @@ var LingXiaPage = (function (exports) {
567
567
  * language: the bridge module is present twice in a page, and the host must
568
568
  * reach the copy the framework hooks read.
569
569
  */
570
- function store() {
570
+ function store$1() {
571
571
  var _a;
572
572
  if (typeof window === "undefined")
573
- return fallbackStore;
573
+ return fallbackStore$1;
574
574
  if (!window.__lxSurfaceContext) {
575
575
  var config = window.__LX_BRIDGE_CFG;
576
576
  window.__lxSurfaceContext = {
@@ -581,7 +581,7 @@ var LingXiaPage = (function (exports) {
581
581
  }
582
582
  return window.__lxSurfaceContext;
583
583
  }
584
- var fallbackStore = {
584
+ var fallbackStore$1 = {
585
585
  value: UNREPORTED,
586
586
  revision: 0,
587
587
  listeners: /* @__PURE__ */ new Set()
@@ -611,7 +611,7 @@ var LingXiaPage = (function (exports) {
611
611
  */
612
612
  function applySurfaceContext(next, revision) {
613
613
  var context = parse(next);
614
- var current = store();
614
+ var current = store$1();
615
615
  if (!context)
616
616
  return;
617
617
  if (typeof revision === "number") {
@@ -639,14 +639,14 @@ var LingXiaPage = (function (exports) {
639
639
  * than this API reports `compact` with a 0×0 viewport.
640
640
  */
641
641
  function getSurfaceContext() {
642
- return store().value;
642
+ return store$1().value;
643
643
  }
644
644
  /**
645
645
  * Follow the adaptive context. Change-only, like `subscribeDisplayLanguage`,
646
646
  * so it composes with `useSyncExternalStore`. Returns an unsubscribe.
647
647
  */
648
648
  function subscribeSurfaceContext(listener) {
649
- var listeners = store().listeners;
649
+ var listeners = store$1().listeners;
650
650
  listeners.add(listener);
651
651
  return function () {
652
652
  listeners.delete(listener);
@@ -701,6 +701,117 @@ var LingXiaPage = (function (exports) {
701
701
  };
702
702
  }
703
703
  //#endregion
704
+ //#region ../lingxia-bridge/dist/es2020/leave-guard.js
705
+ /**
706
+ * Guarding a page against being left with unsaved changes.
707
+ *
708
+ * While a page holds the guard, a user back or home (navigation bar buttons,
709
+ * Android/Harmony system back, the iOS/Harmony edge swipe) does not leave it.
710
+ * The host asks here instead, each holder answers, and when all agree the
711
+ * host carries out what the user asked for.
712
+ *
713
+ * Works without Logic: a View-only (`logic: false`) page owns its drafts, so
714
+ * the guard is held from the View.
715
+ *
716
+ * Its only import-time effect is installing the host hook (idempotent, shared
717
+ * across bundle copies). It reaches the host through the already-booted
718
+ * `window.LingXiaBridge` at call time and never imports the bridge module, so
719
+ * the plain-HTML global build can include it without booting a second bridge.
720
+ */
721
+ function newStore() {
722
+ return {
723
+ holders: [],
724
+ sent: false,
725
+ tail: Promise.resolve(),
726
+ asking: false
727
+ };
728
+ }
729
+ var fallbackStore = newStore();
730
+ function store() {
731
+ if (typeof window === "undefined")
732
+ return fallbackStore;
733
+ var host = window;
734
+ if (!host.__lxLeaveGuard)
735
+ host.__lxLeaveGuard = newStore();
736
+ return host.__lxLeaveGuard;
737
+ }
738
+ function syncGuard() {
739
+ var s = store();
740
+ s.tail = s.tail.then(function () {
741
+ var want = s.holders.length > 0;
742
+ if (want === s.sent)
743
+ return void 0;
744
+ s.sent = want;
745
+ return callHost("navigation.setLeaveGuard", { enabled: want }).catch(function (error) {
746
+ s.sent = !want;
747
+ console.warn("[lingxia] leave guard not applied; the page is not guarded", error);
748
+ });
749
+ });
750
+ }
751
+ /** Host entry point: the user tried to leave a guarded page. */
752
+ function dispatchLeaveRequest(reason) {
753
+ var s = store();
754
+ if (s.asking)
755
+ return;
756
+ s.asking = true;
757
+ var request = { reason: reason };
758
+ var holders = s.holders.slice().reverse();
759
+ var agreed = Promise.resolve(true);
760
+ holders.forEach(function (handler) {
761
+ agreed = agreed.then(function (leave) { return leave ? handler(request) : false; });
762
+ });
763
+ agreed.then(function (leave) { return leave === true; }, function (error) {
764
+ console.error("[lingxia] leave guard handler failed; staying on the page", error);
765
+ return false;
766
+ }).then(function (leave) {
767
+ s.asking = false;
768
+ if (!leave)
769
+ return void 0;
770
+ return callHost("navigation.leave", { reason: reason }).catch(function (error) {
771
+ console.warn("[lingxia] leaving the page failed", error);
772
+ });
773
+ });
774
+ }
775
+ if (typeof window !== "undefined" && !window.__lingxiaDispatchLeaveRequest)
776
+ Object.defineProperty(window, "__lingxiaDispatchLeaveRequest", {
777
+ configurable: false,
778
+ enumerable: false,
779
+ value: dispatchLeaveRequest
780
+ });
781
+ /**
782
+ * Hold this page against user back and home until the returned function is
783
+ * called. Hold while there are unsaved changes; `onRequest` is asked when the
784
+ * user tries to leave and returns whether they may.
785
+ *
786
+ * Holds are independent: each part of a page holds for itself, the page stays
787
+ * guarded while any hold is live, and every holder must agree to leave. A
788
+ * reloaded document, or a page that has left the stack, starts unguarded.
789
+ */
790
+ function holdLeaveGuard(onRequest) {
791
+ var s = store();
792
+ var handler = function (request) { return onRequest(request); };
793
+ s.holders.push(handler);
794
+ syncGuard();
795
+ return function () {
796
+ var index = s.holders.indexOf(handler);
797
+ if (index < 0)
798
+ return;
799
+ s.holders.splice(index, 1);
800
+ syncGuard();
801
+ };
802
+ }
803
+ function callHost(route, input) {
804
+ var bridge = typeof window !== "undefined" ? window.LingXiaBridge : void 0;
805
+ if (!bridge)
806
+ return Promise.reject(/* @__PURE__ */ new Error("LingXiaBridge is not available"));
807
+ try {
808
+ return bridge.invoke(route, input);
809
+ }
810
+ catch (error) {
811
+ return Promise.reject(error);
812
+ }
813
+ }
814
+ //#endregion
704
815
  //#region ../lingxia-page-runtime/dist/shared/startup.js
705
816
  var pending;
706
817
  /** Mount with complete initial data; a delayed startup stays recoverable. */
@@ -834,6 +945,7 @@ var LingXiaPage = (function (exports) {
834
945
  //#endregion
835
946
  exports.getHost = getHost;
836
947
  exports.getPage = getPage;
948
+ exports.holdLeaveGuard = holdLeaveGuard;
837
949
  exports.pageReady = pageReady;
838
950
  exports.subscribeHost = subscribeHost;
839
951
  exports.subscribePage = subscribePage;
package/dist/global.js CHANGED
@@ -2,4 +2,4 @@
2
2
  // the module. The host already booted the bridge, so the build aliases
3
3
  // `@lingxia/bridge` to its side-effect-free parts.
4
4
  export { pageReady, getPage, subscribePage } from "./page.js";
5
- export { getHost, subscribeHost } from "@lingxia/bridge";
5
+ export { getHost, subscribeHost, holdLeaveGuard } from "@lingxia/bridge";
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  export { pageReady, getPage, subscribePage } from "./page.js";
2
- export { getHost, subscribeHost, type LxHost } from "@lingxia/bridge";
2
+ export { getHost, subscribeHost, holdLeaveGuard, type LxHost, type LxLeaveHandler, type LxLeaveReason, type LxLeaveRequest, } from "@lingxia/bridge";
3
3
  export type { ActionMap, DeepReadonly, Snapshot } from "@lingxia/page-runtime";
4
4
  export { registerInlineNativeComponents, registerInlineNativeAuthorComponents, compileInlineNativeRoot, compileInlineNativeForest, unwrapNativeEventPayload, } from "@lingxia/elements";
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  export { pageReady, getPage, subscribePage } from "./page.js";
2
- export { getHost, subscribeHost } from "@lingxia/bridge";
2
+ export { getHost, subscribeHost, holdLeaveGuard, } from "@lingxia/bridge";
3
3
  export { registerInlineNativeComponents, registerInlineNativeAuthorComponents, compileInlineNativeRoot, compileInlineNativeForest, unwrapNativeEventPayload, } from "@lingxia/elements";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingxia/html",
3
- "version": "0.20.1",
3
+ "version": "0.21.0",
4
4
  "private": false,
5
5
  "description": "LingXia HTML runtime for lxapp pages",
6
6
  "type": "module",
@@ -21,9 +21,9 @@
21
21
  "prepublishOnly": "npm run build"
22
22
  },
23
23
  "dependencies": {
24
- "@lingxia/bridge": "~0.20.1",
25
- "@lingxia/elements": "~0.20.1",
26
- "@lingxia/page-runtime": "~0.20.1"
24
+ "@lingxia/bridge": "~0.21.0",
25
+ "@lingxia/elements": "~0.21.0",
26
+ "@lingxia/page-runtime": "~0.21.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@lingxia/bridge": "file:../lingxia-bridge",