ember-nav-stack 6.1.1 → 7.0.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.
Files changed (74) hide show
  1. package/README.md +165 -21
  2. package/addon-main.cjs +4 -0
  3. package/dist/_app_/components/nav-stack.js +1 -0
  4. package/dist/_app_/components/to-nav-stack.js +1 -0
  5. package/dist/_app_/helpers/nav-layer-indices.js +1 -0
  6. package/dist/_app_/modifiers/back-swipe.js +1 -0
  7. package/dist/_app_/services/gesture.js +1 -0
  8. package/dist/_app_/services/nav-stacks.js +1 -0
  9. package/dist/_app_/templates/stackable.js +1 -0
  10. package/dist/back-swipe-gesture.js +261 -0
  11. package/dist/back-swipe-gesture.js.map +1 -0
  12. package/dist/components/nav-stack.js +627 -0
  13. package/dist/components/nav-stack.js.map +1 -0
  14. package/dist/components/to-nav-stack.js +22 -0
  15. package/dist/components/to-nav-stack.js.map +1 -0
  16. package/dist/helpers/nav-layer-indices.js +21 -0
  17. package/dist/helpers/nav-layer-indices.js.map +1 -0
  18. package/dist/index.js +7 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/modifiers/back-swipe.js +40 -0
  21. package/dist/modifiers/back-swipe.js.map +1 -0
  22. package/dist/routes/stackable-route.js +99 -0
  23. package/dist/routes/stackable-route.js.map +1 -0
  24. package/{addon → dist}/services/gesture.js +7 -9
  25. package/dist/services/gesture.js.map +1 -0
  26. package/dist/services/nav-stacks.js +137 -0
  27. package/dist/services/nav-stacks.js.map +1 -0
  28. package/dist/styles/nav-stack.css +399 -0
  29. package/dist/templates/stackable.js +8 -0
  30. package/dist/templates/stackable.js.map +1 -0
  31. package/{addon-test-support → dist/test-support}/in-viewport.js +7 -10
  32. package/dist/test-support/in-viewport.js.map +1 -0
  33. package/dist/test-support/index.js +2 -0
  34. package/dist/test-support/index.js.map +1 -0
  35. package/{addon → dist}/utils/animation.js +17 -40
  36. package/dist/utils/animation.js.map +1 -0
  37. package/{addon → dist}/utils/back-swipe-recognizer.js +29 -49
  38. package/dist/utils/back-swipe-recognizer.js.map +1 -0
  39. package/dist/utils/clone-store.js +88 -0
  40. package/dist/utils/clone-store.js.map +1 -0
  41. package/dist/utils/component.js +107 -0
  42. package/dist/utils/component.js.map +1 -0
  43. package/dist/utils/header-style.js +46 -0
  44. package/dist/utils/header-style.js.map +1 -0
  45. package/dist/utils/transition-decision.js +71 -0
  46. package/dist/utils/transition-decision.js.map +1 -0
  47. package/dist/utils/waiter-state.js +130 -0
  48. package/dist/utils/waiter-state.js.map +1 -0
  49. package/package.json +78 -91
  50. package/CHANGELOG.md +0 -200
  51. package/MODULE_REPORT.md +0 -27
  52. package/RELEASE.md +0 -54
  53. package/addon/components/nav-stack/component.js +0 -683
  54. package/addon/components/nav-stack/template.hbs +0 -37
  55. package/addon/components/to-nav-stack.js +0 -32
  56. package/addon/helpers/nav-layer-indices.js +0 -29
  57. package/addon/routes/stackable-route.js +0 -61
  58. package/addon/services/nav-stacks.js +0 -157
  59. package/addon/utils/component.js +0 -40
  60. package/app/components/nav-stack/component.js +0 -1
  61. package/app/components/nav-stack/template.js +0 -1
  62. package/app/components/to-nav-stack.js +0 -1
  63. package/app/helpers/nav-layer-indices.js +0 -1
  64. package/app/services/gesture.js +0 -1
  65. package/app/services/nav-stacks.js +0 -1
  66. package/app/styles/nav-stack.scss +0 -117
  67. package/app/templates/stackable.hbs +0 -8
  68. package/app/utils/animation.js +0 -1
  69. package/config/deploy.js +0 -29
  70. package/config/environment.js +0 -5
  71. package/config/release.js +0 -21
  72. package/index.js +0 -15
  73. package/tsconfig.json +0 -6
  74. package/vendor/wobble-shim.js +0 -3
package/README.md CHANGED
@@ -1,43 +1,187 @@
1
1
  # ember-nav-stack
2
2
 
