uicore-ts 1.1.385 → 1.1.387
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/compiledScripts/UICore.js +2 -5
- package/compiledScripts/UICore.js.map +2 -2
- package/compiledScripts/UIRoute.d.ts +57 -9
- package/compiledScripts/UIRoute.js +144 -28
- package/compiledScripts/UIRoute.js.map +3 -3
- package/package.json +1 -1
- package/scripts/UICore.ts +8 -11
- package/scripts/UIRoute.ts +228 -34
|
@@ -107,11 +107,8 @@ const _UICore = class extends import_UIObject.UIObject {
|
|
|
107
107
|
});
|
|
108
108
|
};
|
|
109
109
|
window.addEventListener("scroll", didScroll, false);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
};
|
|
113
|
-
window.addEventListener("hashchange", hashDidChange, false);
|
|
114
|
-
hashDidChange();
|
|
110
|
+
import_UIRoute.UIRoute._installNativeHistoryNavigationGuard(() => this.reapplyCurrentRoute());
|
|
111
|
+
this.reapplyCurrentRoute();
|
|
115
112
|
}
|
|
116
113
|
reapplyCurrentRoute() {
|
|
117
114
|
return __async(this, null, function* () {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../scripts/UICore.ts"],
|
|
4
|
-
"sourcesContent": ["import \"./UICoreExtensions\"\nimport { UILanguageService } from \"./UIInterfaces\"\nimport { nil, NO, UIObject } from \"./UIObject\"\nimport { UIRoute } from \"./UIRoute\"\nimport { UIView } from \"./UIView\"\nimport { UIViewController } from \"./UIViewController\"\n\n\nexport class UICore extends UIObject {\n\n rootViewController: UIViewController\n\n\n\n static RootViewControllerClass: typeof UIViewController\n static main: UICore\n\n static languageService: UILanguageService = nil\n\n static readonly broadcastEventName = {\n\n \"RouteDidChange\": \"RouteDidChange\",\n \"WindowDidResize\": \"WindowDidResize\"\n\n }\n\n // See `reapplyCurrentRoute` for why these exist and why route application\n // must always go through it rather than calling `rootViewController.handleRoute`\n // or `handleRouteRecursively` directly.\n _isApplyingRoute = false\n _hasQueuedRouteChangeWhileApplying = false\n _currentRouteApplicationPromise?: Promise<void>\n\n constructor(rootDivElementID: string, rootViewControllerClass: typeof UIViewController, public paddingLength = 20) {\n \n super()\n \n UICore.RootViewControllerClass = rootViewControllerClass\n UICore.main = UICore.main || this\n \n const rootViewElement = document.getElementById(rootDivElementID)\n const rootView = new UIView(rootDivElementID, rootViewElement)\n rootView.pausesPointerEvents = NO //YES;\n rootView.core = this\n \n if (UICore.RootViewControllerClass) {\n \n if (!(UICore.RootViewControllerClass.prototype instanceof UIViewController) ||\n (UICore.RootViewControllerClass as any) === UIViewController) {\n \n console.log(\n \"Error, UICore.RootViewControllerClass must be UIViewController or a subclass of UIViewController, \" +\n \"falling back to UIViewController.\"\n )\n \n UICore.RootViewControllerClass = UIViewController\n \n }\n \n this.rootViewController = new UICore.RootViewControllerClass(rootView)\n \n }\n else {\n \n this.rootViewController = new UIViewController(rootView)\n \n }\n \n this.rootViewController.viewWillAppear().then(() =>\n this.rootViewController.viewDidAppear()\n )\n \n \n this.rootViewController.view.addTargetForControlEvent(\n UIView.controlEvent.PointerUpInside,\n (sender, event) => {\n\n // Only dismiss focus for taps genuinely outside the focused element - not for\n // the tap that just focused it (or a tap on one of its own children). Native\n // controls like <select> gain focus on mousedown and open their own popup; if\n // this handler blurs them on the very next PointerUpInside regardless of target,\n // it closes that popup before the user can pick an option (macOS Chrome renders\n // <select> popups as a native control that closes as soon as its anchor blurs).\n const activeElement = document.activeElement as HTMLElement | null\n const eventTarget = event?.target as Node | null\n\n if (activeElement && eventTarget &&\n (activeElement === eventTarget || activeElement.contains(eventTarget))) {\n return\n }\n\n activeElement?.blur?.()\n\n }\n )\n \n \n const windowDidResize = () => {\n \n UIView.resetLayoutCycleTrackingForAllViews()\n \n this.rootViewController.view.forEachViewInSubtree((view) => {\n \n view._frameCache = undefined\n \n })\n // Doing layout two times to prevent page scrollbars from confusing the layout\n this.rootViewController.view.setNeedsLayout()\n this.rootViewController._triggerLayoutViewSubviews()\n UIView.layoutViewsIfNeeded()\n \n this.rootViewController._triggerLayoutViewSubviews()\n UIView.layoutViewsIfNeeded()\n \n this.rootViewController.view.broadcastEventInSubtree({\n \n name: UICore.broadcastEventName.WindowDidResize,\n parameters: nil\n \n })\n \n }\n \n window.addEventListener(\"resize\", windowDidResize)\n \n const didScroll = () => {\n \n //code\n \n this.rootViewController.view.broadcastEventInSubtree({\n \n name: UIView.broadcastEventName.PageDidScroll,\n parameters: nil\n \n })\n \n \n }\n \n window.addEventListener(\"scroll\", didScroll, false)\n\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAAO;AAEP,sBAAkC;AAClC,qBAAwB;AACxB,oBAAuB;AACvB,8BAAiC;AAG1B,MAAM,UAAN,cAAqB,yBAAS;AAAA,EAyBjC,YAAY,kBAA0B,yBAAyD,gBAAgB,IAAI;AAE/G,UAAM;AAFqF;AAJ/F,4BAAmB;AACnB,8CAAqC;AAOjC,YAAO,0BAA0B;AACjC,YAAO,OAAO,QAAO,QAAQ;AAE7B,UAAM,kBAAkB,SAAS,eAAe,gBAAgB;AAChE,UAAM,WAAW,IAAI,qBAAO,kBAAkB,eAAe;AAC7D,aAAS,sBAAsB;AAC/B,aAAS,OAAO;AAEhB,QAAI,QAAO,yBAAyB;AAEhC,UAAI,EAAE,QAAO,wBAAwB,qBAAqB,6CACrD,QAAO,4BAAoC,0CAAkB;AAE9D,gBAAQ;AAAA,UACJ;AAAA,QAEJ;AAEA,gBAAO,0BAA0B;AAAA,MAErC;AAEA,WAAK,qBAAqB,IAAI,QAAO,wBAAwB,QAAQ;AAAA,IAEzE,OACK;AAED,WAAK,qBAAqB,IAAI,yCAAiB,QAAQ;AAAA,IAE3D;AAEA,SAAK,mBAAmB,eAAe,EAAE;AAAA,MAAK,MAC1C,KAAK,mBAAmB,cAAc;AAAA,IAC1C;AAGA,SAAK,mBAAmB,KAAK;AAAA,MACzB,qBAAO,aAAa;AAAA,MACpB,CAAC,QAAQ,UAAU;AA3E/B;AAmFgB,cAAM,gBAAgB,SAAS;AAC/B,cAAM,cAAc,+BAAO;AAE3B,YAAI,iBAAiB,gBAChB,kBAAkB,eAAe,cAAc,SAAS,WAAW,IAAI;AACxE;AAAA,QACJ;AAEA,6DAAe,SAAf;AAAA,MAEJ;AAAA,IACJ;AAGA,UAAM,kBAAkB,MAAM;AAE1B,2BAAO,oCAAoC;AAE3C,WAAK,mBAAmB,KAAK,qBAAqB,CAAC,SAAS;AAExD,aAAK,cAAc;AAAA,MAEvB,CAAC;AAED,WAAK,mBAAmB,KAAK,eAAe;AAC5C,WAAK,mBAAmB,2BAA2B;AACnD,2BAAO,oBAAoB;AAE3B,WAAK,mBAAmB,2BAA2B;AACnD,2BAAO,oBAAoB;AAE3B,WAAK,mBAAmB,KAAK,wBAAwB;AAAA,QAEjD,MAAM,QAAO,mBAAmB;AAAA,QAChC,YAAY;AAAA,MAEhB,CAAC;AAAA,IAEL;AAEA,WAAO,iBAAiB,UAAU,eAAe;AAEjD,UAAM,YAAY,MAAM;AAIpB,WAAK,mBAAmB,KAAK,wBAAwB;AAAA,QAEjD,MAAM,qBAAO,mBAAmB;AAAA,QAChC,YAAY;AAAA,MAEhB,CAAC;AAAA,IAGL;AAEA,WAAO,iBAAiB,UAAU,WAAW,KAAK;
|
|
4
|
+
"sourcesContent": ["import \"./UICoreExtensions\"\nimport { UILanguageService } from \"./UIInterfaces\"\nimport { nil, NO, UIObject } from \"./UIObject\"\nimport { UIRoute } from \"./UIRoute\"\nimport { UIView } from \"./UIView\"\nimport { UIViewController } from \"./UIViewController\"\n\n\nexport class UICore extends UIObject {\n\n rootViewController: UIViewController\n\n\n\n static RootViewControllerClass: typeof UIViewController\n static main: UICore\n\n static languageService: UILanguageService = nil\n\n static readonly broadcastEventName = {\n\n \"RouteDidChange\": \"RouteDidChange\",\n \"WindowDidResize\": \"WindowDidResize\"\n\n }\n\n // See `reapplyCurrentRoute` for why these exist and why route application\n // must always go through it rather than calling `rootViewController.handleRoute`\n // or `handleRouteRecursively` directly.\n _isApplyingRoute = false\n _hasQueuedRouteChangeWhileApplying = false\n _currentRouteApplicationPromise?: Promise<void>\n\n constructor(rootDivElementID: string, rootViewControllerClass: typeof UIViewController, public paddingLength = 20) {\n \n super()\n \n UICore.RootViewControllerClass = rootViewControllerClass\n UICore.main = UICore.main || this\n \n const rootViewElement = document.getElementById(rootDivElementID)\n const rootView = new UIView(rootDivElementID, rootViewElement)\n rootView.pausesPointerEvents = NO //YES;\n rootView.core = this\n \n if (UICore.RootViewControllerClass) {\n \n if (!(UICore.RootViewControllerClass.prototype instanceof UIViewController) ||\n (UICore.RootViewControllerClass as any) === UIViewController) {\n \n console.log(\n \"Error, UICore.RootViewControllerClass must be UIViewController or a subclass of UIViewController, \" +\n \"falling back to UIViewController.\"\n )\n \n UICore.RootViewControllerClass = UIViewController\n \n }\n \n this.rootViewController = new UICore.RootViewControllerClass(rootView)\n \n }\n else {\n \n this.rootViewController = new UIViewController(rootView)\n \n }\n \n this.rootViewController.viewWillAppear().then(() =>\n this.rootViewController.viewDidAppear()\n )\n \n \n this.rootViewController.view.addTargetForControlEvent(\n UIView.controlEvent.PointerUpInside,\n (sender, event) => {\n\n // Only dismiss focus for taps genuinely outside the focused element - not for\n // the tap that just focused it (or a tap on one of its own children). Native\n // controls like <select> gain focus on mousedown and open their own popup; if\n // this handler blurs them on the very next PointerUpInside regardless of target,\n // it closes that popup before the user can pick an option (macOS Chrome renders\n // <select> popups as a native control that closes as soon as its anchor blurs).\n const activeElement = document.activeElement as HTMLElement | null\n const eventTarget = event?.target as Node | null\n\n if (activeElement && eventTarget &&\n (activeElement === eventTarget || activeElement.contains(eventTarget))) {\n return\n }\n\n activeElement?.blur?.()\n\n }\n )\n \n \n const windowDidResize = () => {\n \n UIView.resetLayoutCycleTrackingForAllViews()\n \n this.rootViewController.view.forEachViewInSubtree((view) => {\n \n view._frameCache = undefined\n \n })\n // Doing layout two times to prevent page scrollbars from confusing the layout\n this.rootViewController.view.setNeedsLayout()\n this.rootViewController._triggerLayoutViewSubviews()\n UIView.layoutViewsIfNeeded()\n \n this.rootViewController._triggerLayoutViewSubviews()\n UIView.layoutViewsIfNeeded()\n \n this.rootViewController.view.broadcastEventInSubtree({\n \n name: UICore.broadcastEventName.WindowDidResize,\n parameters: nil\n \n })\n \n }\n \n window.addEventListener(\"resize\", windowDidResize)\n \n const didScroll = () => {\n \n //code\n \n this.rootViewController.view.broadcastEventInSubtree({\n \n name: UIView.broadcastEventName.PageDidScroll,\n parameters: nil\n \n })\n \n \n }\n \n window.addEventListener(\"scroll\", didScroll, false)\n\n // Native Back/Forward navigation fires `hashchange` directly, which\n // would otherwise reapply the route unconditionally below with no\n // chance for the app to veto it (e.g. to protect unsaved changes) the\n // way `UIRoute.apply()` can. This makes native history traversal go\n // through the same `UIRoute.shouldApplyRouteChange` check first.\n UIRoute._installNativeHistoryNavigationGuard(() => this.reapplyCurrentRoute())\n\n this.reapplyCurrentRoute()\n\n\n }\n\n\n /**\n * Re-runs route handling for `UIRoute.currentRoute` against `rootViewController`,\n * serialized against every other caller of this method (including the\n * `hashchange` listener installed in the constructor).\n *\n * This is the ONLY correct way to force route handling to (re-)run - never\n * call `rootViewController.handleRoute(...)` or `handleRouteRecursively(...)`\n * directly. A route change can legitimately be requested twice in quick\n * succession from independent, uncoordinated places (e.g. two related\n * server-pushed broadcasts derived from one real-world event, each\n * triggering its own re-apply). Without serialization, two concurrent,\n * un-synchronized route applications race on shared root-view-controller\n * state (`contentViewController`, `detailsViewController`) and whichever\n * finishes last wins - regardless of which request was actually last, or\n * which request even reflects the current, settled state. Serializing here\n * instead coalesces a request that arrives mid-application into a single\n * trailing re-run rather than starting a second, overlapping one, and that\n * re-run re-reads `UIRoute.currentRoute` fresh once the burst has settled,\n * so the final state always matches the truly-last route.\n */\n async reapplyCurrentRoute(): Promise<void> {\n\n if (this._isApplyingRoute) {\n\n // Joining an in-flight application: request one more loop pass\n // (guaranteed to observe `UIRoute.currentRoute` fresh, since the\n // owner below re-reads it at the top of every pass) and await the\n // SAME promise the owner is already awaiting, rather than\n // returning early - a caller of this method expects route\n // handling to have actually run by the time it resolves.\n this._hasQueuedRouteChangeWhileApplying = true\n return this._currentRouteApplicationPromise\n\n }\n\n this._isApplyingRoute = true\n\n this._currentRouteApplicationPromise = (async () => {\n\n try {\n\n do {\n\n this._hasQueuedRouteChangeWhileApplying = false\n\n await this.rootViewController.handleRouteRecursively(UIRoute.currentRoute)\n\n this.rootViewController.view.broadcastEventInSubtree({\n\n name: UICore.broadcastEventName.RouteDidChange,\n parameters: nil\n\n })\n\n } while (this._hasQueuedRouteChangeWhileApplying)\n\n }\n finally {\n\n this._isApplyingRoute = false\n this._currentRouteApplicationPromise = undefined\n\n }\n\n })()\n\n return this._currentRouteApplicationPromise\n\n }\n\n\n}\n\n\nArray.prototype.indexOf || (Array.prototype.indexOf = function (d, e) {\n let a\n if (null == this) {\n throw new TypeError(\"\\\"this\\\" is null or not defined\")\n }\n const c = Object(this),\n b = c.length >>> 0\n if (0 === b) {\n return -1\n }\n // @ts-ignore\n a = +e || 0\n Infinity === Math.abs(a) && (a = 0)\n if (a >= b) {\n return -1\n }\n for (a = Math.max(0 <= a ? a : b - Math.abs(a), 0); a < b;) {\n if (a in c && c[a] === d) {\n return a\n }\n a++\n }\n return -1\n})\n\n\n\n\n\n\n\n\n\n\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8BAAO;AAEP,sBAAkC;AAClC,qBAAwB;AACxB,oBAAuB;AACvB,8BAAiC;AAG1B,MAAM,UAAN,cAAqB,yBAAS;AAAA,EAyBjC,YAAY,kBAA0B,yBAAyD,gBAAgB,IAAI;AAE/G,UAAM;AAFqF;AAJ/F,4BAAmB;AACnB,8CAAqC;AAOjC,YAAO,0BAA0B;AACjC,YAAO,OAAO,QAAO,QAAQ;AAE7B,UAAM,kBAAkB,SAAS,eAAe,gBAAgB;AAChE,UAAM,WAAW,IAAI,qBAAO,kBAAkB,eAAe;AAC7D,aAAS,sBAAsB;AAC/B,aAAS,OAAO;AAEhB,QAAI,QAAO,yBAAyB;AAEhC,UAAI,EAAE,QAAO,wBAAwB,qBAAqB,6CACrD,QAAO,4BAAoC,0CAAkB;AAE9D,gBAAQ;AAAA,UACJ;AAAA,QAEJ;AAEA,gBAAO,0BAA0B;AAAA,MAErC;AAEA,WAAK,qBAAqB,IAAI,QAAO,wBAAwB,QAAQ;AAAA,IAEzE,OACK;AAED,WAAK,qBAAqB,IAAI,yCAAiB,QAAQ;AAAA,IAE3D;AAEA,SAAK,mBAAmB,eAAe,EAAE;AAAA,MAAK,MAC1C,KAAK,mBAAmB,cAAc;AAAA,IAC1C;AAGA,SAAK,mBAAmB,KAAK;AAAA,MACzB,qBAAO,aAAa;AAAA,MACpB,CAAC,QAAQ,UAAU;AA3E/B;AAmFgB,cAAM,gBAAgB,SAAS;AAC/B,cAAM,cAAc,+BAAO;AAE3B,YAAI,iBAAiB,gBAChB,kBAAkB,eAAe,cAAc,SAAS,WAAW,IAAI;AACxE;AAAA,QACJ;AAEA,6DAAe,SAAf;AAAA,MAEJ;AAAA,IACJ;AAGA,UAAM,kBAAkB,MAAM;AAE1B,2BAAO,oCAAoC;AAE3C,WAAK,mBAAmB,KAAK,qBAAqB,CAAC,SAAS;AAExD,aAAK,cAAc;AAAA,MAEvB,CAAC;AAED,WAAK,mBAAmB,KAAK,eAAe;AAC5C,WAAK,mBAAmB,2BAA2B;AACnD,2BAAO,oBAAoB;AAE3B,WAAK,mBAAmB,2BAA2B;AACnD,2BAAO,oBAAoB;AAE3B,WAAK,mBAAmB,KAAK,wBAAwB;AAAA,QAEjD,MAAM,QAAO,mBAAmB;AAAA,QAChC,YAAY;AAAA,MAEhB,CAAC;AAAA,IAEL;AAEA,WAAO,iBAAiB,UAAU,eAAe;AAEjD,UAAM,YAAY,MAAM;AAIpB,WAAK,mBAAmB,KAAK,wBAAwB;AAAA,QAEjD,MAAM,qBAAO,mBAAmB;AAAA,QAChC,YAAY;AAAA,MAEhB,CAAC;AAAA,IAGL;AAEA,WAAO,iBAAiB,UAAU,WAAW,KAAK;AAOlD,2BAAQ,qCAAqC,MAAM,KAAK,oBAAoB,CAAC;AAE7E,SAAK,oBAAoB;AAAA,EAG7B;AAAA,EAuBM,sBAAqC;AAAA;AAEvC,UAAI,KAAK,kBAAkB;AAQvB,aAAK,qCAAqC;AAC1C,eAAO,KAAK;AAAA,MAEhB;AAEA,WAAK,mBAAmB;AAExB,WAAK,mCAAmC,MAAY;AAEhD,YAAI;AAEA,aAAG;AAEC,iBAAK,qCAAqC;AAE1C,kBAAM,KAAK,mBAAmB,uBAAuB,uBAAQ,YAAY;AAEzE,iBAAK,mBAAmB,KAAK,wBAAwB;AAAA,cAEjD,MAAM,QAAO,mBAAmB;AAAA,cAChC,YAAY;AAAA,YAEhB,CAAC;AAAA,UAEL,SAAS,KAAK;AAAA,QAElB,UACA;AAEI,eAAK,mBAAmB;AACxB,eAAK,kCAAkC;AAAA,QAE3C;AAAA,MAEJ,IAAG;AAEH,aAAO,KAAK;AAAA,IAEhB;AAAA;AAGJ;AAzNO,IAAM,SAAN;AAAM,OASF,kBAAqC;AATnC,OAWO,qBAAqB;AAAA,EAEjC,kBAAkB;AAAA,EAClB,mBAAmB;AAEvB;AA4MJ,MAAM,UAAU,YAAY,MAAM,UAAU,UAAU,SAAU,GAAG,GAAG;AAClE,MAAI;AACJ,MAAI,QAAQ,MAAM;AACd,UAAM,IAAI,UAAU,+BAAiC;AAAA,EACzD;AACA,QAAM,IAAI,OAAO,IAAI,GACjB,IAAI,EAAE,WAAW;AACrB,MAAI,MAAM,GAAG;AACT,WAAO;AAAA,EACX;AAEA,MAAI,CAAC,KAAK;AACV,eAAa,KAAK,IAAI,CAAC,MAAM,IAAI;AACjC,MAAI,KAAK,GAAG;AACR,WAAO;AAAA,EACX;AACA,OAAK,IAAI,KAAK,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,KAAI;AACxD,QAAI,KAAK,KAAK,EAAE,OAAO,GAAG;AACtB,aAAO;AAAA,IACX;AACA;AAAA,EACJ;AACA,SAAO;AACX;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -17,6 +17,11 @@ export interface UIRouteChangeDescriptor {
|
|
|
17
17
|
export type UIRouteShouldApplyRouteChangeCallbackFunction = (routeChangeDescriptor: UIRouteChangeDescriptor) => boolean | Promise<boolean>;
|
|
18
18
|
export declare class UIRoute extends Array<UIRouteComponent> {
|
|
19
19
|
static shouldApplyRouteChange?: UIRouteShouldApplyRouteChangeCallbackFunction;
|
|
20
|
+
static _presentedRoute: UIRoute;
|
|
21
|
+
static _presentedHistoryPosition: number;
|
|
22
|
+
static _recordedHistoryLength: number;
|
|
23
|
+
static _selfInitiatedRouteChange?: UIRouteChangeDescriptor;
|
|
24
|
+
static _nativeHistoryNavigationGeneration: number;
|
|
20
25
|
constructor(hash?: string);
|
|
21
26
|
static get currentRoute(): UIRoute;
|
|
22
27
|
/**
|
|
@@ -29,11 +34,11 @@ export declare class UIRoute extends Array<UIRouteComponent> {
|
|
|
29
34
|
*/
|
|
30
35
|
apply(forcefully?: boolean): boolean | Promise<boolean>;
|
|
31
36
|
/**
|
|
32
|
-
* Applies this route without consulting `UIRoute.
|
|
37
|
+
* Applies this route without consulting `UIRoute.shouldApplyRouteChange`.
|
|
33
38
|
* Use this only for route changes that are internal bookkeeping or that already
|
|
34
39
|
* passed a higher-level confirmation flow.
|
|
35
40
|
*/
|
|
36
|
-
|
|
41
|
+
applyWithoutRouteChangeApproval(forcefully?: boolean): boolean;
|
|
37
42
|
/**
|
|
38
43
|
* @param forcefully When `true`, replaces the current history entry with
|
|
39
44
|
* this route even when the route is identical to the current URL hash.
|
|
@@ -42,14 +47,57 @@ export declare class UIRoute extends Array<UIRouteComponent> {
|
|
|
42
47
|
applyByReplacingCurrentRouteInHistory(forcefully?: boolean): boolean | Promise<boolean>;
|
|
43
48
|
/**
|
|
44
49
|
* Replaces the current history entry without consulting
|
|
45
|
-
* `UIRoute.
|
|
50
|
+
* `UIRoute.shouldApplyRouteChange`.
|
|
46
51
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
_applyRouteIfAllowed(
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
applyByReplacingCurrentRouteInHistoryWithoutRouteChangeApproval(forcefully?: boolean): boolean;
|
|
53
|
+
_applyRouteAfterRouteChangeApproval(forcefully: boolean, replacesCurrentRouteInHistory: boolean): boolean | Promise<boolean>;
|
|
54
|
+
_applyRouteIfAllowed(isRouteChangeApproved: boolean, forcefully: boolean, replacesCurrentRouteInHistory: boolean): boolean;
|
|
55
|
+
_applyRouteWithoutRouteChangeApproval(forcefully: boolean, replacesCurrentRouteInHistory: boolean): boolean;
|
|
56
|
+
_needsRouteChange(forcefully: boolean): boolean;
|
|
57
|
+
_routeChangeDescriptor(forcefully: boolean, replacesCurrentRouteInHistory: boolean): UIRouteChangeDescriptor;
|
|
58
|
+
/**
|
|
59
|
+
* Installs handling so that native browser Back/Forward navigation
|
|
60
|
+
* consults `shouldApplyRouteChange` the same way `apply()` does, instead
|
|
61
|
+
* of silently bypassing it. Call exactly once, from `UICore`'s
|
|
62
|
+
* constructor.
|
|
63
|
+
*
|
|
64
|
+
* @param reapplyCurrentRoute Re-runs route handling for `UIRoute.currentRoute`.
|
|
65
|
+
* Called once a native navigation is allowed to proceed (or is self-initiated),
|
|
66
|
+
* so its effect actually appears on screen.
|
|
67
|
+
*/
|
|
68
|
+
static _installNativeHistoryNavigationGuard(reapplyCurrentRoute: () => void | Promise<void>): void;
|
|
69
|
+
static _historyPositionFromCurrentEntry(): number | undefined;
|
|
70
|
+
static _recordPresentedRouteAtHistoryPosition(route: UIRoute, historyPosition: number): void;
|
|
71
|
+
/**
|
|
72
|
+
* The single listener installed for every `hashchange` - self-initiated,
|
|
73
|
+
* native Back/Forward, or an untagged bypass (see the class-level comment
|
|
74
|
+
* above `_presentedRoute`) - and the point where those three cases are
|
|
75
|
+
* told apart.
|
|
76
|
+
*
|
|
77
|
+
* @param event The native event. Only used to call
|
|
78
|
+
* `stopImmediatePropagation()` when a native traversal needs to be held
|
|
79
|
+
* back pending `shouldApplyRouteChange`, so `UICore`'s own route handling
|
|
80
|
+
* doesn't run before that check settles.
|
|
81
|
+
* @param reapplyCurrentRoute Forwarded to `_applyHistoryNavigationToRouteAtPositionIfAllowed`
|
|
82
|
+
* - see its own doc comment.
|
|
83
|
+
*/
|
|
84
|
+
static _hashDidChange(event: HashChangeEvent, reapplyCurrentRoute: () => void | Promise<void>): void;
|
|
85
|
+
/**
|
|
86
|
+
* Consults `shouldApplyRouteChange` for a native Back/Forward traversal,
|
|
87
|
+
* exactly like `apply()` does for a programmatic one, then either lets it
|
|
88
|
+
* through or reverts it with `history.go(...)`.
|
|
89
|
+
*
|
|
90
|
+
* @param targetRoute The route the native traversal is asking to move to.
|
|
91
|
+
* @param targetHistoryPosition That route's tagged position, used both to
|
|
92
|
+
* revert (`history.go`) and to detect a newer navigation arriving mid-check.
|
|
93
|
+
* @param navigationGeneration A token captured by the caller at dispatch
|
|
94
|
+
* time. If it no longer matches `_nativeHistoryNavigationGeneration` by
|
|
95
|
+
* the time `shouldApplyRouteChange` resolves, a newer navigation has
|
|
96
|
+
* superseded this one and this check's result is discarded.
|
|
97
|
+
* @param reapplyCurrentRoute Called once the navigation is approved, so
|
|
98
|
+
* `UIRoute.currentRoute`'s new value actually takes effect on screen.
|
|
99
|
+
*/
|
|
100
|
+
static _applyHistoryNavigationToRouteAtPositionIfAllowed(targetRoute: UIRoute, targetHistoryPosition: number, navigationGeneration: number, reapplyCurrentRoute: () => void | Promise<void>): Promise<void>;
|
|
53
101
|
copy(): UIRoute;
|
|
54
102
|
routeByRemovingComponentsOtherThanOnesNamed(componentNames: string[]): UIRoute;
|
|
55
103
|
routeByRemovingComponentNamed(componentName: string): UIRoute;
|
|
@@ -16,13 +16,34 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var __async = (__this, __arguments, generator) => {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
var fulfilled = (value) => {
|
|
22
|
+
try {
|
|
23
|
+
step(generator.next(value));
|
|
24
|
+
} catch (e) {
|
|
25
|
+
reject(e);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
var rejected = (value) => {
|
|
29
|
+
try {
|
|
30
|
+
step(generator.throw(value));
|
|
31
|
+
} catch (e) {
|
|
32
|
+
reject(e);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
36
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
37
|
+
});
|
|
38
|
+
};
|
|
19
39
|
var UIRoute_exports = {};
|
|
20
40
|
__export(UIRoute_exports, {
|
|
21
41
|
UIRoute: () => UIRoute
|
|
22
42
|
});
|
|
23
43
|
module.exports = __toCommonJS(UIRoute_exports);
|
|
24
44
|
var import_UIObject = require("./UIObject");
|
|
25
|
-
|
|
45
|
+
const historyPositionStateKey = "uiRouteHistoryPosition";
|
|
46
|
+
const _UIRoute = class extends Array {
|
|
26
47
|
constructor(hash) {
|
|
27
48
|
super();
|
|
28
49
|
if (!hash || !hash.startsWith) {
|
|
@@ -56,35 +77,35 @@ class UIRoute extends Array {
|
|
|
56
77
|
});
|
|
57
78
|
}
|
|
58
79
|
static get currentRoute() {
|
|
59
|
-
return new
|
|
80
|
+
return new _UIRoute(window.location.hash);
|
|
60
81
|
}
|
|
61
82
|
apply(forcefully = import_UIObject.NO) {
|
|
62
|
-
return this.
|
|
83
|
+
return this._applyRouteAfterRouteChangeApproval(forcefully, import_UIObject.NO);
|
|
63
84
|
}
|
|
64
|
-
|
|
65
|
-
return this.
|
|
85
|
+
applyWithoutRouteChangeApproval(forcefully = import_UIObject.NO) {
|
|
86
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, import_UIObject.NO);
|
|
66
87
|
}
|
|
67
88
|
applyByReplacingCurrentRouteInHistory(forcefully = import_UIObject.NO) {
|
|
68
|
-
return this.
|
|
89
|
+
return this._applyRouteAfterRouteChangeApproval(forcefully, import_UIObject.YES);
|
|
69
90
|
}
|
|
70
|
-
|
|
71
|
-
return this.
|
|
91
|
+
applyByReplacingCurrentRouteInHistoryWithoutRouteChangeApproval(forcefully = import_UIObject.NO) {
|
|
92
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, import_UIObject.YES);
|
|
72
93
|
}
|
|
73
|
-
|
|
74
|
-
if (!this.
|
|
94
|
+
_applyRouteAfterRouteChangeApproval(forcefully, replacesCurrentRouteInHistory) {
|
|
95
|
+
if (!this._needsRouteChange(forcefully)) {
|
|
75
96
|
return import_UIObject.NO;
|
|
76
97
|
}
|
|
77
|
-
const
|
|
78
|
-
if (!
|
|
79
|
-
return this.
|
|
98
|
+
const shouldApplyRouteChange = _UIRoute.shouldApplyRouteChange;
|
|
99
|
+
if (!shouldApplyRouteChange) {
|
|
100
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, replacesCurrentRouteInHistory);
|
|
80
101
|
}
|
|
81
|
-
const shouldProceed =
|
|
82
|
-
this.
|
|
102
|
+
const shouldProceed = shouldApplyRouteChange(
|
|
103
|
+
this._routeChangeDescriptor(forcefully, replacesCurrentRouteInHistory)
|
|
83
104
|
);
|
|
84
105
|
if (shouldProceed instanceof Promise) {
|
|
85
|
-
return shouldProceed.then((
|
|
106
|
+
return shouldProceed.then((isRouteChangeApproved) => {
|
|
86
107
|
return this._applyRouteIfAllowed(
|
|
87
|
-
|
|
108
|
+
isRouteChangeApproved,
|
|
88
109
|
forcefully,
|
|
89
110
|
replacesCurrentRouteInHistory
|
|
90
111
|
);
|
|
@@ -92,16 +113,17 @@ class UIRoute extends Array {
|
|
|
92
113
|
}
|
|
93
114
|
return this._applyRouteIfAllowed(shouldProceed, forcefully, replacesCurrentRouteInHistory);
|
|
94
115
|
}
|
|
95
|
-
_applyRouteIfAllowed(
|
|
96
|
-
if (!
|
|
116
|
+
_applyRouteIfAllowed(isRouteChangeApproved, forcefully, replacesCurrentRouteInHistory) {
|
|
117
|
+
if (!isRouteChangeApproved) {
|
|
97
118
|
return import_UIObject.NO;
|
|
98
119
|
}
|
|
99
|
-
return this.
|
|
120
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, replacesCurrentRouteInHistory);
|
|
100
121
|
}
|
|
101
|
-
|
|
102
|
-
if (!this.
|
|
122
|
+
_applyRouteWithoutRouteChangeApproval(forcefully, replacesCurrentRouteInHistory) {
|
|
123
|
+
if (!this._needsRouteChange(forcefully)) {
|
|
103
124
|
return import_UIObject.NO;
|
|
104
125
|
}
|
|
126
|
+
_UIRoute._selfInitiatedRouteChange = this._routeChangeDescriptor(forcefully, replacesCurrentRouteInHistory);
|
|
105
127
|
if (replacesCurrentRouteInHistory) {
|
|
106
128
|
window.location.replace(this.linkRepresentation);
|
|
107
129
|
} else {
|
|
@@ -109,22 +131,111 @@ class UIRoute extends Array {
|
|
|
109
131
|
}
|
|
110
132
|
return import_UIObject.YES;
|
|
111
133
|
}
|
|
112
|
-
|
|
113
|
-
if (!forcefully && new
|
|
134
|
+
_needsRouteChange(forcefully) {
|
|
135
|
+
if (!forcefully && new _UIRoute(window.location.hash).stringRepresentation == this.stringRepresentation) {
|
|
114
136
|
return import_UIObject.NO;
|
|
115
137
|
}
|
|
116
138
|
return import_UIObject.YES;
|
|
117
139
|
}
|
|
118
|
-
|
|
140
|
+
_routeChangeDescriptor(forcefully, replacesCurrentRouteInHistory) {
|
|
119
141
|
return {
|
|
120
|
-
currentRoute:
|
|
142
|
+
currentRoute: _UIRoute.currentRoute,
|
|
121
143
|
targetRoute: this.copy(),
|
|
122
144
|
forcefully,
|
|
123
145
|
replacesCurrentRouteInHistory
|
|
124
146
|
};
|
|
125
147
|
}
|
|
148
|
+
static _installNativeHistoryNavigationGuard(reapplyCurrentRoute) {
|
|
149
|
+
var _a;
|
|
150
|
+
_UIRoute._recordPresentedRouteAtHistoryPosition(
|
|
151
|
+
_UIRoute.currentRoute,
|
|
152
|
+
(_a = _UIRoute._historyPositionFromCurrentEntry()) != null ? _a : 0
|
|
153
|
+
);
|
|
154
|
+
window.addEventListener(
|
|
155
|
+
"hashchange",
|
|
156
|
+
(event) => _UIRoute._hashDidChange(event, reapplyCurrentRoute)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
static _historyPositionFromCurrentEntry() {
|
|
160
|
+
var _a;
|
|
161
|
+
const historyPosition = (_a = window.history.state) == null ? void 0 : _a[historyPositionStateKey];
|
|
162
|
+
if (typeof historyPosition !== "number") {
|
|
163
|
+
return void 0;
|
|
164
|
+
}
|
|
165
|
+
return historyPosition;
|
|
166
|
+
}
|
|
167
|
+
static _recordPresentedRouteAtHistoryPosition(route, historyPosition) {
|
|
168
|
+
const historyState = Object.assign({}, window.history.state);
|
|
169
|
+
historyState[historyPositionStateKey] = historyPosition;
|
|
170
|
+
window.history.replaceState(historyState, "");
|
|
171
|
+
_UIRoute._presentedRoute = route.copy();
|
|
172
|
+
_UIRoute._presentedHistoryPosition = historyPosition;
|
|
173
|
+
_UIRoute._recordedHistoryLength = window.history.length;
|
|
174
|
+
}
|
|
175
|
+
static _hashDidChange(event, reapplyCurrentRoute) {
|
|
176
|
+
const targetRoute = _UIRoute.currentRoute;
|
|
177
|
+
const selfInitiatedRouteChange = _UIRoute._selfInitiatedRouteChange;
|
|
178
|
+
_UIRoute._selfInitiatedRouteChange = void 0;
|
|
179
|
+
if (selfInitiatedRouteChange && selfInitiatedRouteChange.targetRoute.stringRepresentation === targetRoute.stringRepresentation) {
|
|
180
|
+
let targetHistoryPosition2 = _UIRoute._presentedHistoryPosition;
|
|
181
|
+
if (!selfInitiatedRouteChange.replacesCurrentRouteInHistory) {
|
|
182
|
+
targetHistoryPosition2 = targetHistoryPosition2 + 1;
|
|
183
|
+
}
|
|
184
|
+
_UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition2);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const targetHistoryPosition = _UIRoute._historyPositionFromCurrentEntry();
|
|
188
|
+
if (targetHistoryPosition === void 0) {
|
|
189
|
+
let untaggedHistoryPosition = _UIRoute._presentedHistoryPosition;
|
|
190
|
+
if (window.history.length > _UIRoute._recordedHistoryLength) {
|
|
191
|
+
untaggedHistoryPosition = untaggedHistoryPosition + 1;
|
|
192
|
+
}
|
|
193
|
+
_UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, untaggedHistoryPosition);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (targetHistoryPosition === _UIRoute._presentedHistoryPosition) {
|
|
197
|
+
_UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
event.stopImmediatePropagation();
|
|
201
|
+
const navigationGeneration = ++_UIRoute._nativeHistoryNavigationGeneration;
|
|
202
|
+
_UIRoute._applyHistoryNavigationToRouteAtPositionIfAllowed(
|
|
203
|
+
targetRoute,
|
|
204
|
+
targetHistoryPosition,
|
|
205
|
+
navigationGeneration,
|
|
206
|
+
reapplyCurrentRoute
|
|
207
|
+
).catch((error) => {
|
|
208
|
+
console.error("UIRoute: native history navigation check failed", error);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
static _applyHistoryNavigationToRouteAtPositionIfAllowed(targetRoute, targetHistoryPosition, navigationGeneration, reapplyCurrentRoute) {
|
|
212
|
+
return __async(this, null, function* () {
|
|
213
|
+
const shouldApplyRouteChange = _UIRoute.shouldApplyRouteChange;
|
|
214
|
+
const isRouteChangeApproved = shouldApplyRouteChange ? yield shouldApplyRouteChange({
|
|
215
|
+
currentRoute: _UIRoute._presentedRoute.copy(),
|
|
216
|
+
targetRoute: targetRoute.copy(),
|
|
217
|
+
forcefully: import_UIObject.NO,
|
|
218
|
+
replacesCurrentRouteInHistory: import_UIObject.NO
|
|
219
|
+
}) : import_UIObject.YES;
|
|
220
|
+
if (navigationGeneration !== _UIRoute._nativeHistoryNavigationGeneration) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (_UIRoute.currentRoute.stringRepresentation !== targetRoute.stringRepresentation || _UIRoute._historyPositionFromCurrentEntry() !== targetHistoryPosition) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (isRouteChangeApproved) {
|
|
227
|
+
_UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition);
|
|
228
|
+
yield reapplyCurrentRoute();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
const historyDelta = _UIRoute._presentedHistoryPosition - targetHistoryPosition;
|
|
232
|
+
if (historyDelta !== 0) {
|
|
233
|
+
window.history.go(historyDelta);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
126
237
|
copy() {
|
|
127
|
-
const result = new
|
|
238
|
+
const result = new _UIRoute(this.stringRepresentation);
|
|
128
239
|
return result;
|
|
129
240
|
}
|
|
130
241
|
routeByRemovingComponentsOtherThanOnesNamed(componentNames) {
|
|
@@ -232,7 +343,12 @@ class UIRoute extends Array {
|
|
|
232
343
|
});
|
|
233
344
|
return result;
|
|
234
345
|
}
|
|
235
|
-
}
|
|
346
|
+
};
|
|
347
|
+
let UIRoute = _UIRoute;
|
|
348
|
+
UIRoute._presentedRoute = new _UIRoute();
|
|
349
|
+
UIRoute._presentedHistoryPosition = 0;
|
|
350
|
+
UIRoute._recordedHistoryLength = 0;
|
|
351
|
+
UIRoute._nativeHistoryNavigationGeneration = 0;
|
|
236
352
|
// Annotate the CommonJS export names for ESM import in node:
|
|
237
353
|
0 && (module.exports = {
|
|
238
354
|
UIRoute
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../scripts/UIRoute.ts"],
|
|
4
|
-
"sourcesContent": ["import { IS_NIL, IS_NOT, NO, ValueOf, YES } from \"./UIObject\"\nimport { UIViewController } from \"./UIViewController\"\n\n\nexport type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];\n\nexport type UIRouteParameters<T = any> = {\n \n [key: string]: string;\n \n} | T;\n\n\nexport interface UIRouteComponent<T = any> {\n \n name: string;\n parameters: UIRouteParameters<T>;\n \n}\n\n\nexport interface UIRouteChangeDescriptor {\n \n currentRoute: UIRoute;\n targetRoute: UIRoute;\n forcefully: boolean;\n replacesCurrentRouteInHistory: boolean;\n \n}\n\n\nexport type UIRouteShouldApplyRouteChangeCallbackFunction = (\n routeChangeDescriptor: UIRouteChangeDescriptor\n) => boolean | Promise<boolean>;\n\n\nexport class UIRoute extends Array<UIRouteComponent> {\n \n static shouldApplyRouteChange?: UIRouteShouldApplyRouteChangeCallbackFunction\n \n constructor(hash?: string) {\n \n super()\n \n if (!hash || !hash.startsWith) {\n \n return\n \n }\n \n if (hash.startsWith(\"#\")) {\n hash = hash.slice(1)\n }\n \n hash = decodeURIComponent(hash)\n \n const components = hash.split(\"]\")\n components.forEach(component => {\n \n const componentName = component.split(\"[\")[0]\n const parameters: Record<string, string> = {}\n \n if (!componentName) {\n return\n }\n \n const parametersString = component.split(\"[\")[1] || \"\"\n const parameterPairStrings = parametersString.split(\",\") || []\n \n parameterPairStrings.forEach(pairString => {\n \n const keyAndValueArray = pairString.split(\":\")\n const key = decodeURIComponent(keyAndValueArray[0])\n const value = decodeURIComponent(keyAndValueArray[1])\n \n if (key) {\n parameters[key] = value\n }\n \n })\n \n \n this.push({\n name: componentName,\n parameters: parameters\n })\n \n })\n \n \n }\n \n \n static get currentRoute() {\n \n return new UIRoute(window.location.hash)\n \n }\n \n \n /**\n * @param forcefully When `true`, applies the route even if its string\n * representation is identical to the current URL hash, causing the browser\n * to fire a `hashchange` event and re-run the full `handleRoute` flow.\n * Use this only when you explicitly need re-entrant navigation (e.g. the\n * \"tap the already-active tab to reset to root\" feature in TopBarView).\n * Defaults to `false`.\n */\n apply(forcefully = NO): boolean | Promise<boolean> {\n \n return this._applyRouteAfterRouteApplicationCheck(forcefully, NO)\n \n }\n \n \n /**\n * Applies this route without consulting `UIRoute.routeApplicationShouldProceed`.\n * Use this only for route changes that are internal bookkeeping or that already\n * passed a higher-level confirmation flow.\n */\n applyWithoutRouteApplicationCheck(forcefully = NO): boolean {\n \n return this._applyRouteWithoutRouteApplicationCheck(forcefully, NO)\n \n }\n \n \n /**\n * @param forcefully When `true`, replaces the current history entry with\n * this route even when the route is identical to the current URL hash.\n * Defaults to `false`.\n */\n applyByReplacingCurrentRouteInHistory(forcefully = NO): boolean | Promise<boolean> {\n \n return this._applyRouteAfterRouteApplicationCheck(forcefully, YES)\n \n }\n \n \n /**\n * Replaces the current history entry without consulting\n * `UIRoute.routeApplicationShouldProceed`.\n */\n applyByReplacingCurrentRouteInHistoryWithoutRouteApplicationCheck(forcefully = NO): boolean {\n \n return this._applyRouteWithoutRouteApplicationCheck(forcefully, YES)\n \n }\n \n \n _applyRouteAfterRouteApplicationCheck(\n forcefully: boolean,\n replacesCurrentRouteInHistory: boolean\n ): boolean | Promise<boolean> {\n \n if (!this._needsRouteApplication(forcefully)) {\n return NO\n }\n \n const routeApplicationShouldProceed = UIRoute.shouldApplyRouteChange\n if (!routeApplicationShouldProceed) {\n return this._applyRouteWithoutRouteApplicationCheck(forcefully, replacesCurrentRouteInHistory)\n }\n \n const shouldProceed = routeApplicationShouldProceed(\n this._routeApplication(forcefully, replacesCurrentRouteInHistory)\n )\n \n if (shouldProceed instanceof Promise) {\n return shouldProceed.then(routeApplicationShouldProceed => {\n return this._applyRouteIfAllowed(\n routeApplicationShouldProceed,\n forcefully,\n replacesCurrentRouteInHistory\n )\n })\n }\n \n return this._applyRouteIfAllowed(shouldProceed, forcefully, replacesCurrentRouteInHistory)\n \n }\n \n \n _applyRouteIfAllowed(\n routeApplicationShouldProceed: boolean,\n forcefully: boolean,\n replacesCurrentRouteInHistory: boolean\n ): boolean {\n \n if (!routeApplicationShouldProceed) {\n return NO\n }\n \n return this._applyRouteWithoutRouteApplicationCheck(forcefully, replacesCurrentRouteInHistory)\n \n }\n \n \n _applyRouteWithoutRouteApplicationCheck(\n forcefully: boolean,\n replacesCurrentRouteInHistory: boolean\n ): boolean {\n \n if (!this._needsRouteApplication(forcefully)) {\n return NO\n }\n \n if (replacesCurrentRouteInHistory) {\n window.location.replace(this.linkRepresentation)\n }\n else {\n window.location.hash = this.stringRepresentation\n }\n \n return YES\n \n }\n \n \n _needsRouteApplication(forcefully: boolean): boolean {\n \n if (!forcefully && new UIRoute(window.location.hash).stringRepresentation == this.stringRepresentation) {\n return NO\n }\n \n return YES\n \n }\n \n \n _routeApplication(forcefully: boolean, replacesCurrentRouteInHistory: boolean): UIRouteChangeDescriptor {\n \n return {\n currentRoute: UIRoute.currentRoute,\n targetRoute: this.copy(),\n forcefully: forcefully,\n replacesCurrentRouteInHistory: replacesCurrentRouteInHistory\n }\n \n }\n \n \n override copy() {\n const result = new UIRoute(this.stringRepresentation)\n return result\n }\n \n \n routeByRemovingComponentsOtherThanOnesNamed(componentNames: string[]) {\n const result = this.copy()\n const indexesToRemove: number[] = []\n result.forEach(function (component, index, array) {\n if (!componentNames.contains(component.name)) {\n indexesToRemove.push(index)\n }\n })\n indexesToRemove.forEach(function (indexToRemove, index, array) {\n result.removeElementAtIndex(indexToRemove)\n })\n return result\n }\n \n \n routeByRemovingComponentNamed(componentName: string) {\n const result = this.copy()\n const componentIndex = result.findIndex(function (component, index) {\n return (component.name == componentName)\n })\n if (componentIndex != -1) {\n result.splice(componentIndex, 1)\n }\n return result\n }\n \n \n routeByRemovingParameterInComponent(componentName: string, parameterName: string, removeComponentIfEmpty = NO) {\n let result = this.copy()\n let parameters = result.componentWithName(componentName)?.parameters ?? {}\n delete parameters[parameterName]\n result = result.routeWithComponent(componentName, parameters)\n if (removeComponentIfEmpty && Object.keys(parameters).length == 0) {\n result = result.routeByRemovingComponentNamed(componentName)\n }\n return result\n }\n \n routeBySettingParameterInComponent(componentName: string, parameterName: string, valueToSet: string) {\n let result = this.copy()\n if (IS_NIL(valueToSet) || IS_NIL(parameterName)) {\n return result\n }\n let parameters = result.componentWithName(componentName)?.parameters\n if (IS_NOT(parameters)) {\n parameters = {}\n }\n parameters[parameterName] = valueToSet\n result = result.routeWithComponent(componentName, parameters)\n return result\n }\n \n \n routeWithViewControllerComponent<T extends typeof UIViewController>(\n viewController: T,\n parameters: UIRouteParameters<{ [P in keyof T[\"ParameterIdentifierName\"]]: string }>,\n extendParameters: boolean = NO\n ) {\n \n return this.routeWithComponent(viewController.routeComponentName, parameters, extendParameters)\n \n }\n \n routeWithComponent(name: string, parameters: UIRouteParameters, extendParameters: boolean = NO) {\n \n const result = this.copy()\n let component = result.componentWithName(name)\n if (IS_NOT(component)) {\n component = {\n name: name,\n parameters: {}\n }\n result.push(component)\n }\n \n if (IS_NOT(parameters)) {\n \n parameters = {}\n \n }\n \n if (extendParameters) {\n component.parameters = Object.assign(component.parameters, parameters)\n }\n else {\n component.parameters = parameters\n }\n \n return result\n \n }\n \n navigateBySettingComponent(name: string, parameters: UIRouteParameters, extendParameters: boolean = NO) {\n \n return this.routeWithComponent(name, parameters, extendParameters).apply()\n \n }\n \n \n componentWithViewController<T extends typeof UIViewController>(viewController: T): UIRouteComponent<{ [P in ValueOf<T[\"ParameterIdentifierName\"]>]: string }> | undefined {\n \n return this.componentWithName(viewController.routeComponentName)\n \n }\n \n componentWithName(name: string): UIRouteComponent | undefined {\n let result\n this.forEach(function (component, index, self) {\n if (component.name == name) {\n result = component\n }\n })\n return result\n }\n \n \n get linkRepresentation() {\n return \"#\" + this.stringRepresentation\n }\n \n \n get stringRepresentation() {\n \n let result = \"\"\n this.forEach(function (component, index, self) {\n result = result + component.name\n const parameters = component.parameters\n result = result + \"[\"\n Object.keys(parameters).forEach(function (key, index, keys) {\n if (index) {\n result = result + \",\"\n }\n result = result + encodeURIComponent(key) + \":\" + encodeURIComponent(parameters[key])\n })\n result = result + \"]\"\n })\n \n return result\n \n }\n \n \n}\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["
|
|
4
|
+
"sourcesContent": ["import { IS_NIL, IS_NOT, NO, ValueOf, YES } from \"./UIObject\"\nimport { UIViewController } from \"./UIViewController\"\n\n\nexport type PropType<TObj, TProp extends keyof TObj> = TObj[TProp];\n\nexport type UIRouteParameters<T = any> = {\n \n [key: string]: string;\n \n} | T;\n\n\nexport interface UIRouteComponent<T = any> {\n \n name: string;\n parameters: UIRouteParameters<T>;\n \n}\n\n\nexport interface UIRouteChangeDescriptor {\n \n currentRoute: UIRoute;\n targetRoute: UIRoute;\n forcefully: boolean;\n replacesCurrentRouteInHistory: boolean;\n \n}\n\n\nexport type UIRouteShouldApplyRouteChangeCallbackFunction = (\n routeChangeDescriptor: UIRouteChangeDescriptor\n) => boolean | Promise<boolean>;\n\n\nconst historyPositionStateKey = \"uiRouteHistoryPosition\"\n\n\nexport class UIRoute extends Array<UIRouteComponent> {\n \n static shouldApplyRouteChange?: UIRouteShouldApplyRouteChangeCallbackFunction\n \n // Native Back/Forward navigation fires `hashchange` directly, bypassing\n // `apply()` and therefore `shouldApplyRouteChange` - the fields and\n // methods below (installed via `_installNativeHistoryNavigationGuard`)\n // close that gap by tagging every history entry with a running position,\n // so a native traversal can be told apart from this class's own hash\n // mutations and, when declined, undone with `history.go(...)`.\n static _presentedRoute: UIRoute = new UIRoute()\n static _presentedHistoryPosition = 0\n static _recordedHistoryLength = 0\n static _selfInitiatedRouteChange?: UIRouteChangeDescriptor\n static _nativeHistoryNavigationGeneration = 0\n \n constructor(hash?: string) {\n \n super()\n \n if (!hash || !hash.startsWith) {\n \n return\n \n }\n \n if (hash.startsWith(\"#\")) {\n hash = hash.slice(1)\n }\n \n hash = decodeURIComponent(hash)\n \n const components = hash.split(\"]\")\n components.forEach(component => {\n \n const componentName = component.split(\"[\")[0]\n const parameters: Record<string, string> = {}\n \n if (!componentName) {\n return\n }\n \n const parametersString = component.split(\"[\")[1] || \"\"\n const parameterPairStrings = parametersString.split(\",\") || []\n \n parameterPairStrings.forEach(pairString => {\n \n const keyAndValueArray = pairString.split(\":\")\n const key = decodeURIComponent(keyAndValueArray[0])\n const value = decodeURIComponent(keyAndValueArray[1])\n \n if (key) {\n parameters[key] = value\n }\n \n })\n \n \n this.push({\n name: componentName,\n parameters: parameters\n })\n \n })\n \n \n }\n \n \n static get currentRoute() {\n\n return new UIRoute(window.location.hash)\n\n }\n\n\n /**\n * @param forcefully When `true`, applies the route even if its string\n * representation is identical to the current URL hash, causing the browser\n * to fire a `hashchange` event and re-run the full `handleRoute` flow.\n * Use this only when you explicitly need re-entrant navigation (e.g. the\n * \"tap the already-active tab to reset to root\" feature in TopBarView).\n * Defaults to `false`.\n */\n apply(forcefully = NO): boolean | Promise<boolean> {\n \n return this._applyRouteAfterRouteChangeApproval(forcefully, NO)\n \n }\n \n \n /**\n * Applies this route without consulting `UIRoute.shouldApplyRouteChange`.\n * Use this only for route changes that are internal bookkeeping or that already\n * passed a higher-level confirmation flow.\n */\n applyWithoutRouteChangeApproval(forcefully = NO): boolean {\n \n return this._applyRouteWithoutRouteChangeApproval(forcefully, NO)\n \n }\n \n \n /**\n * @param forcefully When `true`, replaces the current history entry with\n * this route even when the route is identical to the current URL hash.\n * Defaults to `false`.\n */\n applyByReplacingCurrentRouteInHistory(forcefully = NO): boolean | Promise<boolean> {\n \n return this._applyRouteAfterRouteChangeApproval(forcefully, YES)\n \n }\n \n \n /**\n * Replaces the current history entry without consulting\n * `UIRoute.shouldApplyRouteChange`.\n */\n applyByReplacingCurrentRouteInHistoryWithoutRouteChangeApproval(forcefully = NO): boolean {\n \n return this._applyRouteWithoutRouteChangeApproval(forcefully, YES)\n \n }\n \n \n _applyRouteAfterRouteChangeApproval(\n forcefully: boolean,\n replacesCurrentRouteInHistory: boolean\n ): boolean | Promise<boolean> {\n \n if (!this._needsRouteChange(forcefully)) {\n return NO\n }\n \n const shouldApplyRouteChange = UIRoute.shouldApplyRouteChange\n if (!shouldApplyRouteChange) {\n return this._applyRouteWithoutRouteChangeApproval(forcefully, replacesCurrentRouteInHistory)\n }\n \n const shouldProceed = shouldApplyRouteChange(\n this._routeChangeDescriptor(forcefully, replacesCurrentRouteInHistory)\n )\n \n if (shouldProceed instanceof Promise) {\n return shouldProceed.then(isRouteChangeApproved => {\n return this._applyRouteIfAllowed(\n isRouteChangeApproved,\n forcefully,\n replacesCurrentRouteInHistory\n )\n })\n }\n \n return this._applyRouteIfAllowed(shouldProceed, forcefully, replacesCurrentRouteInHistory)\n \n }\n \n \n _applyRouteIfAllowed(\n isRouteChangeApproved: boolean,\n forcefully: boolean,\n replacesCurrentRouteInHistory: boolean\n ): boolean {\n \n if (!isRouteChangeApproved) {\n return NO\n }\n \n return this._applyRouteWithoutRouteChangeApproval(forcefully, replacesCurrentRouteInHistory)\n \n }\n \n \n _applyRouteWithoutRouteChangeApproval(\n forcefully: boolean,\n replacesCurrentRouteInHistory: boolean\n ): boolean {\n \n if (!this._needsRouteChange(forcefully)) {\n return NO\n }\n\n // Tags the hashchange this is about to cause as this class's own\n // doing, so `_hashDidChange` can tell it apart from a native\n // Back/Forward traversal instead of falling back to guessing from\n // `window.history.length`.\n UIRoute._selfInitiatedRouteChange = this._routeChangeDescriptor(forcefully, replacesCurrentRouteInHistory)\n\n if (replacesCurrentRouteInHistory) {\n window.location.replace(this.linkRepresentation)\n }\n else {\n window.location.hash = this.stringRepresentation\n }\n\n return YES\n\n }\n \n \n _needsRouteChange(forcefully: boolean): boolean {\n \n if (!forcefully && new UIRoute(window.location.hash).stringRepresentation == this.stringRepresentation) {\n return NO\n }\n \n return YES\n \n }\n \n \n _routeChangeDescriptor(forcefully: boolean, replacesCurrentRouteInHistory: boolean): UIRouteChangeDescriptor {\n \n return {\n currentRoute: UIRoute.currentRoute,\n targetRoute: this.copy(),\n forcefully: forcefully,\n replacesCurrentRouteInHistory: replacesCurrentRouteInHistory\n }\n\n }\n\n\n /**\n * Installs handling so that native browser Back/Forward navigation\n * consults `shouldApplyRouteChange` the same way `apply()` does, instead\n * of silently bypassing it. Call exactly once, from `UICore`'s\n * constructor.\n *\n * @param reapplyCurrentRoute Re-runs route handling for `UIRoute.currentRoute`.\n * Called once a native navigation is allowed to proceed (or is self-initiated),\n * so its effect actually appears on screen.\n */\n static _installNativeHistoryNavigationGuard(reapplyCurrentRoute: () => void | Promise<void>) {\n\n UIRoute._recordPresentedRouteAtHistoryPosition(\n UIRoute.currentRoute,\n UIRoute._historyPositionFromCurrentEntry() ?? 0\n )\n\n window.addEventListener(\"hashchange\", event =>\n UIRoute._hashDidChange(event, reapplyCurrentRoute)\n )\n\n }\n\n\n static _historyPositionFromCurrentEntry(): number | undefined {\n\n const historyPosition = (window.history.state as Record<string, unknown> | null)?.[historyPositionStateKey]\n if (typeof historyPosition !== \"number\") {\n return undefined\n }\n return historyPosition\n\n }\n\n\n static _recordPresentedRouteAtHistoryPosition(route: UIRoute, historyPosition: number) {\n\n const historyState = Object.assign({}, window.history.state)\n historyState[historyPositionStateKey] = historyPosition\n window.history.replaceState(historyState, \"\")\n\n UIRoute._presentedRoute = route.copy()\n UIRoute._presentedHistoryPosition = historyPosition\n UIRoute._recordedHistoryLength = window.history.length\n\n }\n\n\n /**\n * The single listener installed for every `hashchange` - self-initiated,\n * native Back/Forward, or an untagged bypass (see the class-level comment\n * above `_presentedRoute`) - and the point where those three cases are\n * told apart.\n *\n * @param event The native event. Only used to call\n * `stopImmediatePropagation()` when a native traversal needs to be held\n * back pending `shouldApplyRouteChange`, so `UICore`'s own route handling\n * doesn't run before that check settles.\n * @param reapplyCurrentRoute Forwarded to `_applyHistoryNavigationToRouteAtPositionIfAllowed`\n * - see its own doc comment.\n */\n static _hashDidChange(event: HashChangeEvent, reapplyCurrentRoute: () => void | Promise<void>) {\n\n const targetRoute = UIRoute.currentRoute\n const selfInitiatedRouteChange = UIRoute._selfInitiatedRouteChange\n UIRoute._selfInitiatedRouteChange = undefined\n\n if (\n selfInitiatedRouteChange &&\n selfInitiatedRouteChange.targetRoute.stringRepresentation === targetRoute.stringRepresentation\n ) {\n\n let targetHistoryPosition = UIRoute._presentedHistoryPosition\n if (!selfInitiatedRouteChange.replacesCurrentRouteInHistory) {\n targetHistoryPosition = targetHistoryPosition + 1\n }\n UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition)\n return\n\n }\n\n const targetHistoryPosition = UIRoute._historyPositionFromCurrentEntry()\n if (targetHistoryPosition === undefined) {\n\n // A hash mutation that bypassed this class entirely (e.g. a\n // direct `window.location.hash = ...` assignment) produces an\n // untagged entry. A larger native history length means it was\n // pushed; an unchanged length means the current entry was\n // replaced. Preserve both established escape hatches while\n // bringing the entry into this sequence.\n let untaggedHistoryPosition = UIRoute._presentedHistoryPosition\n if (window.history.length > UIRoute._recordedHistoryLength) {\n untaggedHistoryPosition = untaggedHistoryPosition + 1\n }\n UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, untaggedHistoryPosition)\n return\n\n }\n if (targetHistoryPosition === UIRoute._presentedHistoryPosition) {\n UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition)\n return\n }\n\n event.stopImmediatePropagation()\n const navigationGeneration = ++UIRoute._nativeHistoryNavigationGeneration\n UIRoute._applyHistoryNavigationToRouteAtPositionIfAllowed(\n targetRoute,\n targetHistoryPosition,\n navigationGeneration,\n reapplyCurrentRoute\n ).catch(error => {\n console.error(\"UIRoute: native history navigation check failed\", error)\n })\n\n }\n\n\n /**\n * Consults `shouldApplyRouteChange` for a native Back/Forward traversal,\n * exactly like `apply()` does for a programmatic one, then either lets it\n * through or reverts it with `history.go(...)`.\n *\n * @param targetRoute The route the native traversal is asking to move to.\n * @param targetHistoryPosition That route's tagged position, used both to\n * revert (`history.go`) and to detect a newer navigation arriving mid-check.\n * @param navigationGeneration A token captured by the caller at dispatch\n * time. If it no longer matches `_nativeHistoryNavigationGeneration` by\n * the time `shouldApplyRouteChange` resolves, a newer navigation has\n * superseded this one and this check's result is discarded.\n * @param reapplyCurrentRoute Called once the navigation is approved, so\n * `UIRoute.currentRoute`'s new value actually takes effect on screen.\n */\n static async _applyHistoryNavigationToRouteAtPositionIfAllowed(\n targetRoute: UIRoute,\n targetHistoryPosition: number,\n navigationGeneration: number,\n reapplyCurrentRoute: () => void | Promise<void>\n ) {\n\n const shouldApplyRouteChange = UIRoute.shouldApplyRouteChange\n const isRouteChangeApproved = shouldApplyRouteChange\n ? await shouldApplyRouteChange({\n currentRoute: UIRoute._presentedRoute.copy(),\n targetRoute: targetRoute.copy(),\n forcefully: NO,\n replacesCurrentRouteInHistory: NO\n })\n : YES\n\n if (navigationGeneration !== UIRoute._nativeHistoryNavigationGeneration) {\n return\n }\n if (\n UIRoute.currentRoute.stringRepresentation !== targetRoute.stringRepresentation ||\n UIRoute._historyPositionFromCurrentEntry() !== targetHistoryPosition\n ) {\n return\n }\n\n if (isRouteChangeApproved) {\n UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition)\n await reapplyCurrentRoute()\n return\n }\n\n const historyDelta = UIRoute._presentedHistoryPosition - targetHistoryPosition\n if (historyDelta !== 0) {\n window.history.go(historyDelta)\n }\n\n }\n\n\n override copy() {\n const result = new UIRoute(this.stringRepresentation)\n return result\n }\n \n \n routeByRemovingComponentsOtherThanOnesNamed(componentNames: string[]) {\n const result = this.copy()\n const indexesToRemove: number[] = []\n result.forEach(function (component, index, array) {\n if (!componentNames.contains(component.name)) {\n indexesToRemove.push(index)\n }\n })\n indexesToRemove.forEach(function (indexToRemove, index, array) {\n result.removeElementAtIndex(indexToRemove)\n })\n return result\n }\n \n \n routeByRemovingComponentNamed(componentName: string) {\n const result = this.copy()\n const componentIndex = result.findIndex(function (component, index) {\n return (component.name == componentName)\n })\n if (componentIndex != -1) {\n result.splice(componentIndex, 1)\n }\n return result\n }\n \n \n routeByRemovingParameterInComponent(componentName: string, parameterName: string, removeComponentIfEmpty = NO) {\n let result = this.copy()\n let parameters = result.componentWithName(componentName)?.parameters ?? {}\n delete parameters[parameterName]\n result = result.routeWithComponent(componentName, parameters)\n if (removeComponentIfEmpty && Object.keys(parameters).length == 0) {\n result = result.routeByRemovingComponentNamed(componentName)\n }\n return result\n }\n \n routeBySettingParameterInComponent(componentName: string, parameterName: string, valueToSet: string) {\n let result = this.copy()\n if (IS_NIL(valueToSet) || IS_NIL(parameterName)) {\n return result\n }\n let parameters = result.componentWithName(componentName)?.parameters\n if (IS_NOT(parameters)) {\n parameters = {}\n }\n parameters[parameterName] = valueToSet\n result = result.routeWithComponent(componentName, parameters)\n return result\n }\n \n \n routeWithViewControllerComponent<T extends typeof UIViewController>(\n viewController: T,\n parameters: UIRouteParameters<{ [P in keyof T[\"ParameterIdentifierName\"]]: string }>,\n extendParameters: boolean = NO\n ) {\n \n return this.routeWithComponent(viewController.routeComponentName, parameters, extendParameters)\n \n }\n \n routeWithComponent(name: string, parameters: UIRouteParameters, extendParameters: boolean = NO) {\n \n const result = this.copy()\n let component = result.componentWithName(name)\n if (IS_NOT(component)) {\n component = {\n name: name,\n parameters: {}\n }\n result.push(component)\n }\n \n if (IS_NOT(parameters)) {\n \n parameters = {}\n \n }\n \n if (extendParameters) {\n component.parameters = Object.assign(component.parameters, parameters)\n }\n else {\n component.parameters = parameters\n }\n \n return result\n \n }\n \n navigateBySettingComponent(name: string, parameters: UIRouteParameters, extendParameters: boolean = NO) {\n \n return this.routeWithComponent(name, parameters, extendParameters).apply()\n \n }\n \n \n componentWithViewController<T extends typeof UIViewController>(viewController: T): UIRouteComponent<{ [P in ValueOf<T[\"ParameterIdentifierName\"]>]: string }> | undefined {\n \n return this.componentWithName(viewController.routeComponentName)\n \n }\n \n componentWithName(name: string): UIRouteComponent | undefined {\n let result\n this.forEach(function (component, index, self) {\n if (component.name == name) {\n result = component\n }\n })\n return result\n }\n \n \n get linkRepresentation() {\n return \"#\" + this.stringRepresentation\n }\n \n \n get stringRepresentation() {\n \n let result = \"\"\n this.forEach(function (component, index, self) {\n result = result + component.name\n const parameters = component.parameters\n result = result + \"[\"\n Object.keys(parameters).forEach(function (key, index, keys) {\n if (index) {\n result = result + \",\"\n }\n result = result + encodeURIComponent(key) + \":\" + encodeURIComponent(parameters[key])\n })\n result = result + \"]\"\n })\n \n return result\n \n }\n \n \n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["targetHistoryPosition", "index"]
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uicore-ts",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.387",
|
|
4
4
|
"description": "UICore is a library to build native-like user interfaces using pure Typescript. No HTML is needed at all. Components are described as TS classes and all user interactions are handled explicitly. This library is strongly inspired by the UIKit framework that is used in IOS. In addition, UICore has tools to handle URL based routing, array sorting and filtering and adds a number of other utilities for convenience.",
|
|
5
5
|
"main": "compiledScripts/index.js",
|
|
6
6
|
"types": "compiledScripts/index.d.ts",
|
package/scripts/UICore.ts
CHANGED
|
@@ -139,17 +139,14 @@ export class UICore extends UIObject {
|
|
|
139
139
|
|
|
140
140
|
window.addEventListener("scroll", didScroll, false)
|
|
141
141
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
window.addEventListener("hashchange", hashDidChange, false)
|
|
151
|
-
|
|
152
|
-
hashDidChange()
|
|
142
|
+
// Native Back/Forward navigation fires `hashchange` directly, which
|
|
143
|
+
// would otherwise reapply the route unconditionally below with no
|
|
144
|
+
// chance for the app to veto it (e.g. to protect unsaved changes) the
|
|
145
|
+
// way `UIRoute.apply()` can. This makes native history traversal go
|
|
146
|
+
// through the same `UIRoute.shouldApplyRouteChange` check first.
|
|
147
|
+
UIRoute._installNativeHistoryNavigationGuard(() => this.reapplyCurrentRoute())
|
|
148
|
+
|
|
149
|
+
this.reapplyCurrentRoute()
|
|
153
150
|
|
|
154
151
|
|
|
155
152
|
}
|
package/scripts/UIRoute.ts
CHANGED
|
@@ -34,10 +34,25 @@ export type UIRouteShouldApplyRouteChangeCallbackFunction = (
|
|
|
34
34
|
) => boolean | Promise<boolean>;
|
|
35
35
|
|
|
36
36
|
|
|
37
|
+
const historyPositionStateKey = "uiRouteHistoryPosition"
|
|
38
|
+
|
|
39
|
+
|
|
37
40
|
export class UIRoute extends Array<UIRouteComponent> {
|
|
38
41
|
|
|
39
42
|
static shouldApplyRouteChange?: UIRouteShouldApplyRouteChangeCallbackFunction
|
|
40
43
|
|
|
44
|
+
// Native Back/Forward navigation fires `hashchange` directly, bypassing
|
|
45
|
+
// `apply()` and therefore `shouldApplyRouteChange` - the fields and
|
|
46
|
+
// methods below (installed via `_installNativeHistoryNavigationGuard`)
|
|
47
|
+
// close that gap by tagging every history entry with a running position,
|
|
48
|
+
// so a native traversal can be told apart from this class's own hash
|
|
49
|
+
// mutations and, when declined, undone with `history.go(...)`.
|
|
50
|
+
static _presentedRoute: UIRoute = new UIRoute()
|
|
51
|
+
static _presentedHistoryPosition = 0
|
|
52
|
+
static _recordedHistoryLength = 0
|
|
53
|
+
static _selfInitiatedRouteChange?: UIRouteChangeDescriptor
|
|
54
|
+
static _nativeHistoryNavigationGeneration = 0
|
|
55
|
+
|
|
41
56
|
constructor(hash?: string) {
|
|
42
57
|
|
|
43
58
|
super()
|
|
@@ -92,12 +107,12 @@ export class UIRoute extends Array<UIRouteComponent> {
|
|
|
92
107
|
|
|
93
108
|
|
|
94
109
|
static get currentRoute() {
|
|
95
|
-
|
|
110
|
+
|
|
96
111
|
return new UIRoute(window.location.hash)
|
|
97
|
-
|
|
112
|
+
|
|
98
113
|
}
|
|
99
|
-
|
|
100
|
-
|
|
114
|
+
|
|
115
|
+
|
|
101
116
|
/**
|
|
102
117
|
* @param forcefully When `true`, applies the route even if its string
|
|
103
118
|
* representation is identical to the current URL hash, causing the browser
|
|
@@ -108,19 +123,19 @@ export class UIRoute extends Array<UIRouteComponent> {
|
|
|
108
123
|
*/
|
|
109
124
|
apply(forcefully = NO): boolean | Promise<boolean> {
|
|
110
125
|
|
|
111
|
-
return this.
|
|
126
|
+
return this._applyRouteAfterRouteChangeApproval(forcefully, NO)
|
|
112
127
|
|
|
113
128
|
}
|
|
114
129
|
|
|
115
130
|
|
|
116
131
|
/**
|
|
117
|
-
* Applies this route without consulting `UIRoute.
|
|
132
|
+
* Applies this route without consulting `UIRoute.shouldApplyRouteChange`.
|
|
118
133
|
* Use this only for route changes that are internal bookkeeping or that already
|
|
119
134
|
* passed a higher-level confirmation flow.
|
|
120
135
|
*/
|
|
121
|
-
|
|
136
|
+
applyWithoutRouteChangeApproval(forcefully = NO): boolean {
|
|
122
137
|
|
|
123
|
-
return this.
|
|
138
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, NO)
|
|
124
139
|
|
|
125
140
|
}
|
|
126
141
|
|
|
@@ -132,44 +147,44 @@ export class UIRoute extends Array<UIRouteComponent> {
|
|
|
132
147
|
*/
|
|
133
148
|
applyByReplacingCurrentRouteInHistory(forcefully = NO): boolean | Promise<boolean> {
|
|
134
149
|
|
|
135
|
-
return this.
|
|
150
|
+
return this._applyRouteAfterRouteChangeApproval(forcefully, YES)
|
|
136
151
|
|
|
137
152
|
}
|
|
138
153
|
|
|
139
154
|
|
|
140
155
|
/**
|
|
141
156
|
* Replaces the current history entry without consulting
|
|
142
|
-
* `UIRoute.
|
|
157
|
+
* `UIRoute.shouldApplyRouteChange`.
|
|
143
158
|
*/
|
|
144
|
-
|
|
159
|
+
applyByReplacingCurrentRouteInHistoryWithoutRouteChangeApproval(forcefully = NO): boolean {
|
|
145
160
|
|
|
146
|
-
return this.
|
|
161
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, YES)
|
|
147
162
|
|
|
148
163
|
}
|
|
149
164
|
|
|
150
165
|
|
|
151
|
-
|
|
166
|
+
_applyRouteAfterRouteChangeApproval(
|
|
152
167
|
forcefully: boolean,
|
|
153
168
|
replacesCurrentRouteInHistory: boolean
|
|
154
169
|
): boolean | Promise<boolean> {
|
|
155
170
|
|
|
156
|
-
if (!this.
|
|
171
|
+
if (!this._needsRouteChange(forcefully)) {
|
|
157
172
|
return NO
|
|
158
173
|
}
|
|
159
174
|
|
|
160
|
-
const
|
|
161
|
-
if (!
|
|
162
|
-
return this.
|
|
175
|
+
const shouldApplyRouteChange = UIRoute.shouldApplyRouteChange
|
|
176
|
+
if (!shouldApplyRouteChange) {
|
|
177
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, replacesCurrentRouteInHistory)
|
|
163
178
|
}
|
|
164
179
|
|
|
165
|
-
const shouldProceed =
|
|
166
|
-
this.
|
|
180
|
+
const shouldProceed = shouldApplyRouteChange(
|
|
181
|
+
this._routeChangeDescriptor(forcefully, replacesCurrentRouteInHistory)
|
|
167
182
|
)
|
|
168
183
|
|
|
169
184
|
if (shouldProceed instanceof Promise) {
|
|
170
|
-
return shouldProceed.then(
|
|
185
|
+
return shouldProceed.then(isRouteChangeApproved => {
|
|
171
186
|
return this._applyRouteIfAllowed(
|
|
172
|
-
|
|
187
|
+
isRouteChangeApproved,
|
|
173
188
|
forcefully,
|
|
174
189
|
replacesCurrentRouteInHistory
|
|
175
190
|
)
|
|
@@ -182,42 +197,48 @@ export class UIRoute extends Array<UIRouteComponent> {
|
|
|
182
197
|
|
|
183
198
|
|
|
184
199
|
_applyRouteIfAllowed(
|
|
185
|
-
|
|
200
|
+
isRouteChangeApproved: boolean,
|
|
186
201
|
forcefully: boolean,
|
|
187
202
|
replacesCurrentRouteInHistory: boolean
|
|
188
203
|
): boolean {
|
|
189
204
|
|
|
190
|
-
if (!
|
|
205
|
+
if (!isRouteChangeApproved) {
|
|
191
206
|
return NO
|
|
192
207
|
}
|
|
193
208
|
|
|
194
|
-
return this.
|
|
209
|
+
return this._applyRouteWithoutRouteChangeApproval(forcefully, replacesCurrentRouteInHistory)
|
|
195
210
|
|
|
196
211
|
}
|
|
197
212
|
|
|
198
213
|
|
|
199
|
-
|
|
214
|
+
_applyRouteWithoutRouteChangeApproval(
|
|
200
215
|
forcefully: boolean,
|
|
201
216
|
replacesCurrentRouteInHistory: boolean
|
|
202
217
|
): boolean {
|
|
203
218
|
|
|
204
|
-
if (!this.
|
|
219
|
+
if (!this._needsRouteChange(forcefully)) {
|
|
205
220
|
return NO
|
|
206
221
|
}
|
|
207
|
-
|
|
222
|
+
|
|
223
|
+
// Tags the hashchange this is about to cause as this class's own
|
|
224
|
+
// doing, so `_hashDidChange` can tell it apart from a native
|
|
225
|
+
// Back/Forward traversal instead of falling back to guessing from
|
|
226
|
+
// `window.history.length`.
|
|
227
|
+
UIRoute._selfInitiatedRouteChange = this._routeChangeDescriptor(forcefully, replacesCurrentRouteInHistory)
|
|
228
|
+
|
|
208
229
|
if (replacesCurrentRouteInHistory) {
|
|
209
230
|
window.location.replace(this.linkRepresentation)
|
|
210
231
|
}
|
|
211
232
|
else {
|
|
212
233
|
window.location.hash = this.stringRepresentation
|
|
213
234
|
}
|
|
214
|
-
|
|
235
|
+
|
|
215
236
|
return YES
|
|
216
|
-
|
|
237
|
+
|
|
217
238
|
}
|
|
218
239
|
|
|
219
240
|
|
|
220
|
-
|
|
241
|
+
_needsRouteChange(forcefully: boolean): boolean {
|
|
221
242
|
|
|
222
243
|
if (!forcefully && new UIRoute(window.location.hash).stringRepresentation == this.stringRepresentation) {
|
|
223
244
|
return NO
|
|
@@ -228,7 +249,7 @@ export class UIRoute extends Array<UIRouteComponent> {
|
|
|
228
249
|
}
|
|
229
250
|
|
|
230
251
|
|
|
231
|
-
|
|
252
|
+
_routeChangeDescriptor(forcefully: boolean, replacesCurrentRouteInHistory: boolean): UIRouteChangeDescriptor {
|
|
232
253
|
|
|
233
254
|
return {
|
|
234
255
|
currentRoute: UIRoute.currentRoute,
|
|
@@ -236,10 +257,183 @@ export class UIRoute extends Array<UIRouteComponent> {
|
|
|
236
257
|
forcefully: forcefully,
|
|
237
258
|
replacesCurrentRouteInHistory: replacesCurrentRouteInHistory
|
|
238
259
|
}
|
|
239
|
-
|
|
260
|
+
|
|
240
261
|
}
|
|
241
|
-
|
|
242
|
-
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Installs handling so that native browser Back/Forward navigation
|
|
266
|
+
* consults `shouldApplyRouteChange` the same way `apply()` does, instead
|
|
267
|
+
* of silently bypassing it. Call exactly once, from `UICore`'s
|
|
268
|
+
* constructor.
|
|
269
|
+
*
|
|
270
|
+
* @param reapplyCurrentRoute Re-runs route handling for `UIRoute.currentRoute`.
|
|
271
|
+
* Called once a native navigation is allowed to proceed (or is self-initiated),
|
|
272
|
+
* so its effect actually appears on screen.
|
|
273
|
+
*/
|
|
274
|
+
static _installNativeHistoryNavigationGuard(reapplyCurrentRoute: () => void | Promise<void>) {
|
|
275
|
+
|
|
276
|
+
UIRoute._recordPresentedRouteAtHistoryPosition(
|
|
277
|
+
UIRoute.currentRoute,
|
|
278
|
+
UIRoute._historyPositionFromCurrentEntry() ?? 0
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
window.addEventListener("hashchange", event =>
|
|
282
|
+
UIRoute._hashDidChange(event, reapplyCurrentRoute)
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
static _historyPositionFromCurrentEntry(): number | undefined {
|
|
289
|
+
|
|
290
|
+
const historyPosition = (window.history.state as Record<string, unknown> | null)?.[historyPositionStateKey]
|
|
291
|
+
if (typeof historyPosition !== "number") {
|
|
292
|
+
return undefined
|
|
293
|
+
}
|
|
294
|
+
return historyPosition
|
|
295
|
+
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
static _recordPresentedRouteAtHistoryPosition(route: UIRoute, historyPosition: number) {
|
|
300
|
+
|
|
301
|
+
const historyState = Object.assign({}, window.history.state)
|
|
302
|
+
historyState[historyPositionStateKey] = historyPosition
|
|
303
|
+
window.history.replaceState(historyState, "")
|
|
304
|
+
|
|
305
|
+
UIRoute._presentedRoute = route.copy()
|
|
306
|
+
UIRoute._presentedHistoryPosition = historyPosition
|
|
307
|
+
UIRoute._recordedHistoryLength = window.history.length
|
|
308
|
+
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* The single listener installed for every `hashchange` - self-initiated,
|
|
314
|
+
* native Back/Forward, or an untagged bypass (see the class-level comment
|
|
315
|
+
* above `_presentedRoute`) - and the point where those three cases are
|
|
316
|
+
* told apart.
|
|
317
|
+
*
|
|
318
|
+
* @param event The native event. Only used to call
|
|
319
|
+
* `stopImmediatePropagation()` when a native traversal needs to be held
|
|
320
|
+
* back pending `shouldApplyRouteChange`, so `UICore`'s own route handling
|
|
321
|
+
* doesn't run before that check settles.
|
|
322
|
+
* @param reapplyCurrentRoute Forwarded to `_applyHistoryNavigationToRouteAtPositionIfAllowed`
|
|
323
|
+
* - see its own doc comment.
|
|
324
|
+
*/
|
|
325
|
+
static _hashDidChange(event: HashChangeEvent, reapplyCurrentRoute: () => void | Promise<void>) {
|
|
326
|
+
|
|
327
|
+
const targetRoute = UIRoute.currentRoute
|
|
328
|
+
const selfInitiatedRouteChange = UIRoute._selfInitiatedRouteChange
|
|
329
|
+
UIRoute._selfInitiatedRouteChange = undefined
|
|
330
|
+
|
|
331
|
+
if (
|
|
332
|
+
selfInitiatedRouteChange &&
|
|
333
|
+
selfInitiatedRouteChange.targetRoute.stringRepresentation === targetRoute.stringRepresentation
|
|
334
|
+
) {
|
|
335
|
+
|
|
336
|
+
let targetHistoryPosition = UIRoute._presentedHistoryPosition
|
|
337
|
+
if (!selfInitiatedRouteChange.replacesCurrentRouteInHistory) {
|
|
338
|
+
targetHistoryPosition = targetHistoryPosition + 1
|
|
339
|
+
}
|
|
340
|
+
UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition)
|
|
341
|
+
return
|
|
342
|
+
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const targetHistoryPosition = UIRoute._historyPositionFromCurrentEntry()
|
|
346
|
+
if (targetHistoryPosition === undefined) {
|
|
347
|
+
|
|
348
|
+
// A hash mutation that bypassed this class entirely (e.g. a
|
|
349
|
+
// direct `window.location.hash = ...` assignment) produces an
|
|
350
|
+
// untagged entry. A larger native history length means it was
|
|
351
|
+
// pushed; an unchanged length means the current entry was
|
|
352
|
+
// replaced. Preserve both established escape hatches while
|
|
353
|
+
// bringing the entry into this sequence.
|
|
354
|
+
let untaggedHistoryPosition = UIRoute._presentedHistoryPosition
|
|
355
|
+
if (window.history.length > UIRoute._recordedHistoryLength) {
|
|
356
|
+
untaggedHistoryPosition = untaggedHistoryPosition + 1
|
|
357
|
+
}
|
|
358
|
+
UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, untaggedHistoryPosition)
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
if (targetHistoryPosition === UIRoute._presentedHistoryPosition) {
|
|
363
|
+
UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition)
|
|
364
|
+
return
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
event.stopImmediatePropagation()
|
|
368
|
+
const navigationGeneration = ++UIRoute._nativeHistoryNavigationGeneration
|
|
369
|
+
UIRoute._applyHistoryNavigationToRouteAtPositionIfAllowed(
|
|
370
|
+
targetRoute,
|
|
371
|
+
targetHistoryPosition,
|
|
372
|
+
navigationGeneration,
|
|
373
|
+
reapplyCurrentRoute
|
|
374
|
+
).catch(error => {
|
|
375
|
+
console.error("UIRoute: native history navigation check failed", error)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Consults `shouldApplyRouteChange` for a native Back/Forward traversal,
|
|
383
|
+
* exactly like `apply()` does for a programmatic one, then either lets it
|
|
384
|
+
* through or reverts it with `history.go(...)`.
|
|
385
|
+
*
|
|
386
|
+
* @param targetRoute The route the native traversal is asking to move to.
|
|
387
|
+
* @param targetHistoryPosition That route's tagged position, used both to
|
|
388
|
+
* revert (`history.go`) and to detect a newer navigation arriving mid-check.
|
|
389
|
+
* @param navigationGeneration A token captured by the caller at dispatch
|
|
390
|
+
* time. If it no longer matches `_nativeHistoryNavigationGeneration` by
|
|
391
|
+
* the time `shouldApplyRouteChange` resolves, a newer navigation has
|
|
392
|
+
* superseded this one and this check's result is discarded.
|
|
393
|
+
* @param reapplyCurrentRoute Called once the navigation is approved, so
|
|
394
|
+
* `UIRoute.currentRoute`'s new value actually takes effect on screen.
|
|
395
|
+
*/
|
|
396
|
+
static async _applyHistoryNavigationToRouteAtPositionIfAllowed(
|
|
397
|
+
targetRoute: UIRoute,
|
|
398
|
+
targetHistoryPosition: number,
|
|
399
|
+
navigationGeneration: number,
|
|
400
|
+
reapplyCurrentRoute: () => void | Promise<void>
|
|
401
|
+
) {
|
|
402
|
+
|
|
403
|
+
const shouldApplyRouteChange = UIRoute.shouldApplyRouteChange
|
|
404
|
+
const isRouteChangeApproved = shouldApplyRouteChange
|
|
405
|
+
? await shouldApplyRouteChange({
|
|
406
|
+
currentRoute: UIRoute._presentedRoute.copy(),
|
|
407
|
+
targetRoute: targetRoute.copy(),
|
|
408
|
+
forcefully: NO,
|
|
409
|
+
replacesCurrentRouteInHistory: NO
|
|
410
|
+
})
|
|
411
|
+
: YES
|
|
412
|
+
|
|
413
|
+
if (navigationGeneration !== UIRoute._nativeHistoryNavigationGeneration) {
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
if (
|
|
417
|
+
UIRoute.currentRoute.stringRepresentation !== targetRoute.stringRepresentation ||
|
|
418
|
+
UIRoute._historyPositionFromCurrentEntry() !== targetHistoryPosition
|
|
419
|
+
) {
|
|
420
|
+
return
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (isRouteChangeApproved) {
|
|
424
|
+
UIRoute._recordPresentedRouteAtHistoryPosition(targetRoute, targetHistoryPosition)
|
|
425
|
+
await reapplyCurrentRoute()
|
|
426
|
+
return
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const historyDelta = UIRoute._presentedHistoryPosition - targetHistoryPosition
|
|
430
|
+
if (historyDelta !== 0) {
|
|
431
|
+
window.history.go(historyDelta)
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
|
|
243
437
|
override copy() {
|
|
244
438
|
const result = new UIRoute(this.stringRepresentation)
|
|
245
439
|
return result
|