@stacknav/core 0.3.1 → 0.4.0
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/README.md +111 -59
- package/dist/animate.d.ts +18 -0
- package/dist/animate.d.ts.map +1 -1
- package/dist/animate.js +44 -2
- package/dist/animate.js.map +1 -1
- package/dist/css-vars.d.ts +3 -3
- package/dist/css-vars.d.ts.map +1 -1
- package/dist/css-vars.js +47 -4
- package/dist/css-vars.js.map +1 -1
- package/dist/history-adapter.d.ts +0 -1
- package/dist/history-adapter.d.ts.map +1 -1
- package/dist/history-adapter.js +1 -5
- package/dist/history-adapter.js.map +1 -1
- package/dist/index.d.ts +27 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -15
- package/dist/index.js.map +1 -1
- package/dist/native-transition.d.ts +93 -0
- package/dist/native-transition.d.ts.map +1 -0
- package/dist/{ios-transition.js → native-transition.js} +42 -25
- package/dist/native-transition.js.map +1 -0
- package/dist/navigation-stack.d.ts +3 -0
- package/dist/navigation-stack.d.ts.map +1 -1
- package/dist/navigation-stack.js +42 -3
- package/dist/navigation-stack.js.map +1 -1
- package/dist/platform.d.ts +6 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +22 -0
- package/dist/platform.js.map +1 -0
- package/dist/stacknav.css +4 -13
- package/dist/styles.d.ts +5 -9
- package/dist/styles.d.ts.map +1 -1
- package/dist/styles.js +6 -12
- package/dist/styles.js.map +1 -1
- package/dist/swipe-back.d.ts +4 -0
- package/dist/swipe-back.d.ts.map +1 -0
- package/dist/swipe-back.js +31 -0
- package/dist/swipe-back.js.map +1 -0
- package/package.json +3 -2
- package/src/animate.ts +47 -4
- package/src/css-vars.ts +43 -4
- package/src/history-adapter.ts +1 -5
- package/src/index.ts +49 -24
- package/src/{ios-transition.ts → native-transition.ts} +67 -35
- package/src/navigation-stack.ts +30 -3
- package/src/platform.ts +23 -0
- package/src/styles.ts +6 -12
- package/src/swipe-back.ts +35 -0
- package/dist/edge-pan-gesture.d.ts +0 -36
- package/dist/edge-pan-gesture.d.ts.map +0 -1
- package/dist/edge-pan-gesture.js +0 -155
- package/dist/edge-pan-gesture.js.map +0 -1
- package/dist/ios-transition.d.ts +0 -61
- package/dist/ios-transition.d.ts.map +0 -1
- package/dist/ios-transition.js.map +0 -1
- package/src/edge-pan-gesture.ts +0 -190
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { easings, prefersReducedMotion, type Easing } from './animate.ts';
|
|
2
2
|
import { cssVars, parseEasing, parseNumber, parseRatio, parseTime } from './css-vars.ts';
|
|
3
3
|
import type { SettleInput, StackEntry, Transition } from './navigation-stack.ts';
|
|
4
|
+
import { detectPlatform, type Platform } from './platform.ts';
|
|
4
5
|
|
|
5
|
-
export interface
|
|
6
|
+
export interface NativeTransitionOptions {
|
|
7
|
+
/**
|
|
8
|
+
* Whose push/pop to imitate. `auto` (the default) asks the browser and
|
|
9
|
+
* falls back to `ios`. It picks the defaults for everything below; an
|
|
10
|
+
* option given explicitly wins over the platform's, as a CSS variable wins
|
|
11
|
+
* over both.
|
|
12
|
+
*/
|
|
13
|
+
platform: Platform | 'auto';
|
|
6
14
|
/** ms, programmatic push/pop */
|
|
7
15
|
duration: number;
|
|
8
16
|
/**
|
|
9
17
|
* the curve a programmatic push/pop runs on. CSS runs it, so it has to be
|
|
10
|
-
* one CSS can spell: everything `cubicBezier()
|
|
11
|
-
* carries a `css` property. A bare `(t) => number` of
|
|
12
|
-
* the pages would run `linear` while `progress`
|
|
13
|
-
* a `css` property, or write the whole
|
|
18
|
+
* one CSS can spell: everything `cubicBezier()`, `bezierPath()` and
|
|
19
|
+
* `parseEasing()` build carries a `css` property. A bare `(t) => number` of
|
|
20
|
+
* your own has none, so the pages would run `linear` while `progress`
|
|
21
|
+
* reported your curve; give it a `css` property, or write the whole
|
|
22
|
+
* transition yourself.
|
|
14
23
|
*/
|
|
15
24
|
ease: Easing;
|
|
25
|
+
/** fraction of the width the upper page travels (1 = from off-screen; Android slides a short way and fades) */
|
|
26
|
+
travel: number;
|
|
16
27
|
/** fraction of the width the lower page travels */
|
|
17
28
|
parallax: number;
|
|
29
|
+
/** opacity of the upper page when fully closed (1 = no fade) */
|
|
30
|
+
fade: number;
|
|
18
31
|
dimColor: string;
|
|
19
32
|
/** lower-page overlay opacity at p = 1 (≈0.35 suits dark UIs) */
|
|
20
33
|
dimMax: number;
|
|
@@ -31,11 +44,33 @@ export interface IOSTransitionOptions {
|
|
|
31
44
|
timeScale: number;
|
|
32
45
|
}
|
|
33
46
|
|
|
47
|
+
/** Every option except the platform, which is decided once and has no CSS variable. */
|
|
48
|
+
export type NativeTransitionPreset = Readonly<Omit<NativeTransitionOptions, 'platform'>>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Each platform's own push/pop, as its system animates it.
|
|
52
|
+
*
|
|
53
|
+
* - `ios`: UIKit's navigation push. The upper page slides the full width with
|
|
54
|
+
* a shadow on its leading edge, the lower page parallaxes 30% and dims.
|
|
55
|
+
* - `android`: the framework's activity open/close since Android 13
|
|
56
|
+
* (`activity_open_enter.xml` and friends): both pages slide 96 dp, about a
|
|
57
|
+
* quarter of a phone, over 450 ms on `fast_out_extra_slow_in`, and the
|
|
58
|
+
* upper page fades through the first part of it. No shadow, no dim.
|
|
59
|
+
*/
|
|
60
|
+
export function nativeTransitionPreset(platform: Platform): NativeTransitionPreset {
|
|
61
|
+
const shared = { settleMin: 120, settleMax: 400, settleVelocityFloor: 900, timeScale: 1, dimColor: '#000' };
|
|
62
|
+
return platform === 'android'
|
|
63
|
+
? { ...shared, duration: 450, ease: easings.android, travel: 0.25, parallax: 0.25, fade: 0, dimMax: 0, shadow: 'none', settleEase: easings.androidSettle }
|
|
64
|
+
: { ...shared, duration: 500, ease: easings.ios, travel: 1, parallax: 0.3, fade: 1, dimMax: 0.1, shadow: '-3px 0 14px rgba(0,0,0,0.16)', settleEase: easings.easeOut };
|
|
65
|
+
}
|
|
66
|
+
|
|
34
67
|
/** The CSS custom property behind each option. */
|
|
35
|
-
export const
|
|
68
|
+
export const NATIVE_TRANSITION_CSS_VARS: Readonly<Record<keyof NativeTransitionPreset, string>> = /*#__PURE__*/ Object.freeze({
|
|
36
69
|
duration: '--sn-duration',
|
|
37
70
|
ease: '--sn-easing',
|
|
71
|
+
travel: '--sn-travel',
|
|
38
72
|
parallax: '--sn-parallax',
|
|
73
|
+
fade: '--sn-fade',
|
|
39
74
|
dimColor: '--sn-dim-color',
|
|
40
75
|
dimMax: '--sn-dim-max',
|
|
41
76
|
shadow: '--sn-shadow',
|
|
@@ -46,11 +81,11 @@ export const IOS_TRANSITION_CSS_VARS: Readonly<Record<keyof IOSTransitionOptions
|
|
|
46
81
|
timeScale: '--sn-time-scale',
|
|
47
82
|
});
|
|
48
83
|
|
|
49
|
-
export interface
|
|
50
|
-
/** The JS options: the
|
|
51
|
-
readonly options:
|
|
84
|
+
export interface NativeTransition extends Transition {
|
|
85
|
+
/** The JS options: the platform's preset with the caller's merged in. `platform` is the one chosen. Mutable at runtime. */
|
|
86
|
+
readonly options: NativeTransitionOptions & { platform: Platform };
|
|
52
87
|
/** The values currently in force: `options` with the CSS variables applied over them. */
|
|
53
|
-
readonly resolved: Readonly<
|
|
88
|
+
readonly resolved: Readonly<NativeTransitionOptions & { platform: Platform }>;
|
|
54
89
|
/**
|
|
55
90
|
* Re-reads the CSS variables, from `el` or from the container of the last
|
|
56
91
|
* transition. Called at the start of every transition. Call it directly
|
|
@@ -60,47 +95,40 @@ export interface IOSTransition extends Transition {
|
|
|
60
95
|
}
|
|
61
96
|
|
|
62
97
|
/**
|
|
63
|
-
* The
|
|
64
|
-
* edge
|
|
65
|
-
*
|
|
98
|
+
* The platform's navigation transition: the upper page slides in from the
|
|
99
|
+
* trailing edge while the lower page parallaxes toward the leading edge. On
|
|
100
|
+
* iOS the upper page travels the full width under a shadow and the lower page
|
|
101
|
+
* dims; on Android both travel a short way and the upper page fades. Every
|
|
102
|
+
* value is a function of one number, p.
|
|
66
103
|
*
|
|
67
104
|
* Every option is also a CSS custom property on the container (see
|
|
68
|
-
* `
|
|
69
|
-
* is set wins over the JS option, so a stylesheet can slow the animation
|
|
70
|
-
* or restyle it per theme without the app rebuilding the transition.
|
|
105
|
+
* `NATIVE_TRANSITION_CSS_VARS`), read when a transition starts. A variable
|
|
106
|
+
* that is set wins over the JS option, so a stylesheet can slow the animation
|
|
107
|
+
* down or restyle it per theme without the app rebuilding the transition.
|
|
71
108
|
*
|
|
72
109
|
* p is only ever written at the ends of a phase. The stack puts that phase's
|
|
73
110
|
* duration and curve in `--sn-t` / `--sn-e` and CSS interpolates between the
|
|
74
111
|
* two writes, so the animation costs a handful of style writes rather than one
|
|
75
112
|
* per page per frame, and runs on the compositor rather than the main thread.
|
|
76
113
|
*/
|
|
77
|
-
export function
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
ease: easings.ios,
|
|
81
|
-
parallax: 0.3,
|
|
82
|
-
dimColor: '#000',
|
|
83
|
-
dimMax: 0.1,
|
|
84
|
-
shadow: '-3px 0 14px rgba(0,0,0,0.16)',
|
|
85
|
-
settleMin: 120,
|
|
86
|
-
settleMax: 400,
|
|
87
|
-
settleEase: easings.easeOut,
|
|
88
|
-
settleVelocityFloor: 900,
|
|
89
|
-
timeScale: 1,
|
|
90
|
-
...options,
|
|
91
|
-
};
|
|
114
|
+
export function createNativeTransition(options: Partial<NativeTransitionOptions> = {}): NativeTransition {
|
|
115
|
+
const platform = !options.platform || options.platform === 'auto' ? detectPlatform() : options.platform;
|
|
116
|
+
const o: NativeTransitionOptions & { platform: Platform } = { ...nativeTransitionPreset(platform), ...options, platform };
|
|
92
117
|
|
|
93
118
|
let root: Element | null = null;
|
|
94
|
-
let r:
|
|
119
|
+
let r: NativeTransitionOptions & { platform: Platform } = { ...o };
|
|
95
120
|
|
|
96
121
|
const refresh = (el?: Element | null): void => {
|
|
97
122
|
if (el !== undefined) root = el;
|
|
98
123
|
const read = cssVars(root);
|
|
99
|
-
const v =
|
|
124
|
+
const v = NATIVE_TRANSITION_CSS_VARS;
|
|
100
125
|
r = {
|
|
126
|
+
platform: o.platform,
|
|
101
127
|
duration: parseTime(read(v.duration)) ?? o.duration,
|
|
102
128
|
ease: parseEasing(read(v.ease)) ?? o.ease,
|
|
129
|
+
travel: parseRatio(read(v.travel)) ?? o.travel,
|
|
103
130
|
parallax: parseRatio(read(v.parallax)) ?? o.parallax,
|
|
131
|
+
fade: parseRatio(read(v.fade)) ?? o.fade,
|
|
104
132
|
dimColor: read(v.dimColor) ?? o.dimColor,
|
|
105
133
|
dimMax: parseRatio(read(v.dimMax)) ?? o.dimMax,
|
|
106
134
|
shadow: read(v.shadow) ?? o.shadow,
|
|
@@ -134,7 +162,7 @@ export function createIOSTransition(options: Partial<IOSTransitionOptions> = {})
|
|
|
134
162
|
|
|
135
163
|
return {
|
|
136
164
|
options: o,
|
|
137
|
-
get resolved(): Readonly<
|
|
165
|
+
get resolved(): Readonly<NativeTransitionOptions & { platform: Platform }> {
|
|
138
166
|
return r;
|
|
139
167
|
},
|
|
140
168
|
refresh,
|
|
@@ -164,7 +192,10 @@ export function createIOSTransition(options: Partial<IOSTransitionOptions> = {})
|
|
|
164
192
|
* page, so a resize mid-transition stays honest.
|
|
165
193
|
*/
|
|
166
194
|
apply(lower, upper, p) {
|
|
167
|
-
upper.el.style.transform = shift(1 - p);
|
|
195
|
+
upper.el.style.transform = shift((1 - p) * r.travel);
|
|
196
|
+
// Only a look that fades writes opacity: a property that is not written
|
|
197
|
+
// starts no transition, and a page's own opacity is left alone.
|
|
198
|
+
if (r.fade < 1) upper.el.style.opacity = String(round(r.fade + (1 - r.fade) * p));
|
|
168
199
|
if (lower) {
|
|
169
200
|
lower.el.style.transform = shift(-p * r.parallax);
|
|
170
201
|
dim!.style.opacity = String(p * r.dimMax);
|
|
@@ -173,6 +204,7 @@ export function createIOSTransition(options: Partial<IOSTransitionOptions> = {})
|
|
|
173
204
|
end(lower, upper) {
|
|
174
205
|
upper.el.style.boxShadow = '';
|
|
175
206
|
upper.el.style.transform = '';
|
|
207
|
+
upper.el.style.opacity = '';
|
|
176
208
|
if (lower) lower.el.style.transform = '';
|
|
177
209
|
dim?.remove();
|
|
178
210
|
},
|
package/src/navigation-stack.ts
CHANGED
|
@@ -92,7 +92,9 @@ export class NavigationStack {
|
|
|
92
92
|
readonly pageClass: string;
|
|
93
93
|
entries: StackEntry[] = [];
|
|
94
94
|
busy = false;
|
|
95
|
-
private
|
|
95
|
+
private _destroyed = false;
|
|
96
|
+
private _activeTransition: { lower: StackEntry | null; upper: StackEntry } | null = null;
|
|
97
|
+
private _queue: Array<{ run: () => void; cancel: () => void }> = [];
|
|
96
98
|
private _listeners = new Map<string, Set<Listener<unknown>>>();
|
|
97
99
|
|
|
98
100
|
constructor({ container, transition, pageClass = 'sn-page' }: NavigationStackOptions) {
|
|
@@ -130,6 +132,7 @@ export class NavigationStack {
|
|
|
130
132
|
};
|
|
131
133
|
}
|
|
132
134
|
private _emit<K extends keyof StackEvents>(event: K, detail: StackEvents[K]): void {
|
|
135
|
+
if (this._destroyed) return;
|
|
133
136
|
const set = this._listeners.get(event);
|
|
134
137
|
if (set) set.forEach((fn) => fn(detail));
|
|
135
138
|
}
|
|
@@ -251,13 +254,16 @@ export class NavigationStack {
|
|
|
251
254
|
this._begin(lower, upper, 'interactive');
|
|
252
255
|
return {
|
|
253
256
|
update: (v) => {
|
|
257
|
+
if (this._destroyed) return;
|
|
254
258
|
p = Math.min(1, Math.max(0, v));
|
|
255
259
|
this._apply(lower, upper, p);
|
|
256
260
|
},
|
|
257
261
|
finish: async ({ complete, velocity = 0 }) => {
|
|
262
|
+
if (this._destroyed) return;
|
|
258
263
|
const remainingPx = (complete ? p : 1 - p) * this.width();
|
|
259
264
|
const { duration, ease } = this.transition.settle({ remainingPx, velocity });
|
|
260
265
|
await this._animate(lower, upper, p, complete ? 0 : 1, duration, ease);
|
|
266
|
+
if (this._destroyed) return;
|
|
261
267
|
this._end(lower, upper, 'interactive');
|
|
262
268
|
if (complete) {
|
|
263
269
|
this.entries.pop();
|
|
@@ -271,7 +277,20 @@ export class NavigationStack {
|
|
|
271
277
|
};
|
|
272
278
|
}
|
|
273
279
|
|
|
280
|
+
/** Terminal: queued and subsequent navigation reject with AbortError. */
|
|
274
281
|
destroy(): void {
|
|
282
|
+
if (this._destroyed) return;
|
|
283
|
+
this._destroyed = true;
|
|
284
|
+
this._listeners.clear();
|
|
285
|
+
for (const task of this._queue.splice(0)) task.cancel();
|
|
286
|
+
this.busy = false;
|
|
287
|
+
const active = this._activeTransition;
|
|
288
|
+
this._activeTransition = null;
|
|
289
|
+
if (active) {
|
|
290
|
+
this.transition.end?.(active.lower, active.upper);
|
|
291
|
+
// During a pop the outgoing page has already left entries.
|
|
292
|
+
if (!this.entries.includes(active.upper)) this._unmount(active.upper);
|
|
293
|
+
}
|
|
275
294
|
while (this.entries.length) this._unmount(this.entries.pop()!);
|
|
276
295
|
this.container.classList.remove('sn-container', 'sn-busy');
|
|
277
296
|
this.container.style.removeProperty('--sn-t');
|
|
@@ -280,6 +299,7 @@ export class NavigationStack {
|
|
|
280
299
|
|
|
281
300
|
// -------------------------------------------------------------- internals
|
|
282
301
|
private _setBusy(v: boolean): void {
|
|
302
|
+
if (this._destroyed) return;
|
|
283
303
|
this.busy = v;
|
|
284
304
|
this.container.classList.toggle('sn-busy', v);
|
|
285
305
|
}
|
|
@@ -287,6 +307,8 @@ export class NavigationStack {
|
|
|
287
307
|
/** Serializes operations: while a transition runs, later calls wait their turn. */
|
|
288
308
|
private _run<R>(fn: () => Promise<R>): Promise<R> {
|
|
289
309
|
return new Promise<R>((resolve, reject) => {
|
|
310
|
+
const cancel = () => reject(new DOMException('NavigationStack has been destroyed', 'AbortError'));
|
|
311
|
+
if (this._destroyed) return cancel();
|
|
290
312
|
const task = async () => {
|
|
291
313
|
this._setBusy(true);
|
|
292
314
|
try {
|
|
@@ -298,12 +320,12 @@ export class NavigationStack {
|
|
|
298
320
|
this._drain();
|
|
299
321
|
}
|
|
300
322
|
};
|
|
301
|
-
if (this.busy) this._queue.push(task);
|
|
323
|
+
if (this.busy) this._queue.push({ run: task, cancel });
|
|
302
324
|
else task();
|
|
303
325
|
});
|
|
304
326
|
}
|
|
305
327
|
private _drain(): void {
|
|
306
|
-
if (!this.busy && this._queue.length) this._queue.shift()
|
|
328
|
+
if (!this._destroyed && !this.busy && this._queue.length) this._queue.shift()!.run();
|
|
307
329
|
}
|
|
308
330
|
|
|
309
331
|
private async _popRevealing(depth: number, animated: boolean, source: NavigationSource): Promise<StackEntry> {
|
|
@@ -312,6 +334,7 @@ export class NavigationStack {
|
|
|
312
334
|
while (this.entries.length > depth) removed.push(this._unmount(this.entries.pop()!)); // intermediate pages: removed without animation
|
|
313
335
|
const lower = this.top;
|
|
314
336
|
await this._transition(lower, upper, 1, 0, animated, 'pop');
|
|
337
|
+
if (this._destroyed) return upper;
|
|
315
338
|
removed.push(this._unmount(upper));
|
|
316
339
|
this._settle();
|
|
317
340
|
this._emit('pop', { entry: upper, removed, entries: this.entries.slice(), source });
|
|
@@ -344,11 +367,13 @@ export class NavigationStack {
|
|
|
344
367
|
* `prefers-reduced-motion` reduces every phase to.
|
|
345
368
|
*/
|
|
346
369
|
private _timing(duration: number, ease?: Easing): void {
|
|
370
|
+
if (this._destroyed) return;
|
|
347
371
|
this.container.style.setProperty('--sn-t', cssDuration(duration));
|
|
348
372
|
this.container.style.setProperty('--sn-e', cssEasing(ease));
|
|
349
373
|
}
|
|
350
374
|
|
|
351
375
|
private _begin(lower: StackEntry | null, upper: StackEntry, kind: TransitionKind): void {
|
|
376
|
+
this._activeTransition = { lower, upper };
|
|
352
377
|
this._timing(0);
|
|
353
378
|
lower?.el.classList.add('sn-page-visible', 'sn-page-lower');
|
|
354
379
|
upper.el.classList.add('sn-page-visible', 'sn-page-upper');
|
|
@@ -360,6 +385,8 @@ export class NavigationStack {
|
|
|
360
385
|
this._emit('progress', { lower, upper, p });
|
|
361
386
|
}
|
|
362
387
|
private _end(lower: StackEntry | null, upper: StackEntry, kind: TransitionKind): void {
|
|
388
|
+
if (this._destroyed) return;
|
|
389
|
+
this._activeTransition = null;
|
|
363
390
|
this._timing(0);
|
|
364
391
|
upper.el.classList.remove('sn-page-upper');
|
|
365
392
|
lower?.el.classList.remove('sn-page-lower');
|
package/src/platform.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Which native platform the page is running on, so the transition can follow
|
|
2
|
+
// that platform's own push/pop. Nothing here is certain: user agents lie, and
|
|
3
|
+
// desktop browsers are neither. iOS is the default when nothing is recognized,
|
|
4
|
+
// because that is the look most web apps expect from a stack transition.
|
|
5
|
+
|
|
6
|
+
export type Platform = 'ios' | 'android';
|
|
7
|
+
|
|
8
|
+
export function isIOSBrowser(): boolean {
|
|
9
|
+
if (typeof navigator === 'undefined') return false;
|
|
10
|
+
return /iP(hone|ad|od)/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isAndroidBrowser(): boolean {
|
|
14
|
+
if (typeof navigator === 'undefined') return false;
|
|
15
|
+
const hints = (navigator as { userAgentData?: { platform?: string } }).userAgentData;
|
|
16
|
+
if (hints?.platform) return hints.platform === 'Android';
|
|
17
|
+
return /\bAndroid\b/.test(navigator.userAgent ?? '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** `android` on an Android browser, otherwise `ios`. */
|
|
21
|
+
export function detectPlatform(): Platform {
|
|
22
|
+
return !isIOSBrowser() && isAndroidBrowser() ? 'android' : 'ios';
|
|
23
|
+
}
|
package/src/styles.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The only styles the engine needs. These cover layout and the motion itself,
|
|
3
3
|
* not appearance: the look lives in the transition and is tuned through the
|
|
4
|
-
* `--sn-*` custom properties (see `
|
|
4
|
+
* `--sn-*` custom properties (see `NATIVE_TRANSITION_CSS_VARS` and the README).
|
|
5
5
|
* Those properties are documented but deliberately not declared, because
|
|
6
6
|
* leaving one unset is what makes its JS default apply.
|
|
7
7
|
*
|
|
@@ -20,8 +20,7 @@
|
|
|
20
20
|
* - `.sn-container:dir(rtl)`: reading direction is a CSS question, so the
|
|
21
21
|
* transform the engine writes is signed by `--sn-dir` rather than by JS.
|
|
22
22
|
* - `.sn-page`: absolutely fills the container and is its own scroll container.
|
|
23
|
-
* `
|
|
24
|
-
* drags reach the gesture; `visibility: hidden` keeps pages beneath the top
|
|
23
|
+
* `visibility: hidden` keeps pages beneath the top
|
|
25
24
|
* mounted (scroll position, form state) but out of sight and out of the
|
|
26
25
|
* accessibility tree. The identity transform is the resting state, and the
|
|
27
26
|
* containing block the dim overlay is positioned against.
|
|
@@ -30,14 +29,11 @@
|
|
|
30
29
|
* transition in flight. Only these transition, and only these are promoted,
|
|
31
30
|
* so a deep stack costs nothing at rest. Both `transform` and `opacity` are
|
|
32
31
|
* listed, so a transition of your own can fade a page as well as move it and
|
|
33
|
-
* still be run by the browser; the iOS look only ever changes `transform
|
|
34
|
-
* and a property that does not change
|
|
32
|
+
* still be run by the browser; the iOS look only ever changes `transform`
|
|
33
|
+
* (the Android look fades as well), and a property that does not change
|
|
34
|
+
* starts no transition.
|
|
35
35
|
* - `.sn-dim`: the overlay the lower page dims behind. CSS resolves its colour;
|
|
36
36
|
* the transition supplies a fallback colour and writes its opacity.
|
|
37
|
-
* - `.sn-edge`: the strip the swipe-back gesture listens on. `inset-inline-start`
|
|
38
|
-
* puts it on the leading edge in either reading direction; its width is the
|
|
39
|
-
* gesture's `edgeWidth` option, and whether it is shown at all follows from
|
|
40
|
-
* the container's classes.
|
|
41
37
|
* - `.sn-busy`: no clicks land on a page that is mid-transition, and a drag
|
|
42
38
|
* does not select the text under it.
|
|
43
39
|
* - `prefers-reduced-motion`: forces the phase duration to zero in CSS. The
|
|
@@ -47,12 +43,10 @@
|
|
|
47
43
|
export const STACKNAV_CSS =
|
|
48
44
|
'.sn-container{position:relative;overflow:hidden}' +
|
|
49
45
|
'.sn-container:dir(rtl){--sn-dir:-1}' +
|
|
50
|
-
'.sn-page{position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;overscroll-behavior-y:contain;
|
|
46
|
+
'.sn-page{position:absolute;inset:0;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;overscroll-behavior-y:contain;visibility:hidden;transform:translate3d(0,0,0)}' +
|
|
51
47
|
'.sn-page-visible{visibility:visible}' +
|
|
52
48
|
'.sn-page-upper,.sn-page-lower{will-change:transform;transition-property:transform,opacity;transition-duration:var(--sn-t,0s);transition-timing-function:var(--sn-e,linear)}' +
|
|
53
49
|
'.sn-dim{position:fixed;inset:0;z-index:2147483647;pointer-events:none;background:var(--sn-dim-color,var(--sn-dim-fallback,#000));opacity:0;transition:opacity var(--sn-t,0s) var(--sn-e,linear)}' +
|
|
54
|
-
'.sn-edge{position:absolute;inset-block:0;inset-inline-start:0;z-index:10;touch-action:none}' +
|
|
55
|
-
'.sn-container:not(.sn-can-pop) .sn-edge,.sn-container.sn-anywhere .sn-edge{display:none}' +
|
|
56
50
|
'.sn-busy{user-select:none;-webkit-user-select:none}' +
|
|
57
51
|
'.sn-busy .sn-page{pointer-events:none}' +
|
|
58
52
|
'@media(prefers-reduced-motion:reduce){.sn-container{--sn-t:0s!important}}';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Browser suppression is best effort; browser buttons and OS gestures remain available. */
|
|
2
|
+
export type SwipeBackMode = 'browser' | 'disabled';
|
|
3
|
+
|
|
4
|
+
interface Suppression {
|
|
5
|
+
count: number;
|
|
6
|
+
value: string;
|
|
7
|
+
priority: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Several outlets can share one viewport. Restore it only after the last
|
|
11
|
+
// suppression request is released, including when stacks are destroyed.
|
|
12
|
+
const suppressions = /*#__PURE__*/ new WeakMap<HTMLElement, Suppression>();
|
|
13
|
+
|
|
14
|
+
export function suppressBrowserSwipe(container: HTMLElement): () => void {
|
|
15
|
+
const root = container.ownerDocument.documentElement;
|
|
16
|
+
const property = 'overscroll-behavior-x';
|
|
17
|
+
let state = suppressions.get(root);
|
|
18
|
+
if (!state) {
|
|
19
|
+
state = { count: 0, value: root.style.getPropertyValue(property), priority: root.style.getPropertyPriority(property) };
|
|
20
|
+
suppressions.set(root, state);
|
|
21
|
+
root.style.setProperty(property, 'contain', 'important');
|
|
22
|
+
}
|
|
23
|
+
state.count++;
|
|
24
|
+
let released = false;
|
|
25
|
+
return () => {
|
|
26
|
+
if (released) return;
|
|
27
|
+
released = true;
|
|
28
|
+
if (--state.count) return;
|
|
29
|
+
suppressions.delete(root);
|
|
30
|
+
// Do not overwrite a newer declaration installed by the application.
|
|
31
|
+
if (root.style.getPropertyValue(property) !== 'contain' || root.style.getPropertyPriority(property) !== 'important') return;
|
|
32
|
+
if (state.value) root.style.setProperty(property, state.value, state.priority);
|
|
33
|
+
else root.style.removeProperty(property);
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import type { NavigationStack } from './navigation-stack.ts';
|
|
2
|
-
export interface EdgePanGestureOptions {
|
|
3
|
-
/** px strip on the leading edge that starts the gesture */
|
|
4
|
-
edgeWidth: number;
|
|
5
|
-
/** recognize the drag from anywhere on the page */
|
|
6
|
-
anywhere: boolean;
|
|
7
|
-
/** px of horizontal movement before the drag begins */
|
|
8
|
-
startSlop: number;
|
|
9
|
-
/** px of vertical movement that hands the touch back to scrolling */
|
|
10
|
-
verticalCancelSlop: number;
|
|
11
|
-
/** fraction of the width dragged that completes without velocity */
|
|
12
|
-
completeThreshold: number;
|
|
13
|
-
/** px/s toward the trailing edge that completes the pop regardless of distance */
|
|
14
|
-
completeVelocity: number;
|
|
15
|
-
/** px/s back toward the leading edge that cancels the pop regardless of distance */
|
|
16
|
-
cancelVelocity: number;
|
|
17
|
-
velocitySamples: number;
|
|
18
|
-
}
|
|
19
|
-
export interface EdgePanGesture {
|
|
20
|
-
readonly options: EdgePanGestureOptions;
|
|
21
|
-
/** Re-reads options changed at runtime, such as `edgeWidth` and `anywhere`. */
|
|
22
|
-
refresh(): void;
|
|
23
|
-
attach(stack: NavigationStack): EdgePanGesture;
|
|
24
|
-
detach(): void;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Recognizes a horizontal drag from the leading edge and drives the stack's
|
|
28
|
-
* interactive pop from it. Built on pointer events, so it handles both mouse
|
|
29
|
-
* and touch. Vertical movement early in the gesture hands the touch back to
|
|
30
|
-
* native scrolling.
|
|
31
|
-
*
|
|
32
|
-
* "Leading" is whichever edge the container reads from, so in a right-to-left
|
|
33
|
-
* container the strip sits on the right and back is a drag to the left.
|
|
34
|
-
*/
|
|
35
|
-
export declare function createEdgePanGesture(options?: Partial<EdgePanGestureOptions>): EdgePanGesture;
|
|
36
|
-
//# sourceMappingURL=edge-pan-gesture.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"edge-pan-gesture.d.ts","sourceRoot":"","sources":["../src/edge-pan-gesture.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAwB,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAEnF,MAAM,WAAW,qBAAqB;IACpC,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,mDAAmD;IACnD,QAAQ,EAAE,OAAO,CAAC;IAClB,uDAAuD;IACvD,SAAS,EAAE,MAAM,CAAC;IAClB,qEAAqE;IACrE,kBAAkB,EAAE,MAAM,CAAC;IAC3B,oEAAoE;IACpE,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kFAAkF;IAClF,gBAAgB,EAAE,MAAM,CAAC;IACzB,oFAAoF;IACpF,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,qBAAqB,CAAC;IACxC,+EAA+E;IAC/E,OAAO,IAAI,IAAI,CAAC;IAChB,MAAM,CAAC,KAAK,EAAE,eAAe,GAAG,cAAc,CAAC;IAC/C,MAAM,IAAI,IAAI,CAAC;CAChB;AAcD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,OAAO,CAAC,qBAAqB,CAAM,GAAG,cAAc,CA4IjG"}
|
package/dist/edge-pan-gesture.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Recognizes a horizontal drag from the leading edge and drives the stack's
|
|
3
|
-
* interactive pop from it. Built on pointer events, so it handles both mouse
|
|
4
|
-
* and touch. Vertical movement early in the gesture hands the touch back to
|
|
5
|
-
* native scrolling.
|
|
6
|
-
*
|
|
7
|
-
* "Leading" is whichever edge the container reads from, so in a right-to-left
|
|
8
|
-
* container the strip sits on the right and back is a drag to the left.
|
|
9
|
-
*/
|
|
10
|
-
export function createEdgePanGesture(options = {}) {
|
|
11
|
-
const o = {
|
|
12
|
-
edgeWidth: 28,
|
|
13
|
-
anywhere: false,
|
|
14
|
-
startSlop: 6,
|
|
15
|
-
verticalCancelSlop: 10,
|
|
16
|
-
completeThreshold: 0.5,
|
|
17
|
-
completeVelocity: 500,
|
|
18
|
-
cancelVelocity: -500,
|
|
19
|
-
velocitySamples: 6,
|
|
20
|
-
...options,
|
|
21
|
-
};
|
|
22
|
-
let stack;
|
|
23
|
-
let strip = null;
|
|
24
|
-
/**
|
|
25
|
-
* Which way forward is, read the same way the pages read it: `--sn-dir` when
|
|
26
|
-
* the stylesheet sets it, the container's reading direction otherwise. Taking
|
|
27
|
-
* it from one place is what keeps the drag and the transition from disagreeing
|
|
28
|
-
* about which edge is the back edge.
|
|
29
|
-
*/
|
|
30
|
-
const direction = () => {
|
|
31
|
-
const style = typeof getComputedStyle === 'function' ? getComputedStyle(stack.container) : null;
|
|
32
|
-
const declared = Number(style?.getPropertyValue('--sn-dir'));
|
|
33
|
-
if (declared < 0)
|
|
34
|
-
return -1;
|
|
35
|
-
if (declared > 0)
|
|
36
|
-
return 1;
|
|
37
|
-
return style?.direction === 'rtl' ? -1 : 1;
|
|
38
|
-
};
|
|
39
|
-
let drag = null;
|
|
40
|
-
let suppressClick = false;
|
|
41
|
-
let offs = [];
|
|
42
|
-
const onDown = (ev) => {
|
|
43
|
-
if (drag || !stack.canPop())
|
|
44
|
-
return;
|
|
45
|
-
if (ev.pointerType === 'mouse' && ev.button !== 0)
|
|
46
|
-
return;
|
|
47
|
-
if (!o.anywhere && ev.target !== strip)
|
|
48
|
-
return;
|
|
49
|
-
drag = { id: ev.pointerId, target: ev.currentTarget, x0: ev.clientX, y0: ev.clientY, dir: direction(), handle: null, p: 1, samples: [[ev.clientX, performance.now()]] };
|
|
50
|
-
};
|
|
51
|
-
const onMove = (ev) => {
|
|
52
|
-
if (!drag || ev.pointerId !== drag.id)
|
|
53
|
-
return;
|
|
54
|
-
// Signed so that "forward along the drag" is always positive, whichever
|
|
55
|
-
// edge the container calls leading.
|
|
56
|
-
const dx = (ev.clientX - drag.x0) * drag.dir;
|
|
57
|
-
const dy = ev.clientY - drag.y0;
|
|
58
|
-
if (!drag.handle) {
|
|
59
|
-
if (Math.abs(dy) > o.verticalCancelSlop && Math.abs(dy) > Math.abs(dx)) {
|
|
60
|
-
drag = null;
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (dx < o.startSlop)
|
|
64
|
-
return;
|
|
65
|
-
const handle = stack.beginInteractivePop();
|
|
66
|
-
if (!handle) {
|
|
67
|
-
drag = null;
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
drag.handle = handle;
|
|
71
|
-
try {
|
|
72
|
-
drag.target.setPointerCapture?.(ev.pointerId);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
/* capture is best-effort */
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
drag.samples.push([ev.clientX, performance.now()]);
|
|
79
|
-
if (drag.samples.length > o.velocitySamples)
|
|
80
|
-
drag.samples.shift();
|
|
81
|
-
drag.p = 1 - Math.min(1, Math.max(0, (dx - o.startSlop) / stack.width()));
|
|
82
|
-
drag.handle.update(drag.p);
|
|
83
|
-
};
|
|
84
|
-
const onUp = (ev) => {
|
|
85
|
-
if (!drag || ev.pointerId !== drag.id)
|
|
86
|
-
return;
|
|
87
|
-
const d = drag;
|
|
88
|
-
drag = null;
|
|
89
|
-
if (!d.handle)
|
|
90
|
-
return;
|
|
91
|
-
const s = d.samples;
|
|
92
|
-
const [x1, t1] = s[0];
|
|
93
|
-
const [x2, t2] = s[s.length - 1];
|
|
94
|
-
const velocity = (t2 > t1 ? ((x2 - x1) / (t2 - t1)) * 1000 : 0) * d.dir;
|
|
95
|
-
const cancelled = ev.type === 'pointercancel';
|
|
96
|
-
const complete = !cancelled && (velocity > o.completeVelocity || (d.p < 1 - o.completeThreshold && velocity > o.cancelVelocity));
|
|
97
|
-
// The click that follows a drag release must not activate whatever is under the pointer.
|
|
98
|
-
suppressClick = true;
|
|
99
|
-
setTimeout(() => {
|
|
100
|
-
suppressClick = false;
|
|
101
|
-
}, 0);
|
|
102
|
-
void d.handle.finish({ complete, velocity });
|
|
103
|
-
};
|
|
104
|
-
const onClick = (ev) => {
|
|
105
|
-
if (suppressClick) {
|
|
106
|
-
ev.stopPropagation();
|
|
107
|
-
ev.preventDefault();
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
const EVENTS = { pointerdown: onDown, pointermove: onMove, pointerup: onUp, pointercancel: onUp };
|
|
111
|
-
const listen = (el) => Object.entries(EVENTS).forEach(([k, f]) => el.addEventListener(k, f));
|
|
112
|
-
const unlisten = (el) => Object.entries(EVENTS).forEach(([k, f]) => el.removeEventListener(k, f));
|
|
113
|
-
/**
|
|
114
|
-
* Whether the strip is shown at all is a CSS question — the stylesheet hides
|
|
115
|
-
* it when there is nothing to go back to, or when the whole page is the
|
|
116
|
-
* target — so this only has to say what is true.
|
|
117
|
-
*/
|
|
118
|
-
const refresh = () => {
|
|
119
|
-
if (!strip)
|
|
120
|
-
return;
|
|
121
|
-
strip.style.width = o.edgeWidth + 'px';
|
|
122
|
-
stack.container.classList.toggle('sn-anywhere', o.anywhere);
|
|
123
|
-
stack.container.classList.toggle('sn-can-pop', stack.entries.length > 1);
|
|
124
|
-
};
|
|
125
|
-
const gesture = {
|
|
126
|
-
options: o,
|
|
127
|
-
refresh,
|
|
128
|
-
attach(s) {
|
|
129
|
-
stack = s;
|
|
130
|
-
strip = document.createElement('div');
|
|
131
|
-
strip.className = 'sn-edge';
|
|
132
|
-
strip.setAttribute('aria-hidden', 'true');
|
|
133
|
-
stack.container.append(strip);
|
|
134
|
-
// Pointer events from the strip bubble here; one listener per event
|
|
135
|
-
// avoids applying each move and recording its velocity sample twice.
|
|
136
|
-
listen(stack.container);
|
|
137
|
-
stack.container.addEventListener('click', onClick, true);
|
|
138
|
-
offs = ['push', 'pop', 'replace', 'reset'].map((e) => stack.on(e, refresh));
|
|
139
|
-
refresh();
|
|
140
|
-
return gesture;
|
|
141
|
-
},
|
|
142
|
-
detach() {
|
|
143
|
-
if (!strip)
|
|
144
|
-
return;
|
|
145
|
-
offs.forEach((f) => f());
|
|
146
|
-
unlisten(stack.container);
|
|
147
|
-
stack.container.removeEventListener('click', onClick, true);
|
|
148
|
-
stack.container.classList.remove('sn-anywhere', 'sn-can-pop');
|
|
149
|
-
strip.remove();
|
|
150
|
-
strip = null;
|
|
151
|
-
},
|
|
152
|
-
};
|
|
153
|
-
return gesture;
|
|
154
|
-
}
|
|
155
|
-
//# sourceMappingURL=edge-pan-gesture.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"edge-pan-gesture.js","sourceRoot":"","sources":["../src/edge-pan-gesture.ts"],"names":[],"mappings":"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"}
|