3
- This addon is used by Yapp to blend mobile-style stack navigation with Ember routing. It draws heavy inspiration from ember-elsewhere.
3
+ iOS-style stack navigation for Ember. Routes push and pop horizontally across one or more visual layers, with spring-driven animations and an interactive back-swipe gesture. Inspired by [ember-elsewhere](https://github.com/ef4/ember-elsewhere).
4
4
 
5
- This addon's current status is "opensource, but unpolished". It lacks tests, docs, and other niceties of a well-maintained Ember addon.
5
+ ## Compatibility
6
6
 
7
- Compatibility
8
- ------------------------------------------------------------------------------
7
+ - **ember-source**: `>= 4.12.0` (declared as a peer dependency)
8
+ - **Node**: `>= 20.19`
9
+ - **ember-cli**: any version compatible with the host app
9
10
 
10
- * Ember.js v3.24 or above
11
- * Ember CLI v3.24 or above
12
- * Node.js v12 or above
11
+ ## Installation
13
12
 
13
+ ```
14
+ ember install ember-nav-stack
15
+ ```
14
16
 
15
- Installation
16
- ------------------------------------------------------------------------------
17
+ Then import the addon's stylesheet from your app's entry point:
17
18
 
19
+ ```js
20
+ // app/app.js
21
+ import 'ember-nav-stack/styles/nav-stack.css';
18
22
  ```
19
- ember install ember-nav-stack
23
+
24
+ (Or `@import 'ember-nav-stack/styles/nav-stack.css';` from your top-level CSS.) Unlike the pre-v6 versions of this addon, v6+ ships as a [v2 addon](https://github.com/embroider-build/embroider/blob/main/docs/v2-addon-format.md) and does not auto-merge styles into your build.
25
+
26
+ ## Concepts
27
+
28
+ The addon revolves around three pieces:
29
+
30
+ - **`NavStacksService`** (`nav-stacks`) — a shared store of stack contents keyed by layer index. Items are pushed and popped via `<ToNavStack>` (most consumers never call the service directly).
31
+ - **`<NavStack>`** — renders one visual stack: the current item, the previous-item header, the spring animations between stack states, and the back-swipe gesture.
32
+ - **`<ToNavStack>`** — declarative push. Mounting one adds an item to the stack at the given layer; unmounting removes it.
33
+
34
+ A typical app renders one `<NavStack>` per layer index (often just one for the base layer, with modal/overlay layers as needed) and uses `<ToNavStack>` from inside each route's template to declare the route's content as a stack item.
35
+
36
+ ## Two patterns: ad-hoc components vs. routed pages
37
+
38
+ ### 1. Ad-hoc `<NavStack>` + `<ToNavStack>` directly
39
+
40
+ Use this when the stack content isn't tied to routes — e.g. a modal flow inside a single page, a multi-step form.
41
+
42
+ ```hbs
43
+ {{!-- app/templates/application.hbs --}}
44
+ <div style="width:320px;height:480px;position:relative">
45
+ <NavStack @layer={{0}} @back={{this.handleBack}} />
46
+ </div>
47
+
48
+ <ToNavStack
49
+ @layer={{0}}
50
+ @item={{component "my-first-step" model=this.model}}
51
+ @header={{component "my-first-step/header" model=this.model}}
52
+ />
53
+
54
+ {{#if this.isAtSecondStep}}
55
+ <ToNavStack
56
+ @layer={{0}}
57
+ @item={{component "my-second-step" model=this.model}}
58
+ @header={{component "my-second-step/header" model=this.model}}
59
+ />
60
+ {{/if}}
61
+ ```
62
+
63
+ Each `<ToNavStack>` describes one stack item — its body (`@item`) and its header (`@header`) as curried components. Mounting more `<ToNavStack>` instances pushes them onto the stack in mount order; unmounting them pops.
64
+
65
+ ### 2. `StackableRoute` for route-driven stacks
66
+
67
+ When your stack maps to routes (one stack item per route in a parent → child → grandchild URL), extend `StackableRoute` and rely on the convention:
68
+
69
+ ```js
70
+ // app/routes/page.js
71
+ import StackableRoute from 'ember-nav-stack/routes/stackable-route';
72
+
73
+ export default class PageRoute extends StackableRoute {
74
+ // Optionally:
75
+ // templateName — the route's template name; defaults to 'stackable'
76
+ // which auto-renders <ToNavStack> for this route
77
+ // newLayer = true — this route is on a new visual layer (e.g. a modal
78
+ // stack above the base navigation)
79
+ // routableTemplateName — override the routing-component name (see below)
80
+ }
20
81
  ```
21
82
 
83
+ `StackableRoute` does three things for you:
84
+
85
+ 1. **`templateName = 'stackable'`** by default. The shipped `stackable` template renders `<ToNavStack @layer={{this.layerIndex}} @item={{component this.routeComponent ...}} @header={{component this.headerComponent ...}} />` plus `{{outlet}}`, so you don't write that boilerplate per route.
86
+ 2. **Computes `layerIndex`** by walking up the route tree (via the public `RouterService.currentRoute` / `activeTransition.to` chain). Routes that opt into a new layer with `newLayer = true` get parent's `layerIndex + 1`.
87
+ 3. **Provides a `back` action** that transitions to the parent route. Wire it to your back button via `{{on "click" (route-action "back")}}` (or your preferred action-delivery mechanism).
88
+
89
+ ### The `routable-components/...` convention
90
+
91
+ `StackableRoute` derives the component names for `@item` and `@header` from the route's name:
22
92
 
23
- Usage
24
- ------------------------------------------------------------------------------
93
+ | Route name | `routeComponent` (item) | `headerComponent` |
94
+ |---|---|---|
95
+ | `page` | `routable-components/page` | `routable-components/page/header` |
96
+ | `yapp.page.schedule-item` | `routable-components/yapp/page/schedule-item` | `routable-components/yapp/page/schedule-item/header` |
25
97
 
26
- [Longer description of how to use the addon in apps.]
98
+ So a route called `page` resolves to component `routable-components/page` (i.e. `app/components/routable-components/page.{js,hbs}` in classic layout, or whatever your resolver does for that name). Headers live at `<route-component-name>/header`.
27
99
 
28
- Testing
29
- ------------------------------------------------------------------------------
100
+ Override per-route via `routableTemplateName = 'something-else'` if a single component should back multiple routes.
30
101
 
31
- `ember-nav-stack` registers waiters for stack recomputes and transition animations. Ember's built-in test helpers such as `visit` and `click` wait for these operations automatically, so downstream apps should not need custom "nav stack idle" helpers or manual `waitFor` calls.
102
+ ## `<NavStack>` arguments
32
103
 
104
+ | Arg | Required | Description |
105
+ |---|---|---|
106
+ | `@layer` | yes | Integer ≥ 0. Identifies which layer this NavStack renders. Items pushed via `<ToNavStack @layer={{n}}>` appear here when `n === @layer`. |
107
+ | `@back` | no | Callback invoked when a back-swipe completes successfully. Typically wired via `{{route-action "back"}}` when using `StackableRoute`. Without this, the back-swipe gesture is disabled. |
108
+ | `@footer` | no | A curried component to render below the stack (e.g. a tab bar). Often only set on layer 0. |
109
+ | `@birdsEyeDebugging` | no | If truthy, applies an `.is-birdsEyeDebugging` class for a debug view that shows all layers at once. |
110
+ | `@extractComponentKey` | no | Override how `NavStack` derives a stable identity for a stack item's component (used to detect "did the root component change?" → cut vs. slide). The default reads the curried component's resolved name + bound `model.id`. Most apps don't need to override. |
111
+ | `@onActiveItemChange` | no | Callback invoked whenever the top of the stack changes identity (push, pop, root swap, initial render). Receives `{ isInitialRender, previousDepth, currentDepth, previousTopKey, currentTopKey }`, where the `*TopKey` values are the top stack item's component name. Use this — not subclassing — to react to navigation (e.g. moving keyboard focus to the newly active heading, recording analytics page views). |
112
+
113
+ ## `<ToNavStack>` arguments
114
+
115
+ | Arg | Required | Description |
116
+ |---|---|---|
117
+ | `@layer` | yes | Integer ≥ 0. Which `<NavStack>` should receive this item. |
118
+ | `@item` | yes | A curried component (`{{component 'my-page' model=this.model}}`) rendered as the stack item's body. |
119
+ | `@header` | no | A curried component rendered in the stack item's header slot. Receives `@model`, `@controller`, and `@back` as arguments when the parent `<NavStack>` has them. |
120
+
121
+ ## Animation behavior
122
+
123
+ `NavStack` chooses one of these transitions automatically based on observed stack state:
124
+
125
+ | Situation | Animation |
126
+ |---|---|
127
+ | First render of the stack | `cut` (no animation, snap into place) |
128
+ | Layer > 0 appearing from empty | `slideUp` (slides up from below) |
129
+ | Layer > 0 disappearing to empty | `slideDown` (slides down off-screen) |
130
+ | Same root, deeper push | `slideForward` (new page slides in from the right) |
131
+ | Same root, shallower pop | `slideBack` (current page slides off to the right) |
132
+ | Root component changed | `cut` (instant swap — used for tab switches) |
133
+
134
+ The decision is made by `decideTransition` (`src/utils/transition-decision.js`), which you can unit-test in isolation if you're forking or debugging. See its source comment for the full truth table.
135
+
136
+ ## Disabling animation (e.g. for tests)
137
+
138
+ ```js
139
+ // config/environment.js
140
+ ENV['ember-nav-stack'] = { suppressAnimation: true };
141
+ ```
142
+
143
+ When set, `<NavStack>` skips its springs and snaps directly to the post-transition position. Useful in test environments that don't need to wait on rAF-driven animations.
144
+
145
+ ## Test waiters
146
+
147
+ `ember-nav-stack` registers waiters for stack recomputes, transition animations, and the initial render so Ember's built-in test helpers (`visit`, `click`, `settled`, etc.) wait correctly. **You should not need custom "wait for nav stack idle" helpers or manual `waitFor` calls** when navigation is your only async source.
148
+
149
+ If you do need a programmatic hook:
150
+
151
+ ```js
152
+ import { getOwner } from '@ember/application';
153
+
154
+ let navStacks = getOwner(this).lookup('service:nav-stacks');
155
+ await navStacks.waitUntilTransitionIdle();
156
+ ```
157
+
158
+ ## CSS classes you can target
159
+
160
+ The addon owns these class names. Consumers can style them (or query them in tests) but should not rely on internal structure:
161
+
162
+ - `.NavStack` — the stack container element
163
+ - `.NavStack--layer0`, `.NavStack--layer1`, … — added per layer index
164
+ - `.NavStack--withFooter` — added when `@footer` is set
165
+ - `.NavStack-item`, `.NavStack-item-0`, `.NavStack-item-1`, … — per-item containers
166
+ - `.NavStack-itemContainer` — the horizontally-translated container that holds items
167
+ - `.NavStack-header` — wrapper for the current + parent header
168
+ - `.NavStack-currentHeaderContainer`, `.NavStack-parentItemHeaderContainer` — the two live header slots
169
+ - `.NavStack-footer` — wrapper for `@footer`
170
+
171
+ `.NavStack-gestureBackTargetHeader` may briefly appear during a completed back-swipe; it's an overlay clone that gets cleaned up automatically.
172
+
173
+ ## Test support
174
+
175
+ ```js
176
+ import { isInViewport, getElementInViewportRatio } from 'ember-nav-stack/test-support';
177
+ ```
33
178
 
34
- Contributing
35
- ------------------------------------------------------------------------------
179
+ Two helpers for asserting on the visible position of stack items in tests. See `src/test-support/in-viewport.js` for details.
36
180
 
37
- See the [Contributing](CONTRIBUTING.md) guide for details.
181
+ ## Contributing
38
182
 
183
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and development notes.
39
184
 
40
- License
41
- ------------------------------------------------------------------------------
185
+ ## License
42
186
 
43
187
  This project is licensed under the [MIT License](LICENSE.md).
package/addon-main.cjs ADDED
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+
3
+ const { addonV1Shim } = require('@embroider/addon-shim');
4
+ module.exports = addonV1Shim(__dirname);
@@ -0,0 +1 @@
1
+ export { default } from "ember-nav-stack/components/nav-stack";
@@ -0,0 +1 @@
1
+ export { default } from "ember-nav-stack/components/to-nav-stack";
@@ -0,0 +1 @@
1
+ export { default } from "ember-nav-stack/helpers/nav-layer-indices";
@@ -0,0 +1 @@
1
+ export { default } from "ember-nav-stack/modifiers/back-swipe";
@@ -0,0 +1 @@
1
+ export { default } from "ember-nav-stack/services/gesture";
@@ -0,0 +1 @@
1
+ export { default } from "ember-nav-stack/services/nav-stacks";
@@ -0,0 +1 @@
1
+ export { default } from "ember-nav-stack/templates/stackable";
@@ -0,0 +1,261 @@
1
+ import Hammer from 'hammerjs';
2
+ import { run } from '@ember/runloop';
3
+ import { Spring } from 'wobble';
4
+ import { macroCondition, isTesting } from '@embroider/macros';
5
+ import BackSwipeRecognizer from './utils/back-swipe-recognizer.js';
6
+ import { setTransform } from './utils/animation.js';
7
+ import { currentTransitionPercentage, styleHeaderElements, HEADER_PARALLAX_OFFSET } from './utils/header-style.js';
8
+
9
+ // Spring configuration for the swipe-driven horizontal animation. Tuned to
10
+ // match the programmatic spring used by `NavStack#horizontalTransition`.
11
+ const SWIPE_SPRING_CONFIG = {
12
+ stiffness: 1000,
13
+ damping: 500,
14
+ mass: 3
15
+ };
16
+
17
+ // Encapsulates the back-swipe gesture state and behavior. NavStack
18
+ // instantiates one of these in its `did-insert` callback and tears it down
19
+ // on `will-destroy`. The gesture owns:
20
+ //
21
+ // - The Hammer.js manager + BackSwipeRecognizer
22
+ // - The pan handler context (element refs, startingX, backX, thresholdX)
23
+ // - The swipe-driven Spring (cancellation, onUpdate, onStop)
24
+ // - The target-header overlay snapshot used to bridge the re-render gap
25
+ // after a completed back-swipe (see PR #79 for the bug fix)
26
+ // - Registration with the gesture service for cross-component recognizer
27
+ // ordering (preferRecognizer / stopPreferringRecognizer)
28
+ //
29
+ // NavStack communicates with the gesture via three callbacks supplied at
30
+ // construction time:
31
+ //
32
+ // getCanNavigateBack() → boolean
33
+ // Returns whether back navigation is currently allowed (typically
34
+ // `stackDepth > 1` and `args.back` defined). Re-evaluated on every
35
+ // `setupContext()` call so the gesture stays in sync with stack changes.
36
+ //
37
+ // onBack()
38
+ // Invoked when a back-swipe completes successfully. Wraps the
39
+ // consumer's `@back` callback.
40
+ //
41
+ // onBackSwipeOverlay(clone)
42
+ // Invoked with the cloned target-header element immediately before
43
+ // `onBack`. NavStack stores the clone so its `handleStackDepthChange`
44
+ // can schedule cleanup once the stack pop actually lands (see the
45
+ // gesture-back overlay machinery in PR #79).
46
+ //
47
+ // NavStack drives the gesture's pan-recognizer state during programmatic
48
+ // transitions via `disablePan()` (called from `transitionDidBegin`) and
49
+ // `setupContext()` (called from `transitionDidEnd` to re-grab DOM refs and
50
+ // re-enable the recognizer once the animation completes).
51
+ class BackSwipeGesture {
52
+ constructor({
53
+ element,
54
+ navStacksService,
55
+ gestureService,
56
+ getCanNavigateBack,
57
+ onBack,
58
+ onBackSwipeOverlay
59
+ }) {
60
+ this.element = element;
61
+ this.navStacksService = navStacksService;
62
+ this.gestureService = gestureService;
63
+ this.getCanNavigateBack = getCanNavigateBack;
64
+ this.onBack = onBack;
65
+ this.onBackSwipeOverlay = onBackSwipeOverlay;
66
+ this.hammer = new Hammer.Manager(element, {
67
+ inputClass: Hammer.TouchMouseInput,
68
+ recognizers: [[BackSwipeRecognizer]]
69
+ });
70
+ this.setupContext();
71
+ this.hammer.on('pan', this._handlePanEvent);
72
+ this.gestureService.register(this, this.hammer.get('pan'));
73
+ }
74
+ teardown() {
75
+ if (this.hammer) {
76
+ this.gestureService.unregister(this, this.hammer.get('pan'));
77
+ this.hammer.off('pan');
78
+ this.hammer.destroy();
79
+ this.hammer = null;
80
+ }
81
+ // If a swipe completed and produced an overlay but the consumer hasn't
82
+ // claimed it via `onBackSwipeOverlay` yet (or we're being destroyed in
83
+ // mid-stride), drop it so it doesn't leak into the DOM.
84
+ if (this._unclaimedOverlay?.parentNode) {
85
+ this._unclaimedOverlay.parentNode.removeChild(this._unclaimedOverlay);
86
+ }
87
+ this._unclaimedOverlay = null;
88
+ }
89
+
90
+ // Called by NavStack when a programmatic transition starts. Disables the
91
+ // pan recognizer so a new swipe can't start while the animation is
92
+ // running. A swipe-driven spring that's already in flight is left to
93
+ // complete on its own — the programmatic transition's spring tracks its
94
+ // active spring separately (NavStack#_horizontalSpring) and cancels any
95
+ // prior horizontal spring before starting.
96
+ disablePan() {
97
+ this.hammer?.get('pan').set({
98
+ enable: false
99
+ });
100
+ }
101
+
102
+ // Called by NavStack after a programmatic transition completes (and also
103
+ // by the constructor for initial wiring). Re-grabs DOM refs (item width
104
+ // may have changed), recomputes startingX/backX/thresholdX, and
105
+ // re-enables the recognizer.
106
+ setupContext() {
107
+ this.containerElement = this.element.querySelector('.NavStack-itemContainer');
108
+ this.currentHeaderElement = this.element.querySelector('.NavStack-currentHeaderContainer');
109
+ this.parentHeaderElement = this.element.querySelector('.NavStack-parentItemHeaderContainer');
110
+ this.startingX = this._getX(this.containerElement);
111
+ let currentStackItemElement = this.element.querySelector('.NavStack-item:last-child');
112
+ if (!currentStackItemElement) {
113
+ this.canNavigateBack = false;
114
+ return;
115
+ }
116
+ let itemWidth = currentStackItemElement.getBoundingClientRect().width;
117
+ this.backX = this.startingX + itemWidth;
118
+ this.thresholdX = itemWidth / 2;
119
+ this.canNavigateBack = this.getCanNavigateBack();
120
+ this.hammer?.get('pan').set({
121
+ enable: true,
122
+ threshold: 9
123
+ });
124
+ }
125
+
126
+ // Called by the gesture service when another gesture-aware component
127
+ // registers — we must require its pan recognizer to fail before ours can
128
+ // succeed (or vice versa). Matches the public surface the service
129
+ // expects on registered objects.
130
+ preferRecognizer(recognizer) {
131
+ this.hammer?.get('pan').requireFailure(recognizer);
132
+ }
133
+ stopPreferringRecognizer(recognizer) {
134
+ this.hammer?.get('pan').dropRequireFailure(recognizer);
135
+ }
136
+ _handlePanEvent = ev => {
137
+ if (this._activeSpring) {
138
+ return;
139
+ }
140
+ // Clamp the gesture's effective displacement at 0 so it can never travel
141
+ // left of its starting position. Without this, sliding the finger past
142
+ // origin produces a negative `deltaX`, and `currentTransitionPercentage`
143
+ // — which takes Math.abs of the delta — interprets it as forward
144
+ // progress: parent header fades in and current header fades out as if a
145
+ // forward transition were under way, the opposite of the back-swipe the
146
+ // user actually started. Clamping makes the gesture latch at origin
147
+ // until the finger crosses back rightward.
148
+ let effectiveDeltaX = Math.max(0, ev.deltaX);
149
+ setTransform(this.containerElement, `translateX(${this.startingX + effectiveDeltaX}px)`);
150
+ let ratio = currentTransitionPercentage(this.startingX, this.backX, this.startingX + effectiveDeltaX);
151
+ styleHeaderElements(ratio, true, this.currentHeaderElement, this.parentHeaderElement);
152
+ if (this.currentHeaderElement) {
153
+ this.currentHeaderElement.style.opacity = ratio;
154
+ }
155
+ if (this.parentHeaderElement) {
156
+ this.parentHeaderElement.style.opacity = 1 - ratio;
157
+ }
158
+ if (ev.isFinal) {
159
+ this._handlePanEnd(ev);
160
+ }
161
+ };
162
+ _handlePanEnd(ev) {
163
+ let effectiveDeltaX = Math.max(0, ev.deltaX);
164
+ let shouldNavigateBack = this._adjustX(ev.center.x) >= this.thresholdX && this.canNavigateBack;
165
+ let initialVelocity = ev.velocityX;
166
+ let fromValue = this.startingX + effectiveDeltaX;
167
+ let toValue = shouldNavigateBack ? this.backX : this.startingX;
168
+ this.navStacksService.notifyTransitionStart();
169
+ let finalize = () => {
170
+ if (shouldNavigateBack) {
171
+ // The cross-fade ended with parentHeaderElement (target page header)
172
+ // visible. onBack() will eventually re-render the live header
173
+ // containers, swapping content: currentHeaderContainer becomes the
174
+ // target page's header. Snapshot the visible target header into an
175
+ // overlay clone so it stays on screen across that re-render.
176
+ // Cleanup is owned by the parent NavStack and runs once the stack
177
+ // pop actually lands (see PR #79 for the full lifecycle).
178
+ let overlay = this._cloneTargetHeaderOverlay();
179
+ if (this.currentHeaderElement) {
180
+ this.currentHeaderElement.style.opacity = 0;
181
+ setTransform(this.currentHeaderElement, `translateX(${HEADER_PARALLAX_OFFSET}px)`);
182
+ }
183
+ if (this.parentHeaderElement) {
184
+ this.parentHeaderElement.style.opacity = 0;
185
+ setTransform(this.parentHeaderElement, `translateX(${-HEADER_PARALLAX_OFFSET}px)`);
186
+ }
187
+ this.navStacksService.notifyTransitionEnd();
188
+ this._activeSpring = null;
189
+ this._unclaimedOverlay = overlay;
190
+ this.onBackSwipeOverlay?.(overlay);
191
+ this._unclaimedOverlay = null;
192
+ run(this.onBack);
193
+ } else {
194
+ setTransform(this.containerElement, `translateX(${this.startingX}px)`);
195
+ if (this.currentHeaderElement) {
196
+ this.currentHeaderElement.style.opacity = 1;
197
+ setTransform(this.currentHeaderElement, 'translateX(0px)');
198
+ }
199
+ if (this.parentHeaderElement) {
200
+ this.parentHeaderElement.style.opacity = 0;
201
+ setTransform(this.parentHeaderElement, `translateX(${-HEADER_PARALLAX_OFFSET}px)`);
202
+ }
203
+ this.navStacksService.notifyTransitionEnd();
204
+ this._activeSpring = null;
205
+ }
206
+ };
207
+ if (fromValue === toValue && initialVelocity === 0) {
208
+ finalize();
209
+ return;
210
+ }
211
+ let spring = new Spring({
212
+ initialVelocity,
213
+ fromValue,
214
+ toValue,
215
+ ...SWIPE_SPRING_CONFIG
216
+ });
217
+ this._activeSpring = spring;
218
+ spring.onUpdate(s => {
219
+ setTransform(this.containerElement, `translateX(${s.currentValue}px)`);
220
+ styleHeaderElements(currentTransitionPercentage(this.startingX, this.backX, s.currentValue), false, this.parentHeaderElement, this.currentHeaderElement);
221
+ // If the spring's momentum carries it past the back-threshold after
222
+ // the user released below the threshold, retarget to backX so the
223
+ // animation completes the back-swipe rather than snapping back.
224
+ if (!shouldNavigateBack && s.currentValue >= this.startingX + this.thresholdX) {
225
+ shouldNavigateBack = true;
226
+ spring.updateConfig({
227
+ toValue: this.backX
228
+ });
229
+ }
230
+ }).onStop(() => finalize()).start();
231
+ }
232
+ _cloneTargetHeaderOverlay() {
233
+ let liveParent = this.element.querySelector('.NavStack-parentItemHeaderContainer');
234
+ if (!liveParent) {
235
+ return null;
236
+ }
237
+ let clone = liveParent.cloneNode(true);
238
+ clone.classList.remove('NavStack-parentItemHeaderContainer');
239
+ clone.classList.add('NavStack-gestureBackTargetHeader');
240
+ clone.style.opacity = '1';
241
+ setTransform(clone, 'translateX(0px)');
242
+ // Append last so it paints on top of currentHeaderContainer.
243
+ this.element.querySelector('.NavStack-header').appendChild(clone);
244
+ return clone;
245
+ }
246
+ _getX(element) {
247
+ return this._adjustX(element.getBoundingClientRect().left);
248
+ }
249
+ _adjustX(x) {
250
+ if (macroCondition(isTesting())) {
251
+ let testingEl = document.querySelector('#ember-testing');
252
+ if (testingEl) {
253
+ return x - testingEl.getBoundingClientRect().left;
254
+ }
255
+ }
256
+ return x;
257
+ }
258
+ }
259
+
260
+ export { BackSwipeGesture as default };
261
+ //# sourceMappingURL=back-swipe-gesture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"back-swipe-gesture.js","sources":["../src/back-swipe-gesture.js"],"sourcesContent":["import Hammer from 'hammerjs';\nimport { run } from '@ember/runloop';\nimport { Spring } from 'wobble';\nimport { macroCondition, isTesting } from '@embroider/macros';\nimport BackSwipeRecognizer from './utils/back-swipe-recognizer.js';\nimport { setTransform } from './utils/animation.js';\nimport {\n HEADER_PARALLAX_OFFSET,\n currentTransitionPercentage,\n styleHeaderElements,\n} from './utils/header-style.js';\n\n// Spring configuration for the swipe-driven horizontal animation. Tuned to\n// match the programmatic spring used by `NavStack#horizontalTransition`.\nconst SWIPE_SPRING_CONFIG = {\n stiffness: 1000,\n damping: 500,\n mass: 3,\n};\n\n// Encapsulates the back-swipe gesture state and behavior. NavStack\n// instantiates one of these in its `did-insert` callback and tears it down\n// on `will-destroy`. The gesture owns:\n//\n// - The Hammer.js manager + BackSwipeRecognizer\n// - The pan handler context (element refs, startingX, backX, thresholdX)\n// - The swipe-driven Spring (cancellation, onUpdate, onStop)\n// - The target-header overlay snapshot used to bridge the re-render gap\n// after a completed back-swipe (see PR #79 for the bug fix)\n// - Registration with the gesture service for cross-component recognizer\n// ordering (preferRecognizer / stopPreferringRecognizer)\n//\n// NavStack communicates with the gesture via three callbacks supplied at\n// construction time:\n//\n// getCanNavigateBack() → boolean\n// Returns whether back navigation is currently allowed (typically\n// `stackDepth > 1` and `args.back` defined). Re-evaluated on every\n// `setupContext()` call so the gesture stays in sync with stack changes.\n//\n// onBack()\n// Invoked when a back-swipe completes successfully. Wraps the\n// consumer's `@back` callback.\n//\n// onBackSwipeOverlay(clone)\n// Invoked with the cloned target-header element immediately before\n// `onBack`. NavStack stores the clone so its `handleStackDepthChange`\n// can schedule cleanup once the stack pop actually lands (see the\n// gesture-back overlay machinery in PR #79).\n//\n// NavStack drives the gesture's pan-recognizer state during programmatic\n// transitions via `disablePan()` (called from `transitionDidBegin`) and\n// `setupContext()` (called from `transitionDidEnd` to re-grab DOM refs and\n// re-enable the recognizer once the animation completes).\nexport default class BackSwipeGesture {\n constructor({\n element,\n navStacksService,\n gestureService,\n getCanNavigateBack,\n onBack,\n onBackSwipeOverlay,\n }) {\n this.element = element;\n this.navStacksService = navStacksService;\n this.gestureService = gestureService;\n this.getCanNavigateBack = getCanNavigateBack;\n this.onBack = onBack;\n this.onBackSwipeOverlay = onBackSwipeOverlay;\n\n this.hammer = new Hammer.Manager(element, {\n inputClass: Hammer.TouchMouseInput,\n recognizers: [[BackSwipeRecognizer]],\n });\n this.setupContext();\n this.hammer.on('pan', this._handlePanEvent);\n this.gestureService.register(this, this.hammer.get('pan'));\n }\n\n teardown() {\n if (this.hammer) {\n this.gestureService.unregister(this, this.hammer.get('pan'));\n this.hammer.off('pan');\n this.hammer.destroy();\n this.hammer = null;\n }\n // If a swipe completed and produced an overlay but the consumer hasn't\n // claimed it via `onBackSwipeOverlay` yet (or we're being destroyed in\n // mid-stride), drop it so it doesn't leak into the DOM.\n if (this._unclaimedOverlay?.parentNode) {\n this._unclaimedOverlay.parentNode.removeChild(this._unclaimedOverlay);\n }\n this._unclaimedOverlay = null;\n }\n\n // Called by NavStack when a programmatic transition starts. Disables the\n // pan recognizer so a new swipe can't start while the animation is\n // running. A swipe-driven spring that's already in flight is left to\n // complete on its own — the programmatic transition's spring tracks its\n // active spring separately (NavStack#_horizontalSpring) and cancels any\n // prior horizontal spring before starting.\n disablePan() {\n this.hammer?.get('pan').set({ enable: false });\n }\n\n // Called by NavStack after a programmatic transition completes (and also\n // by the constructor for initial wiring). Re-grabs DOM refs (item width\n // may have changed), recomputes startingX/backX/thresholdX, and\n // re-enables the recognizer.\n setupContext() {\n this.containerElement = this.element.querySelector(\n '.NavStack-itemContainer',\n );\n this.currentHeaderElement = this.element.querySelector(\n '.NavStack-currentHeaderContainer',\n );\n this.parentHeaderElement = this.element.querySelector(\n '.NavStack-parentItemHeaderContainer',\n );\n this.startingX = this._getX(this.containerElement);\n let currentStackItemElement = this.element.querySelector(\n '.NavStack-item:last-child',\n );\n if (!currentStackItemElement) {\n this.canNavigateBack = false;\n return;\n }\n let itemWidth = currentStackItemElement.getBoundingClientRect().width;\n this.backX = this.startingX + itemWidth;\n this.thresholdX = itemWidth / 2;\n this.canNavigateBack = this.getCanNavigateBack();\n this.hammer?.get('pan').set({ enable: true, threshold: 9 });\n }\n\n // Called by the gesture service when another gesture-aware component\n // registers — we must require its pan recognizer to fail before ours can\n // succeed (or vice versa). Matches the public surface the service\n // expects on registered objects.\n preferRecognizer(recognizer) {\n this.hammer?.get('pan').requireFailure(recognizer);\n }\n\n stopPreferringRecognizer(recognizer) {\n this.hammer?.get('pan').dropRequireFailure(recognizer);\n }\n\n _handlePanEvent = (ev) => {\n if (this._activeSpring) {\n return;\n }\n // Clamp the gesture's effective displacement at 0 so it can never travel\n // left of its starting position. Without this, sliding the finger past\n // origin produces a negative `deltaX`, and `currentTransitionPercentage`\n // — which takes Math.abs of the delta — interprets it as forward\n // progress: parent header fades in and current header fades out as if a\n // forward transition were under way, the opposite of the back-swipe the\n // user actually started. Clamping makes the gesture latch at origin\n // until the finger crosses back rightward.\n let effectiveDeltaX = Math.max(0, ev.deltaX);\n setTransform(\n this.containerElement,\n `translateX(${this.startingX + effectiveDeltaX}px)`,\n );\n let ratio = currentTransitionPercentage(\n this.startingX,\n this.backX,\n this.startingX + effectiveDeltaX,\n );\n styleHeaderElements(\n ratio,\n true,\n this.currentHeaderElement,\n this.parentHeaderElement,\n );\n if (this.currentHeaderElement) {\n this.currentHeaderElement.style.opacity = ratio;\n }\n if (this.parentHeaderElement) {\n this.parentHeaderElement.style.opacity = 1 - ratio;\n }\n if (ev.isFinal) {\n this._handlePanEnd(ev);\n }\n };\n\n _handlePanEnd(ev) {\n let effectiveDeltaX = Math.max(0, ev.deltaX);\n let shouldNavigateBack =\n this._adjustX(ev.center.x) >= this.thresholdX && this.canNavigateBack;\n let initialVelocity = ev.velocityX;\n let fromValue = this.startingX + effectiveDeltaX;\n let toValue = shouldNavigateBack ? this.backX : this.startingX;\n this.navStacksService.notifyTransitionStart();\n\n let finalize = () => {\n if (shouldNavigateBack) {\n // The cross-fade ended with parentHeaderElement (target page header)\n // visible. onBack() will eventually re-render the live header\n // containers, swapping content: currentHeaderContainer becomes the\n // target page's header. Snapshot the visible target header into an\n // overlay clone so it stays on screen across that re-render.\n // Cleanup is owned by the parent NavStack and runs once the stack\n // pop actually lands (see PR #79 for the full lifecycle).\n let overlay = this._cloneTargetHeaderOverlay();\n if (this.currentHeaderElement) {\n this.currentHeaderElement.style.opacity = 0;\n setTransform(\n this.currentHeaderElement,\n `translateX(${HEADER_PARALLAX_OFFSET}px)`,\n );\n }\n if (this.parentHeaderElement) {\n this.parentHeaderElement.style.opacity = 0;\n setTransform(\n this.parentHeaderElement,\n `translateX(${-HEADER_PARALLAX_OFFSET}px)`,\n );\n }\n this.navStacksService.notifyTransitionEnd();\n this._activeSpring = null;\n this._unclaimedOverlay = overlay;\n this.onBackSwipeOverlay?.(overlay);\n this._unclaimedOverlay = null;\n run(this.onBack);\n } else {\n setTransform(this.containerElement, `translateX(${this.startingX}px)`);\n if (this.currentHeaderElement) {\n this.currentHeaderElement.style.opacity = 1;\n setTransform(this.currentHeaderElement, 'translateX(0px)');\n }\n if (this.parentHeaderElement) {\n this.parentHeaderElement.style.opacity = 0;\n setTransform(\n this.parentHeaderElement,\n `translateX(${-HEADER_PARALLAX_OFFSET}px)`,\n );\n }\n this.navStacksService.notifyTransitionEnd();\n this._activeSpring = null;\n }\n };\n\n if (fromValue === toValue && initialVelocity === 0) {\n finalize();\n return;\n }\n let spring = new Spring({\n initialVelocity,\n fromValue,\n toValue,\n ...SWIPE_SPRING_CONFIG,\n });\n this._activeSpring = spring;\n spring\n .onUpdate((s) => {\n setTransform(this.containerElement, `translateX(${s.currentValue}px)`);\n styleHeaderElements(\n currentTransitionPercentage(\n this.startingX,\n this.backX,\n s.currentValue,\n ),\n false,\n this.parentHeaderElement,\n this.currentHeaderElement,\n );\n // If the spring's momentum carries it past the back-threshold after\n // the user released below the threshold, retarget to backX so the\n // animation completes the back-swipe rather than snapping back.\n if (\n !shouldNavigateBack &&\n s.currentValue >= this.startingX + this.thresholdX\n ) {\n shouldNavigateBack = true;\n spring.updateConfig({ toValue: this.backX });\n }\n })\n .onStop(() => finalize())\n .start();\n }\n\n _cloneTargetHeaderOverlay() {\n let liveParent = this.element.querySelector(\n '.NavStack-parentItemHeaderContainer',\n );\n if (!liveParent) {\n return null;\n }\n let clone = liveParent.cloneNode(true);\n clone.classList.remove('NavStack-parentItemHeaderContainer');\n clone.classList.add('NavStack-gestureBackTargetHeader');\n clone.style.opacity = '1';\n setTransform(clone, 'translateX(0px)');\n // Append last so it paints on top of currentHeaderContainer.\n this.element.querySelector('.NavStack-header').appendChild(clone);\n return clone;\n }\n\n _getX(element) {\n return this._adjustX(element.getBoundingClientRect().left);\n }\n\n _adjustX(x) {\n if (macroCondition(isTesting())) {\n let testingEl = document.querySelector('#ember-testing');\n if (testingEl) {\n return x - testingEl.getBoundingClientRect().left;\n }\n }\n return x;\n }\n}\n"],"names":["SWIPE_SPRING_CONFIG","stiffness","damping","mass","BackSwipeGesture","constructor","element","navStacksService","gestureService","getCanNavigateBack","onBack","onBackSwipeOverlay","hammer","Hammer","Manager","inputClass","TouchMouseInput","recognizers","BackSwipeRecognizer","setupContext","on","_handlePanEvent","register","get","teardown","unregister","off","destroy","_unclaimedOverlay","parentNode","removeChild","disablePan","set","enable","containerElement","querySelector","currentHeaderElement","parentHeaderElement","startingX","_getX","currentStackItemElement","canNavigateBack","itemWidth","getBoundingClientRect","width","backX","thresholdX","threshold","preferRecognizer","recognizer","requireFailure","stopPreferringRecognizer","dropRequireFailure","ev","_activeSpring","effectiveDeltaX","Math","max","deltaX","setTransform","ratio","currentTransitionPercentage","styleHeaderElements","style","opacity","isFinal","_handlePanEnd","shouldNavigateBack","_adjustX","center","x","initialVelocity","velocityX","fromValue","toValue","notifyTransitionStart","finalize","overlay","_cloneTargetHeaderOverlay","HEADER_PARALLAX_OFFSET","notifyTransitionEnd","run","spring","Spring","onUpdate","s","currentValue","updateConfig","onStop","start","liveParent","clone","cloneNode","classList","remove","add","appendChild","left","macroCondition","isTesting","testingEl","document"],"mappings":"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"}