chayns-api 3.1.0-beta.1 → 3.1.0-beta.2

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.
@@ -4,16 +4,17 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.BlockRegistry = void 0;
7
+ var _calls = require("../../calls");
7
8
  const BLOCK_TIMEOUT_MS = 30000;
8
9
  let _nextId = 1;
9
10
  class BlockRegistry {
10
11
  layerBlocks = new Map();
11
- changeListeners = new Set();
12
12
  beforeUnloadCount = 0;
13
13
  beforeUnloadHandler = e => {
14
14
  e.preventDefault();
15
- Reflect.set(e, 'returnValue', '');
15
+ e.returnValue = '';
16
16
  };
17
+ totalBlockCount = 0;
17
18
  add(layer, callback, opts = {}) {
18
19
  var _opts$scope, _opts$isBeforeUnload;
19
20
  const entry = {
@@ -33,34 +34,27 @@ class BlockRegistry {
33
34
  if (entry.opts.isBeforeUnload) {
34
35
  this.incrementBeforeUnload();
35
36
  }
36
- this.notifyChange();
37
+ this.incrementTotalBlocks();
37
38
  return () => this.remove(layer.id, entry);
38
39
  }
39
40
  remove(layerId, entry) {
40
41
  const set = this.layerBlocks.get(layerId);
41
42
  if (!set) return;
42
- const didDelete = set.delete(entry);
43
- if (!didDelete) return;
43
+ if (!set.delete(entry)) return;
44
44
  if (set.size === 0) this.layerBlocks.delete(layerId);
45
45
  if (entry.opts.isBeforeUnload) {
46
46
  this.decrementBeforeUnload();
47
47
  }
48
- this.notifyChange();
48
+ this.decrementTotalBlocks();
49
49
  }
50
50
  removeAllForLayer(layerId) {
51
51
  const set = this.layerBlocks.get(layerId);
52
52
  if (!set) return;
53
53
  for (const entry of set) {
54
54
  if (entry.opts.isBeforeUnload) this.decrementBeforeUnload();
55
+ this.decrementTotalBlocks();
55
56
  }
56
57
  this.layerBlocks.delete(layerId);
57
- this.notifyChange();
58
- }
59
- subscribeToChanges(listener) {
60
- this.changeListeners.add(listener);
61
- return () => {
62
- this.changeListeners.delete(listener);
63
- };
64
58
  }
65
59
  collectApplicableBlocks(targetLayer) {
66
60
  const result = [];
@@ -73,20 +67,6 @@ class BlockRegistry {
73
67
  this.collectGlobalFromActiveDescendants(targetLayer, result);
74
68
  return result;
75
69
  }
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
- }
90
70
  collectGlobalFromActiveDescendants(layer, out) {
91
71
  const activeChildId = layer.getActiveChildId();
92
72
  if (!activeChildId) return;
@@ -102,19 +82,6 @@ class BlockRegistry {
102
82
  }
103
83
  this.collectGlobalFromActiveDescendants(child, out);
104
84
  }
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
- }
118
85
  async checkBlocks(targetLayer) {
119
86
  const blocks = this.collectApplicableBlocks(targetLayer);
120
87
  if (blocks.length === 0) return true;
@@ -127,7 +94,7 @@ class BlockRegistry {
127
94
  resolve(false);
128
95
  }, BLOCK_TIMEOUT_MS))]);
129
96
  return result;
130
- } catch {
97
+ } catch (err) {
131
98
  return false;
132
99
  }
133
100
  }
@@ -144,10 +111,29 @@ class BlockRegistry {
144
111
  window.removeEventListener('beforeunload', this.beforeUnloadHandler);
145
112
  }
146
113
  }
