@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/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.