@leavepulse/ui 0.76.0 → 0.76.1

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 CHANGED
@@ -113,7 +113,10 @@ provideOverlayHistory(true, { router })
113
113
  `update:open` true while the overlay is still mounted.
114
114
  - `history` on a single overlay wins over the app-wide mode: `false` keeps an
115
115
  overlay out of history, for one the app already opens from its route query
116
- (it would otherwise take two entries for one dialog).
116
+ (it would otherwise take two entries for one dialog). An overlay the app
117
+ writes into the query with `router.replace` needs nothing: when it closes,
118
+ the address the app left is kept, and the overlay does not open again from
119
+ the entry under it.
117
120
  - A real navigation (a link, `router.push` to another path) closes the open
118
121
  overlays, and the entries they leave under the new page are skipped, so Back
119
122
  from it lands on the page in one press. A push that keeps the path (a query
@@ -11,6 +11,7 @@ const fresh = {
11
11
  closed: /* @__PURE__ */ new Map(),
12
12
  dead: /* @__PURE__ */ new Map(),
13
13
  silent: 0,
14
+ keep: null,
14
15
  waiting: [],
15
16
  href: "",
16
17
  serial: 0,
@@ -75,6 +76,9 @@ function handlePop(event) {
75
76
  s.href = location.href;
76
77
  if (s.silent > 0) {
77
78
  s.silent--;
79
+ const keep = s.silent === 0 ? s.keep : null;
80
+ if (s.silent === 0) s.keep = null;
81
+ if (keep && keep.href !== location.href) restore(keep, event);
78
82
  if (before === location.href) event.stopImmediatePropagation();
79
83
  flushWaiting();
80
84
  return;
@@ -121,6 +125,14 @@ function handlePop(event) {
121
125
  leavePage();
122
126
  }
123
127
  }
128
+ function restore(keep, event) {
129
+ const mark = markOf(event.state);
130
+ const base = keep.state && typeof keep.state === "object" ? { ...keep.state } : {};
131
+ delete base[KEY];
132
+ own(() => history.replaceState(mark ? { ...base, [KEY]: mark } : base, "", keep.href));
133
+ s.href = location.href;
134
+ event.stopImmediatePropagation();
135
+ }
124
136
  const DEAD_LIMIT = 200;
125
137
  function bury(item) {
126
138
  s.dead.set(item.token, item);
@@ -217,6 +229,7 @@ function dropEntry(entry) {
217
229
  const positioned = onTop();
218
230
  const removed = s.live.splice(at);
219
231
  if (positioned) {
232
+ s.keep = { href: location.href, state: history.state };
220
233
  silentGo(-removed.length);
221
234
  return;
222
235
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leavepulse/ui",
3
- "version": "0.76.0",
3
+ "version": "0.76.1",
4
4
  "type": "module",
5
5
  "description": "LeavePulse token-driven Vue component kit.",
6
6
  "repository": {
@@ -31,6 +31,11 @@
31
31
  * entries keep working once Back returns past it.
32
32
  * - Someone else's `replaceState` on a kit entry keeps the kit's key, so a
33
33
  * router saving its scroll position does not orphan the entry.
34
+ * - An app that keeps an overlay in its query through `replace` rewrites the
35
+ * address of the kit's entry when the overlay closes, and the entry under it
36
+ * still holds the old one. Taking the entry out lands there, so the address
37
+ * the app left is put back on it and the step never reaches the router:
38
+ * handed over, it would navigate to the old query and open the overlay again.
34
39
  */
35
40
 
36
41
  const KEY = "__lpOverlay"
@@ -79,6 +84,8 @@ interface EngineState {
79
84
  dead: Map<string, Tracked>
80
85
  /** Pops the kit started itself and swallows on arrival. */
81
86
  silent: number
87
+ /** What the page showed before the kit took entries out, to be kept where the step lands. */
88
+ keep: { href: string; state: unknown } | null
82
89
  waiting: Array<() => void>
83
90
  href: string
84
91
  serial: number
@@ -98,6 +105,7 @@ const fresh: EngineState = {
98
105
  closed: new Map(),
99
106
  dead: new Map(),
100
107
  silent: 0,
108
+ keep: null,
101
109
  waiting: [],
102
110
  href: "",
103
111
  serial: 0,
@@ -174,6 +182,9 @@ function handlePop(event: PopStateEvent) {
174
182
  s.href = location.href
175
183
  if (s.silent > 0) {
176
184
  s.silent--
185
+ const keep = s.silent === 0 ? s.keep : null
186
+ if (s.silent === 0) s.keep = null
187
+ if (keep && keep.href !== location.href) restore(keep, event)
177
188
  if (before === location.href) event.stopImmediatePropagation()
178
189
  flushWaiting()
179
190
  return
@@ -229,6 +240,16 @@ function handlePop(event: PopStateEvent) {
229
240
  }
230
241
  }
231
242
 
243
+ /* The address the app left on the entry the kit took out, put back where the step landed. */
244
+ function restore(keep: { href: string; state: unknown }, event: PopStateEvent) {
245
+ const mark = markOf(event.state)
246
+ const base = keep.state && typeof keep.state === "object" ? { ...(keep.state as Record<string, unknown>) } : {}
247
+ delete base[KEY]
248
+ own(() => history.replaceState(mark ? { ...base, [KEY]: mark } : base, "", keep.href))
249
+ s.href = location.href
250
+ event.stopImmediatePropagation()
251
+ }
252
+
232
253
  /* Buried entries are kept by token; the oldest go first, history this deep is never walked back. */
233
254
  const DEAD_LIMIT = 200
234
255
  function bury(item: Tracked) {
@@ -364,6 +385,7 @@ export function dropEntry(entry: HistoryEntry): void {
364
385
  const positioned = onTop()
365
386
  const removed = s.live.splice(at)
366
387
  if (positioned) {
388
+ s.keep = { href: location.href, state: history.state }
367
389
  silentGo(-removed.length)
368
390
  return
369
391
  }