ember-nav-stack 6.1.2 → 7.1.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 (79) hide show
  1. package/README.md +165 -21
  2. package/addon-main.cjs +4 -0
  3. package/dist/_app_/components/nav-stack-inner-wrapper.js +1 -0
  4. package/dist/_app_/components/nav-stack.js +1 -0
  5. package/dist/_app_/components/to-nav-stack.js +1 -0
  6. package/dist/_app_/helpers/nav-layer-indices.js +1 -0
  7. package/dist/_app_/modifiers/back-swipe.js +1 -0
  8. package/dist/_app_/services/gesture.js +1 -0
  9. package/dist/_app_/services/nav-stacks.js +1 -0
  10. package/dist/_app_/templates/stackable.js +1 -0
  11. package/dist/back-swipe-gesture.js +261 -0
  12. package/dist/back-swipe-gesture.js.map +1 -0
  13. package/dist/components/nav-stack-inner-wrapper.js +10 -0
  14. package/dist/components/nav-stack-inner-wrapper.js.map +1 -0
  15. package/dist/components/nav-stack.js +700 -0
  16. package/dist/components/nav-stack.js.map +1 -0
  17. package/dist/components/to-nav-stack.js +22 -0
  18. package/dist/components/to-nav-stack.js.map +1 -0
  19. package/dist/helpers/nav-layer-indices.js +21 -0
  20. package/dist/helpers/nav-layer-indices.js.map +1 -0
  21. package/dist/index.js +7 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/modifiers/back-swipe.js +40 -0
  24. package/dist/modifiers/back-swipe.js.map +1 -0
  25. package/dist/routes/stackable-route.js +99 -0
  26. package/dist/routes/stackable-route.js.map +1 -0
  27. package/{addon → dist}/services/gesture.js +7 -9
  28. package/dist/services/gesture.js.map +1 -0
  29. package/dist/services/nav-stacks.js +137 -0
  30. package/dist/services/nav-stacks.js.map +1 -0
  31. package/dist/styles/nav-stack.css +399 -0
  32. package/dist/templates/stackable.js +8 -0
  33. package/dist/templates/stackable.js.map +1 -0
  34. package/{addon-test-support → dist/test-support}/in-viewport.js +7 -10
  35. package/dist/test-support/in-viewport.js.map +1 -0
  36. package/dist/test-support/index.js +2 -0
  37. package/dist/test-support/index.js.map +1 -0
  38. package/{addon → dist}/utils/animation.js +17 -40
  39. package/dist/utils/animation.js.map +1 -0
  40. package/{addon → dist}/utils/back-swipe-recognizer.js +29 -49
  41. package/dist/utils/back-swipe-recognizer.js.map +1 -0
  42. package/dist/utils/clone-store.js +88 -0
  43. package/dist/utils/clone-store.js.map +1 -0
  44. package/dist/utils/component.js +121 -0
  45. package/dist/utils/component.js.map +1 -0
  46. package/dist/utils/header-style.js +46 -0
  47. package/dist/utils/header-style.js.map +1 -0
  48. package/dist/utils/transition-decision.js +71 -0
  49. package/dist/utils/transition-decision.js.map +1 -0
  50. package/dist/utils/waiter-state.js +130 -0
  51. package/dist/utils/waiter-state.js.map +1 -0
  52. package/package.json +79 -91
  53. package/.vscode/settings.json +0 -2
  54. package/CHANGELOG.md +0 -208
  55. package/MODULE_REPORT.md +0 -27
  56. package/RELEASE.md +0 -54
  57. package/addon/components/nav-stack/component.js +0 -690
  58. package/addon/components/nav-stack/template.hbs +0 -37
  59. package/addon/components/to-nav-stack.js +0 -32
  60. package/addon/helpers/nav-layer-indices.js +0 -29
  61. package/addon/routes/stackable-route.js +0 -61
  62. package/addon/services/nav-stacks.js +0 -157
  63. package/addon/utils/component.js +0 -40
  64. package/app/components/nav-stack/component.js +0 -1
  65. package/app/components/nav-stack/template.js +0 -1
  66. package/app/components/to-nav-stack.js +0 -1
  67. package/app/helpers/nav-layer-indices.js +0 -1
  68. package/app/services/gesture.js +0 -1
  69. package/app/services/nav-stacks.js +0 -1
  70. package/app/styles/nav-stack.scss +0 -117
  71. package/app/templates/stackable.hbs +0 -8
  72. package/app/utils/animation.js +0 -1
  73. package/config/deploy.js +0 -29
  74. package/config/environment.js +0 -5
  75. package/config/release.js +0 -21
  76. package/docs/ember-nav-stack-waiters-plan.md +0 -125
  77. package/index.js +0 -15
  78. package/tsconfig.json +0 -6
  79. package/vendor/wobble-shim.js +0 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"back-swipe-recognizer.js","sources":["../../src/utils/back-swipe-recognizer.js"],"sourcesContent":["import Hammer from 'hammerjs';\nimport { macroCondition, isTesting } from '@embroider/macros';\nconst { DIRECTION_RIGHT } = Hammer;\nconst FIELD_REGEXP = /input|textarea|select/i;\n\n/* This recognizer subclasses the Pan recognizer and adds the constraint that the initial touch\n * must be in the validLeftAreaPercent portion of the screen.\n */\nexport default class BackSwipeRecognizer extends Hammer.Pan {\n constructor(options) {\n super(options);\n this.options = Object.assign({}, this.defaults, options || {});\n this.captureClick = (ev) => {\n ev.stopPropagation(); // Stop the click from being propagated.\n this.manager.element.removeEventListener(\n 'click',\n this.captureClick,\n true,\n );\n };\n }\n\n defaults = {\n enable: true,\n event: 'pan',\n threshold: 10,\n pointers: 1,\n direction: DIRECTION_RIGHT,\n validLeftAreaPercent: 33,\n };\n\n recognize(inputData) {\n if (inputData.isFirst) {\n this.isInitialTouchInValidArea =\n this.checkInitialTouchInValidArea(inputData);\n }\n let isOverElementThatPreventsScrollingInteraction =\n this.shouldPreventScrollingInteraction(inputData);\n if (isOverElementThatPreventsScrollingInteraction) {\n this.manager.stop();\n return;\n }\n this.captureGhostClickIfNeeded(inputData);\n super.recognize(inputData);\n if (inputData.isFinal) {\n setTimeout(() => {\n this.state = Hammer.STATE_POSSIBLE;\n }, 0);\n }\n }\n\n shouldPreventScrollingInteraction(inputData) {\n let { target } = inputData;\n return (\n inputData.isFirst &&\n ((target && target.tagName.match(FIELD_REGEXP)) ||\n (target && target.hasAttribute('data-prevent-scrolling')))\n );\n }\n\n captureGhostClickIfNeeded(inputData) {\n if (\n inputData.srcEvent.type === 'mouseup' &&\n this.state & Hammer.STATE_BEGAN\n ) {\n this.manager.element.addEventListener('click', this.captureClick, true);\n setTimeout(() => {\n this.manager.element.removeEventListener(\n 'click',\n this.captureClick,\n true,\n );\n }, 0);\n }\n }\n\n reset() {\n this.isInitialTouchInValidArea = undefined;\n }\n\n checkInitialTouchInValidArea(inputData) {\n if (macroCondition(isTesting())) {\n let testingEl = document.querySelector('#ember-testing');\n if (testingEl) {\n let testingRect = testingEl.getBoundingClientRect();\n let minValidX = testingRect.left;\n let maxValidX =\n minValidX +\n testingEl.clientWidth * (this.options.validLeftAreaPercent / 100);\n return (\n inputData.center.x >= minValidX && inputData.center.x <= maxValidX\n );\n }\n }\n let maxValidX =\n window.innerWidth * (this.options.validLeftAreaPercent / 100);\n return inputData.center.x <= maxValidX;\n }\n\n attrTest(input) {\n return this.isInitialTouchInValidArea && super.attrTest(input);\n }\n}\n"],"names":["DIRECTION_RIGHT","Hammer","FIELD_REGEXP","BackSwipeRecognizer","Pan","constructor","options","Object","assign","defaults","captureClick","ev","stopPropagation","manager","element","removeEventListener","enable","event","threshold","pointers","direction","validLeftAreaPercent","recognize","inputData","isFirst","isInitialTouchInValidArea","checkInitialTouchInValidArea","isOverElementThatPreventsScrollingInteraction","shouldPreventScrollingInteraction","stop","captureGhostClickIfNeeded","isFinal","setTimeout","state","STATE_POSSIBLE","target","tagName","match","hasAttribute","srcEvent","type","STATE_BEGAN","addEventListener","reset","undefined","macroCondition","isTesting","testingEl","document","querySelector","testingRect","getBoundingClientRect","minValidX","left","maxValidX","clientWidth","center","x","window","innerWidth","attrTest","input"],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ // Tracks groups of cloned DOM nodes so they can be removed from the document
