chayns-api 3.1.0-beta.0 → 3.1.0-beta.1
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/cjs/utils/history/BlockRegistry.js +46 -3
- package/dist/cjs/utils/history/NavigationQueue.js +3 -2
- package/dist/cjs/utils/history/nativeBackHandling.js +61 -0
- package/dist/cjs/utils/history/navigationIndex.js +32 -0
- package/dist/cjs/utils/history/rootLayer.js +48 -10
- package/dist/esm/utils/history/BlockRegistry.js +46 -3
- package/dist/esm/utils/history/NavigationQueue.js +3 -2
- package/dist/esm/utils/history/nativeBackHandling.js +59 -0
- package/dist/esm/utils/history/navigationIndex.js +29 -0
- package/dist/esm/utils/history/rootLayer.js +50 -12
- package/dist/types/utils/history/BlockRegistry.d.ts +7 -0
- package/dist/types/utils/history/NavigationQueue.d.ts +9 -0
- package/dist/types/utils/history/nativeBackHandling.d.ts +47 -0
- package/dist/types/utils/history/navigationIndex.d.ts +3 -0
- package/package.json +1 -1
|
@@ -8,10 +8,11 @@ const BLOCK_TIMEOUT_MS = 30000;
|
|
|
8
8
|
let _nextId = 1;
|
|
9
9
|
class BlockRegistry {
|
|
10
10
|
layerBlocks = new Map();
|
|
11
|
+
changeListeners = new Set();
|
|
11
12
|
beforeUnloadCount = 0;
|
|
12
13
|
beforeUnloadHandler = e => {
|
|
13
14
|
e.preventDefault();
|
|
14
|
-
e
|
|
15
|
+
Reflect.set(e, 'returnValue', '');
|
|
15
16
|
};
|
|
16
17
|
add(layer, callback, opts = {}) {
|
|
17
18
|
var _opts$scope, _opts$isBeforeUnload;
|
|
@@ -32,16 +33,19 @@ class BlockRegistry {
|
|
|
32
33
|
if (entry.opts.isBeforeUnload) {
|
|
33
34
|
this.incrementBeforeUnload();
|
|
34
35
|
}
|
|
36
|
+
this.notifyChange();
|
|
35
37
|
return () => this.remove(layer.id, entry);
|
|
36
38
|
}
|
|
37
39
|
remove(layerId, entry) {
|
|
38
40
|
const set = this.layerBlocks.get(layerId);
|
|
39
41
|
if (!set) return;
|
|
40
|
-
set.delete(entry);
|
|
42
|
+
const didDelete = set.delete(entry);
|
|
43
|
+
if (!didDelete) return;
|
|
41
44
|
if (set.size === 0) this.layerBlocks.delete(layerId);
|
|
42
45
|
if (entry.opts.isBeforeUnload) {
|
|
43
46
|
this.decrementBeforeUnload();
|
|
44
47
|
}
|
|
48
|
+
this.notifyChange();
|
|
45
49
|
}
|
|
46
50
|
removeAllForLayer(layerId) {
|
|
47
51
|
const set = this.layerBlocks.get(layerId);
|
|
@@ -50,6 +54,13 @@ class BlockRegistry {
|
|
|
50
54
|
if (entry.opts.isBeforeUnload) this.decrementBeforeUnload();
|
|
51
55
|
}
|
|
52
56
|
this.layerBlocks.delete(layerId);
|
|
57
|
+
this.notifyChange();
|
|
58
|
+
}
|
|
59
|
+
subscribeToChanges(listener) {
|
|
60
|
+
this.changeListeners.add(listener);
|
|
61
|
+
return () => {
|
|
62
|
+
this.changeListeners.delete(listener);
|
|
63
|
+
};
|
|
53
64
|
}
|
|
54
65
|
collectApplicableBlocks(targetLayer) {
|
|
55
66
|
const result = [];
|
|
@@ -62,6 +73,20 @@ class BlockRegistry {
|
|
|
62
73
|
this.collectGlobalFromActiveDescendants(targetLayer, result);
|
|
63
74
|
return result;
|
|
64
75
|
}
|
|
76
|
+
hasActiveBlocks(rootLayer) {
|
|
77
|
+
return this.collectActiveChainBlocks(rootLayer).length > 0;
|
|
78
|
+
}
|
|
79
|
+
async checkActiveBlocks(rootLayer) {
|
|
80
|
+
const blocks = this.collectActiveChainBlocks(rootLayer);
|
|
81
|
+
if (blocks.length === 0) return true;
|
|
82
|
+
const results = await Promise.all(blocks.map(b => this.runBlock(b)));
|
|
83
|
+
return results.every(Boolean);
|
|
84
|
+
}
|
|
85
|
+
collectActiveChainBlocks(rootLayer) {
|
|
86
|
+
const result = [];
|
|
87
|
+
this.collectFromActiveChain(rootLayer, result);
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
65
90
|
collectGlobalFromActiveDescendants(layer, out) {
|
|
66
91
|
const activeChildId = layer.getActiveChildId();
|
|
67
92
|
if (!activeChildId) return;
|
|
@@ -77,6 +102,19 @@ class BlockRegistry {
|
|
|
77
102
|
}
|
|
78
103
|
this.collectGlobalFromActiveDescendants(child, out);
|
|
79
104
|
}
|
|
105
|
+
collectFromActiveChain(layer, out) {
|
|
106
|
+
const set = this.layerBlocks.get(layer.id);
|
|
107
|
+
if (set) {
|
|
108
|
+
for (const entry of set) {
|
|
109
|
+
out.push(entry);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const activeChildId = layer.getActiveChildId();
|
|
113
|
+
if (!activeChildId) return;
|
|
114
|
+
const child = layer.getChildLayer(activeChildId);
|
|
115
|
+
if (!child) return;
|
|
116
|
+
this.collectFromActiveChain(child, out);
|
|
117
|
+
}
|
|
80
118
|
async checkBlocks(targetLayer) {
|
|
81
119
|
const blocks = this.collectApplicableBlocks(targetLayer);
|
|
82
120
|
if (blocks.length === 0) return true;
|
|
@@ -89,7 +127,7 @@ class BlockRegistry {
|
|
|
89
127
|
resolve(false);
|
|
90
128
|
}, BLOCK_TIMEOUT_MS))]);
|
|
91
129
|
return result;
|
|
92
|
-
} catch
|
|
130
|
+
} catch {
|
|
93
131
|
return false;
|
|
94
132
|
}
|
|
95
133
|
}
|
|
@@ -106,5 +144,10 @@ class BlockRegistry {
|
|
|
106
144
|
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
|
|
107
145
|
}
|
|
108
146
|
}
|
|
147
|
+
notifyChange() {
|
|
148
|
+
for (const listener of this.changeListeners) {
|
|
149
|
+
listener();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
109
152
|
}
|
|
110
153
|
exports.BlockRegistry = BlockRegistry;
|
|
@@ -60,7 +60,6 @@ class NavigationQueue {
|
|
|
60
60
|
return this.processPopstate(op);
|
|
61
61
|
default:
|
|
62
62
|
{
|
|
63
|
-
const _exhaustive = op;
|
|
64
63
|
return {
|
|
65
64
|
isOk: false,
|
|
66
65
|
reason: 'error',
|
|
@@ -296,7 +295,7 @@ class NavigationQueue {
|
|
|
296
295
|
changedLayerIds.add(this.deps.getRoot().id);
|
|
297
296
|
}
|
|
298
297
|
const target = this.resolveLowestCommonLayer(changedLayerIds);
|
|
299
|
-
if (target) {
|
|
298
|
+
if (target && op.skipBlockCheck !== true) {
|
|
300
299
|
const allowed = await this.deps.checkBlocks(target);
|
|
301
300
|
if (!allowed) {
|
|
302
301
|
await this.deps.silentGo(+1);
|
|
@@ -367,6 +366,7 @@ class NavigationQueue {
|
|
|
367
366
|
return lca;
|
|
368
367
|
}
|
|
369
368
|
commit(isReplace) {
|
|
369
|
+
var _this$deps$onCommit, _this$deps;
|
|
370
370
|
if (!(0, _window.hasWindowHistory)()) return;
|
|
371
371
|
const url = this.deps.projectUrl();
|
|
372
372
|
const state = this.deps.projectState();
|
|
@@ -383,6 +383,7 @@ class NavigationQueue {
|
|
|
383
383
|
} else {
|
|
384
384
|
window.history.pushState(stateWithMeta, '', url);
|
|
385
385
|
}
|
|
386
|
+
(_this$deps$onCommit = (_this$deps = this.deps).onCommit) === null || _this$deps$onCommit === void 0 || _this$deps$onCommit.call(_this$deps);
|
|
386
387
|
}
|
|
387
388
|
}
|
|
388
389
|
exports.NavigationQueue = NavigationQueue;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.NativeBackHandler = void 0;
|
|
7
|
+
var _calls = require("../../calls");
|
|
8
|
+
var _IChaynsReact = require("../../types/IChaynsReact");
|
|
9
|
+
var _navigationIndex = require("./navigationIndex");
|
|
10
|
+
var _window = require("./window");
|
|
11
|
+
const DISABLE_SWIPE_BACK_GESTURE_ACTION = 249;
|
|
12
|
+
class NativeBackHandler {
|
|
13
|
+
bypassNextPopstateBlockCheck = false;
|
|
14
|
+
constructor(opts) {
|
|
15
|
+
this.opts = opts;
|
|
16
|
+
}
|
|
17
|
+
sync = () => {
|
|
18
|
+
if (!(0, _window.hasWindowHistory)() || !NativeBackHandler.isSupported()) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const next = this.shouldEnableInterception();
|
|
22
|
+
if (this.isInterceptionEnabled === next) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
void (0, _calls.invokeCall)({
|
|
26
|
+
action: DISABLE_SWIPE_BACK_GESTURE_ACTION,
|
|
27
|
+
value: {
|
|
28
|
+
enabled: next
|
|
29
|
+
}
|
|
30
|
+
}, next ? this.handleNativeBack : undefined);
|
|
31
|
+
this.isInterceptionEnabled = next;
|
|
32
|
+
};
|
|
33
|
+
consumeBypassFlag() {
|
|
34
|
+
if (!this.bypassNextPopstateBlockCheck) return false;
|
|
35
|
+
this.bypassNextPopstateBlockCheck = false;
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
static isSupported() {
|
|
39
|
+
try {
|
|
40
|
+
var _getDevice$app;
|
|
41
|
+
return ((_getDevice$app = (0, _calls.getDevice)().app) === null || _getDevice$app === void 0 ? void 0 : _getDevice$app.flavor) === _IChaynsReact.AppFlavor.Chayns;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
shouldEnableInterception() {
|
|
47
|
+
return this.opts.blockRegistry.hasActiveBlocks(this.opts.rootLayer) || (0, _navigationIndex.getCurrentIdx)() > 0;
|
|
48
|
+
}
|
|
49
|
+
handleNativeBack = () => {
|
|
50
|
+
void this.runNativeBack();
|
|
51
|
+
};
|
|
52
|
+
async runNativeBack() {
|
|
53
|
+
const isAllowed = await this.opts.blockRegistry.checkActiveBlocks(this.opts.rootLayer);
|
|
54
|
+
if (!isAllowed || !(0, _window.hasWindowHistory)()) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.bypassNextPopstateBlockCheck = true;
|
|
58
|
+
window.history.back();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.NativeBackHandler = NativeBackHandler;
|
|
@@ -4,10 +4,14 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.consumeSilent = consumeSilent;
|
|
7
|
+
exports.extractHistoryIndex = extractHistoryIndex;
|
|
7
8
|
exports.getCurrentIdx = getCurrentIdx;
|
|
8
9
|
exports.incrementIdx = incrementIdx;
|
|
10
|
+
exports.setCurrentIdx = setCurrentIdx;
|
|
9
11
|
exports.silentGo = silentGo;
|
|
12
|
+
exports.syncCurrentIdxFromState = syncCurrentIdxFromState;
|
|
10
13
|
var _window = require("./window");
|
|
14
|
+
const CHAYNS_HISTORY_STATE_KEY = '__chaynsHistory';
|
|
11
15
|
let currentIdx = 0;
|
|
12
16
|
let pendingSilentCount = 0;
|
|
13
17
|
let silentResolve = null;
|
|
@@ -17,6 +21,34 @@ function incrementIdx() {
|
|
|
17
21
|
function getCurrentIdx() {
|
|
18
22
|
return currentIdx;
|
|
19
23
|
}
|
|
24
|
+
function setCurrentIdx(idx) {
|
|
25
|
+
if (!Number.isInteger(idx) || idx < 0) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
currentIdx = idx;
|
|
29
|
+
}
|
|
30
|
+
function extractHistoryIndex(raw) {
|
|
31
|
+
if (!raw || typeof raw !== 'object') {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const chaynsHistory = raw[CHAYNS_HISTORY_STATE_KEY];
|
|
35
|
+
if (!chaynsHistory || typeof chaynsHistory !== 'object') {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const idx = chaynsHistory.__idx;
|
|
39
|
+
if (typeof idx !== 'number' || !Number.isInteger(idx) || idx < 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return idx;
|
|
43
|
+
}
|
|
44
|
+
function syncCurrentIdxFromState(raw) {
|
|
45
|
+
const idx = extractHistoryIndex(raw);
|
|
46
|
+
if (idx === null) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
currentIdx = idx;
|
|
50
|
+
return idx;
|
|
51
|
+
}
|
|
20
52
|
function silentGo(delta) {
|
|
21
53
|
if (!(0, _window.hasWindowHistory)()) return Promise.resolve();
|
|
22
54
|
return new Promise(resolve => {
|
|
@@ -17,6 +17,7 @@ var _window = require("./window");
|
|
|
17
17
|
var _equality = require("../equality");
|
|
18
18
|
var _calls = require("../../calls");
|
|
19
19
|
var _segments = require("./segments");
|
|
20
|
+
var _nativeBackHandling = require("./nativeBackHandling");
|
|
20
21
|
function getInitialPathname(overrideUrl) {
|
|
21
22
|
if (overrideUrl) {
|
|
22
23
|
try {
|
|
@@ -48,21 +49,30 @@ function resolveSegmentsFrom(overrideUrl, startIndex) {
|
|
|
48
49
|
function initRootChaynsHistoryLayer(opts = {}) {
|
|
49
50
|
var _opts$segmentCount;
|
|
50
51
|
const blockRegistry = new _BlockRegistry.BlockRegistry();
|
|
51
|
-
let
|
|
52
|
-
let queue;
|
|
52
|
+
let queueRef = null;
|
|
53
53
|
const deps = {
|
|
54
54
|
getRoot: () => rootLayer,
|
|
55
|
-
getQueue: () =>
|
|
55
|
+
getQueue: () => {
|
|
56
|
+
if (!queueRef) {
|
|
57
|
+
throw new Error('[chaynsHistory] NavigationQueue not initialized yet.');
|
|
58
|
+
}
|
|
59
|
+
return queueRef;
|
|
60
|
+
},
|
|
56
61
|
getBlockRegistry: () => blockRegistry
|
|
57
62
|
};
|
|
58
|
-
rootLayer = new _HistoryLayer.ChaynsHistoryLayer({
|
|
63
|
+
const rootLayer = new _HistoryLayer.ChaynsHistoryLayer({
|
|
59
64
|
id: 'root',
|
|
60
65
|
parent: null,
|
|
61
66
|
deps,
|
|
62
67
|
segmentCount: (_opts$segmentCount = opts.segmentCount) !== null && _opts$segmentCount !== void 0 ? _opts$segmentCount : 0,
|
|
63
68
|
segments: opts.segmentCount ? resolveInitialSegments(opts.url, opts.segmentCount) : []
|
|
64
69
|
});
|
|
65
|
-
|
|
70
|
+
const nativeBackHandler = new _nativeBackHandling.NativeBackHandler({
|
|
71
|
+
rootLayer,
|
|
72
|
+
blockRegistry
|
|
73
|
+
});
|
|
74
|
+
const syncNativeHandling = nativeBackHandler.sync;
|
|
75
|
+
const queue = new _NavigationQueue.NavigationQueue({
|
|
66
76
|
getRoot: () => rootLayer,
|
|
67
77
|
findLayer: id => (0, _layerTree.findChaynsHistoryLayerById)(rootLayer, id),
|
|
68
78
|
checkBlocks: target => blockRegistry.checkBlocks(target),
|
|
@@ -80,6 +90,7 @@ function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
80
90
|
silentGo: delta => (0, _navigationIndex.silentGo)(delta),
|
|
81
91
|
getCurrentIdx: () => (0, _navigationIndex.getCurrentIdx)(),
|
|
82
92
|
incrementIdx: () => (0, _navigationIndex.incrementIdx)(),
|
|
93
|
+
onCommit: syncNativeHandling,
|
|
83
94
|
applyUrlSegments: () => {
|
|
84
95
|
if (!(0, _window.hasWindowHistory)()) return {
|
|
85
96
|
changedLayerIds: new Set()
|
|
@@ -103,7 +114,9 @@ function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
103
114
|
};
|
|
104
115
|
}
|
|
105
116
|
});
|
|
117
|
+
queueRef = queue;
|
|
106
118
|
const existingState = (0, _window.hasWindowHistory)() ? window.history.state : null;
|
|
119
|
+
(0, _navigationIndex.syncCurrentIdxFromState)(existingState);
|
|
107
120
|
if (!(0, _stateProjector.hasChaynsHistoryState)(existingState)) {
|
|
108
121
|
var _opts$segmentCount2, _opts$url;
|
|
109
122
|
const segmentCount = (_opts$segmentCount2 = opts.segmentCount) !== null && _opts$segmentCount2 !== void 0 ? _opts$segmentCount2 : 0;
|
|
@@ -137,7 +150,7 @@ function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
137
150
|
};
|
|
138
151
|
delete foreign.__chaynsHistory;
|
|
139
152
|
const initialState = (0, _stateProjector.projectToState)(rootLayer, foreign);
|
|
140
|
-
const idx = (0, _navigationIndex.
|
|
153
|
+
const idx = (0, _navigationIndex.getCurrentIdx)();
|
|
141
154
|
window.history.replaceState({
|
|
142
155
|
...initialState,
|
|
143
156
|
__chaynsHistory: {
|
|
@@ -147,17 +160,42 @@ function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
147
160
|
}, '', window.location.href);
|
|
148
161
|
} else {
|
|
149
162
|
(0, _stateProjector.applyStateToTree)(rootLayer, existing);
|
|
163
|
+
if ((0, _navigationIndex.syncCurrentIdxFromState)(existing) === null) {
|
|
164
|
+
const foreign = {
|
|
165
|
+
...(existing !== null && existing !== void 0 ? existing : {})
|
|
166
|
+
};
|
|
167
|
+
delete foreign.__chaynsHistory;
|
|
168
|
+
const currentState = (0, _stateProjector.projectToState)(rootLayer, foreign);
|
|
169
|
+
const idx = (0, _navigationIndex.getCurrentIdx)();
|
|
170
|
+
window.history.replaceState({
|
|
171
|
+
...currentState,
|
|
172
|
+
__chaynsHistory: {
|
|
173
|
+
...currentState.__chaynsHistory,
|
|
174
|
+
__idx: idx
|
|
175
|
+
}
|
|
176
|
+
}, '', window.location.href);
|
|
177
|
+
}
|
|
150
178
|
}
|
|
179
|
+
blockRegistry.subscribeToChanges(syncNativeHandling);
|
|
151
180
|
window.addEventListener('popstate', event => {
|
|
152
|
-
|
|
181
|
+
(0, _navigationIndex.syncCurrentIdxFromState)(event.state);
|
|
182
|
+
if ((0, _navigationIndex.consumeSilent)()) {
|
|
183
|
+
syncNativeHandling();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
153
186
|
const raw = event.state;
|
|
154
|
-
if (!(0, _stateProjector.hasChaynsHistoryState)(raw)) {
|
|
187
|
+
if (!(0, _stateProjector.hasChaynsHistoryState)(raw)) {
|
|
188
|
+
syncNativeHandling();
|
|
189
|
+
} else {
|
|
190
|
+
const skipBlockCheck = nativeBackHandler.consumeBypassFlag();
|
|
155
191
|
void queue.enqueue({
|
|
156
192
|
kind: 'popstate',
|
|
157
|
-
rawState: raw
|
|
158
|
-
|
|
193
|
+
rawState: raw,
|
|
194
|
+
skipBlockCheck
|
|
195
|
+
}).finally(syncNativeHandling);
|
|
159
196
|
}
|
|
160
197
|
});
|
|
198
|
+
syncNativeHandling();
|
|
161
199
|
}
|
|
162
200
|
return {
|
|
163
201
|
rootLayer
|
|
@@ -6,10 +6,11 @@ let _nextId = 1;
|
|
|
6
6
|
export class BlockRegistry {
|
|
7
7
|
constructor() {
|
|
8
8
|
_defineProperty(this, "layerBlocks", new Map());
|
|
9
|
+
_defineProperty(this, "changeListeners", new Set());
|
|
9
10
|
_defineProperty(this, "beforeUnloadCount", 0);
|
|
10
11
|
_defineProperty(this, "beforeUnloadHandler", e => {
|
|
11
12
|
e.preventDefault();
|
|
12
|
-
e
|
|
13
|
+
Reflect.set(e, 'returnValue', '');
|
|
13
14
|
});
|
|
14
15
|
}
|
|
15
16
|
add(layer, callback, opts = {}) {
|
|
@@ -31,16 +32,19 @@ export class BlockRegistry {
|
|
|
31
32
|
if (entry.opts.isBeforeUnload) {
|
|
32
33
|
this.incrementBeforeUnload();
|
|
33
34
|
}
|
|
35
|
+
this.notifyChange();
|
|
34
36
|
return () => this.remove(layer.id, entry);
|
|
35
37
|
}
|
|
36
38
|
remove(layerId, entry) {
|
|
37
39
|
const set = this.layerBlocks.get(layerId);
|
|
38
40
|
if (!set) return;
|
|
39
|
-
set.delete(entry);
|
|
41
|
+
const didDelete = set.delete(entry);
|
|
42
|
+
if (!didDelete) return;
|
|
40
43
|
if (set.size === 0) this.layerBlocks.delete(layerId);
|
|
41
44
|
if (entry.opts.isBeforeUnload) {
|
|
42
45
|
this.decrementBeforeUnload();
|
|
43
46
|
}
|
|
47
|
+
this.notifyChange();
|
|
44
48
|
}
|
|
45
49
|
removeAllForLayer(layerId) {
|
|
46
50
|
const set = this.layerBlocks.get(layerId);
|
|
@@ -49,6 +53,13 @@ export class BlockRegistry {
|
|
|
49
53
|
if (entry.opts.isBeforeUnload) this.decrementBeforeUnload();
|
|
50
54
|
}
|
|
51
55
|
this.layerBlocks.delete(layerId);
|
|
56
|
+
this.notifyChange();
|
|
57
|
+
}
|
|
58
|
+
subscribeToChanges(listener) {
|
|
59
|
+
this.changeListeners.add(listener);
|
|
60
|
+
return () => {
|
|
61
|
+
this.changeListeners.delete(listener);
|
|
62
|
+
};
|
|
52
63
|
}
|
|
53
64
|
collectApplicableBlocks(targetLayer) {
|
|
54
65
|
const result = [];
|
|
@@ -61,6 +72,20 @@ export class BlockRegistry {
|
|
|
61
72
|
this.collectGlobalFromActiveDescendants(targetLayer, result);
|
|
62
73
|
return result;
|
|
63
74
|
}
|
|
75
|
+
hasActiveBlocks(rootLayer) {
|
|
76
|
+
return this.collectActiveChainBlocks(rootLayer).length > 0;
|
|
77
|
+
}
|
|
78
|
+
async checkActiveBlocks(rootLayer) {
|
|
79
|
+
const blocks = this.collectActiveChainBlocks(rootLayer);
|
|
80
|
+
if (blocks.length === 0) return true;
|
|
81
|
+
const results = await Promise.all(blocks.map(b => this.runBlock(b)));
|
|
82
|
+
return results.every(Boolean);
|
|
83
|
+
}
|
|
84
|
+
collectActiveChainBlocks(rootLayer) {
|
|
85
|
+
const result = [];
|
|
86
|
+
this.collectFromActiveChain(rootLayer, result);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
64
89
|
collectGlobalFromActiveDescendants(layer, out) {
|
|
65
90
|
const activeChildId = layer.getActiveChildId();
|
|
66
91
|
if (!activeChildId) return;
|
|
@@ -76,6 +101,19 @@ export class BlockRegistry {
|
|
|
76
101
|
}
|
|
77
102
|
this.collectGlobalFromActiveDescendants(child, out);
|
|
78
103
|
}
|
|
104
|
+
collectFromActiveChain(layer, out) {
|
|
105
|
+
const set = this.layerBlocks.get(layer.id);
|
|
106
|
+
if (set) {
|
|
107
|
+
for (const entry of set) {
|
|
108
|
+
out.push(entry);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const activeChildId = layer.getActiveChildId();
|
|
112
|
+
if (!activeChildId) return;
|
|
113
|
+
const child = layer.getChildLayer(activeChildId);
|
|
114
|
+
if (!child) return;
|
|
115
|
+
this.collectFromActiveChain(child, out);
|
|
116
|
+
}
|
|
79
117
|
async checkBlocks(targetLayer) {
|
|
80
118
|
const blocks = this.collectApplicableBlocks(targetLayer);
|
|
81
119
|
if (blocks.length === 0) return true;
|
|
@@ -88,7 +126,7 @@ export class BlockRegistry {
|
|
|
88
126
|
resolve(false);
|
|
89
127
|
}, BLOCK_TIMEOUT_MS))]);
|
|
90
128
|
return result;
|
|
91
|
-
} catch
|
|
129
|
+
} catch {
|
|
92
130
|
return false;
|
|
93
131
|
}
|
|
94
132
|
}
|
|
@@ -105,4 +143,9 @@ export class BlockRegistry {
|
|
|
105
143
|
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
|
|
106
144
|
}
|
|
107
145
|
}
|
|
146
|
+
notifyChange() {
|
|
147
|
+
for (const listener of this.changeListeners) {
|
|
148
|
+
listener();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
108
151
|
}
|
|
@@ -58,7 +58,6 @@ export class NavigationQueue {
|
|
|
58
58
|
return this.processPopstate(op);
|
|
59
59
|
default:
|
|
60
60
|
{
|
|
61
|
-
const _exhaustive = op;
|
|
62
61
|
return {
|
|
63
62
|
isOk: false,
|
|
64
63
|
reason: 'error',
|
|
@@ -294,7 +293,7 @@ export class NavigationQueue {
|
|
|
294
293
|
changedLayerIds.add(this.deps.getRoot().id);
|
|
295
294
|
}
|
|
296
295
|
const target = this.resolveLowestCommonLayer(changedLayerIds);
|
|
297
|
-
if (target) {
|
|
296
|
+
if (target && op.skipBlockCheck !== true) {
|
|
298
297
|
const allowed = await this.deps.checkBlocks(target);
|
|
299
298
|
if (!allowed) {
|
|
300
299
|
await this.deps.silentGo(+1);
|
|
@@ -365,6 +364,7 @@ export class NavigationQueue {
|
|
|
365
364
|
return lca;
|
|
366
365
|
}
|
|
367
366
|
commit(isReplace) {
|
|
367
|
+
var _this$deps$onCommit, _this$deps;
|
|
368
368
|
if (!hasWindowHistory()) return;
|
|
369
369
|
const url = this.deps.projectUrl();
|
|
370
370
|
const state = this.deps.projectState();
|
|
@@ -381,5 +381,6 @@ export class NavigationQueue {
|
|
|
381
381
|
} else {
|
|
382
382
|
window.history.pushState(stateWithMeta, '', url);
|
|
383
383
|
}
|
|
384
|
+
(_this$deps$onCommit = (_this$deps = this.deps).onCommit) === null || _this$deps$onCommit === void 0 || _this$deps$onCommit.call(_this$deps);
|
|
384
385
|
}
|
|
385
386
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
2
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
3
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
4
|
+
import { getDevice, invokeCall } from '../../calls';
|
|
5
|
+
import { AppFlavor } from '../../types/IChaynsReact';
|
|
6
|
+
import { getCurrentIdx } from './navigationIndex';
|
|
7
|
+
import { hasWindowHistory } from './window';
|
|
8
|
+
const DISABLE_SWIPE_BACK_GESTURE_ACTION = 249;
|
|
9
|
+
export class NativeBackHandler {
|
|
10
|
+
constructor(opts) {
|
|
11
|
+
_defineProperty(this, "opts", void 0);
|
|
12
|
+
_defineProperty(this, "isInterceptionEnabled", void 0);
|
|
13
|
+
_defineProperty(this, "bypassNextPopstateBlockCheck", false);
|
|
14
|
+
_defineProperty(this, "sync", () => {
|
|
15
|
+
if (!hasWindowHistory() || !NativeBackHandler.isSupported()) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const next = this.shouldEnableInterception();
|
|
19
|
+
if (this.isInterceptionEnabled === next) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
void invokeCall({
|
|
23
|
+
action: DISABLE_SWIPE_BACK_GESTURE_ACTION,
|
|
24
|
+
value: {
|
|
25
|
+
enabled: next
|
|
26
|
+
}
|
|
27
|
+
}, next ? this.handleNativeBack : undefined);
|
|
28
|
+
this.isInterceptionEnabled = next;
|
|
29
|
+
});
|
|
30
|
+
_defineProperty(this, "handleNativeBack", () => {
|
|
31
|
+
void this.runNativeBack();
|
|
32
|
+
});
|
|
33
|
+
this.opts = opts;
|
|
34
|
+
}
|
|
35
|
+
consumeBypassFlag() {
|
|
36
|
+
if (!this.bypassNextPopstateBlockCheck) return false;
|
|
37
|
+
this.bypassNextPopstateBlockCheck = false;
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
static isSupported() {
|
|
41
|
+
try {
|
|
42
|
+
var _getDevice$app;
|
|
43
|
+
return ((_getDevice$app = getDevice().app) === null || _getDevice$app === void 0 ? void 0 : _getDevice$app.flavor) === AppFlavor.Chayns;
|
|
44
|
+
} catch {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
shouldEnableInterception() {
|
|
49
|
+
return this.opts.blockRegistry.hasActiveBlocks(this.opts.rootLayer) || getCurrentIdx() > 0;
|
|
50
|
+
}
|
|
51
|
+
async runNativeBack() {
|
|
52
|
+
const isAllowed = await this.opts.blockRegistry.checkActiveBlocks(this.opts.rootLayer);
|
|
53
|
+
if (!isAllowed || !hasWindowHistory()) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.bypassNextPopstateBlockCheck = true;
|
|
57
|
+
window.history.back();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasWindowHistory } from './window';
|
|
2
|
+
const CHAYNS_HISTORY_STATE_KEY = '__chaynsHistory';
|
|
2
3
|
let currentIdx = 0;
|
|
3
4
|
let pendingSilentCount = 0;
|
|
4
5
|
let silentResolve = null;
|
|
@@ -8,6 +9,34 @@ export function incrementIdx() {
|
|
|
8
9
|
export function getCurrentIdx() {
|
|
9
10
|
return currentIdx;
|
|
10
11
|
}
|
|
12
|
+
export function setCurrentIdx(idx) {
|
|
13
|
+
if (!Number.isInteger(idx) || idx < 0) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
currentIdx = idx;
|
|
17
|
+
}
|
|
18
|
+
export function extractHistoryIndex(raw) {
|
|
19
|
+
if (!raw || typeof raw !== 'object') {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const chaynsHistory = raw[CHAYNS_HISTORY_STATE_KEY];
|
|
23
|
+
if (!chaynsHistory || typeof chaynsHistory !== 'object') {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
const idx = chaynsHistory.__idx;
|
|
27
|
+
if (typeof idx !== 'number' || !Number.isInteger(idx) || idx < 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return idx;
|
|
31
|
+
}
|
|
32
|
+
export function syncCurrentIdxFromState(raw) {
|
|
33
|
+
const idx = extractHistoryIndex(raw);
|
|
34
|
+
if (idx === null) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
currentIdx = idx;
|
|
38
|
+
return idx;
|
|
39
|
+
}
|
|
11
40
|
export function silentGo(delta) {
|
|
12
41
|
if (!hasWindowHistory()) return Promise.resolve();
|
|
13
42
|
return new Promise(resolve => {
|
|
@@ -4,11 +4,12 @@ import { BlockRegistry } from './BlockRegistry';
|
|
|
4
4
|
import { findChaynsHistoryLayerById } from './layerTree';
|
|
5
5
|
import { projectToUrl, parseFromUrl } from './url';
|
|
6
6
|
import { projectToState, applyStateToTree, diffIncomingState, hasChaynsHistoryState } from './stateProjector';
|
|
7
|
-
import { silentGo, consumeSilent, incrementIdx, getCurrentIdx } from './navigationIndex';
|
|
7
|
+
import { silentGo, consumeSilent, incrementIdx, getCurrentIdx, syncCurrentIdxFromState } from './navigationIndex';
|
|
8
8
|
import { hasWindowHistory } from './window';
|
|
9
9
|
import { shallowEqualArr } from '../equality';
|
|
10
|
-
import { getSite } from
|
|
10
|
+
import { getSite } from '../../calls';
|
|
11
11
|
import { normalizeHistorySegments } from './segments';
|
|
12
|
+
import { NativeBackHandler } from './nativeBackHandling';
|
|
12
13
|
function getInitialPathname(overrideUrl) {
|
|
13
14
|
if (overrideUrl) {
|
|
14
15
|
try {
|
|
@@ -40,21 +41,30 @@ export function resolveSegmentsFrom(overrideUrl, startIndex) {
|
|
|
40
41
|
export function initRootChaynsHistoryLayer(opts = {}) {
|
|
41
42
|
var _opts$segmentCount;
|
|
42
43
|
const blockRegistry = new BlockRegistry();
|
|
43
|
-
let
|
|
44
|
-
let queue;
|
|
44
|
+
let queueRef = null;
|
|
45
45
|
const deps = {
|
|
46
46
|
getRoot: () => rootLayer,
|
|
47
|
-
getQueue: () =>
|
|
47
|
+
getQueue: () => {
|
|
48
|
+
if (!queueRef) {
|
|
49
|
+
throw new Error('[chaynsHistory] NavigationQueue not initialized yet.');
|
|
50
|
+
}
|
|
51
|
+
return queueRef;
|
|
52
|
+
},
|
|
48
53
|
getBlockRegistry: () => blockRegistry
|
|
49
54
|
};
|
|
50
|
-
rootLayer = new ChaynsHistoryLayer({
|
|
55
|
+
const rootLayer = new ChaynsHistoryLayer({
|
|
51
56
|
id: 'root',
|
|
52
57
|
parent: null,
|
|
53
58
|
deps,
|
|
54
59
|
segmentCount: (_opts$segmentCount = opts.segmentCount) !== null && _opts$segmentCount !== void 0 ? _opts$segmentCount : 0,
|
|
55
60
|
segments: opts.segmentCount ? resolveInitialSegments(opts.url, opts.segmentCount) : []
|
|
56
61
|
});
|
|
57
|
-
|
|
62
|
+
const nativeBackHandler = new NativeBackHandler({
|
|
63
|
+
rootLayer,
|
|
64
|
+
blockRegistry
|
|
65
|
+
});
|
|
66
|
+
const syncNativeHandling = nativeBackHandler.sync;
|
|
67
|
+
const queue = new NavigationQueue({
|
|
58
68
|
getRoot: () => rootLayer,
|
|
59
69
|
findLayer: id => findChaynsHistoryLayerById(rootLayer, id),
|
|
60
70
|
checkBlocks: target => blockRegistry.checkBlocks(target),
|
|
@@ -72,6 +82,7 @@ export function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
72
82
|
silentGo: delta => silentGo(delta),
|
|
73
83
|
getCurrentIdx: () => getCurrentIdx(),
|
|
74
84
|
incrementIdx: () => incrementIdx(),
|
|
85
|
+
onCommit: syncNativeHandling,
|
|
75
86
|
applyUrlSegments: () => {
|
|
76
87
|
if (!hasWindowHistory()) return {
|
|
77
88
|
changedLayerIds: new Set()
|
|
@@ -95,7 +106,9 @@ export function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
95
106
|
};
|
|
96
107
|
}
|
|
97
108
|
});
|
|
109
|
+
queueRef = queue;
|
|
98
110
|
const existingState = hasWindowHistory() ? window.history.state : null;
|
|
111
|
+
syncCurrentIdxFromState(existingState);
|
|
99
112
|
if (!hasChaynsHistoryState(existingState)) {
|
|
100
113
|
var _opts$segmentCount2, _opts$url;
|
|
101
114
|
const segmentCount = (_opts$segmentCount2 = opts.segmentCount) !== null && _opts$segmentCount2 !== void 0 ? _opts$segmentCount2 : 0;
|
|
@@ -129,7 +142,7 @@ export function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
129
142
|
};
|
|
130
143
|
delete foreign.__chaynsHistory;
|
|
131
144
|
const initialState = projectToState(rootLayer, foreign);
|
|
132
|
-
const idx =
|
|
145
|
+
const idx = getCurrentIdx();
|
|
133
146
|
window.history.replaceState({
|
|
134
147
|
...initialState,
|
|
135
148
|
__chaynsHistory: {
|
|
@@ -139,17 +152,42 @@ export function initRootChaynsHistoryLayer(opts = {}) {
|
|
|
139
152
|
}, '', window.location.href);
|
|
140
153
|
} else {
|
|
141
154
|
applyStateToTree(rootLayer, existing);
|
|
155
|
+
if (syncCurrentIdxFromState(existing) === null) {
|
|
156
|
+
const foreign = {
|
|
157
|
+
...(existing !== null && existing !== void 0 ? existing : {})
|
|
158
|
+
};
|
|
159
|
+
delete foreign.__chaynsHistory;
|
|
160
|
+
const currentState = projectToState(rootLayer, foreign);
|
|
161
|
+
const idx = getCurrentIdx();
|
|
162
|
+
window.history.replaceState({
|
|
163
|
+
...currentState,
|
|
164
|
+
__chaynsHistory: {
|
|
165
|
+
...currentState.__chaynsHistory,
|
|
166
|
+
__idx: idx
|
|
167
|
+
}
|
|
168
|
+
}, '', window.location.href);
|
|
169
|
+
}
|
|
142
170
|
}
|
|
171
|
+
blockRegistry.subscribeToChanges(syncNativeHandling);
|
|
143
172
|
window.addEventListener('popstate', event => {
|
|
144
|
-
|
|
173
|
+
syncCurrentIdxFromState(event.state);
|
|
174
|
+
if (consumeSilent()) {
|
|
175
|
+
syncNativeHandling();
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
145
178
|
const raw = event.state;
|
|
146
|
-
if (!hasChaynsHistoryState(raw)) {
|
|
179
|
+
if (!hasChaynsHistoryState(raw)) {
|
|
180
|
+
syncNativeHandling();
|
|
181
|
+
} else {
|
|
182
|
+
const skipBlockCheck = nativeBackHandler.consumeBypassFlag();
|
|
147
183
|
void queue.enqueue({
|
|
148
184
|
kind: 'popstate',
|
|
149
|
-
rawState: raw
|
|
150
|
-
|
|
185
|
+
rawState: raw,
|
|
186
|
+
skipBlockCheck
|
|
187
|
+
}).finally(syncNativeHandling);
|
|
151
188
|
}
|
|
152
189
|
});
|
|
190
|
+
syncNativeHandling();
|
|
153
191
|
}
|
|
154
192
|
return {
|
|
155
193
|
rootLayer
|
|
@@ -8,6 +8,7 @@ interface BlockEntry {
|
|
|
8
8
|
export declare class BlockRegistry {
|
|
9
9
|
/** Per-layer block list. */
|
|
10
10
|
private readonly layerBlocks;
|
|
11
|
+
private readonly changeListeners;
|
|
11
12
|
/** Number of blocks with `isBeforeUnload: true`. When > 0, listener is attached. */
|
|
12
13
|
private beforeUnloadCount;
|
|
13
14
|
private readonly beforeUnloadHandler;
|
|
@@ -15,6 +16,7 @@ export declare class BlockRegistry {
|
|
|
15
16
|
remove(layerId: string, entry: BlockEntry): void;
|
|
16
17
|
/** Remove all blocks registered for a layer (called on destroy). */
|
|
17
18
|
removeAllForLayer(layerId: string): void;
|
|
19
|
+
subscribeToChanges(listener: () => void): () => void;
|
|
18
20
|
/**
|
|
19
21
|
* Collects all blocks applicable to a navigation targeting `targetLayer`.
|
|
20
22
|
*
|
|
@@ -24,7 +26,11 @@ export declare class BlockRegistry {
|
|
|
24
26
|
* **active-chain descendants** (not inactive subtrees).
|
|
25
27
|
*/
|
|
26
28
|
collectApplicableBlocks(targetLayer: ChaynsHistoryLayer): BlockEntry[];
|
|
29
|
+
hasActiveBlocks(rootLayer: ChaynsHistoryLayer): boolean;
|
|
30
|
+
checkActiveBlocks(rootLayer: ChaynsHistoryLayer): Promise<boolean>;
|
|
31
|
+
private collectActiveChainBlocks;
|
|
27
32
|
private collectGlobalFromActiveDescendants;
|
|
33
|
+
private collectFromActiveChain;
|
|
28
34
|
/**
|
|
29
35
|
* Runs all applicable block callbacks in parallel.
|
|
30
36
|
* Returns `true` if navigation is allowed (no block), `false` otherwise.
|
|
@@ -34,5 +40,6 @@ export declare class BlockRegistry {
|
|
|
34
40
|
private runBlock;
|
|
35
41
|
private incrementBeforeUnload;
|
|
36
42
|
private decrementBeforeUnload;
|
|
43
|
+
private notifyChange;
|
|
37
44
|
}
|
|
38
45
|
export {};
|
|
@@ -48,6 +48,13 @@ export type NavOp = {
|
|
|
48
48
|
} | {
|
|
49
49
|
kind: 'popstate';
|
|
50
50
|
rawState: unknown;
|
|
51
|
+
/**
|
|
52
|
+
* @internal Skips the block check pipeline for this popstate. Used when
|
|
53
|
+
* the popstate was triggered by us right after a native-back callback
|
|
54
|
+
* that already ran `checkActiveBlocks` — re-running it would prompt the
|
|
55
|
+
* user twice.
|
|
56
|
+
*/
|
|
57
|
+
skipBlockCheck?: boolean;
|
|
51
58
|
};
|
|
52
59
|
export type NavResult = ChaynsHistoryActionResult;
|
|
53
60
|
export interface NavigationQueueDeps {
|
|
@@ -78,6 +85,8 @@ export interface NavigationQueueDeps {
|
|
|
78
85
|
getCurrentIdx: () => number;
|
|
79
86
|
/** Increments the navigation index and returns the new value. */
|
|
80
87
|
incrementIdx: () => number;
|
|
88
|
+
/** Called after a browser history commit finished. */
|
|
89
|
+
onCommit?: () => void;
|
|
81
90
|
/**
|
|
82
91
|
* Re-parse segments from `window.location.pathname` and apply them to each
|
|
83
92
|
* layer in the active chain based on its `segmentCount`. Called after
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ChaynsHistoryLayer } from '../../handler/history/HistoryLayer';
|
|
2
|
+
import type { BlockRegistry } from './BlockRegistry';
|
|
3
|
+
export interface NativeBackHandlerOptions {
|
|
4
|
+
rootLayer: ChaynsHistoryLayer;
|
|
5
|
+
blockRegistry: BlockRegistry;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Coordinates the native swipe-back gesture (Chayns app action 249) with the
|
|
9
|
+
* JS history queue.
|
|
10
|
+
*
|
|
11
|
+
* Why we disable the gesture as soon as the user has navigated inside the app
|
|
12
|
+
* (`getCurrentIdx() > 0`) and not only while a block is registered:
|
|
13
|
+
* The native swipe animation runs independently of our JS handling. Without
|
|
14
|
+
* intercepting it, a swipe-back would (a) play the native pop animation,
|
|
15
|
+
* (b) pop the browser entry, (c) fire popstate, and only THEN would the
|
|
16
|
+
* queue evaluate the block and silently push forward again — producing the
|
|
17
|
+
* "navigate back, then snap forward" jitter described in the bug report.
|
|
18
|
+
* By intercepting from the first own history entry on, every back must come
|
|
19
|
+
* through the registered callback where we can resolve blocks before
|
|
20
|
+
* mutating history.
|
|
21
|
+
*
|
|
22
|
+
* Instances are scoped to a single root layer; module-level state is avoided
|
|
23
|
+
* so re-inits (HMR, tests, multiple roots) cannot desynchronise.
|
|
24
|
+
*/
|
|
25
|
+
export declare class NativeBackHandler {
|
|
26
|
+
private readonly opts;
|
|
27
|
+
private isInterceptionEnabled;
|
|
28
|
+
/**
|
|
29
|
+
* Set to `true` right before `history.back()` is triggered from
|
|
30
|
+
* {@link handleNativeBack}. Consumed by the popstate listener so the
|
|
31
|
+
* queue can skip the duplicate block check (we already ran it).
|
|
32
|
+
*/
|
|
33
|
+
private bypassNextPopstateBlockCheck;
|
|
34
|
+
constructor(opts: NativeBackHandlerOptions);
|
|
35
|
+
/** Re-evaluates the desired native gesture state and pushes it to the app. */
|
|
36
|
+
sync: () => void;
|
|
37
|
+
/**
|
|
38
|
+
* Returns `true` exactly once after a {@link handleNativeBack}-initiated
|
|
39
|
+
* `history.back()`. The popstate listener uses this so the queue can skip
|
|
40
|
+
* the (already performed) block check.
|
|
41
|
+
*/
|
|
42
|
+
consumeBypassFlag(): boolean;
|
|
43
|
+
private static isSupported;
|
|
44
|
+
private shouldEnableInterception;
|
|
45
|
+
private handleNativeBack;
|
|
46
|
+
private runNativeBack;
|
|
47
|
+
}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
export declare function incrementIdx(): number;
|
|
3
3
|
/** Get current index for stamping real entries or direction comparison. */
|
|
4
4
|
export declare function getCurrentIdx(): number;
|
|
5
|
+
export declare function setCurrentIdx(idx: number): void;
|
|
6
|
+
export declare function extractHistoryIndex(raw: unknown): number | null;
|
|
7
|
+
export declare function syncCurrentIdxFromState(raw: unknown): number | null;
|
|
5
8
|
/**
|
|
6
9
|
* Performs a `history.go(delta)` that is silently ignored by our popstate handler.
|
|
7
10
|
* Returns a promise that resolves when the popstate for this go() fires.
|