@metron-labs/quote-react 0.1.57 → 0.1.59
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.
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Once per page; stays installed, as it only acts on our own steps. */
|
|
2
|
+
export declare function installQuoteHistory(): void;
|
|
3
|
+
/** Push or replace a history entry for one of our steps (or drop the hash, with url's hash empty). */
|
|
4
|
+
export declare function setStep(method: "pushState" | "replaceState", state: unknown, url: URL): void;
|
|
5
|
+
/** history.back() over our own step, skipping any twin a host script pushed on top of it. */
|
|
6
|
+
export declare function stepBack(): void;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// The quote's steps are history entries on the host's own page: #quote-entry (the dialog),
|
|
2
|
+
// #matched-products and #quote-request. Hosts have their own history listeners, and two of them
|
|
3
|
+
// break back/forward between our steps (both seen on Materials Market):
|
|
4
|
+
//
|
|
5
|
+
// - Turbo (and Turbolinks) treat a step back onto the page's own entry, which holds their state,
|
|
6
|
+
// as a page visit: the page is fetched and re-rendered, the widget remounts and its open dialog
|
|
7
|
+
// is lost. With Turbo Drive off for the page, Turbo still does this.
|
|
8
|
+
// - Salesfire's back-button trap pushes a copy of the current entry after a click, so every step
|
|
9
|
+
// we push gets a twin and our own back (closing the dialog) lands on the twin.
|
|
10
|
+
//
|
|
11
|
+
// So popstates between our steps and the page's entry on the same URL are hidden from the host:
|
|
12
|
+
// a capturing listener runs before theirs (at the target, capture listeners go first) and shows
|
|
13
|
+
// them an event without state, which Turbo and Salesfire ignore. The entry's real state is put back
|
|
14
|
+
// afterwards. Twins are skipped in the direction of travel: known when we step back ourselves, and
|
|
15
|
+
// for the browser's buttons read from the Navigation API where there is one.
|
|
16
|
+
const STEPS = { "#quote-entry": "entry", "#matched-products": "results", "#quote-request": "basket" };
|
|
17
|
+
let installed = false;
|
|
18
|
+
// Where history was last seen. Our own pushes go through setStep, so this only goes stale when
|
|
19
|
+
// the host pushes, and then the path or search has changed and nothing here applies.
|
|
20
|
+
let lastUrl = "";
|
|
21
|
+
let steppingBack = false;
|
|
22
|
+
const navigation = () => window.navigation;
|
|
23
|
+
// The index the last history move started from. currententrychange fires before popstate.
|
|
24
|
+
let movedFrom = -1;
|
|
25
|
+
/** -1 back, 1 forward, 0 unknown. */
|
|
26
|
+
function direction() {
|
|
27
|
+
if (steppingBack)
|
|
28
|
+
return -1;
|
|
29
|
+
const now = navigation()?.currentEntry?.index;
|
|
30
|
+
if (now == null || movedFrom < 0 || now === movedFrom)
|
|
31
|
+
return 0;
|
|
32
|
+
return now < movedFrom ? -1 : 1;
|
|
33
|
+
}
|
|
34
|
+
function onPopState(event) {
|
|
35
|
+
const from = new URL(lastUrl || window.location.href);
|
|
36
|
+
const to = new URL(window.location.href);
|
|
37
|
+
lastUrl = to.href;
|
|
38
|
+
if (from.pathname !== to.pathname || from.search !== to.search)
|
|
39
|
+
return;
|
|
40
|
+
if (!(from.hash in STEPS) && !(to.hash in STEPS))
|
|
41
|
+
return;
|
|
42
|
+
const heading = from.href === to.href ? direction() : 0;
|
|
43
|
+
if (heading) {
|
|
44
|
+
// Landed on a twin someone pushed after our step: nothing changed, so nobody needs to hear
|
|
45
|
+
// about it. Keep going to the entry the move was meant for.
|
|
46
|
+
event.stopImmediatePropagation();
|
|
47
|
+
window.history.go(heading);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
steppingBack = false;
|
|
51
|
+
const real = event.state;
|
|
52
|
+
Object.defineProperty(event, "state", { value: null });
|
|
53
|
+
// Turbo answers a stateless popstate by stamping its own state on the entry. Once every
|
|
54
|
+
// listener has run, put back what was there - for our steps, our own state, so a stamp left
|
|
55
|
+
// by an older release doesn't stick.
|
|
56
|
+
setTimeout(() => {
|
|
57
|
+
if (window.location.href !== to.href)
|
|
58
|
+
return;
|
|
59
|
+
const stage = STEPS[to.hash];
|
|
60
|
+
const keep = stage && real?.turbo ? { boqQuoteStage: stage } : real;
|
|
61
|
+
// Only when it changed: history.state is a copy, so compare contents. On a host with no router
|
|
62
|
+
// of its own this never writes, and analytics watching replaceState see nothing.
|
|
63
|
+
if (JSON.stringify(window.history.state) !== JSON.stringify(keep))
|
|
64
|
+
window.history.replaceState(keep, "", to.href);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** Once per page; stays installed, as it only acts on our own steps. */
|
|
68
|
+
export function installQuoteHistory() {
|
|
69
|
+
if (installed || typeof window === "undefined")
|
|
70
|
+
return;
|
|
71
|
+
installed = true;
|
|
72
|
+
lastUrl = window.location.href;
|
|
73
|
+
window.addEventListener("popstate", onPopState, { capture: true });
|
|
74
|
+
navigation()?.addEventListener("currententrychange", (event) => {
|
|
75
|
+
movedFrom = event.from?.index ?? -1;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Push or replace a history entry for one of our steps (or drop the hash, with url's hash empty). */
|
|
79
|
+
export function setStep(method, state, url) {
|
|
80
|
+
window.history[method](state, "", url);
|
|
81
|
+
lastUrl = window.location.href;
|
|
82
|
+
}
|
|
83
|
+
/** history.back() over our own step, skipping any twin a host script pushed on top of it. */
|
|
84
|
+
export function stepBack() {
|
|
85
|
+
steppingBack = true;
|
|
86
|
+
window.history.back();
|
|
87
|
+
}
|