@swiftbrowser/web 0.10.1 → 0.10.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.
- package/package.json +1 -1
- package/src/browserHistory.ts +35 -7
- package/src/renderer.ts +13 -5
package/package.json
CHANGED
package/src/browserHistory.ts
CHANGED
|
@@ -34,6 +34,8 @@
|
|
|
34
34
|
* the entry and, once its animation ends, hides the live page until a new
|
|
35
35
|
* frame is committed, so a pop that finished animating by then would stay
|
|
36
36
|
* under the snapshot for Safari's 3 s watchdog, taking the touches unseen.
|
|
37
|
+
* A push is made at the renderer's `sb:precommit`, before the pushed screen
|
|
38
|
+
* is laid out (see `sync`), and this module never forces a layout.
|
|
37
39
|
*/
|
|
38
40
|
import type { Renderer } from './renderer';
|
|
39
41
|
|
|
@@ -50,10 +52,18 @@ interface HistoryState {
|
|
|
50
52
|
const stateOf = (value: unknown): HistoryState | null => (typeof value === 'object' && value !== null && (value as HistoryState).sbHistory === true ? (value as HistoryState) : null);
|
|
51
53
|
|
|
52
54
|
function shown(win: Window, el: HTMLElement): boolean {
|
|
53
|
-
if (el.
|
|
55
|
+
if (!el.isConnected) return false;
|
|
56
|
+
// Style only, never layout: a layout forced here before a push would hand
|
|
57
|
+
// Safari the pushed screen's size for the entry it covers (see `sync`).
|
|
58
|
+
for (let node: HTMLElement | null = el; node; node = node.parentElement) {
|
|
59
|
+
if (node.hidden || win.getComputedStyle(node).display === 'none') return false;
|
|
60
|
+
}
|
|
54
61
|
return win.getComputedStyle(el).visibility !== 'hidden';
|
|
55
62
|
}
|
|
56
63
|
|
|
64
|
+
/** When `sync` runs: the renderer's `sb:precommit` (the pass's ops applied, not yet laid out), its `sb:commit`, or `enable`. */
|
|
65
|
+
export type SyncStage = 'precommit' | 'commit' | 'enabling';
|
|
66
|
+
|
|
57
67
|
/** How long a back the browser made waits for the app to pop a level before what it shows is recorded as it is. */
|
|
58
68
|
const DISMISS_WAIT_MS = 1000;
|
|
59
69
|
|
|
@@ -103,13 +113,14 @@ export class BrowserHistory {
|
|
|
103
113
|
if (!current) this.win.history.replaceState({ ...(this.win.history.state ?? {}), ...this.entry(0, this.selection()) }, '');
|
|
104
114
|
this.watchReplaceState();
|
|
105
115
|
this.renderer.interaction.nativeBackGesture = true;
|
|
116
|
+
this.root.addEventListener('sb:precommit', this.onPreCommit);
|
|
106
117
|
this.root.addEventListener('sb:commit', this.onCommit);
|
|
107
118
|
this.win.addEventListener('popstate', this.onPopState);
|
|
108
119
|
// Enabled before the first commit (a mount, or a remount such as React's
|
|
109
120
|
// StrictMode makes in development) the screen is not drawn yet: fewer
|
|
110
121
|
// levels than the entry records is not the app popping, so only the
|
|
111
122
|
// levels already shown are pushed here and the first commit settles the rest.
|
|
112
|
-
this.sync(
|
|
123
|
+
this.sync('enabling');
|
|
113
124
|
}
|
|
114
125
|
|
|
115
126
|
dispose(): void {
|
|
@@ -121,6 +132,7 @@ export class BrowserHistory {
|
|
|
121
132
|
this.landingUrl = null;
|
|
122
133
|
this.syncOnArrival = false;
|
|
123
134
|
this.unwatchReplaceState();
|
|
135
|
+
this.root.removeEventListener('sb:precommit', this.onPreCommit);
|
|
124
136
|
this.root.removeEventListener('sb:commit', this.onCommit);
|
|
125
137
|
this.win.removeEventListener('popstate', this.onPopState);
|
|
126
138
|
}
|
|
@@ -292,15 +304,31 @@ export class BrowserHistory {
|
|
|
292
304
|
}
|
|
293
305
|
}
|
|
294
306
|
|
|
295
|
-
private readonly
|
|
307
|
+
private readonly onPreCommit = (): void => this.sync('precommit');
|
|
308
|
+
private readonly onCommit = (): void => this.sync('commit');
|
|
296
309
|
|
|
297
|
-
/**
|
|
298
|
-
|
|
310
|
+
/**
|
|
311
|
+
* New levels get entries; levels the app dismissed itself take the history
|
|
312
|
+
* back with them; a moved sidebar selection is an entry too.
|
|
313
|
+
*
|
|
314
|
+
* The entries are pushed at the `precommit`, before the renderer lays the
|
|
315
|
+
* new screen out, and nothing else happens then: Safari sizes the entry
|
|
316
|
+
* being left from the page's last layout at the push (its snapshot's
|
|
317
|
+
* render tree size, more than half of which the page must have again
|
|
318
|
+
* before the snapshot comes down after a swipe back to it), so a push
|
|
319
|
+
* after that layout counted the pushed screen into the entry it covers,
|
|
320
|
+
* and a root lighter than the screen over it stayed under the snapshot
|
|
321
|
+
* until Safari's 3 s watchdog after every swipe back. The rest waits for
|
|
322
|
+
* the `commit`, when the pass is drawn. `enabling`: nothing is drawn yet,
|
|
323
|
+
* so no level is taken as dismissed.
|
|
324
|
+
*/
|
|
325
|
+
sync(stage: SyncStage = 'commit'): void {
|
|
299
326
|
if (!this.enabled) return;
|
|
327
|
+
const dismissing = this.dismissing;
|
|
328
|
+
if (stage === 'precommit' && dismissing) return;
|
|
300
329
|
const depth = this.depth();
|
|
301
330
|
const recorded = this.recorded();
|
|
302
331
|
const selection = this.selection();
|
|
303
|
-
const dismissing = this.dismissing;
|
|
304
332
|
if (dismissing) {
|
|
305
333
|
// Dismissing for a `popstate`: the entry already names the target depth.
|
|
306
334
|
if (depth > dismissing.target && performance.now() < dismissing.until) {
|
|
@@ -320,7 +348,7 @@ export class BrowserHistory {
|
|
|
320
348
|
}
|
|
321
349
|
for (let level = recorded + 1; level <= depth; level++) this.win.history.pushState(this.entry(level, selection), '');
|
|
322
350
|
} else if (depth < recorded) {
|
|
323
|
-
if (
|
|
351
|
+
if (stage !== 'commit') return;
|
|
324
352
|
// The app popped or dismissed on its own (back button, swipe, code): leave those entries.
|
|
325
353
|
this.go(depth - recorded, depth);
|
|
326
354
|
} else if (this.movingTo === null && selection !== this.recordedSelection()) {
|
package/src/renderer.ts
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* element tree as `LayoutNode`s; id 0 is the root container supplied by the
|
|
6
6
|
* host page. Every op is applied to the DOM immediately; at `commit` the
|
|
7
7
|
* layout engine (src/layout) computes a frame for every element and
|
|
8
|
-
* `layoutDom.ts` positions the elements absolutely from it (a `sb:
|
|
9
|
-
* CustomEvent is dispatched on the root
|
|
10
|
-
*
|
|
8
|
+
* `layoutDom.ts` positions the elements absolutely from it (a `sb:precommit`
|
|
9
|
+
* CustomEvent is dispatched on the root once the pass's ops are applied and
|
|
10
|
+
* before that layout, and a `sb:commit` after it, so the browser history,
|
|
11
|
+
* tests and tooling can observe pass boundaries). Animated passes are handed
|
|
12
|
+
* to the `Animator`.
|
|
11
13
|
*
|
|
12
14
|
* Most kinds are a single DOM element whose children are the element's
|
|
13
15
|
* children. Compound kinds (`navstack`, `navscreen`, `section`, `toggle`,
|
|
@@ -1062,6 +1064,10 @@ export class Renderer {
|
|
|
1062
1064
|
this.passRootRect = null;
|
|
1063
1065
|
this.preSnapshots.clear();
|
|
1064
1066
|
this.preRects.clear();
|
|
1067
|
+
// Before the layout that first holds this pass's new screens: a
|
|
1068
|
+
// history entry pushed here (browserHistory.ts) is sized by Safari
|
|
1069
|
+
// from the page's last layout, without them.
|
|
1070
|
+
this.root.dispatchEvent(new CustomEvent('sb:precommit', { detail: { commits: this.commits + 1 } }));
|
|
1065
1071
|
const changes = this.layout();
|
|
1066
1072
|
this.animator.commitNextFrame(pass, op.animation, changes);
|
|
1067
1073
|
this.commits += 1;
|
|
@@ -3024,8 +3030,10 @@ function afterAnimation(target: HTMLElement, expectedMs: number, done: () => voi
|
|
|
3024
3030
|
|
|
3025
3031
|
function animatePush(screen: HTMLElement): void {
|
|
3026
3032
|
screen.classList.remove('sb-navscreen--push');
|
|
3027
|
-
//
|
|
3028
|
-
|
|
3033
|
+
// Flush the style (not the layout: a layout forced here, with the screen
|
|
3034
|
+
// just inserted, would size the history entry it covers by it, see
|
|
3035
|
+
// browserHistory.ts) so re-adding the class restarts the animation.
|
|
3036
|
+
void getComputedStyle(screen).animationName;
|
|
3029
3037
|
screen.classList.add('sb-navscreen--push');
|
|
3030
3038
|
afterAnimation(screen, NAV_TRANSITION_MS, () => screen.classList.remove('sb-navscreen--push'));
|
|
3031
3039
|
}
|