@thepassle/app-tools 0.9.11 → 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/package.json +1 -1
- package/router/index.js +20 -14
- package/router/plugins/offline.js +2 -2
- package/types/router/types.d.ts +5 -4
- package/api/README.md +0 -443
- package/dialog/README.md +0 -252
- package/env/README.md +0 -77
- package/pwa/README.md +0 -103
- package/router/README.md +0 -502
- package/state/README.md +0 -25
- package/utils/README.md +0 -139
package/dialog/README.md
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
# Pwa
|
|
2
|
-
|
|
3
|
-
## Install
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
npm i -S @thepassle/app-tools
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Usage
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import { Dialog } from '@thepassle/app-tools/dialog.js';
|
|
13
|
-
|
|
14
|
-
const dialog = new Dialog({
|
|
15
|
-
foo: {
|
|
16
|
-
opening: (context) => {context.dialog.querySelector('form').innerHTML = 'hello world';},
|
|
17
|
-
opened: (context) => {},
|
|
18
|
-
closing: (context) => {},
|
|
19
|
-
closed: (context) => {}
|
|
20
|
-
},
|
|
21
|
-
bar: someAbstraction({
|
|
22
|
-
title: 'foo',
|
|
23
|
-
import: () => import('./my-component.js'),
|
|
24
|
-
render: () => html`<my-dialog></my-dialog>`
|
|
25
|
-
}),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
dialog.open({id: 'foo'});
|
|
29
|
-
await dialog.opened;
|
|
30
|
-
dialog.isOpen; // true
|
|
31
|
-
/** Or */
|
|
32
|
-
dialog.opened.then((context) => {});
|
|
33
|
-
|
|
34
|
-
dialog.close();
|
|
35
|
-
await dialog.closed;
|
|
36
|
-
dialog.isOpen; // false
|
|
37
|
-
/** Or */
|
|
38
|
-
dialog.closed.then((context) => {});
|
|
39
|
-
|
|
40
|
-
dialog.addEventListener('opening', ({context}) => {});
|
|
41
|
-
dialog.addEventListener('opened', ({context}) => {});
|
|
42
|
-
dialog.addEventListener('closing', ({context}) => {
|
|
43
|
-
console.log(dialog.returnValue);
|
|
44
|
-
});
|
|
45
|
-
dialog.addEventListener('closed', ({context}) => {
|
|
46
|
-
const { id, dialog } = context;
|
|
47
|
-
|
|
48
|
-
if (id === 'foo') {
|
|
49
|
-
console.log(dialog.returnValue);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
if(dialog.returnValue === 'dismiss') {
|
|
53
|
-
console.log('Dialog was closed via light dismiss');
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if(dialog.returnValue === 'programmatic') {
|
|
57
|
-
console.log('Dialog was closed via `dialog.close()`');
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
dialog.modify((dialogNode) => {
|
|
62
|
-
dialogNode.classList.add('foo');
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
/** You can also pass parameters to the dialog renderer */
|
|
66
|
-
dialog.open({
|
|
67
|
-
id: 'foo',
|
|
68
|
-
parameters: {
|
|
69
|
-
foo: 'bar'
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
## Callbacks
|
|
75
|
-
|
|
76
|
-
```js
|
|
77
|
-
import { Dialog } from '@thepassle/app-tools/dialog.js';
|
|
78
|
-
|
|
79
|
-
const dialog = new Dialog({
|
|
80
|
-
foo: {
|
|
81
|
-
/**
|
|
82
|
-
* Executed right after the dialog has been created and added to the DOM, before animations have run
|
|
83
|
-
* Can be used for setup work, like adding `id`s to the dialog, lazy loading,
|
|
84
|
-
* and rendering to the dialog's DOM
|
|
85
|
-
*/
|
|
86
|
-
opening: (context) => {
|
|
87
|
-
context.dialog; // dialog node
|
|
88
|
-
context.id; // 'foo';
|
|
89
|
-
context.parameters; // { bar: 'bar' }
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Executed after animations for the dialog element have run
|
|
94
|
-
*/
|
|
95
|
-
opened: (context) => {},
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Executed when the native <dialog>'s `close` event has fired, on "light dismiss",
|
|
99
|
-
* escape was pressed, or `dialog.close` was called
|
|
100
|
-
* Executed before animations
|
|
101
|
-
*
|
|
102
|
-
* Has access to `dialog.returnValue`
|
|
103
|
-
*/
|
|
104
|
-
closing: (context) => {},
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Executed after the dialog's close animations have run and right before the dialog node is removed from the DOM
|
|
108
|
-
*
|
|
109
|
-
* Has access to `dialog.returnValue`
|
|
110
|
-
*/
|
|
111
|
-
closed: (context) => {}
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
dialog.open({id: 'foo', parameters: {bar: 'bar'}})
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
## Styling the dialog
|
|
119
|
-
|
|
120
|
-
It's recommended to provide a unique ID for the kind of dialog you want to show. For example:
|
|
121
|
-
|
|
122
|
-
```js
|
|
123
|
-
import { Dialog } from '@thepassle/app-tools/dialog.js';
|
|
124
|
-
|
|
125
|
-
const dialog = new Dialog({
|
|
126
|
-
foo: {
|
|
127
|
-
opening: ({dialog}) => {
|
|
128
|
-
dialog.id = 'foo';
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
You can then, in your global stylesheet, select the dialog like so:
|
|
135
|
-
```css
|
|
136
|
-
dialog[app-tools]#foo {
|
|
137
|
-
border-radius: 10px;
|
|
138
|
-
background: lightgrey;
|
|
139
|
-
/* etc */
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
@media (max-width: 600px) {
|
|
143
|
-
dialog[app-tools]#foo {
|
|
144
|
-
width: 90%;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
## Animating the dialog
|
|
150
|
-
|
|
151
|
-
```js
|
|
152
|
-
import { Dialog } from '@thepassle/app-tools/dialog.js';
|
|
153
|
-
|
|
154
|
-
const dialog = new Dialog({
|
|
155
|
-
foo: {
|
|
156
|
-
opening: ({dialog}) => {
|
|
157
|
-
dialog.id = 'foo';
|
|
158
|
-
dialog.form.innerHTML = 'hello world';
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
dialog.open({id: 'foo'});
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
```css
|
|
167
|
-
dialog[app-tools]#foo {
|
|
168
|
-
opacity: 0;
|
|
169
|
-
transform: translateY(40px);
|
|
170
|
-
transition: opacity .3s ease-out, transform .3s ease-out;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
dialog[app-tools][open]#foo {
|
|
174
|
-
opacity: 1;
|
|
175
|
-
transform: translateY(0);
|
|
176
|
-
}
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
## Abstractions
|
|
180
|
-
|
|
181
|
-
It can be useful to declare some abstractions for the different kinds of dialogs you want to use in your app. Here's an example using Lit:
|
|
182
|
-
|
|
183
|
-
```js
|
|
184
|
-
import { html, render } from 'lit';
|
|
185
|
-
import { Dialog } from '@thepassle/app-tools/dialog.js';
|
|
186
|
-
|
|
187
|
-
function modal(config) {
|
|
188
|
-
return {
|
|
189
|
-
opening: ({dialog, parameters}) => {
|
|
190
|
-
config.import();
|
|
191
|
-
render(config.render({parameters, title: config.title}), dialog.form);
|
|
192
|
-
},
|
|
193
|
-
closing: ({dialog}) => {
|
|
194
|
-
console.log(dialog.returnValue); // "bar"
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
const dialog = new Dialog({
|
|
200
|
-
foo: modal({
|
|
201
|
-
title: 'Cart',
|
|
202
|
-
import: () => import('./shopping-cart.js'),
|
|
203
|
-
render: ({title, parameters}) => html`
|
|
204
|
-
<h1>${title}</h1>
|
|
205
|
-
<shopping-cart foo=${parameters.foo}></shopping-cart>
|
|
206
|
-
<button value="bar">Close</button>
|
|
207
|
-
`
|
|
208
|
-
})
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
dialog.open({id: 'foo', parameters: { foo: 'bar' }});
|
|
212
|
-
```
|
|
213
|
-
|
|
214
|
-
### Context menu
|
|
215
|
-
|
|
216
|
-
```js
|
|
217
|
-
import { computePosition } from '@floating-ui/dom';
|
|
218
|
-
import { Dialog } from '@thepassle/app-tools';
|
|
219
|
-
|
|
220
|
-
export const dialog = new Dialog({
|
|
221
|
-
context: context()
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
function context() {
|
|
225
|
-
return {
|
|
226
|
-
opening: async ({dialog, parameters, id}) => {
|
|
227
|
-
dialog.id = 'context';
|
|
228
|
-
render(parameters.template(), dialog.form);
|
|
229
|
-
|
|
230
|
-
if (!media.MAX.XS()) {
|
|
231
|
-
const { x, y } = await computePosition(
|
|
232
|
-
parameters.target,
|
|
233
|
-
dialog,
|
|
234
|
-
{ placement: 'bottom-end'}
|
|
235
|
-
);
|
|
236
|
-
Object.assign(dialog.style, {
|
|
237
|
-
marginLeft: `${x}px`,
|
|
238
|
-
marginTop: `${y}px`,
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
dialog.open({
|
|
246
|
-
id: 'context',
|
|
247
|
-
parameters: {
|
|
248
|
-
target: e.target,
|
|
249
|
-
template: () => html`<h1>hello world</h1>`
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
```
|
package/env/README.md
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Env
|
|
2
|
-
|
|
3
|
-
## Install
|
|
4
|
-
|
|
5
|
-
```
|
|
6
|
-
npm i -S @thepassle/app-tools
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## Usage
|
|
10
|
-
|
|
11
|
-
```js
|
|
12
|
-
import { DEV, PROD } from '@thepassle/app-tools/env.js';
|
|
13
|
-
|
|
14
|
-
if (DEV) {
|
|
15
|
-
console.log('Running in dev mode');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (PROD) {
|
|
19
|
-
console.log('Running in prod mode');
|
|
20
|
-
}
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
OR
|
|
24
|
-
|
|
25
|
-
```js
|
|
26
|
-
import { ENV } from '@thepassle/app-tools/env.js';
|
|
27
|
-
|
|
28
|
-
if (ENV === 'dev') {
|
|
29
|
-
console.log('Running in dev mode');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
if (ENV === 'prod') {
|
|
33
|
-
console.log('Running in prod mode');
|
|
34
|
-
}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Custom Env Modes
|
|
38
|
-
|
|
39
|
-
In certain situations you might want to set your own env mode.
|
|
40
|
-
|
|
41
|
-
Demos that should use "just" json files instead of a full api server might be a good use case for it.
|
|
42
|
-
Here is an example using it in combination with the [Api Module](../api/README.md).
|
|
43
|
-
|
|
44
|
-
```js
|
|
45
|
-
import { ENV } from '@thepassle/app-tools/env.js';
|
|
46
|
-
|
|
47
|
-
const baseURLs = {
|
|
48
|
-
'prod': 'https://api.domain.com', // prod api server
|
|
49
|
-
'dev': 'http://localhost:8888', // local api server
|
|
50
|
-
'dev-static': 'http://localhost:8000' // demos using "just" json files
|
|
51
|
-
}
|
|
52
|
-
const typedEnv = /** @type {keyof baseURLs} */ (ENV);
|
|
53
|
-
|
|
54
|
-
export const api = new Api({
|
|
55
|
-
baseURL: baseURLs[typedEnv],
|
|
56
|
-
});
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
Then in your HTML demo files you can set these special env values.
|
|
60
|
-
|
|
61
|
-
```html
|
|
62
|
-
<my-el api-endpoint="recommendation.json"></my-el>
|
|
63
|
-
|
|
64
|
-
<script type="module">
|
|
65
|
-
import { ENV, setEnv } from '@thepassle/app-tools/env.js';
|
|
66
|
-
setEnv('dev-static');
|
|
67
|
-
import '../my-el.js';
|
|
68
|
-
</script>
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
When resolving imports, Node will look for keys in the package export to figure out which file to use. For example "default", "import", or "require".
|
|
72
|
-
|
|
73
|
-
Tools however can use custom keys here as well, like "types", "browser", "development", or "production". Depending on what kind of import it is, and which environment (dev/prod) tools (like bundlers or dev servers) can resolve/distinguish between which file should actually be used.
|
|
74
|
-
|
|
75
|
-
## Acknowledgement
|
|
76
|
-
|
|
77
|
-
This was inspired by [`esm-env`](https://github.com/benmccann/esm-env) by Ben McCann. I've added to my own repo because I like to have useful utils like these centralized in one place for my personal projects, especially when they're as small and elegant as this.
|
package/pwa/README.md
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
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.
|