147
- notifyChange() {
148
- for (const listener of this.changeListeners) {
149
- listener();
114
+ incrementTotalBlocks() {
115
+ this.totalBlockCount++;
116
+ if (this.totalBlockCount > 0) {
117
+ this.setNativeNavigationEnabled(true);
150
118
  }
151
119
  }
120
+ decrementTotalBlocks() {
121
+ if (this.totalBlockCount === 0) return;
122
+ this.totalBlockCount--;
123
+ if (this.totalBlockCount === 0) {
124
+ this.setNativeNavigationEnabled(false);
125
+ }
126
+ }
127
+ setNativeNavigationEnabled(enabled) {
128
+ try {
129
+ void (0, _calls.invokeCall)({
130
+ action: 249,
131
+ value: {
132
+ enabled
133
+ }
134
+ }, this.nativeNavigationCallback);
135
+ } catch {}
136
+ }
137
+ nativeNavigationCallback = () => {};
152
138
  }
153
139
  exports.BlockRegistry = BlockRegistry;
@@ -60,6 +60,7 @@ class NavigationQueue {
60
60
  return this.processPopstate(op);
61
61
  default:
62
62
  {
63
+ const _exhaustive = op;
63
64
  return {
64
65
  isOk: false,
65
66
  reason: 'error',
@@ -295,7 +296,7 @@ class NavigationQueue {
295
296
  changedLayerIds.add(this.deps.getRoot().id);
296
297
  }
297
298
  const target = this.resolveLowestCommonLayer(changedLayerIds);
298
- if (target && op.skipBlockCheck !== true) {
299
+ if (target) {
299
300
  const allowed = await this.deps.checkBlocks(target);
300
301
  if (!allowed) {
301
302
  await this.deps.silentGo(+1);
@@ -366,7 +367,6 @@ class NavigationQueue {
366
367
  return lca;
367
368
  }
368
369
  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,7 +383,6 @@ 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);
387
386
  }
388
387
  }
389
388
  exports.NavigationQueue = NavigationQueue;
@@ -4,14 +4,10 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.consumeSilent = consumeSilent;
7
- exports.extractHistoryIndex = extractHistoryIndex;
8
7
  exports.getCurrentIdx = getCurrentIdx;
9
8
  exports.incrementIdx = incrementIdx;
10
- exports.setCurrentIdx = setCurrentIdx;
11
9
  exports.silentGo = silentGo;
12
- exports.syncCurrentIdxFromState = syncCurrentIdxFromState;
13
10
  var _window = require("./window");
14
- const CHAYNS_HISTORY_STATE_KEY = '__chaynsHistory';
15
11
  let currentIdx = 0;
16
12
  let pendingSilentCount = 0;
17
13
  let silentResolve = null;
@@ -21,34 +17,6 @@ function incrementIdx() {
21
17
  function getCurrentIdx() {
22
18
  return currentIdx;
23
19
  }
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
- }
52
20
  function silentGo(delta) {
53
21
  if (!(0, _window.hasWindowHistory)()) return Promise.resolve();
54
22
  return new Promise(resolve => {
@@ -17,7 +17,6 @@ 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");
21
20
  function getInitialPathname(overrideUrl) {
22
21
  if (overrideUrl) {
23
22
  try {
@@ -49,30 +48,21 @@ function resolveSegmentsFrom(overrideUrl, startIndex) {
49
48
  function initRootChaynsHistoryLayer(opts = {}) {
50
49
  var _opts$segmentCount;
51
50
  const blockRegistry = new _BlockRegistry.BlockRegistry();
52
- let queueRef = null;
51
+ let rootLayer;
52
+ let queue;
53
53
  const deps = {
54
54
  getRoot: () => rootLayer,
55
- getQueue: () => {
56
- if (!queueRef) {
57
- throw new Error('[chaynsHistory] NavigationQueue not initialized yet.');
58
- }
59
- return queueRef;
60
- },
55
+ getQueue: () => queue,
61
56
  getBlockRegistry: () => blockRegistry
62
57
  };
63
- const rootLayer = new _HistoryLayer.ChaynsHistoryLayer({
58
+ rootLayer = new _HistoryLayer.ChaynsHistoryLayer({
64
59
  id: 'root',
65
60
  parent: null,
66
61
  deps,
67
62
  segmentCount: (_opts$segmentCount = opts.segmentCount) !== null && _opts$segmentCount !== void 0 ? _opts$segmentCount : 0,
68
63
  segments: opts.segmentCount ? resolveInitialSegments(opts.url, opts.segmentCount) : []
69
64
  });
70
- const nativeBackHandler = new _nativeBackHandling.NativeBackHandler({
71
- rootLayer,
72
- blockRegistry
73
- });
74
- const syncNativeHandling = nativeBackHandler.sync;
75
- const queue = new _NavigationQueue.NavigationQueue({
65
+ queue = new _NavigationQueue.NavigationQueue({
76
66
  getRoot: () => rootLayer,
77
67
  findLayer: id => (0, _layerTree.findChaynsHistoryLayerById)(rootLayer, id),
78
68
  checkBlocks: target => blockRegistry.checkBlocks(target),
@@ -90,7 +80,6 @@ function initRootChaynsHistoryLayer(opts = {}) {
90
80
  silentGo: delta => (0, _navigationIndex.silentGo)(delta),
91
81
  getCurrentIdx: () => (0, _navigationIndex.getCurrentIdx)(),
92
82
  incrementIdx: () => (0, _navigationIndex.incrementIdx)(),
93
- onCommit: syncNativeHandling,
94
83
  applyUrlSegments: () => {
95
84
  if (!(0, _window.hasWindowHistory)()) return {
96
85
  changedLayerIds: new Set()
@@ -114,9 +103,7 @@ function initRootChaynsHistoryLayer(opts = {}) {
114
103
  };
115
104
  }
116
105
  });
117
- queueRef = queue;
118
106
  const existingState = (0, _window.hasWindowHistory)() ? window.history.state : null;
119
- (0, _navigationIndex.syncCurrentIdxFromState)(existingState);
120
107
  if (!(0, _stateProjector.hasChaynsHistoryState)(existingState)) {
121
108
  var _opts$segmentCount2, _opts$url;
122
109
  const segmentCount = (_opts$segmentCount2 = opts.segmentCount) !== null && _opts$segmentCount2 !== void 0 ? _opts$segmentCount2 : 0;
@@ -150,7 +137,7 @@ function initRootChaynsHistoryLayer(opts = {}) {
150
137
  };
151
138
  delete foreign.__chaynsHistory;
152
139
  const initialState = (0, _stateProjector.projectToState)(rootLayer, foreign);
153
- const idx = (0, _navigationIndex.getCurrentIdx)();
140
+ const idx = (0, _navigationIndex.incrementIdx)();
154
141
  window.history.replaceState({
155
142
  ...initialState,
156
143
  __chaynsHistory: {
@@ -160,42 +147,17 @@ function initRootChaynsHistoryLayer(opts = {}) {
160
147
  }, '', window.location.href);
161
148
  } else {
162
149
  (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
- }
178
150
  }
179
- blockRegistry.subscribeToChanges(syncNativeHandling);
180
151
  window.addEventListener('popstate', event => {
181
- (0, _navigationIndex.syncCurrentIdxFromState)(event.state);
182
- if ((0, _navigationIndex.consumeSilent)()) {
183
- syncNativeHandling();
184
- return;
185
- }
152
+ if ((0, _navigationIndex.consumeSilent)()) return;
186
153
  const raw = event.state;
187
- if (!(0, _stateProjector.hasChaynsHistoryState)(raw)) {
188
- syncNativeHandling();
189
- } else {
190
- const skipBlockCheck = nativeBackHandler.consumeBypassFlag();
154
+ if (!(0, _stateProjector.hasChaynsHistoryState)(raw)) {} else {
191
155
  void queue.enqueue({
192
156
  kind: 'popstate',
193
- rawState: raw,
194
- skipBlockCheck
195
- }).finally(syncNativeHandling);
157
+ rawState: raw
158
+ });
196
159
  }
197
160
  });
198
- syncNativeHandling();
199
161
  }
200
162
  return {
201
163
  rootLayer
@@ -1,17 +1,19 @@
1
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
2
  function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
3
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 { invokeCall } from '../../calls';
4
5
  const BLOCK_TIMEOUT_MS = 30000;
5
6
  let _nextId = 1;
6
7
  export class BlockRegistry {
7
8
  constructor() {
8
9
  _defineProperty(this, "layerBlocks", new Map());
9
- _defineProperty(this, "changeListeners", new Set());
10
10
  _defineProperty(this, "beforeUnloadCount", 0);
11
11
  _defineProperty(this, "beforeUnloadHandler", e => {
12
12
  e.preventDefault();
13
- Reflect.set(e, 'returnValue', '');
13
+ e.returnValue = '';
14
14
  });
15
+ _defineProperty(this, "totalBlockCount", 0);
16
+ _defineProperty(this, "nativeNavigationCallback", () => {});
15
17
  }
16
18
  add(layer, callback, opts = {}) {
17
19
  var _opts$scope, _opts$isBeforeUnload;
@@ -32,34 +34,27 @@ export class BlockRegistry {
32
34
  if (entry.opts.isBeforeUnload) {
33
35
  this.incrementBeforeUnload();
34
36
  }
35
- this.notifyChange();
37
+ this.incrementTotalBlocks();
36
38
  return () => this.remove(layer.id, entry);
37
39
  }
38
40
  remove(layerId, entry) {
39
41
  const set = this.layerBlocks.get(layerId);
40
42
  if (!set) return;
41
- const didDelete = set.delete(entry);
42
- if (!didDelete) return;
43
+ if (!set.delete(entry)) return;
43
44
  if (set.size === 0) this.layerBlocks.delete(layerId);
44
45
  if (entry.opts.isBeforeUnload) {
45
46
  this.decrementBeforeUnload();
46
47
  }
47
- this.notifyChange();
48
+ this.decrementTotalBlocks();
48
49
  }
49
50
  removeAllForLayer(layerId) {
50
51
  const set = this.layerBlocks.get(layerId);
51
52
  if (!set) return;
52
53
  for (const entry of set) {
53
54
  if (entry.opts.isBeforeUnload) this.decrementBeforeUnload();
55
+ this.decrementTotalBlocks();
54
56
  }
55
57
  this.layerBlocks.delete(layerId);
56
- this.notifyChange();
57
- }
58
- subscribeToChanges(listener) {
59
- this.changeListeners.add(listener);
60
- return () => {
61
- this.changeListeners.delete(listener);
62
- };
63
58
  }
64
59
  collectApplicableBlocks(targetLayer) {
65
60
  const result = [];
@@ -72,20 +67,6 @@ export class BlockRegistry {
72
67
  this.collectGlobalFromActiveDescendants(targetLayer, result);
73
68
  return result;
74
69
  }
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
- }
89
70
  collectGlobalFromActiveDescendants(layer, out) {
90
71
  const activeChildId = layer.getActiveChildId();
91
72
  if (!activeChildId) return;
@@ -101,19 +82,6 @@ export class BlockRegistry {
101
82
  }
102
83
  this.collectGlobalFromActiveDescendants(child, out);
103
84
  }
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
- }
117
85
  async checkBlocks(targetLayer) {
118
86
  const blocks = this.collectApplicableBlocks(targetLayer);
119
87
  if (blocks.length === 0) return true;
@@ -126,7 +94,7 @@ export class BlockRegistry {
126
94
  resolve(false);
127
95
  }, BLOCK_TIMEOUT_MS))]);
128
96
  return result;
129
- } catch {
97
+ } catch (err) {
130
98
  return false;
131
99
  }
132
100
  }
@@ -143,9 +111,27 @@ export class BlockRegistry {
143
111
  window.removeEventListener('beforeunload', this.beforeUnloadHandler);
144
112
  }
145
113
  }
146
- notifyChange() {
147
- for (const listener of this.changeListeners) {
148
- listener();
114
+ incrementTotalBlocks() {
115
+ this.totalBlockCount++;
116
+ if (this.totalBlockCount > 0) {
117
+ this.setNativeNavigationEnabled(true);
149
118
  }
150
119
  }
120
+ decrementTotalBlocks() {
121
+ if (this.totalBlockCount === 0) return;
122
+ this.totalBlockCount--;
123
+ if (this.totalBlockCount === 0) {
124
+ this.setNativeNavigationEnabled(false);
125
+ }
126
+ }
127
+ setNativeNavigationEnabled(enabled) {
128
+ try {
129
+ void invokeCall({
130
+ action: 249,
131
+ value: {
132
+ enabled
133
+ }
134
+ }, this.nativeNavigationCallback);
135
+ } catch {}
136
+ }
151
137
  }
@@ -58,6 +58,7 @@ export class NavigationQueue {
58
58
  return this.processPopstate(op);
59
59
  default:
60
60
  {
61
+ const _exhaustive = op;
61
62
  return {
62
63
  isOk: false,
63
64
  reason: 'error',
@@ -293,7 +294,7 @@ export class NavigationQueue {
293
294
  changedLayerIds.add(this.deps.getRoot().id);
294
295
  }
295
296
  const target = this.resolveLowestCommonLayer(changedLayerIds);
296
- if (target && op.skipBlockCheck !== true) {
297
+ if (target) {
297
298
  const allowed = await this.deps.checkBlocks(target);
298
299
  if (!allowed) {
299
300
  await this.deps.silentGo(+1);
@@ -364,7 +365,6 @@ export class NavigationQueue {
364
365
  return lca;
365
366
  }
366
367
  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,6 +381,5 @@ 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);
385
384
  }
386
385
  }
@@ -1,5 +1,4 @@
1
1
  import { hasWindowHistory } from './window';
2
- const CHAYNS_HISTORY_STATE_KEY = '__chaynsHistory';
3
2
  let currentIdx = 0;
4
3
  let pendingSilentCount = 0;
5
4
  let silentResolve = null;
@@ -9,34 +8,6 @@ export function incrementIdx() {
9
8
  export function getCurrentIdx() {
10
9
  return currentIdx;
11
10
  }
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
- }
40
11
  export function silentGo(delta) {
41
12
  if (!hasWindowHistory()) return Promise.resolve();
42
13
  return new Promise(resolve => {
@@ -4,12 +4,11 @@ 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, syncCurrentIdxFromState } from './navigationIndex';
7
+ import { silentGo, consumeSilent, incrementIdx, getCurrentIdx } from './navigationIndex';
8
8
  import { hasWindowHistory } from './window';
9
9
  import { shallowEqualArr } from '../equality';
10
- import { getSite } from '../../calls';
10
+ import { getSite } from "../../calls";
11
11
  import { normalizeHistorySegments } from './segments';
12
- import { NativeBackHandler } from './nativeBackHandling';
13
12
  function getInitialPathname(overrideUrl) {
14
13
  if (overrideUrl) {
15
14
  try {
@@ -41,30 +40,21 @@ export function resolveSegmentsFrom(overrideUrl, startIndex) {
41
40
  export function initRootChaynsHistoryLayer(opts = {}) {
42
41
  var _opts$segmentCount;
43
42
  const blockRegistry = new BlockRegistry();
44
- let queueRef = null;
43
+ let rootLayer;
44
+ let queue;
45
45
  const deps = {
46
46
  getRoot: () => rootLayer,
47
- getQueue: () => {
48
- if (!queueRef) {
49
- throw new Error('[chaynsHistory] NavigationQueue not initialized yet.');
50
- }
51
- return queueRef;
52
- },
47
+ getQueue: () => queue,
53
48
  getBlockRegistry: () => blockRegistry
54
49
  };
55
- const rootLayer = new ChaynsHistoryLayer({
50
+ rootLayer = new ChaynsHistoryLayer({
56
51
  id: 'root',
57
52
  parent: null,
58
53
  deps,
59
54
  segmentCount: (_opts$segmentCount = opts.segmentCount) !== null && _opts$segmentCount !== void 0 ? _opts$segmentCount : 0,
60
55
  segments: opts.segmentCount ? resolveInitialSegments(opts.url, opts.segmentCount) : []
61
56
  });
62
- const nativeBackHandler = new NativeBackHandler({
63
- rootLayer,
64
- blockRegistry
65
- });
66
- const syncNativeHandling = nativeBackHandler.sync;
67
- const queue = new NavigationQueue({
57
+ queue = new NavigationQueue({
68
58
  getRoot: () => rootLayer,
69
59
  findLayer: id => findChaynsHistoryLayerById(rootLayer, id),
70
60
  checkBlocks: target => blockRegistry.checkBlocks(target),
@@ -82,7 +72,6 @@ export function initRootChaynsHistoryLayer(opts = {}) {
82
72
  silentGo: delta => silentGo(delta),
83
73
  getCurrentIdx: () => getCurrentIdx(),
84
74
  incrementIdx: () => incrementIdx(),
85
- onCommit: syncNativeHandling,
86
75
  applyUrlSegments: () => {
87
76
  if (!hasWindowHistory()) return {
88
77
  changedLayerIds: new Set()
@@ -106,9 +95,7 @@ export function initRootChaynsHistoryLayer(opts = {}) {
106
95
  };
107
96
  }
108
97
  });
109
- queueRef = queue;
110
98
  const existingState = hasWindowHistory() ? window.history.state : null;
111
- syncCurrentIdxFromState(existingState);
112
99
  if (!hasChaynsHistoryState(existingState)) {
113
100
  var _opts$segmentCount2, _opts$url;
114
101
  const segmentCount = (_opts$segmentCount2 = opts.segmentCount) !== null && _opts$segmentCount2 !== void 0 ? _opts$segmentCount2 : 0;
@@ -142,7 +129,7 @@ export function initRootChaynsHistoryLayer(opts = {}) {
142
129
  };
143
130
  delete foreign.__chaynsHistory;
144
131
  const initialState = projectToState(rootLayer, foreign);
145
- const idx = getCurrentIdx();
132
+ const idx = incrementIdx();
146
133
  window.history.replaceState({
147
134
  ...initialState,
148
135
  __chaynsHistory: {
@@ -152,42 +139,17 @@ export function initRootChaynsHistoryLayer(opts = {}) {
152
139
  }, '', window.location.href);
153
140
  } else {
154
141
  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
- }
170
142
  }
171
- blockRegistry.subscribeToChanges(syncNativeHandling);
172
143
  window.addEventListener('popstate', event => {
173
- syncCurrentIdxFromState(event.state);
174
- if (consumeSilent()) {
175
- syncNativeHandling();
176
- return;
177
- }
144
+ if (consumeSilent()) return;
178
145
  const raw = event.state;
179
- if (!hasChaynsHistoryState(raw)) {
180
- syncNativeHandling();
181
- } else {
182
- const skipBlockCheck = nativeBackHandler.consumeBypassFlag();
146
+ if (!hasChaynsHistoryState(raw)) {} else {
183
147
  void queue.enqueue({
184
148
  kind: 'popstate',
185
- rawState: raw,
186
- skipBlockCheck
187
- }).finally(syncNativeHandling);
149
+ rawState: raw
150
+ });
188
151
  }
189
152
  });
190
- syncNativeHandling();
191
153
  }
192
154
  return {
193
155
  rootLayer
@@ -8,15 +8,16 @@ interface BlockEntry {
8
8
  export declare class BlockRegistry {
9
9
  /** Per-layer block list. */
10
10
  private readonly layerBlocks;
11
- private readonly changeListeners;
12
11
  /** Number of blocks with `isBeforeUnload: true`. When > 0, listener is attached. */
13
12
  private beforeUnloadCount;
14
13
  private readonly beforeUnloadHandler;
14
+ /** Total number of currently registered blocks across all layers.
15
+ * Used to toggle the native navigation handling (chayns app, action 249). */
16
+ private totalBlockCount;
15
17
  add(layer: ChaynsHistoryLayer, callback: () => Promise<boolean>, opts?: ChaynsHistoryBlockOptions): () => void;
16
18
  remove(layerId: string, entry: BlockEntry): void;
17
19
  /** Remove all blocks registered for a layer (called on destroy). */
18
20
  removeAllForLayer(layerId: string): void;
19
- subscribeToChanges(listener: () => void): () => void;
20
21
  /**
21
22
  * Collects all blocks applicable to a navigation targeting `targetLayer`.
22
23
  *
@@ -26,11 +27,7 @@ export declare class BlockRegistry {
26
27
  * **active-chain descendants** (not inactive subtrees).
27
28
  */
28
29
  collectApplicableBlocks(targetLayer: ChaynsHistoryLayer): BlockEntry[];
29
- hasActiveBlocks(rootLayer: ChaynsHistoryLayer): boolean;
30
- checkActiveBlocks(rootLayer: ChaynsHistoryLayer): Promise<boolean>;
31
- private collectActiveChainBlocks;
32
30
  private collectGlobalFromActiveDescendants;
33
- private collectFromActiveChain;
34
31
  /**
35
32
  * Runs all applicable block callbacks in parallel.
36
33
  * Returns `true` if navigation is allowed (no block), `false` otherwise.
@@ -40,6 +37,29 @@ export declare class BlockRegistry {
40
37
  private runBlock;
41
38
  private incrementBeforeUnload;
42
39
  private decrementBeforeUnload;
43
- private notifyChange;
40
+ private incrementTotalBlocks;
41
+ private decrementTotalBlocks;
42
+ /**
43
+ * Toggles the host app's native navigation handling so that a registered
44
+ * block actually prevents the user from navigating natively (e.g. via the
45
+ * Android back button or the swipe gesture in the chayns app).
46
+ *
47
+ * Maps to chayns invokeCall action `249`:
48
+ * ```
49
+ * chayns.invokeCall({ action: 249, value: { enabled, callback } });
50
+ * ```
51
+ *
52
+ * `enabled: true` here means: "a block is active, take over navigation" —
53
+ * the host disables its own native back handling and instead forwards the
54
+ * event to us via the provided callback, where we can run our block logic.
55
+ */
56
+ private setNativeNavigationEnabled;
57
+ /**
58
+ * Invoked by the host app when the user attempts a native navigation
59
+ * gesture while blocks are active. We run the block callbacks against the
60
+ * current root layer; if any of them denies, navigation stays cancelled.
61
+ * When all blocks allow the navigation we trigger a normal back.
62
+ */
63
+ private readonly nativeNavigationCallback;
44
64
  }
45
65
  export {};
@@ -48,13 +48,6 @@ 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;
58
51
  };
59
52
  export type NavResult = ChaynsHistoryActionResult;
60
53
  export interface NavigationQueueDeps {
@@ -85,8 +78,6 @@ export interface NavigationQueueDeps {
85
78
  getCurrentIdx: () => number;
86
79
  /** Increments the navigation index and returns the new value. */
87
80
  incrementIdx: () => number;
88
- /** Called after a browser history commit finished. */
89
- onCommit?: () => void;
90
81
  /**
91
82
  * Re-parse segments from `window.location.pathname` and apply them to each
92
83
  * layer in the active chain based on its `segmentCount`. Called after
@@ -2,9 +2,6 @@
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;
8
5
  /**
9
6
  * Performs a `history.go(delta)` that is silently ignored by our popstate handler.
10
7
  * Returns a promise that resolves when the popstate for this go() fires.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chayns-api",
3
- "version": "3.1.0-beta.1",
3
+ "version": "3.1.0-beta.2",
4
4
  "description": "new chayns api",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -1,61 +0,0 @@
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;
@@ -1,59 +0,0 @@
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,47 +0,0 @@
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
- }