2
+ // as a unit. NavStack creates four groups during its animations:
3
+ //
4
+ // - 'stackItems' — the last item cloned for a slideBack
5
+ // - 'headers' — the leaving header cloned for slide animations
6
+ // - 'elements' — the whole NavStack cloned for slideDown
7
+ // - 'gestureBackOverlays' — target-header snapshot during a back-swipe
8
+ // (lifecycle described in PR #79's commit body)
9
+ //
10
+ // The store doesn't know how to CREATE the clones — callers do the
11
+ // `.cloneNode(true)` + attach themselves and pass the result to `track()`.
12
+ // What the store provides is uniform cleanup (`remove()`, `clear()`,
13
+ // `clearAll()`) with a `parentNode?.removeChild` guard so a clone whose
14
+ // parent has already been removed from the DOM is dropped silently rather
15
+ // than throwing.
16
+ class CloneStore {
17
+ #groups = new Map();
18
+
19
+ // Records `clone` under `groupName`. Tracking the same clone twice in the
20
+ // same group is a no-op. (Tracking the same clone in MULTIPLE groups is
21
+ // not prevented — but no caller does that today; each clone has one
22
+ // logical owner group based on which slide animation created it.)
23
+ track(groupName, clone) {
24
+ if (!clone) {
25
+ return;
26
+ }
27
+ let group = this.#groups.get(groupName);
28
+ if (!group) {
29
+ group = [];
30
+ this.#groups.set(groupName, group);
31
+ }
32
+ if (group.indexOf(clone) === -1) {
33
+ group.push(clone);
34
+ }
35
+ }
36
+
37
+ // Removes a specific clone from a group AND from the DOM. Safe to call
38
+ // when the clone has already been detached.
39
+ remove(groupName, clone) {
40
+ if (!clone) {
41
+ return;
42
+ }
43
+ let group = this.#groups.get(groupName);
44
+ if (group) {
45
+ let idx = group.indexOf(clone);
46
+ if (idx !== -1) {
47
+ group.splice(idx, 1);
48
+ }
49
+ }
50
+ if (clone.parentNode) {
51
+ clone.parentNode.removeChild(clone);
52
+ }
53
+ }
54
+
55
+ // Removes every clone in a group from the DOM and empties the group.
56
+ // Safe to call when the group doesn't exist yet.
57
+ clear(groupName) {
58
+ let group = this.#groups.get(groupName);
59
+ if (!group) {
60
+ return;
61
+ }
62
+ let clone;
63
+ while (clone = group.pop()) {
64
+ if (clone.parentNode) {
65
+ clone.parentNode.removeChild(clone);
66
+ }
67
+ }
68
+ }
69
+
70
+ // Clears every group. Used in NavStack's willDestroy to sweep anything
71
+ // left over from abandoned transitions.
72
+ clearAll() {
73
+ for (let groupName of this.#groups.keys()) {
74
+ this.clear(groupName);
75
+ }
76
+ }
77
+
78
+ // Convenience for reading "the most recently tracked clone in this group"
79
+ // — used by NavStack's slideDown which needs the cloned-NavStack element
80
+ // it just produced. Returns undefined for empty/missing groups.
81
+ last(groupName) {
82
+ let group = this.#groups.get(groupName);
83
+ return group?.[group.length - 1];
84
+ }
85
+ }
86
+
87
+ export { CloneStore as default };
88
+ //# sourceMappingURL=clone-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clone-store.js","sources":["../../src/utils/clone-store.js"],"sourcesContent":["// Tracks groups of cloned DOM nodes so they can be removed from the document\n// as a unit. NavStack creates four groups during its animations:\n//\n// - 'stackItems' — the last item cloned for a slideBack\n// - 'headers' — the leaving header cloned for slide animations\n// - 'elements' — the whole NavStack cloned for slideDown\n// - 'gestureBackOverlays' — target-header snapshot during a back-swipe\n// (lifecycle described in PR #79's commit body)\n//\n// The store doesn't know how to CREATE the clones — callers do the\n// `.cloneNode(true)` + attach themselves and pass the result to `track()`.\n// What the store provides is uniform cleanup (`remove()`, `clear()`,\n// `clearAll()`) with a `parentNode?.removeChild` guard so a clone whose\n// parent has already been removed from the DOM is dropped silently rather\n// than throwing.\nexport default class CloneStore {\n #groups = new Map();\n\n // Records `clone` under `groupName`. Tracking the same clone twice in the\n // same group is a no-op. (Tracking the same clone in MULTIPLE groups is\n // not prevented — but no caller does that today; each clone has one\n // logical owner group based on which slide animation created it.)\n track(groupName, clone) {\n if (!clone) {\n return;\n }\n let group = this.#groups.get(groupName);\n if (!group) {\n group = [];\n this.#groups.set(groupName, group);\n }\n if (group.indexOf(clone) === -1) {\n group.push(clone);\n }\n }\n\n // Removes a specific clone from a group AND from the DOM. Safe to call\n // when the clone has already been detached.\n remove(groupName, clone) {\n if (!clone) {\n return;\n }\n let group = this.#groups.get(groupName);\n if (group) {\n let idx = group.indexOf(clone);\n if (idx !== -1) {\n group.splice(idx, 1);\n }\n }\n if (clone.parentNode) {\n clone.parentNode.removeChild(clone);\n }\n }\n\n // Removes every clone in a group from the DOM and empties the group.\n // Safe to call when the group doesn't exist yet.\n clear(groupName) {\n let group = this.#groups.get(groupName);\n if (!group) {\n return;\n }\n let clone;\n while ((clone = group.pop())) {\n if (clone.parentNode) {\n clone.parentNode.removeChild(clone);\n }\n }\n }\n\n // Clears every group. Used in NavStack's willDestroy to sweep anything\n // left over from abandoned transitions.\n clearAll() {\n for (let groupName of this.#groups.keys()) {\n this.clear(groupName);\n }\n }\n\n // Convenience for reading \"the most recently tracked clone in this group\"\n // — used by NavStack's slideDown which needs the cloned-NavStack element\n // it just produced. Returns undefined for empty/missing groups.\n last(groupName) {\n let group = this.#groups.get(groupName);\n return group?.[group.length - 1];\n }\n}\n"],"names":["CloneStore","Map","track","groupName","clone","group","get","set","indexOf","push","remove","idx","splice","parentNode","removeChild","clear","pop","clearAll","keys","last","length"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACe,MAAMA,UAAU,CAAC;AAC9B,EAAA,OAAO,GAAG,IAAIC,GAAG,EAAE;;AAEnB;AACA;AACA;AACA;AACAC,EAAAA,KAAKA,CAACC,SAAS,EAAEC,KAAK,EAAE;IACtB,IAAI,CAACA,KAAK,EAAE;AACV,MAAA;AACF,IAAA;IACA,IAAIC,KAAK,GAAG,IAAI,CAAC,OAAO,CAACC,GAAG,CAACH,SAAS,CAAC;IACvC,IAAI,CAACE,KAAK,EAAE;AACVA,MAAAA,KAAK,GAAG,EAAE;MACV,IAAI,CAAC,OAAO,CAACE,GAAG,CAACJ,SAAS,EAAEE,KAAK,CAAC;AACpC,IAAA;IACA,IAAIA,KAAK,CAACG,OAAO,CAACJ,KAAK,CAAC,KAAK,EAAE,EAAE;AAC/BC,MAAAA,KAAK,CAACI,IAAI,CAACL,KAAK,CAAC;AACnB,IAAA;AACF,EAAA;;AAEA;AACA;AACAM,EAAAA,MAAMA,CAACP,SAAS,EAAEC,KAAK,EAAE;IACvB,IAAI,CAACA,KAAK,EAAE;AACV,MAAA;AACF,IAAA;IACA,IAAIC,KAAK,GAAG,IAAI,CAAC,OAAO,CAACC,GAAG,CAACH,SAAS,CAAC;AACvC,IAAA,IAAIE,KAAK,EAAE;AACT,MAAA,IAAIM,GAAG,GAAGN,KAAK,CAACG,OAAO,CAACJ,KAAK,CAAC;AAC9B,MAAA,IAAIO,GAAG,KAAK,EAAE,EAAE;AACdN,QAAAA,KAAK,CAACO,MAAM,CAACD,GAAG,EAAE,CAAC,CAAC;AACtB,MAAA;AACF,IAAA;IACA,IAAIP,KAAK,CAACS,UAAU,EAAE;AACpBT,MAAAA,KAAK,CAACS,UAAU,CAACC,WAAW,CAACV,KAAK,CAAC;AACrC,IAAA;AACF,EAAA;;AAEA;AACA;EACAW,KAAKA,CAACZ,SAAS,EAAE;IACf,IAAIE,KAAK,GAAG,IAAI,CAAC,OAAO,CAACC,GAAG,CAACH,SAAS,CAAC;IACvC,IAAI,CAACE,KAAK,EAAE;AACV,MAAA;AACF,IAAA;AACA,IAAA,IAAID,KAAK;AACT,IAAA,OAAQA,KAAK,GAAGC,KAAK,CAACW,GAAG,EAAE,EAAG;MAC5B,IAAIZ,KAAK,CAACS,UAAU,EAAE;AACpBT,QAAAA,KAAK,CAACS,UAAU,CAACC,WAAW,CAACV,KAAK,CAAC;AACrC,MAAA;AACF,IAAA;AACF,EAAA;;AAEA;AACA;AACAa,EAAAA,QAAQA,GAAG;IACT,KAAK,IAAId,SAAS,IAAI,IAAI,CAAC,OAAO,CAACe,IAAI,EAAE,EAAE;AACzC,MAAA,IAAI,CAACH,KAAK,CAACZ,SAAS,CAAC;AACvB,IAAA;AACF,EAAA;;AAEA;AACA;AACA;EACAgB,IAAIA,CAAChB,SAAS,EAAE;IACd,IAAIE,KAAK,GAAG,IAAI,CAAC,OAAO,CAACC,GAAG,CAACH,SAAS,CAAC;AACvC,IAAA,OAAOE,KAAK,GAAGA,KAAK,CAACe,MAAM,GAAG,CAAC,CAAC;AAClC,EAAA;AACF;;;;"}
@@ -0,0 +1,121 @@
1
+ import { macroCondition, isDevelopingApp } from '@embroider/macros';
2
+
3
+ function extractComponentKey(componentRef) {
4
+ if (!componentRef) {
5
+ return 'none';
6
+ }
7
+ let result = getComponentRefName(componentRef);
8
+ // Dev-only safeguard: if every access path failed to produce a name, the
9
+ // Glimmer reference's shape has likely changed (it has before — see the
10
+ // INNER-slot-as-string vs. -as-object update further down) and we'd
11
+ // otherwise return `undefined` silently. That makes
12
+ // `rootComponentKey !== this._rootComponentKey` always compare
13
+ // `'undefined' === 'undefined'`, so root-change cuts (e.g. tab switches)
14
+ // would never fire and the addon would silently fall through to a slide
15
+ // animation. Failing loud here surfaces the breakage at the source.
16
+ //
17
+ // Gated on `@embroider/macros`'s `isDevelopingApp()` so the entire check
18
+ // (including the message string) is stripped from production builds by
19
+ // the consumer's babel pipeline — both Embroider and classic ember-cli
20
+ // run the macros transform on v2 addon code, unlike
21
+ // `babel-plugin-debug-macros` (which only applies to app + v1 addon
22
+ // code in classic builds and would leave the string in the bundle).
23
+ if (macroCondition(isDevelopingApp())) {
24
+ if (result === undefined) {
25
+ throw new Error('[ember-nav-stack] extractComponentKey could not derive a name from the component reference. The Glimmer reference shape has likely changed; see src/utils/component.js.');
26
+ }
27
+ }
28
+ let modelId = getComponentRefModelId(componentRef);
29
+ if (modelId) {
30
+ result += `:${modelId}`;
31
+ }
32
+ return result;
33
+ }
34
+
35
+ // Component-name-only key for a curried component reference. Skips the
36
+ // `model.id` appendage that `extractComponentKey` adds because, unlike the
37
+ // component name (which is set at curry time), the model ref's `lastValue`
38
+ // resolves lazily — the active-item-change path needs a key that's stable
39
+ // the moment the curry exists, otherwise a single push fires twice as the
40
+ // model id transitions from undefined to its concrete value across two
41
+ // render ticks.
42
+ function extractComponentName(componentRef) {
43
+ if (!componentRef) {
44
+ return 'none';
45
+ }
46
+ return getComponentRefName(componentRef);
47
+ }
48
+ function getComponentRefName(componentRef) {
49
+ if (componentRef.name) {
50
+ return componentRef.name;
51
+ }
52
+ let innerSym = Object.getOwnPropertySymbols(componentRef).find(s => s.description === 'INNER');
53
+ let inner = componentRef.inner || innerSym && componentRef[innerSym];
54
+ // In modern Glimmer, a curried component's INNER slot holds the resolved
55
+ // component name as a string. Older builds wrapped it in an object with
56
+ // a `.name` property.
57
+ if (typeof inner === 'string') {
58
+ return inner;
59
+ }
60
+ return inner?.name;
61
+ }
62
+
63
+ // Resolve a Glimmer reference to its current value without importing from
64
+ // `@glimmer/reference` (which would couple this addon to a specific
65
+ // Glimmer release line and cause dedupe headaches under strict resolvers).
66
+ // Three paths:
67
+ // 1. `ref.value()` — classic Glimmer (pre-2020-ish).
68
+ // 2. `ref.compute()` — modern Glimmer. Calls the underlying compute
69
+ // function, which both recomputes the live value AND consumes the
70
+ // ref's tag so we participate in autotracking. This is critical for
71
+ // the `stackItemsFingerprint` getter: without it, we'd be reading
72
+ // `lastValue` (a snapshot from the last render that consumed this
73
+ // ref) and miss in-place arg changes — e.g. a parent route swapping
74
+ // its `model` while keeping the same curried component instance
75
+ // alive. The fingerprint would then stay stale and the NavStack
76
+ // would miss the root-key change entirely (next stack mutation
77
+ // would lump the root change with the depth change and degenerate
78
+ // to a cut, suppressing the expected slide).
79
+ // 3. `ref.lastValue` — last-resort fallback.
80
+ // `extractComponentKey` is
81
+ // invoked from `<NavStack>`'s reactive `stackItemsFingerprint` getter
82
+ // whose own re-evaluation is what propagated the change in the first place.
83
+ function valueForRef(ref) {
84
+ if (!ref) {
85
+ return ref;
86
+ }
87
+ if (typeof ref.value === 'function') {
88
+ return ref.value();
89
+ }
90
+ if (typeof ref.compute === 'function') {
91
+ return ref.compute();
92
+ }
93
+ return ref.lastValue;
94
+ }
95
+ function getComponentRefModelId(componentRef) {
96
+ let argsSym = Object.getOwnPropertySymbols(componentRef).find(s => s.description === 'ARGS');
97
+ let args = componentRef.args || argsSym && componentRef[argsSym];
98
+ if (!args || !args.named) {
99
+ return;
100
+ }
101
+ let named = args.named;
102
+ let modelRef;
103
+ if (typeof named.has === 'function' && named.has('model')) {
104
+ // Older Glimmer exposed named args as a Map-like with has/get.
105
+ modelRef = named.get('model');
106
+ } else if (named.model) {
107
+ // Modern Glimmer: args.named is a POJO of references keyed by arg name.
108
+ modelRef = named.model;
109
+ }
110
+ if (!modelRef) {
111
+ return;
112
+ }
113
+ let model = valueForRef(modelRef);
114
+ if (model) {
115
+ return model.id;
116
+ }
117
+ return;
118
+ }
119
+
120
+ export { extractComponentKey, extractComponentName };
121
+ //# sourceMappingURL=component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component.js","sources":["../../src/utils/component.js"],"sourcesContent":["import { macroCondition, isDevelopingApp } from '@embroider/macros';\n\nexport function extractComponentKey(componentRef) {\n if (!componentRef) {\n return 'none';\n }\n let result = getComponentRefName(componentRef);\n // Dev-only safeguard: if every access path failed to produce a name, the\n // Glimmer reference's shape has likely changed (it has before — see the\n // INNER-slot-as-string vs. -as-object update further down) and we'd\n // otherwise return `undefined` silently. That makes\n // `rootComponentKey !== this._rootComponentKey` always compare\n // `'undefined' === 'undefined'`, so root-change cuts (e.g. tab switches)\n // would never fire and the addon would silently fall through to a slide\n // animation. Failing loud here surfaces the breakage at the source.\n //\n // Gated on `@embroider/macros`'s `isDevelopingApp()` so the entire check\n // (including the message string) is stripped from production builds by\n // the consumer's babel pipeline — both Embroider and classic ember-cli\n // run the macros transform on v2 addon code, unlike\n // `babel-plugin-debug-macros` (which only applies to app + v1 addon\n // code in classic builds and would leave the string in the bundle).\n if (macroCondition(isDevelopingApp())) {\n if (result === undefined) {\n throw new Error(\n '[ember-nav-stack] extractComponentKey could not derive a name from the component reference. The Glimmer reference shape has likely changed; see src/utils/component.js.',\n );\n }\n }\n let modelId = getComponentRefModelId(componentRef);\n if (modelId) {\n result += `:${modelId}`;\n }\n return result;\n}\n\n// Component-name-only key for a curried component reference. Skips the\n// `model.id` appendage that `extractComponentKey` adds because, unlike the\n// component name (which is set at curry time), the model ref's `lastValue`\n// resolves lazily — the active-item-change path needs a key that's stable\n// the moment the curry exists, otherwise a single push fires twice as the\n// model id transitions from undefined to its concrete value across two\n// render ticks.\nexport function extractComponentName(componentRef) {\n if (!componentRef) {\n return 'none';\n }\n return getComponentRefName(componentRef);\n}\n\nfunction getComponentRefName(componentRef) {\n if (componentRef.name) {\n return componentRef.name;\n }\n let innerSym = Object.getOwnPropertySymbols(componentRef).find(\n (s) => s.description === 'INNER',\n );\n let inner = componentRef.inner || (innerSym && componentRef[innerSym]);\n // In modern Glimmer, a curried component's INNER slot holds the resolved\n // component name as a string. Older builds wrapped it in an object with\n // a `.name` property.\n if (typeof inner === 'string') {\n return inner;\n }\n return inner?.name;\n}\n\n// Resolve a Glimmer reference to its current value without importing from\n// `@glimmer/reference` (which would couple this addon to a specific\n// Glimmer release line and cause dedupe headaches under strict resolvers).\n// Three paths:\n// 1. `ref.value()` — classic Glimmer (pre-2020-ish).\n// 2. `ref.compute()` — modern Glimmer. Calls the underlying compute\n// function, which both recomputes the live value AND consumes the\n// ref's tag so we participate in autotracking. This is critical for\n// the `stackItemsFingerprint` getter: without it, we'd be reading\n// `lastValue` (a snapshot from the last render that consumed this\n// ref) and miss in-place arg changes — e.g. a parent route swapping\n// its `model` while keeping the same curried component instance\n// alive. The fingerprint would then stay stale and the NavStack\n// would miss the root-key change entirely (next stack mutation\n// would lump the root change with the depth change and degenerate\n// to a cut, suppressing the expected slide).\n// 3. `ref.lastValue` — last-resort fallback.\n// `extractComponentKey` is\n// invoked from `<NavStack>`'s reactive `stackItemsFingerprint` getter\n// whose own re-evaluation is what propagated the change in the first place.\nfunction valueForRef(ref) {\n if (!ref) {\n return ref;\n }\n if (typeof ref.value === 'function') {\n return ref.value();\n }\n if (typeof ref.compute === 'function') {\n return ref.compute();\n }\n return ref.lastValue;\n}\n\nfunction getComponentRefModelId(componentRef) {\n let argsSym = Object.getOwnPropertySymbols(componentRef).find(\n (s) => s.description === 'ARGS',\n );\n let args = componentRef.args || (argsSym && componentRef[argsSym]);\n if (!args || !args.named) {\n return;\n }\n let named = args.named;\n let modelRef;\n if (typeof named.has === 'function' && named.has('model')) {\n // Older Glimmer exposed named args as a Map-like with has/get.\n modelRef = named.get('model');\n } else if (named.model) {\n // Modern Glimmer: args.named is a POJO of references keyed by arg name.\n modelRef = named.model;\n }\n if (!modelRef) {\n return;\n }\n let model = valueForRef(modelRef);\n if (model) {\n return model.id;\n }\n return;\n}\n"],"names":["extractComponentKey","componentRef","result","getComponentRefName","macroCondition","isDevelopingApp","undefined","Error","modelId","getComponentRefModelId","extractComponentName","name","innerSym","Object","getOwnPropertySymbols","find","s","description","inner","valueForRef","ref","value","compute","lastValue","argsSym","args","named","modelRef","has","get","model","id"],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { setTransform } from './animation.js';
2
+
3
+ // Pixels of parallax applied to the header elements during a horizontal
4
+ // transition. The currentHeaderContainer slides this far while the parent
5
+ // (target page) header slides in from the opposite direction.
6
+ const HEADER_PARALLAX_OFFSET = 60;
7
+
8
+ // Maps an animation's current value (e.g. translateX in CSS px) to a 0..1
9
+ // ratio representing how far the transition has progressed from fromValue
10
+ // to toValue. Returns 1 when fromValue is unset or equal to toValue (used by
11
+ // the no-op case in horizontalTransition).
12
+ function currentTransitionPercentage(fromValue, toValue, currentValue) {
13
+ if (fromValue === undefined || fromValue === toValue) {
14
+ return 1;
15
+ }
16
+ let percentage = Math.abs((currentValue - fromValue) / (toValue - fromValue));
17
+ if (toValue > fromValue) {
18
+ return 1 - percentage;
19
+ }
20
+ return percentage;
21
+ }
22
+
23
+ // Cross-fades and translates two header elements as a horizontal transition
24
+ // progresses. `isForward` controls which direction the offset goes (forward
25
+ // transitions push the leaving header rightward; back transitions leftward).
26
+ // Either element argument may be null — both are conditionally available
27
+ // during slideBack/slideForward and the swipe-driven path.
28
+ function styleHeaderElements(transitionRatio, isForward, currentHeaderElement, otherHeaderElement) {
29
+ let startingOffset = HEADER_PARALLAX_OFFSET;
30
+ if (!isForward) {
31
+ transitionRatio = 1 - transitionRatio;
32
+ startingOffset = -1 * startingOffset;
33
+ }
34
+ let xOffset = transitionRatio * -1 * startingOffset;
35
+ if (currentHeaderElement) {
36
+ currentHeaderElement.style.opacity = transitionRatio;
37
+ setTransform(currentHeaderElement, `translateX(${startingOffset + xOffset}px)`);
38
+ }
39
+ if (otherHeaderElement) {
40
+ otherHeaderElement.style.opacity = 1 - transitionRatio;
41
+ setTransform(otherHeaderElement, `translateX(${xOffset}px)`);
42
+ }
43
+ }
44
+
45
+ export { HEADER_PARALLAX_OFFSET, currentTransitionPercentage, styleHeaderElements };
46
+ //# sourceMappingURL=header-style.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header-style.js","sources":["../../src/utils/header-style.js"],"sourcesContent":["import { setTransform } from './animation.js';\n\n// Pixels of parallax applied to the header elements during a horizontal\n// transition. The currentHeaderContainer slides this far while the parent\n// (target page) header slides in from the opposite direction.\nexport const HEADER_PARALLAX_OFFSET = 60;\n\n// Maps an animation's current value (e.g. translateX in CSS px) to a 0..1\n// ratio representing how far the transition has progressed from fromValue\n// to toValue. Returns 1 when fromValue is unset or equal to toValue (used by\n// the no-op case in horizontalTransition).\nexport function currentTransitionPercentage(fromValue, toValue, currentValue) {\n if (fromValue === undefined || fromValue === toValue) {\n return 1;\n }\n let percentage = Math.abs((currentValue - fromValue) / (toValue - fromValue));\n if (toValue > fromValue) {\n return 1 - percentage;\n }\n return percentage;\n}\n\n// Cross-fades and translates two header elements as a horizontal transition\n// progresses. `isForward` controls which direction the offset goes (forward\n// transitions push the leaving header rightward; back transitions leftward).\n// Either element argument may be null — both are conditionally available\n// during slideBack/slideForward and the swipe-driven path.\nexport function styleHeaderElements(\n transitionRatio,\n isForward,\n currentHeaderElement,\n otherHeaderElement,\n) {\n let startingOffset = HEADER_PARALLAX_OFFSET;\n if (!isForward) {\n transitionRatio = 1 - transitionRatio;\n startingOffset = -1 * startingOffset;\n }\n let xOffset = transitionRatio * -1 * startingOffset;\n if (currentHeaderElement) {\n currentHeaderElement.style.opacity = transitionRatio;\n setTransform(\n currentHeaderElement,\n `translateX(${startingOffset + xOffset}px)`,\n );\n }\n if (otherHeaderElement) {\n otherHeaderElement.style.opacity = 1 - transitionRatio;\n setTransform(otherHeaderElement, `translateX(${xOffset}px)`);\n }\n}\n"],"names":["HEADER_PARALLAX_OFFSET","currentTransitionPercentage","fromValue","toValue","currentValue","undefined","percentage","Math","abs","styleHeaderElements","transitionRatio","isForward","currentHeaderElement","otherHeaderElement","startingOffset","xOffset","style","opacity","setTransform"],"mappings":";;AAEA;AACA;AACA;AACO,MAAMA,sBAAsB,GAAG;;AAEtC;AACA;AACA;AACA;AACO,SAASC,2BAA2BA,CAACC,SAAS,EAAEC,OAAO,EAAEC,YAAY,EAAE;AAC5E,EAAA,IAAIF,SAAS,KAAKG,SAAS,IAAIH,SAAS,KAAKC,OAAO,EAAE;AACpD,IAAA,OAAO,CAAC;AACV,EAAA;AACA,EAAA,IAAIG,UAAU,GAAGC,IAAI,CAACC,GAAG,CAAC,CAACJ,YAAY,GAAGF,SAAS,KAAKC,OAAO,GAAGD,SAAS,CAAC,CAAC;EAC7E,IAAIC,OAAO,GAAGD,SAAS,EAAE;IACvB,OAAO,CAAC,GAAGI,UAAU;AACvB,EAAA;AACA,EAAA,OAAOA,UAAU;AACnB;;AAEA;AACA;AACA;AACA;AACA;AACO,SAASG,mBAAmBA,CACjCC,eAAe,EACfC,SAAS,EACTC,oBAAoB,EACpBC,kBAAkB,EAClB;EACA,IAAIC,cAAc,GAAGd,sBAAsB;EAC3C,IAAI,CAACW,SAAS,EAAE;IACdD,eAAe,GAAG,CAAC,GAAGA,eAAe;AACrCI,IAAAA,cAAc,GAAG,EAAE,GAAGA,cAAc;AACtC,EAAA;AACA,EAAA,IAAIC,OAAO,GAAGL,eAAe,GAAG,EAAE,GAAGI,cAAc;AACnD,EAAA,IAAIF,oBAAoB,EAAE;AACxBA,IAAAA,oBAAoB,CAACI,KAAK,CAACC,OAAO,GAAGP,eAAe;IACpDQ,YAAY,CACVN,oBAAoB,EACpB,CAAA,WAAA,EAAcE,cAAc,GAAGC,OAAO,KACxC,CAAC;AACH,EAAA;AACA,EAAA,IAAIF,kBAAkB,EAAE;AACtBA,IAAAA,kBAAkB,CAACG,KAAK,CAACC,OAAO,GAAG,CAAC,GAAGP,eAAe;AACtDQ,IAAAA,YAAY,CAACL,kBAAkB,EAAE,CAAA,WAAA,EAAcE,OAAO,KAAK,CAAC;AAC9D,EAAA;AACF;;;;"}
@@ -0,0 +1,71 @@
1
+ // Pure function mapping an observed stack-state change to a transition intent.
2
+ //
3
+ // The full truth table is exercised by
4
+ // test-app/tests/unit/utils/transition-decision-test.js. Summary:
5
+ //
6
+ // isInitialRender → cut
7
+ // layer>0, prev=0, next>0 → slideUp (underlay appears)
8
+ // prev=undefined (first observation) → slideUp
9
+ // layer>0, prev>0, next=0 → slideDown (underlay disappears)
10
+ // newRootKey ≠ previousRootKey → cut, startsRootChange=true
11
+ // next<prev, !rootJustChanged → slideBack
12
+ // next<prev, rootJustChanged → cut (depth change rides on root change)
13
+ // next>prev, !rootJustChanged → slideForward
14
+ // next>prev, rootJustChanged → cut
15
+ // next===prev, same root, !initial → none
16
+ //
17
+ // `rootJustChanged` is set by the caller when a previous call in the same
18
+ // runloop tick produced a root-change intent. It exists because multiple
19
+ // service flushes can land in the same tick (e.g. parent-route swap +
20
+ // drilled-child exit during a tab switch); without coalescing, the second
21
+ // flush would animate over the cut from the first.
22
+
23
+ function decideTransition({
24
+ isInitialRender,
25
+ previousDepth,
26
+ newDepth,
27
+ previousRootKey,
28
+ newRootKey,
29
+ layer,
30
+ rootJustChanged
31
+ }) {
32
+ if (isInitialRender) {
33
+ return {
34
+ kind: 'cut'
35
+ };
36
+ }
37
+ let appearsAsUnderlay = layer > 0 && newDepth > 0 && previousDepth === 0;
38
+ if (appearsAsUnderlay || previousDepth === undefined) {
39
+ return {
40
+ kind: 'slideUp'
41
+ };
42
+ }
43
+ let disappearsAsUnderlay = layer > 0 && newDepth === 0 && previousDepth > 0;
44
+ if (disappearsAsUnderlay) {
45
+ return {
46
+ kind: 'slideDown'
47
+ };
48
+ }
49
+ if (newRootKey !== previousRootKey) {
50
+ return {
51
+ kind: 'cut',
52
+ startsRootChange: true
53
+ };
54
+ }
55
+ if (newDepth < previousDepth) {
56
+ return {
57
+ kind: rootJustChanged ? 'cut' : 'slideBack'
58
+ };
59
+ }
60
+ if (newDepth > previousDepth) {
61
+ return {
62
+ kind: rootJustChanged ? 'cut' : 'slideForward'
63
+ };
64
+ }
65
+ return {
66
+ kind: 'none'
67
+ };
68
+ }
69
+
70
+ export { decideTransition };
71
+ //# sourceMappingURL=transition-decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transition-decision.js","sources":["../../src/utils/transition-decision.js"],"sourcesContent":["// Pure function mapping an observed stack-state change to a transition intent.\n//\n// The full truth table is exercised by\n// test-app/tests/unit/utils/transition-decision-test.js. Summary:\n//\n// isInitialRender → cut\n// layer>0, prev=0, next>0 → slideUp (underlay appears)\n// prev=undefined (first observation) → slideUp\n// layer>0, prev>0, next=0 → slideDown (underlay disappears)\n// newRootKey ≠ previousRootKey → cut, startsRootChange=true\n// next<prev, !rootJustChanged → slideBack\n// next<prev, rootJustChanged → cut (depth change rides on root change)\n// next>prev, !rootJustChanged → slideForward\n// next>prev, rootJustChanged → cut\n// next===prev, same root, !initial → none\n//\n// `rootJustChanged` is set by the caller when a previous call in the same\n// runloop tick produced a root-change intent. It exists because multiple\n// service flushes can land in the same tick (e.g. parent-route swap +\n// drilled-child exit during a tab switch); without coalescing, the second\n// flush would animate over the cut from the first.\n\nexport function decideTransition({\n isInitialRender,\n previousDepth,\n newDepth,\n previousRootKey,\n newRootKey,\n layer,\n rootJustChanged,\n}) {\n if (isInitialRender) {\n return { kind: 'cut' };\n }\n\n let appearsAsUnderlay = layer > 0 && newDepth > 0 && previousDepth === 0;\n if (appearsAsUnderlay || previousDepth === undefined) {\n return { kind: 'slideUp' };\n }\n\n let disappearsAsUnderlay = layer > 0 && newDepth === 0 && previousDepth > 0;\n if (disappearsAsUnderlay) {\n return { kind: 'slideDown' };\n }\n\n if (newRootKey !== previousRootKey) {\n return { kind: 'cut', startsRootChange: true };\n }\n\n if (newDepth < previousDepth) {\n return { kind: rootJustChanged ? 'cut' : 'slideBack' };\n }\n if (newDepth > previousDepth) {\n return { kind: rootJustChanged ? 'cut' : 'slideForward' };\n }\n\n return { kind: 'none' };\n}\n"],"names":["decideTransition","isInitialRender","previousDepth","newDepth","previousRootKey","newRootKey","layer","rootJustChanged","kind","appearsAsUnderlay","undefined","disappearsAsUnderlay","startsRootChange"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEO,SAASA,gBAAgBA,CAAC;EAC/BC,eAAe;EACfC,aAAa;EACbC,QAAQ;EACRC,eAAe;EACfC,UAAU;EACVC,KAAK;AACLC,EAAAA;AACF,CAAC,EAAE;AACD,EAAA,IAAIN,eAAe,EAAE;IACnB,OAAO;AAAEO,MAAAA,IAAI,EAAE;KAAO;AACxB,EAAA;AAEA,EAAA,IAAIC,iBAAiB,GAAGH,KAAK,GAAG,CAAC,IAAIH,QAAQ,GAAG,CAAC,IAAID,aAAa,KAAK,CAAC;AACxE,EAAA,IAAIO,iBAAiB,IAAIP,aAAa,KAAKQ,SAAS,EAAE;IACpD,OAAO;AAAEF,MAAAA,IAAI,EAAE;KAAW;AAC5B,EAAA;AAEA,EAAA,IAAIG,oBAAoB,GAAGL,KAAK,GAAG,CAAC,IAAIH,QAAQ,KAAK,CAAC,IAAID,aAAa,GAAG,CAAC;AAC3E,EAAA,IAAIS,oBAAoB,EAAE;IACxB,OAAO;AAAEH,MAAAA,IAAI,EAAE;KAAa;AAC9B,EAAA;EAEA,IAAIH,UAAU,KAAKD,eAAe,EAAE;IAClC,OAAO;AAAEI,MAAAA,IAAI,EAAE,KAAK;AAAEI,MAAAA,gBAAgB,EAAE;KAAM;AAChD,EAAA;EAEA,IAAIT,QAAQ,GAAGD,aAAa,EAAE;IAC5B,OAAO;AAAEM,MAAAA,IAAI,EAAED,eAAe,GAAG,KAAK,GAAG;KAAa;AACxD,EAAA;EACA,IAAIJ,QAAQ,GAAGD,aAAa,EAAE;IAC5B,OAAO;AAAEM,MAAAA,IAAI,EAAED,eAAe,GAAG,KAAK,GAAG;KAAgB;AAC3D,EAAA;EAEA,OAAO;AAAEC,IAAAA,IAAI,EAAE;GAAQ;AACzB;;;;"}
@@ -0,0 +1,130 @@
1
+ import { buildWaiter } from '@ember/test-waiters';
2
+
3
+ // `buildWaiter` registers a named waiter globally in `@ember/test-waiters`.
4
+ // Hoist these to module scope so every WaiterState instance shares the same
5
+ // two waiters — without this, each `new WaiterState()` (notably in unit
6
+ // tests) would register a fresh duplicate, accumulating noise in the global
7
+ // waiter registry over a test run.
8
+ const TRANSITION_WAITER = buildWaiter('ember-nav-stack:transition');
9
+ const STACK_WAITER = buildWaiter('ember-nav-stack:stack-update');
10
+
11
+ // Encapsulates the three test-waiter tokens NavStacks needs to manage
12
+ // (transition, stack-update, initial-render), a counter for currently
13
+ // running transitions, and a single-resolver "wait until idle" promise.
14
+ //
15
+ // State diagram:
16
+ //
17
+ // Tokens (each independently OPEN or CLOSED):
18
+ // - transition opens when runningTransitions goes 0 → 1,
19
+ // closes when runningTransitions returns to 0
20
+ // - stackUpdate opens on beginStackUpdate (idempotent),
21
+ // closes on endStackUpdate
22
+ // - initialRender opens once at construction, closes once on
23
+ // markInitialRenderComplete (subsequent calls
24
+ // are no-ops)
25
+ //
26
+ // isIdle === all three tokens CLOSED && runningTransitions === 0
27
+ //
28
+ // whenIdle() returns a Promise that resolves the next time isIdle()
29
+ // becomes true and maybeResolveIdle() is invoked. Repeat callers
30
+ // while waiting share one promise.
31
+ //
32
+ // `maybeResolveIdle()` is an explicit method (not auto-called from
33
+ // end*/markX) so the caller can wrap it in `next()` (or any other
34
+ // scheduler) — that defers resolution past the current runloop tick,
35
+ // giving other code in the same tick a chance to start another
36
+ // transition before "idle" is declared.
37
+ class WaiterState {
38
+ constructor() {
39
+ this._transitionToken = null;
40
+ this._stackUpdateToken = null;
41
+ this._initialRenderToken = STACK_WAITER.beginAsync();
42
+ this._runningTransitions = 0;
43
+ this._whenIdlePromise = null;
44
+ this._whenIdleResolve = null;
45
+ }
46
+
47
+ // Transition counter
48
+
49
+ beginTransition() {
50
+ this._runningTransitions++;
51
+ if (this._runningTransitions === 1) {
52
+ this._transitionToken = TRANSITION_WAITER.beginAsync();
53
+ }
54
+ }
55
+ endTransition() {
56
+ this._runningTransitions--;
57
+ if (this._runningTransitions < 0) {
58
+ this._runningTransitions = 0;
59
+ }
60
+ if (this._runningTransitions === 0 && this._transitionToken) {
61
+ TRANSITION_WAITER.endAsync(this._transitionToken);
62
+ this._transitionToken = null;
63
+ }
64
+ }
65
+ runningTransitions() {
66
+ return this._runningTransitions;
67
+ }
68
+ isRunningTransitions() {
69
+ return this._runningTransitions > 0;
70
+ }
71
+
72
+ // Stack-update token (coalesced across pushItem/removeItem in one flush)
73
+
74
+ beginStackUpdate() {
75
+ if (!this._stackUpdateToken) {
76
+ this._stackUpdateToken = STACK_WAITER.beginAsync();
77
+ }
78
+ }
79
+ endStackUpdate() {
80
+ if (this._stackUpdateToken) {
81
+ STACK_WAITER.endAsync(this._stackUpdateToken);
82
+ this._stackUpdateToken = null;
83
+ }
84
+ }
85
+ hasPendingStackUpdate() {
86
+ return !!this._stackUpdateToken;
87
+ }
88
+
89
+ // Initial-render token (open from construction, closed at most once)
90
+
91
+ markInitialRenderComplete() {
92
+ if (this._initialRenderToken) {
93
+ STACK_WAITER.endAsync(this._initialRenderToken);
94
+ this._initialRenderToken = null;
95
+ }
96
+ }
97
+ hasPendingInitialRender() {
98
+ return !!this._initialRenderToken;
99
+ }
100
+
101
+ // Idle queries + promise
102
+
103
+ isIdle() {
104
+ return this._runningTransitions === 0 && !this._stackUpdateToken && !this._initialRenderToken;
105
+ }
106
+ whenIdle() {
107
+ if (this._whenIdlePromise) {
108
+ return this._whenIdlePromise;
109
+ }
110
+ this._whenIdlePromise = new Promise(resolve => {
111
+ this._whenIdleResolve = resolve;
112
+ });
113
+ return this._whenIdlePromise;
114
+ }
115
+
116
+ // Resolves any outstanding whenIdle() promise if all tokens are closed
117
+ // and no transitions are running. No-op otherwise. Callers schedule this
118
+ // via `next()` (or similar) to defer resolution past the current tick.
119
+ maybeResolveIdle() {
120
+ if (this.isIdle() && this._whenIdleResolve) {
121
+ let resolve = this._whenIdleResolve;
122
+ this._whenIdleResolve = null;
123
+ this._whenIdlePromise = null;
124
+ resolve();
125
+ }
126
+ }
127
+ }
128
+
129
+ export { WaiterState as default };
130
+ //# sourceMappingURL=waiter-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waiter-state.js","sources":["../../src/utils/waiter-state.js"],"sourcesContent":["import { buildWaiter } from '@ember/test-waiters';\n\n// `buildWaiter` registers a named waiter globally in `@ember/test-waiters`.\n// Hoist these to module scope so every WaiterState instance shares the same\n// two waiters — without this, each `new WaiterState()` (notably in unit\n// tests) would register a fresh duplicate, accumulating noise in the global\n// waiter registry over a test run.\nconst TRANSITION_WAITER = buildWaiter('ember-nav-stack:transition');\nconst STACK_WAITER = buildWaiter('ember-nav-stack:stack-update');\n\n// Encapsulates the three test-waiter tokens NavStacks needs to manage\n// (transition, stack-update, initial-render), a counter for currently\n// running transitions, and a single-resolver \"wait until idle\" promise.\n//\n// State diagram:\n//\n// Tokens (each independently OPEN or CLOSED):\n// - transition opens when runningTransitions goes 0 → 1,\n// closes when runningTransitions returns to 0\n// - stackUpdate opens on beginStackUpdate (idempotent),\n// closes on endStackUpdate\n// - initialRender opens once at construction, closes once on\n// markInitialRenderComplete (subsequent calls\n// are no-ops)\n//\n// isIdle === all three tokens CLOSED && runningTransitions === 0\n//\n// whenIdle() returns a Promise that resolves the next time isIdle()\n// becomes true and maybeResolveIdle() is invoked. Repeat callers\n// while waiting share one promise.\n//\n// `maybeResolveIdle()` is an explicit method (not auto-called from\n// end*/markX) so the caller can wrap it in `next()` (or any other\n// scheduler) — that defers resolution past the current runloop tick,\n// giving other code in the same tick a chance to start another\n// transition before \"idle\" is declared.\nexport default class WaiterState {\n constructor() {\n this._transitionToken = null;\n this._stackUpdateToken = null;\n this._initialRenderToken = STACK_WAITER.beginAsync();\n this._runningTransitions = 0;\n this._whenIdlePromise = null;\n this._whenIdleResolve = null;\n }\n\n // Transition counter\n\n beginTransition() {\n this._runningTransitions++;\n if (this._runningTransitions === 1) {\n this._transitionToken = TRANSITION_WAITER.beginAsync();\n }\n }\n\n endTransition() {\n this._runningTransitions--;\n if (this._runningTransitions < 0) {\n this._runningTransitions = 0;\n }\n if (this._runningTransitions === 0 && this._transitionToken) {\n TRANSITION_WAITER.endAsync(this._transitionToken);\n this._transitionToken = null;\n }\n }\n\n runningTransitions() {\n return this._runningTransitions;\n }\n\n isRunningTransitions() {\n return this._runningTransitions > 0;\n }\n\n // Stack-update token (coalesced across pushItem/removeItem in one flush)\n\n beginStackUpdate() {\n if (!this._stackUpdateToken) {\n this._stackUpdateToken = STACK_WAITER.beginAsync();\n }\n }\n\n endStackUpdate() {\n if (this._stackUpdateToken) {\n STACK_WAITER.endAsync(this._stackUpdateToken);\n this._stackUpdateToken = null;\n }\n }\n\n hasPendingStackUpdate() {\n return !!this._stackUpdateToken;\n }\n\n // Initial-render token (open from construction, closed at most once)\n\n markInitialRenderComplete() {\n if (this._initialRenderToken) {\n STACK_WAITER.endAsync(this._initialRenderToken);\n this._initialRenderToken = null;\n }\n }\n\n hasPendingInitialRender() {\n return !!this._initialRenderToken;\n }\n\n // Idle queries + promise\n\n isIdle() {\n return (\n this._runningTransitions === 0 &&\n !this._stackUpdateToken &&\n !this._initialRenderToken\n );\n }\n\n whenIdle() {\n if (this._whenIdlePromise) {\n return this._whenIdlePromise;\n }\n this._whenIdlePromise = new Promise((resolve) => {\n this._whenIdleResolve = resolve;\n });\n return this._whenIdlePromise;\n }\n\n // Resolves any outstanding whenIdle() promise if all tokens are closed\n // and no transitions are running. No-op otherwise. Callers schedule this\n // via `next()` (or similar) to defer resolution past the current tick.\n maybeResolveIdle() {\n if (this.isIdle() && this._whenIdleResolve) {\n let resolve = this._whenIdleResolve;\n this._whenIdleResolve = null;\n this._whenIdlePromise = null;\n resolve();\n }\n }\n}\n"],"names":["TRANSITION_WAITER","buildWaiter","STACK_WAITER","WaiterState","constructor","_transitionToken","_stackUpdateToken","_initialRenderToken","beginAsync","_runningTransitions","_whenIdlePromise","_whenIdleResolve","beginTransition","endTransition","endAsync","runningTransitions","isRunningTransitions","beginStackUpdate","endStackUpdate","hasPendingStackUpdate","markInitialRenderComplete","hasPendingInitialRender","isIdle","whenIdle","Promise","resolve","maybeResolveIdle"],"mappings":"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"}