@thepassle/app-tools 0.9.2 → 0.9.4
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/api/README.md +443 -0
- package/dialog/README.md +252 -0
- package/env/README.md +29 -0
- package/package.json +2 -2
- package/pwa/README.md +103 -0
- package/router/README.md +491 -0
- package/state/README.md +25 -0
- package/utils/README.md +139 -0
- package/utils/log.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thepassle/app-tools",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"default": "./router.js"
|
|
36
36
|
},
|
|
37
37
|
"./router/plugins/*": {
|
|
38
|
-
"types": "./types/router/
|
|
38
|
+
"types": "./types/router/plugins/*",
|
|
39
39
|
"default": "./router/plugins/*"
|
|
40
40
|
},
|
|
41
41
|
"./api/plugins/*": {
|
package/pwa/README.md
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Pwa
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm i -S @thepassle/app-tools
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
Sets up global listeners for `'beforeinstallprompt'`, `'controllerchange'` as a side effect and correctly handles reloading when `'controllerchange'` has fired; it only reloads when a new service worker has activated, and there was a previous worker.
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { PROD } from '@thepassle/app-tools/env.js';
|
|
15
|
+
import { pwa } from '@thepassle/app-tools/pwa.js';
|
|
16
|
+
|
|
17
|
+
pwa.updateAvailable; // false
|
|
18
|
+
pwa.installable; // false
|
|
19
|
+
pwa.installPrompt; // undefined
|
|
20
|
+
/** Whether or not the PWA is running in standalone mode, and thus is installed as PWA */
|
|
21
|
+
pwa.isInstalled; // false
|
|
22
|
+
|
|
23
|
+
if (PROD) {
|
|
24
|
+
pwa.register('./sw.js', { scope: './' })
|
|
25
|
+
.catch(() => {
|
|
26
|
+
console.log('Failed to register SW.')
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Fires an event when the PWA is considered installable by the browser */
|
|
31
|
+
pwa.addEventListener('installable', () => {
|
|
32
|
+
pwa.installable; // true
|
|
33
|
+
pwa.installPrompt; // stores the beforeinstallprompt event
|
|
34
|
+
pwa.triggerPrompt(); // trigger the prompt
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
pwa.addEventListener('installed', ({installed}) => {
|
|
38
|
+
if (installed) {
|
|
39
|
+
/** The user accepted the install prompt, the PWA is successfully installed */
|
|
40
|
+
} else {
|
|
41
|
+
/** The user denied the prompt */
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Fires when a service worker update is available
|
|
47
|
+
* Can be used to display a notification dot/icon to signal the user that an update is available,
|
|
48
|
+
* or conditionally render some kind of "update" button
|
|
49
|
+
*/
|
|
50
|
+
pwa.addEventListener('update-available', () => {
|
|
51
|
+
pwa.updateAvailable; // true
|
|
52
|
+
pwa.update(); // Sends a `{type: 'SKIP_WAITING'}` to the waiting service worker so it can take control of the page
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* PWA kill switch. Unregisters service worker, deletes caches, reloads the browser
|
|
57
|
+
*/
|
|
58
|
+
pwa.kill();
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Capabilities
|
|
62
|
+
|
|
63
|
+
```js
|
|
64
|
+
import { capabilities } from '@thepassle/app-tools/pwa.js';
|
|
65
|
+
import { when } from '@thepassle/app-tools/utils.js';
|
|
66
|
+
|
|
67
|
+
when(capabilities.WAKELOCK, () => html`<button>Request wakelock</button>`);
|
|
68
|
+
|
|
69
|
+
// capabilities.BADGING
|
|
70
|
+
// capabilities.NOTIFICATION
|
|
71
|
+
// capabilities.SHARE
|
|
72
|
+
// capabilities.SERVICEWORKER
|
|
73
|
+
// capabilities.WAKELOCK
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Catching the `update` in your service worker file
|
|
77
|
+
|
|
78
|
+
The `pwa.update()` method will `postMessage({type: 'SKIP_WAITING'})` to the currently `'waiting'` service worker. This is aligned with Workbox's defaults. However, if you are not using Workbox, make sure to add the following code to your service worker:
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
self.addEventListener('message', (event) => {
|
|
82
|
+
if (event?.data?.type === 'SKIP_WAITING') {
|
|
83
|
+
self.skipWaiting();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Reloading when `'controllerchange'` fires
|
|
89
|
+
|
|
90
|
+
The following code snippet usually does the rounds on the internet:
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
let refreshing;
|
|
94
|
+
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
|
95
|
+
if (refreshing) return;
|
|
96
|
+
window.location.reload();
|
|
97
|
+
refreshing = true;
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
However, this will also reload the page _the first time a user visits the page_ and leads to an unnecessary initial reload.
|
|
102
|
+
|
|
103
|
+
`pwa` handles updates by making sure there was an old service worker to replace, to avoid the unnecessary initial reload.
|
package/router/README.md
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
# Router
|
|
2
|
+
|
|
3
|
+
A simple, modular Single Page Application router.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
npm i -S @thepassle/app-tools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
import { Router } from '@thepassle/app-tools/router.js';
|
|
15
|
+
import { lazy } from '@thepassle/app-tools/router/plugins/lazy.js';
|
|
16
|
+
import { offline } from '@thepassle/app-tools/router/plugins/offline.js';
|
|
17
|
+
import { resetFocus } from '@thepassle/app-tools/router/plugins/resetFocus.js';
|
|
18
|
+
import { scrollToTop } from '@thepassle/app-tools/router/plugins/scrollToTop.js';
|
|
19
|
+
import { checkServiceWorkerUpdate } from '@thepassle/app-tools/router/plugins/checkServiceWorkerUpdate.js';
|
|
20
|
+
|
|
21
|
+
export const router = new Router({
|
|
22
|
+
/** Plugins to be run for every route */
|
|
23
|
+
plugins: [
|
|
24
|
+
/** Redirects to an offline page */
|
|
25
|
+
offline,
|
|
26
|
+
/** Checks for service worker updates on route navigations */
|
|
27
|
+
checkServiceWorkerUpdate,
|
|
28
|
+
scrollToTop,
|
|
29
|
+
resetFocus
|
|
30
|
+
],
|
|
31
|
+
/** Fallback route when the user navigates to a route that doesnt exist */
|
|
32
|
+
fallback: '/404',
|
|
33
|
+
routes: [
|
|
34
|
+
{
|
|
35
|
+
path: '/',
|
|
36
|
+
title: 'home',
|
|
37
|
+
render: () => html`<product-list></product-list>`
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
path: '/cart',
|
|
41
|
+
title: 'cart',
|
|
42
|
+
plugins: [
|
|
43
|
+
lazy(() => import('./shopping-card.js'))
|
|
44
|
+
],
|
|
45
|
+
render: () => html`<shopping-cart></shopping-cart>`
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: '/product/:name',
|
|
49
|
+
title: ({params}) => `Product ${params.name}`,
|
|
50
|
+
plugins: [
|
|
51
|
+
lazy(() => import('./product-page.js'))
|
|
52
|
+
],
|
|
53
|
+
render: ({params}) => html`<product-page id="${params.name}"></product-page>`
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
path: '/admin',
|
|
57
|
+
title: 'Admin',
|
|
58
|
+
plugins: [
|
|
59
|
+
{
|
|
60
|
+
shouldNavigate: () => ({
|
|
61
|
+
condition: () => state.user.isAdmin,
|
|
62
|
+
redirect: '/'
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
],
|
|
66
|
+
render: () => html`<admin-page></admin-page>`
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path: '/offline',
|
|
70
|
+
title: 'Offline',
|
|
71
|
+
render: () => html`<offline-page></offline-page>`
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
path: '/404',
|
|
75
|
+
title: 'Not found',
|
|
76
|
+
render: () => html`<404-page></404-page>`
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
router.addEventListener('route-changed', ({context}) => {
|
|
82
|
+
document.querySelector('#outlet').innerHTML = router.render();
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
router.navigate('/cart');
|
|
86
|
+
|
|
87
|
+
router.context.url;
|
|
88
|
+
router.context.params;
|
|
89
|
+
router.context.query;
|
|
90
|
+
router.context.title;
|
|
91
|
+
|
|
92
|
+
// Cleans up global event listeners
|
|
93
|
+
router.uninstall();
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Polyfill
|
|
97
|
+
|
|
98
|
+
The router makes use of the `URLPattern` api, which you may need to polyfill in your application. You can use [`urlpattern-polyfill`](https://www.npmjs.com/package/urlpattern-polyfill) for this.
|
|
99
|
+
|
|
100
|
+
If you use [`@web/rollup-plugin-polyfills-loader`](https://www.npmjs.com/package/@web/rollup-plugin-polyfills-loader) in your rollup build you can use the `URLPattern` config option:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
polyfillsLoader({
|
|
104
|
+
polyfills: {
|
|
105
|
+
URLPattern: true,
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Base
|
|
111
|
+
|
|
112
|
+
When using a Single Page Application (SPA) router, make sure to set the `<base href="/">` element in your HTML. Also make sure that your dev server is configured for SPA Navigations. When using `@web/dev-server`, you can use the `--app-index` configuration options.
|
|
113
|
+
|
|
114
|
+
E.g.: `web-dev-server --app-index index.html`, or `web-dev-server --app-index foo/index.html`
|
|
115
|
+
|
|
116
|
+
When using a different base, for example if your app is running on `my-app.com/foo/`, make sure to adjust your routing configuration:
|
|
117
|
+
|
|
118
|
+
```html
|
|
119
|
+
<html>
|
|
120
|
+
<head>
|
|
121
|
+
<base href="/foo/">
|
|
122
|
+
</head>
|
|
123
|
+
<body>
|
|
124
|
+
<a href="/foo/">home</a>
|
|
125
|
+
<a href="foo">foo</a>
|
|
126
|
+
<a href="bar/123">bar</a>
|
|
127
|
+
<main></main>
|
|
128
|
+
</body>
|
|
129
|
+
<script type="module">
|
|
130
|
+
import { Router } from 'https://unpkg.com/@thepassle/app-tools/router.js';
|
|
131
|
+
|
|
132
|
+
const router = new Router({
|
|
133
|
+
routes: [
|
|
134
|
+
{
|
|
135
|
+
path: '/foo/',
|
|
136
|
+
title: 'Hello',
|
|
137
|
+
render: () => 'home'
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
path: 'foo',
|
|
141
|
+
title: 'Foo',
|
|
142
|
+
render: () => 'foo'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
path: 'bar/:id',
|
|
146
|
+
title: ({params}) => `Bar ${params.id}`,
|
|
147
|
+
render: ({params}) => `bar ${params.id}`
|
|
148
|
+
},
|
|
149
|
+
]
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
router.addEventListener('route-changed', ({context}) => {
|
|
153
|
+
const route = router.render();
|
|
154
|
+
document.querySelector('main').innerHTML = route;
|
|
155
|
+
});
|
|
156
|
+
</script>
|
|
157
|
+
</html>
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Rendering
|
|
161
|
+
|
|
162
|
+
The router is framework agnostic. Rendering the route is left to the consumer of the router. The application is then in charge of rendering whatever is returned from the `render` function. Here's a basic example:
|
|
163
|
+
|
|
164
|
+
### Using vanilla js
|
|
165
|
+
|
|
166
|
+
Route:
|
|
167
|
+
```js
|
|
168
|
+
{
|
|
169
|
+
path: '/',
|
|
170
|
+
title: 'Home',
|
|
171
|
+
render: (context) => 'Home route'
|
|
172
|
+
}
|
|
173
|
+
```
|
|
174
|
+
App:
|
|
175
|
+
```js
|
|
176
|
+
router.addEventListener('route-changed', () => {
|
|
177
|
+
const route = router.render();
|
|
178
|
+
document.querySelector('#outlet').innerHTML = route;
|
|
179
|
+
});
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Using lit-html
|
|
183
|
+
|
|
184
|
+
Route:
|
|
185
|
+
```js
|
|
186
|
+
{
|
|
187
|
+
path: '/',
|
|
188
|
+
title: 'Home',
|
|
189
|
+
render: (context) => html`<my-el></my-el>`
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
App:
|
|
193
|
+
```js
|
|
194
|
+
import { html, render } from 'lit';
|
|
195
|
+
|
|
196
|
+
router.addEventListener('route-changed', () => {
|
|
197
|
+
render(router.render(), document.querySelector('#outlet'))
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Using LitElement
|
|
202
|
+
|
|
203
|
+
Route:
|
|
204
|
+
```js
|
|
205
|
+
{
|
|
206
|
+
path: '/',
|
|
207
|
+
title: 'Home',
|
|
208
|
+
render: (context) => html`<my-el></my-el>`
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
App:
|
|
212
|
+
```js
|
|
213
|
+
import { LitElement } from 'lit';
|
|
214
|
+
|
|
215
|
+
class MyEl extends LitElement {
|
|
216
|
+
firstUpdated() {
|
|
217
|
+
router.addEventListener('route-changed', () => {
|
|
218
|
+
this.requestUpdate();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
render() {
|
|
223
|
+
return router.render();
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Composable
|
|
229
|
+
|
|
230
|
+
Use plugins to customize your navigations to fit your needs. You can add plugins for all navigations, or for specific routes.
|
|
231
|
+
|
|
232
|
+
```js
|
|
233
|
+
const router = new Router({
|
|
234
|
+
/** These plugins will run for any navigation */
|
|
235
|
+
plugins: [],
|
|
236
|
+
routes: [
|
|
237
|
+
{
|
|
238
|
+
path: '/foo',
|
|
239
|
+
title: 'Foo',
|
|
240
|
+
/** These plugins will run for this route only */
|
|
241
|
+
plugins: [],
|
|
242
|
+
render: () => html`<my-el></my-el>`
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### `lazy`
|
|
249
|
+
|
|
250
|
+
Lazily import resources or components on route navigations
|
|
251
|
+
|
|
252
|
+
```js
|
|
253
|
+
import { lazy } from '@thepassle/app-tools/router/plugins/lazy.js';
|
|
254
|
+
|
|
255
|
+
const router = new Router({
|
|
256
|
+
routes: [
|
|
257
|
+
{
|
|
258
|
+
path: '/foo',
|
|
259
|
+
title: 'Foo',
|
|
260
|
+
plugins: [
|
|
261
|
+
lazy(() => import('./my-el.js')),
|
|
262
|
+
],
|
|
263
|
+
render: () => html`<my-el></my-el>`
|
|
264
|
+
},
|
|
265
|
+
]
|
|
266
|
+
});
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### `data`
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
import { api } from '@thepassle/app-tools/api.js';
|
|
273
|
+
import { data } from '@thepassle/app-tools/router/plugins/data.js';
|
|
274
|
+
|
|
275
|
+
const router = new Router({
|
|
276
|
+
routes: [
|
|
277
|
+
{
|
|
278
|
+
path: '/pokemon/:id',
|
|
279
|
+
title: (context) => `Pokemon ${context.params.id}`,
|
|
280
|
+
plugins: [
|
|
281
|
+
data((context) => api.get(`https://pokeapi.co/api/v2/pokemon/${context.params.id}`)),
|
|
282
|
+
],
|
|
283
|
+
// context.data is a promise
|
|
284
|
+
render: (context) => html`<pokemon-card .data=${context.data}></pokemon-card>`
|
|
285
|
+
},
|
|
286
|
+
]
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### `redirect`
|
|
291
|
+
|
|
292
|
+
```js
|
|
293
|
+
import { redirect } from '@thepassle/app-tools/router/plugins/redirect.js';
|
|
294
|
+
|
|
295
|
+
const router = new Router({
|
|
296
|
+
routes: [
|
|
297
|
+
{
|
|
298
|
+
path: '/foo',
|
|
299
|
+
title: 'Foo',
|
|
300
|
+
plugins: [
|
|
301
|
+
redirect('/404'),
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
]
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### `checkServiceWorkerUpdate`
|
|
309
|
+
|
|
310
|
+
Checks for service worker updates on route navigations
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
import { checkServiceWorkerUpdate } from '@thepassle/app-tools/router/plugins/checkServiceWorkerUpdate.js';
|
|
314
|
+
|
|
315
|
+
const router = new Router({
|
|
316
|
+
plugins: [
|
|
317
|
+
checkServiceWorkerUpdate
|
|
318
|
+
],
|
|
319
|
+
routes: [
|
|
320
|
+
{
|
|
321
|
+
path: '/foo',
|
|
322
|
+
title: 'Foo',
|
|
323
|
+
render: () => html`<my-el></my-el>`
|
|
324
|
+
},
|
|
325
|
+
]
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### `offline`
|
|
330
|
+
|
|
331
|
+
Redirects to an offline page when the user is offline
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
import { offline, offlinePlugin } from '@thepassle/app-tools/router/plugins/offline.js';
|
|
335
|
+
|
|
336
|
+
const router = new Router({
|
|
337
|
+
plugins: [
|
|
338
|
+
/** Redirects to `/offline` by default */
|
|
339
|
+
offline
|
|
340
|
+
/** Or */
|
|
341
|
+
offlinePlugin('/my-offline-page')
|
|
342
|
+
],
|
|
343
|
+
routes: [
|
|
344
|
+
{
|
|
345
|
+
path: '/offline',
|
|
346
|
+
title: 'Offline',
|
|
347
|
+
render: () => html`<offline-page></offline-page>`
|
|
348
|
+
},
|
|
349
|
+
]
|
|
350
|
+
});
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### `appName`
|
|
354
|
+
|
|
355
|
+
Prepends the name of your app to the title
|
|
356
|
+
|
|
357
|
+
```js
|
|
358
|
+
import { appName } from '@thepassle/app-tools/router/plugins/appName.js';
|
|
359
|
+
|
|
360
|
+
const router = new Router({
|
|
361
|
+
plugins: [
|
|
362
|
+
appName('My App -')
|
|
363
|
+
],
|
|
364
|
+
routes: [
|
|
365
|
+
{
|
|
366
|
+
path: '/foo',
|
|
367
|
+
title: 'Foo',
|
|
368
|
+
render: () => html`<my-el></my-el>`
|
|
369
|
+
},
|
|
370
|
+
]
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Will result in the title for route `/foo` being:
|
|
375
|
+
`My App - Foo`
|
|
376
|
+
|
|
377
|
+
### `scrollToTop`
|
|
378
|
+
|
|
379
|
+
Scrolls the page to the top after a navigation
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
import { scrollToTop } from '@thepassle/app-tools/router/plugins/scrollToTop.js';
|
|
383
|
+
|
|
384
|
+
const router = new Router({
|
|
385
|
+
plugins: [
|
|
386
|
+
scrollToTop
|
|
387
|
+
],
|
|
388
|
+
routes: [
|
|
389
|
+
{
|
|
390
|
+
path: '/foo',
|
|
391
|
+
title: 'Foo',
|
|
392
|
+
render: () => html`<my-el></my-el>`
|
|
393
|
+
},
|
|
394
|
+
]
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
### `resetFocus`
|
|
399
|
+
|
|
400
|
+
Resets focus to the start of the document
|
|
401
|
+
|
|
402
|
+
```js
|
|
403
|
+
import { resetFocus } from '@thepassle/app-tools/router/plugins/resetFocus.js';
|
|
404
|
+
|
|
405
|
+
const router = new Router({
|
|
406
|
+
plugins: [
|
|
407
|
+
resetFocus
|
|
408
|
+
],
|
|
409
|
+
routes: [
|
|
410
|
+
{
|
|
411
|
+
path: '/foo',
|
|
412
|
+
title: 'Foo',
|
|
413
|
+
render: () => html`<my-el></my-el>`
|
|
414
|
+
},
|
|
415
|
+
]
|
|
416
|
+
});
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
## Plugin API
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
const router = new Router({
|
|
423
|
+
plugins: [
|
|
424
|
+
{
|
|
425
|
+
shouldNavigate: (context) => ({
|
|
426
|
+
condition: () => false,
|
|
427
|
+
redirect: '/'
|
|
428
|
+
}),
|
|
429
|
+
beforeNavigation: (context) => {
|
|
430
|
+
|
|
431
|
+
},
|
|
432
|
+
afterNavigation: (context) => {
|
|
433
|
+
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
]
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
All plugins have access to the `context` object for the current route. Given the following route, the context includes:
|
|
441
|
+
|
|
442
|
+
```js
|
|
443
|
+
{
|
|
444
|
+
path: '/users/:id',
|
|
445
|
+
title: ({params, query}) => `Hello world ${params.id} ${query.foo}`,
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
`www.my-website.com/users/123?foo=bar`
|
|
450
|
+
|
|
451
|
+
```js
|
|
452
|
+
context.params; // { id: 123 }
|
|
453
|
+
context.query; // { foo: 'bar' }
|
|
454
|
+
context.title; // "Hello world 123 bar"
|
|
455
|
+
context.url; // URL instance of "www.my-website.com/users/123?foo=bar"
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### `shouldNavigate`
|
|
459
|
+
|
|
460
|
+
Can be used to protect routes based on a condition function. Should return an object containing a `condition` function, and a `redirect`. When the `condition` returns `false`, it will redirect to the path provided by `redirect`.
|
|
461
|
+
|
|
462
|
+
```js
|
|
463
|
+
const myPlugin = {
|
|
464
|
+
shouldNavigate: (context) => ({
|
|
465
|
+
/** A condition function to determine whether or not the navigation should take place */
|
|
466
|
+
condition: () => state.user.isAdmin,
|
|
467
|
+
/** Where to send the user in case the condition is false */
|
|
468
|
+
redirect: '/'
|
|
469
|
+
}),
|
|
470
|
+
}
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### `beforeNavigation`
|
|
474
|
+
|
|
475
|
+
Runs before the navigation takes place
|
|
476
|
+
|
|
477
|
+
```js
|
|
478
|
+
const myPlugin = {
|
|
479
|
+
beforeNavigation: (context) => {}
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### `afterNavigation`
|
|
484
|
+
|
|
485
|
+
Runs after the navigation takes place
|
|
486
|
+
|
|
487
|
+
```js
|
|
488
|
+
const myPlugin = {
|
|
489
|
+
afterNavigation: (context) => {}
|
|
490
|
+
}
|
|
491
|
+
```
|
package/state/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# State
|
|
2
|
+
|
|
3
|
+
## Install
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npm i -S @thepassle/app-tools
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { State } from '@thepassle/app-tools/state.js';
|
|
13
|
+
|
|
14
|
+
const state = new State();
|
|
15
|
+
|
|
16
|
+
/** Or pass a default state */
|
|
17
|
+
const state = new State({foo: 'foo'});
|
|
18
|
+
|
|
19
|
+
state.setState({foo: 'bar'}); // state === {foo: 'bar'}
|
|
20
|
+
state.setState((old) => ({...old, bar: 'bar'})); // state === {foo: 'bar', bar: 'bar'}
|
|
21
|
+
|
|
22
|
+
state.addEventListener('state-changed', ({state}) => {});
|
|
23
|
+
|
|
24
|
+
state.getState(); // {foo: 'bar', bar: 'bar'};
|
|
25
|
+
```
|