@inglorious/web 6.4.2 → 6.5.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
@@ -1045,6 +1045,10 @@ api.notify("navigate", "/users/456")
1045
1045
  // Or navigate back in history
1046
1046
  api.notify("navigate", -1)
1047
1047
 
1048
+ // Go back, or to "/users" when the app was opened on this page and has
1049
+ // nothing of its own to go back to (a deep link, a fresh tab)
1050
+ api.notify("back", "/users")
1051
+
1048
1052
  // With options
1049
1053
  api.notify("navigate", {
1050
1054
  to: "/users/456",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inglorious/web",
3
- "version": "6.4.2",
3
+ "version": "6.5.1",
4
4
  "description": "A new web framework that leverages the power of the Inglorious Store combined with the performance and simplicity of lit-html.",
5
5
  "author": "Matteo Antony Mistretta <antony.mistretta@gmail.com> (https://ingloriouscoderz.it)",
6
6
  "contributors": [
@@ -1,4 +1,4 @@
1
- import { clone, get, set } from "@inglorious/utils/objects"
1
+ import { clone, get, set } from "@inglorious/utils/object.js"
2
2
 
3
3
  /**
4
4
  * @typedef {import('../../types/form.js').FormEntity} FormEntity
@@ -1,4 +1,4 @@
1
- import { get } from "@inglorious/utils/objects"
1
+ import { get } from "@inglorious/utils/object.js"
2
2
 
3
3
  /**
4
4
  * Retrieves the validation error for a specific form field.
@@ -8,6 +8,8 @@ import { toCamelCase } from "@inglorious/utils/data-structures/string.js"
8
8
 
9
9
  const SKIP_FULL_MATCH_GROUP = 1 // .match() result at index 0 is the full string
10
10
  const REMOVE_COLON_PREFIX = 1
11
+ const FIRST_HISTORY_INDEX = 0 // The entry an app starts on, and the fallback for an entry that carries no index.
12
+ const PUSH_HISTORY_STEP = 1 // How far a push advances the index. A replacement leaves it where it is.
11
13
 
12
14
  export function createHandlers(routeConfig) {
13
15
  /**
@@ -25,6 +27,18 @@ export function createHandlers(routeConfig) {
25
27
  * @param {Api} api - The application API.
26
28
  */
27
29
  init(entity, payload, api) {
30
+ /* Stamp the entry the app starts on, so that reloading keeps the app in
31
+ its place in the session history instead of restarting the count.
32
+ `withTransitions` reads this index back to tell a push from a traversal
33
+ and in which direction, so it has to be set before anything navigates. */
34
+ if (window.history.state?.idx == null) {
35
+ window.history.replaceState(
36
+ { ...window.history.state, idx: FIRST_HISTORY_INDEX },
37
+ "",
38
+ )
39
+ }
40
+ entity.idx = window.history.state?.idx ?? FIRST_HISTORY_INDEX
41
+
28
42
  // Handle initial route
29
43
  const { pathname, search } = window.location
30
44
  const initialPath = pathname + search
@@ -107,6 +121,9 @@ export function createHandlers(routeConfig) {
107
121
  }
108
122
 
109
123
  updateRouter(entity, route)
124
+ // Adopt the index of the entry just traversed, so the next push counts
125
+ // from where the history actually is rather than from where it was.
126
+ entity.idx = window.history.state?.idx ?? FIRST_HISTORY_INDEX
110
127
 
111
128
  api.notify("routeChange", {
112
129
  route: entity.route,
@@ -184,6 +201,10 @@ export function createHandlers(routeConfig) {
184
201
  // Prepare history state
185
202
  const historyState = {
186
203
  ...state,
204
+ /* Written before the notification that triggers the render, so that
205
+ anything reading the history entry during that render sees the new
206
+ index. `withTransitions` depends on this ordering. */
207
+ idx: replace ? entity.idx : entity.idx + PUSH_HISTORY_STEP,
187
208
  route: entity.route,
188
209
  params: entity.params,
189
210
  query: entity.query,
@@ -193,10 +214,34 @@ export function createHandlers(routeConfig) {
193
214
  // Navigate
194
215
  const method = replace ? "replaceState" : "pushState"
195
216
  history[method](historyState, "", path)
217
+ // Advance the counter as well as stamping the entry, or the next push
218
+ // writes the same index and reads as a replacement.
219
+ entity.idx = historyState.idx
196
220
 
197
221
  api.notify("routeChange", historyState)
198
222
  },
199
223
 
224
+ /**
225
+ * Goes back one entry when the app has an entry of its own to go back to,
226
+ * and otherwise navigates to the fallback in place. An app opened on a deep
227
+ * link starts at the first index, where `history.back()` would leave the
228
+ * app or do nothing at all.
229
+ * @param {RouterEntity} entity - The router entity.
230
+ * @param {string|{to: string, params?: object, state?: object}} [fallback] - Where to go when there is nothing to go back to.
231
+ * @param {Api} api - The application API.
232
+ */
233
+ back(entity, fallback, api) {
234
+ if (entity.idx > FIRST_HISTORY_INDEX) {
235
+ history.back()
236
+ return
237
+ }
238
+
239
+ if (fallback == null) return
240
+
241
+ const options = typeof fallback === "string" ? { to: fallback } : fallback
242
+ api.notify(`#${entity.id}:navigate`, { ...options, replace: true })
243
+ },
244
+
200
245
  /**
201
246
  * Handles successful loading of a lazily-loaded route module.
202
247
  * Registers the loaded type in the runtime type registry via `api.setType`
package/types/router.d.ts CHANGED
@@ -38,6 +38,8 @@ export interface RouterEntity {
38
38
  query?: QueryParams
39
39
  /** The hash from the current URL. */
40
40
  hash?: string
41
+ /** The position of the current entry in the session history, counted from the entry the app started on. */
42
+ idx?: number
41
43
  /** Whether a route is currently loading asynchronously. */
42
44
  isLoading?: boolean
43
45
  /** An error that occurred during route loading. */
@@ -104,6 +106,19 @@ export interface RouterType {
104
106
  api: Api,
105
107
  ): void | Promise<void>
106
108
 
109
+ /**
110
+ * Goes back one history entry when the app pushed one, and otherwise
111
+ * navigates to the fallback, replacing the current entry.
112
+ * @param {RouterEntity} entity - The router entity.
113
+ * @param {string|Omit<NavigatePayload, "replace">} [fallback] - Where to go when there is nothing to go back to.
114
+ * @param {StoreApi} api - The store API.
115
+ */
116
+ back(
117
+ entity: RouterEntity,
118
+ fallback: string | Omit<NavigatePayload, "replace"> | undefined,
119
+ api: Api,
120
+ ): void
121
+
107
122
  /**
108
123
  * Synchronizes the router entity's state with data from a routing event,
109
124
  * typically triggered by a `popstate` event (browser back/forward).