@thepassle/app-tools 0.9.12 → 0.9.13

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/router/README.md DELETED
@@ -1,502 +0,0 @@
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
- static properties = {
217
- route: {}
218
- }
219
-
220
- firstUpdated() {
221
- router.addEventListener('route-changed', () => {
222
- this.route = router.render();
223
- });
224
- }
225
-
226
- render() {
227
- return this.route;
228
- }
229
- }
230
- ```
231
-
232
- ## Composable
233
-
234
- Use plugins to customize your navigations to fit your needs. You can add plugins for all navigations, or for specific routes.
235
-
236
- ```js
237
- const router = new Router({
238
- /** These plugins will run for any navigation */
239
- plugins: [],
240
- routes: [
241
- {
242
- path: '/foo',
243
- title: 'Foo',
244
- /** These plugins will run for this route only */
245
- plugins: [],
246
- render: () => html`<my-el></my-el>`
247
- }
248
- ]
249
- })
250
- ```
251
-
252
- ### `lazy`
253
-
254
- Lazily import resources or components on route navigations
255
-
256
- ```js
257
- import { lazy } from '@thepassle/app-tools/router/plugins/lazy.js';
258
-
259
- const router = new Router({
260
- routes: [
261
- {
262
- path: '/foo',
263
- title: 'Foo',
264
- plugins: [
265
- lazy(() => import('./my-el.js')),
266
- ],
267
- render: () => html`<my-el></my-el>`
268
- },
269
- ]
270
- });
271
- ```
272
-
273
- ### `data`
274
-
275
- ```js
276
- import { api } from '@thepassle/app-tools/api.js';
277
- import { data } from '@thepassle/app-tools/router/plugins/data.js';
278
-
279
- const router = new Router({
280
- routes: [
281
- {
282
- path: '/pokemon/:id',
283
- title: (context) => `Pokemon ${context.params.id}`,
284
- plugins: [
285
- data((context) => api.get(`https://pokeapi.co/api/v2/pokemon/${context.params.id}`)),
286
- ],
287
- // context.data is a promise
288
- render: (context) => html`<pokemon-card .data=${context.data}></pokemon-card>`
289
- },
290
- ]
291
- });
292
- ```
293
-
294
- ### `redirect`
295
-
296
- ```js
297
- import { redirect } from '@thepassle/app-tools/router/plugins/redirect.js';
298
-
299
- const router = new Router({
300
- routes: [
301
- {
302
- path: '/foo',
303
- title: 'Foo',
304
- plugins: [
305
- redirect('/404'),
306
- ],
307
- },
308
- {
309
- path: '/legacy/detail/:product',
310
- title: 'Foo',
311
- plugins: [
312
- redirect(context => '/detail/${context.params.product}'),
313
- ],
314
- },
315
- ]
316
- });
317
- ```
318
-
319
- ### `checkServiceWorkerUpdate`
320
-
321
- Checks for service worker updates on route navigations
322
-
323
- ```js
324
- import { checkServiceWorkerUpdate } from '@thepassle/app-tools/router/plugins/checkServiceWorkerUpdate.js';
325
-
326
- const router = new Router({
327
- plugins: [
328
- checkServiceWorkerUpdate
329
- ],
330
- routes: [
331
- {
332
- path: '/foo',
333
- title: 'Foo',
334
- render: () => html`<my-el></my-el>`
335
- },
336
- ]
337
- });
338
- ```
339
-
340
- ### `offline`
341
-
342
- Redirects to an offline page when the user is offline
343
-
344
- ```js
345
- import { offline, offlinePlugin } from '@thepassle/app-tools/router/plugins/offline.js';
346
-
347
- const router = new Router({
348
- plugins: [
349
- /** Redirects to `/offline` by default */
350
- offline
351
- /** Or */
352
- offlinePlugin('/my-offline-page')
353
- ],
354
- routes: [
355
- {
356
- path: '/offline',
357
- title: 'Offline',
358
- render: () => html`<offline-page></offline-page>`
359
- },
360
- ]
361
- });
362
- ```
363
-
364
- ### `appName`
365
-
366
- Prepends the name of your app to the title
367
-
368
- ```js
369
- import { appName } from '@thepassle/app-tools/router/plugins/appName.js';
370
-
371
- const router = new Router({
372
- plugins: [
373
- appName('My App -')
374
- ],
375
- routes: [
376
- {
377
- path: '/foo',
378
- title: 'Foo',
379
- render: () => html`<my-el></my-el>`
380
- },
381
- ]
382
- });
383
- ```
384
-
385
- Will result in the title for route `/foo` being:
386
- `My App - Foo`
387
-
388
- ### `scrollToTop`
389
-
390
- Scrolls the page to the top after a navigation
391
-
392
- ```js
393
- import { scrollToTop } from '@thepassle/app-tools/router/plugins/scrollToTop.js';
394
-
395
- const router = new Router({
396
- plugins: [
397
- scrollToTop
398
- ],
399
- routes: [
400
- {
401
- path: '/foo',
402
- title: 'Foo',
403
- render: () => html`<my-el></my-el>`
404
- },
405
- ]
406
- });
407
- ```
408
-
409
- ### `resetFocus`
410
-
411
- Resets focus to the start of the document
412
-
413
- ```js
414
- import { resetFocus } from '@thepassle/app-tools/router/plugins/resetFocus.js';
415
-
416
- const router = new Router({
417
- plugins: [
418
- resetFocus
419
- ],
420
- routes: [
421
- {
422
- path: '/foo',
423
- title: 'Foo',
424
- render: () => html`<my-el></my-el>`
425
- },
426
- ]
427
- });
428
- ```
429
-
430
- ## Plugin API
431
-
432
- ```js
433
- const router = new Router({
434
- plugins: [
435
- {
436
- shouldNavigate: (context) => ({
437
- condition: () => false,
438
- redirect: '/'
439
- }),
440
- beforeNavigation: (context) => {
441
-
442
- },
443
- afterNavigation: (context) => {
444
-
445
- }
446
- }
447
- ]
448
- });
449
- ```
450
-
451
- All plugins have access to the `context` object for the current route. Given the following route, the context includes:
452
-
453
- ```js
454
- {
455
- path: '/users/:id',
456
- title: ({params, query}) => `Hello world ${params.id} ${query.foo}`,
457
- }
458
- ```
459
-
460
- `www.my-website.com/users/123?foo=bar`
461
-
462
- ```js
463
- context.params; // { id: 123 }
464
- context.query; // { foo: 'bar' }
465
- context.title; // "Hello world 123 bar"
466
- context.url; // URL instance of "www.my-website.com/users/123?foo=bar"
467
- ```
468
-
469
- ### `shouldNavigate`
470
-
471
- 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`.
472
-
473
- ```js
474
- const myPlugin = {
475
- shouldNavigate: (context) => ({
476
- /** A condition function to determine whether or not the navigation should take place */
477
- condition: () => state.user.isAdmin,
478
- /** Where to send the user in case the condition is false */
479
- redirect: '/'
480
- }),
481
- }
482
- ```
483
-
484
- ### `beforeNavigation`
485
-
486
- Runs before the navigation takes place
487
-
488
- ```js
489
- const myPlugin = {
490
- beforeNavigation: (context) => {}
491
- }
492
- ```
493
-
494
- ### `afterNavigation`
495
-
496
- Runs after the navigation takes place
497
-
498
- ```js
499
- const myPlugin = {
500
- afterNavigation: (context) => {}
501
- }
502
- ```
package/state/README.md DELETED
@@ -1,25 +0,0 @@
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
- ```
package/utils/README.md DELETED
@@ -1,139 +0,0 @@
1
- # Utils
2
-
3
- ## Install
4
-
5
- ```
6
- npm i -S @thepassle/app-tools
7
- ```
8
-
9
- ## Usage
10
-
11
- ### `log`
12
-
13
- You can enable logs via the `setDebug` function, or by setting a `?app-tools-debug=true` query param in the browser url.
14
-
15
- ```js
16
- import { log, setDebug, getDebug } from '@thepassle/app-tools/utils/log.js';
17
-
18
- // Enable logs to show up in the console
19
- setDebug(true);
20
-
21
- getDebug(); // true
22
-
23
- log('foo', { foo: 'bar'});
24
- ```
25
-
26
- ### `when`
27
-
28
- ```js
29
- import { when } from '@thepassle/app-tools/utils.js';
30
-
31
- when(true, () => html`only truthy result`);
32
- when(true,
33
- () => html`truthy result`,
34
- () => html`falsy result`
35
- );
36
- ```
37
-
38
- ### `waitUntil`
39
-
40
- ```js
41
- import { waitUntil } from '@thepassle/app-tools/utils/async.js';
42
-
43
- await waitUntil(() => true, {
44
- timeout: 1000,
45
- message: 'waitUntil timed out',
46
- interval: 50
47
- });
48
- ```
49
-
50
- ### `setAbortableTimeout`
51
-
52
- ```js
53
- import { setAbortableTimeout } from '@thepassle/app-tools/utils/async.js';
54
-
55
- const controller = new AbortController();
56
- const { signal } = controller;
57
-
58
- setAbortableTimeout(
59
- () => { console.log(1); },
60
- 2000,
61
- { signal }
62
- );
63
-
64
- controller.abort();
65
- ```
66
-
67
- ### `debounce`
68
-
69
- ```js
70
- import { debounce } from '@thepassle/app-tools/utils/async.js';
71
-
72
- function foo() {
73
- console.log(1);
74
- }
75
-
76
- const debouncedFoo = debounce(foo);
77
-
78
- debouncedFoo();
79
- debouncedFoo();
80
- debouncedFoo();
81
-
82
- // console.log only called once
83
- ```
84
-
85
- ### `media`
86
-
87
- ```js
88
- import { media } from '@thepassle/app-tools/utils/media.js'
89
-
90
- media.DARK_MODE(); // true
91
-
92
- // Or provide a callback that fires whenever the mediaquery changes
93
- // If you provide a callback, the function returns a cleanup function
94
- const cleanup = media.DARK_MODE((matches) => {
95
- console.log(matches); // true
96
- });
97
- // Removes the listener
98
- cleanup();
99
-
100
- // If you don't provide a callback, the function just returns the `matches` boolean
101
- media.MIN.XL();
102
- media.MAX.LG();
103
- media.MIN.MD();
104
- media.MAX.SM();
105
- media.MIN.XS(); // etc
106
-
107
- media.STANDALONE();
108
- media.REDUCED_MOTION();
109
- media.LIGHT_MODE();
110
- ```
111
-
112
- ### `Service`
113
-
114
- ```js
115
- import { createService } from '@thepassle/app-tools/utils/Service.js';
116
- import { html, nothing } from 'lit';
117
-
118
- const Service = createService({
119
- initialized: () => nothing,
120
- pending: () => html`<app-spinner></app-spinner>`,
121
- success: () => nothing,
122
- error: (e) => html`<app-error>${e}</app-error>`,
123
- });
124
-
125
- class MyApp extends LitElement {
126
- foo = new Service(this, () => api.get('/foo'));
127
-
128
- connectedCallback() {
129
- super.connectedCallback();
130
- this.foo.request();
131
- }
132
-
133
- render() {
134
- return this.foo.render({
135
- success: (data) => html`<h1>${data.name}</h2>`
136
- });
137
- }
138
- }
139
- ```