@reykjavik/webtools 0.1.20 → 0.1.22

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/CHANGELOG.md CHANGED
@@ -4,13 +4,22 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
- ## 0.1.20
7
+ ## 0.1.22
8
8
 
9
- _2024-03-11_
9
+ _2024-03-14_
10
+
11
+ - fix: Relax unnecessary restriction on fallback props
12
+
13
+ ## 0.1.21
14
+
15
+ _2024-03-14_
10
16
 
11
- - fix: Make all pached `Intl.*` methods bound to their instances
17
+ - feat: Add `@reykjavik/webtools/remix/Wait` component
18
+ - feat: Add `@reykjavik/webtools/remix/http` module — with `isClientFetch`
19
+ helper
20
+ - feat: Add `@reykjavik/webtools/async` module with promise helpers
12
21
 
13
- ## 0.1.18 – 0.1.19
22
+ ## 0.1.18 – 0.1.20
14
23
 
15
24
  _2024-03-11_
16
25
 
@@ -20,6 +29,7 @@ _2024-03-11_
20
29
  (not just a single character) This fix corrects the sorting of initial
21
30
  letters, but characters inside the string stay mixed in with their
22
31
  unaccented base character.
32
+ - fix: Make all pached `Intl.*` methods bound to their instances
23
33
 
24
34
  ## 0.1.16 – 0.1.17
25
35
 
@@ -49,8 +59,8 @@ _2024-03-06_
49
59
 
50
60
  _2024-02-29_
51
61
 
52
- - feat: Add `@reykjavik/webtools/next/vanillaExtract` component — with
53
- plain-CSS injection helpers
62
+ - feat: Add `@reykjavik/webtools/next/vanillaExtract` module — with plain-CSS
63
+ injection helpers
54
64
  - fix: Mark `peerDependencies` as optional
55
65
 
56
66
  ## 0.1.11
package/README.md CHANGED
@@ -25,6 +25,9 @@ bun add @reykjavik/webtools
25
25
  - [`@reykjavik/webtools/CookieHubConsent`](#reykjavikwebtoolscookiehubconsent)
26
26
  - [`CookieHubProvider` component](#cookiehubprovider-component)
27
27
  - [`useCookieHubConsent`](#usecookiehubconsent)
28
+ - [`@reykjavik/webtools/async`](#reykjavikwebtoolsasync)
29
+ - [`promiseAllObject`](#promiseallobject)
30
+ - [`maxWait`](#maxwait)
28
31
  - [`@reykjavik/webtools/vanillaExtract`](#reykjavikwebtoolsvanillaextract)
29
32
  - [`vanillaGlobal`](#vanillaglobal)
30
33
  - [`vanillaProps`](#vanillaprops)
@@ -35,6 +38,7 @@ bun add @reykjavik/webtools
35
38
  - [Limitations](#limitations)
36
39
  - [Framework Specific Tools](#framework-specific-tools)
37
40
  - [Next.js Tools](#nextjs-tools)
41
+ - [Remix.run Tools](#remixrun-tools)
38
42
  - [Contributing](#contributing)
39
43
  - [Changelog](#changelog)
40
44
 
@@ -165,7 +169,7 @@ behavior.
165
169
  ### `toSec` TTL helper
166
170
 
167
171
  **Syntax:**
168
- `` toSec; (ttl: number | `${number}${'s'|'m'|'h'|'d'|'w'}`) => number ``
172
+ `` toSec(ttl: number | `${number}${'s'|'m'|'h'|'d'|'w'}`): number ``
169
173
 
170
174
  Converts a `TTL` (max-age) value into seconds, and returns `0` for bad and/or
171
175
  negative input values.
@@ -253,6 +257,62 @@ this hook will return an empty object.
253
257
 
254
258
  ---
255
259
 
260
+ ## `@reykjavik/webtools/async`
261
+
262
+ Contains a few small helpers for working with async functions and promises.
263
+
264
+ ---
265
+
266
+ ### `promiseAllObject`
267
+
268
+ **Syntax:**
269
+ `promiseAllObject<T extends PlainObj>(promisesMap: T>): Promise<{ [K in keyof T]: Awaited<T[K]>; }>`
270
+
271
+ A variation of `Promise.all()` that accepts an object with named promises and
272
+ returns a same-shaped object with the resolved values.
273
+
274
+ ```ts
275
+ import { promiseAllObject } from '@reykjavik/webtools/async';
276
+
277
+ const { user, posts } = await promiseAllObject({
278
+ user: fetchUser(),
279
+ posts: fetchPosts(),
280
+ });
281
+ ```
282
+
283
+ ---
284
+
285
+ ### `maxWait`
286
+
287
+ **Syntax:** `maxWait(timeout: number, promises: Array<any>): Promise<void>`
288
+ **Syntax:**
289
+ `maxWait<T extends PlainObj>(timeout: number, promises: T): Promise<{ [K in keyof T]: { value: Awaited<T[K]> } | undefined }>`
290
+
291
+ This somewhat esoteric helper resolves soon as all of the passed `promises`
292
+ have resolved, or after `timeout` milliseconds — whichever comes first.
293
+
294
+ If an object is passed, the resolved value will be an object with the same
295
+ keys, with undefined values for any promises that didn't resolve in time, and
296
+ the resolved values in a `value` container object.
297
+
298
+ ```ts
299
+ import { maxWait } from '@reykjavik/webtools/async';
300
+
301
+ const user = fetchUser();
302
+ const posts = fetchPosts();
303
+
304
+ // Array of promises resolves to void
305
+ await maxWait(500, [user, posts]);
306
+
307
+ // Object of promises resolves to an object with any resolved values at that time
308
+ const { user, posts } = await maxWait(500, { user, posts });
309
+
310
+ console.log(user?.value); // undefined | User
311
+ console.log(posts?.value); // undefined | Array<Post>
312
+ ```
313
+
314
+ ---
315
+
256
316
  ## `@reykjavik/webtools/vanillaExtract`
257
317
 
258
318
  Contains helpers for writing [vanilla-extract](https://vanilla-extract.style)
@@ -265,7 +325,7 @@ CSS.
265
325
 
266
326
  ### `vanillaGlobal`
267
327
 
268
- **Syntax:** `vanillaGlobal: (css: string) => void`
328
+ **Syntax:** `vanillaGlobal(css: string): void`
269
329
 
270
330
  Inserts free-form CSS as a vanilla-extract `globalStyle`.
271
331
 
@@ -282,7 +342,7 @@ vanillaGlobal(`
282
342
 
283
343
  ### `vanillaProps`
284
344
 
285
- **Syntax:** `vanillaProps: (css: string) => GlobalStyleRule`
345
+ **Syntax:** `vanillaProps(css: string): GlobalStyleRule`
286
346
 
287
347
  Spreads the return value into a style object, to inject free-form CSS
288
348
  properties (or nested blocks)
@@ -307,8 +367,8 @@ const myStyle = style({
307
367
 
308
368
  ### `vanillaClass`
309
369
 
310
- **Syntax:** `vanillaClass: (css: string) => string`
311
- **Syntax:** `vanillaClass: (debugId: string, css: string) => string`
370
+ **Syntax:** `vanillaClass(css: string): string`
371
+ **Syntax:** `vanillaClass(debugId: string, css: string): string`
312
372
 
313
373
  Returns a scoped cssClassName styled with free-form CSS. This function is a
314
374
  thin wrapper around vanilla-extract's `style` function.
@@ -333,8 +393,8 @@ export const humanReadableClass = vanillaClass(
333
393
 
334
394
  ### `vanillaClassNested`
335
395
 
336
- **Syntax:** `vanillaClassNested: (css: string) => string`
337
- **Syntax:** `vanillaClassNested: (debugId: string, css: string) => string`
396
+ **Syntax:** `vanillaClassNested(css: string): string`
397
+ **Syntax:** `vanillaClassNested(debugId: string, css: string): string`
338
398
 
339
399
  Returns a scoped cssClassName styled with free-form CSS.
340
400
 
@@ -373,7 +433,7 @@ comments, etc. If you need something more sophisticated, use a custom
373
433
 
374
434
  ### `vanillaNest`
375
435
 
376
- **Syntax:** `vanillaNest: (ampSelector: string, css: string) => string`
436
+ **Syntax:** `vanillaNest(ampSelector: string, css: string): string`
377
437
 
378
438
  Replaces all `&` tokens with the given selector string, in a direct (read.
379
439
  "dumb") way. It's mainly useful when used with style-mixins, etc.
@@ -504,7 +564,16 @@ detection test.)
504
564
  This package contains some helpers and components that are specifically
505
565
  designed for use in [Next.js](https://nextjs.org/) projects.
506
566
 
507
- See [README-nextjs.md](README-nextjs.md) for more info.
567
+ See [README-nextjs.md](./README-nextjs.md) for more info.
568
+
569
+ ---
570
+
571
+ ### Remix.run Tools
572
+
573
+ This package contains some helpers and components that are specifically
574
+ designed for use in [Remix.run](https://remix.run) projects.
575
+
576
+ See [README-remix.md](./README-remix.md) for more info.
508
577
 
509
578
  ---
510
579
 
package/async.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ type PlainObj = Record<string, unknown>;
2
+ /**
3
+ * Simple sleep function. Returns a promise that resolves after `length`
4
+ * milliseconds.
5
+ */
6
+ export declare const sleep: (length: number) => Promise<void>;
7
+ /**
8
+ * Resolves soon as all of the passed `promises` have resolved, or after
9
+ * `timeout` milliseconds — whichever comes first.
10
+ *
11
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#maxwait
12
+ */
13
+ export declare function maxWait(timeout: number, promises: Array<unknown>): Promise<void>;
14
+ export declare function maxWait<PromiseMap extends PlainObj>(timeout: number, promises: PromiseMap): Promise<{
15
+ -readonly [K in keyof PromiseMap]: {
16
+ value: Awaited<PromiseMap[K]>;
17
+ } | undefined;
18
+ }>;
19
+ /**
20
+ * A variation of `Promise.all()` that accepts an object with named promises
21
+ * and returns a same-shaped object with the resolved values.
22
+ *
23
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#promiseallobject
24
+ */
25
+ export declare const promiseAllObject: <T extends PlainObj>(promisesMap: T) => Promise<{ -readonly [K in keyof T]: Awaited<T[K]>; }>;
26
+ export {};
package/async.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.promiseAllObject = exports.maxWait = exports.sleep = void 0;
4
+ /**
5
+ * Simple sleep function. Returns a promise that resolves after `length`
6
+ * milliseconds.
7
+ */
8
+ const sleep = (length) => new Promise((resolve) => setTimeout(resolve, length));
9
+ exports.sleep = sleep;
10
+ function maxWait(timeout, promises) {
11
+ if (Array.isArray(promises)) {
12
+ return Promise.race([(0, exports.sleep)(timeout), Promise.all(promises).then(() => undefined)]);
13
+ }
14
+ return Promise.race([(0, exports.sleep)(timeout), Promise.all(Object.values(promises))]).then(() => {
15
+ Object.entries(promises).forEach(([key, value]) => {
16
+ if (value instanceof Promise) {
17
+ promises[key] = undefined;
18
+ value.then((value) => {
19
+ promises[key] = { value };
20
+ });
21
+ }
22
+ else {
23
+ promises[key] = { value };
24
+ }
25
+ });
26
+ return (0, exports.sleep)(0).then(() => promises);
27
+ });
28
+ }
29
+ exports.maxWait = maxWait;
30
+ // ---------------------------------------------------------------------------
31
+ // Adapted from https://github.com/marcelowa/promise-all-properties
32
+ /**
33
+ * A variation of `Promise.all()` that accepts an object with named promises
34
+ * and returns a same-shaped object with the resolved values.
35
+ *
36
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#promiseallobject
37
+ */
38
+ const promiseAllObject = (promisesMap) => Promise.all(Object.values(promisesMap)).then((results) => {
39
+ const keys = Object.keys(promisesMap);
40
+ const resolvedMap = {};
41
+ for (let i = 0; i < results.length; i++) {
42
+ resolvedMap[keys[i]] = results[i];
43
+ }
44
+ return resolvedMap;
45
+ });
46
+ exports.promiseAllObject = promiseAllObject;
package/esm/async.d.ts ADDED
@@ -0,0 +1,26 @@
1
+ type PlainObj = Record<string, unknown>;
2
+ /**
3
+ * Simple sleep function. Returns a promise that resolves after `length`
4
+ * milliseconds.
5
+ */
6
+ export declare const sleep: (length: number) => Promise<void>;
7
+ /**
8
+ * Resolves soon as all of the passed `promises` have resolved, or after
9
+ * `timeout` milliseconds — whichever comes first.
10
+ *
11
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#maxwait
12
+ */
13
+ export declare function maxWait(timeout: number, promises: Array<unknown>): Promise<void>;
14
+ export declare function maxWait<PromiseMap extends PlainObj>(timeout: number, promises: PromiseMap): Promise<{
15
+ -readonly [K in keyof PromiseMap]: {
16
+ value: Awaited<PromiseMap[K]>;
17
+ } | undefined;
18
+ }>;
19
+ /**
20
+ * A variation of `Promise.all()` that accepts an object with named promises
21
+ * and returns a same-shaped object with the resolved values.
22
+ *
23
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#promiseallobject
24
+ */
25
+ export declare const promiseAllObject: <T extends PlainObj>(promisesMap: T) => Promise<{ -readonly [K in keyof T]: Awaited<T[K]>; }>;
26
+ export {};
package/esm/async.js ADDED
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Simple sleep function. Returns a promise that resolves after `length`
3
+ * milliseconds.
4
+ */
5
+ export const sleep = (length) => new Promise((resolve) => setTimeout(resolve, length));
6
+ export function maxWait(timeout, promises) {
7
+ if (Array.isArray(promises)) {
8
+ return Promise.race([sleep(timeout), Promise.all(promises).then(() => undefined)]);
9
+ }
10
+ return Promise.race([sleep(timeout), Promise.all(Object.values(promises))]).then(() => {
11
+ Object.entries(promises).forEach(([key, value]) => {
12
+ if (value instanceof Promise) {
13
+ promises[key] = undefined;
14
+ value.then((value) => {
15
+ promises[key] = { value };
16
+ });
17
+ }
18
+ else {
19
+ promises[key] = { value };
20
+ }
21
+ });
22
+ return sleep(0).then(() => promises);
23
+ });
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Adapted from https://github.com/marcelowa/promise-all-properties
27
+ /**
28
+ * A variation of `Promise.all()` that accepts an object with named promises
29
+ * and returns a same-shaped object with the resolved values.
30
+ *
31
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README.md#promiseallobject
32
+ */
33
+ export const promiseAllObject = (promisesMap) => Promise.all(Object.values(promisesMap)).then((results) => {
34
+ const keys = Object.keys(promisesMap);
35
+ const resolvedMap = {};
36
+ for (let i = 0; i < results.length; i++) {
37
+ resolvedMap[keys[i]] = results[i];
38
+ }
39
+ return resolvedMap;
40
+ });
package/esm/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  /// <reference path="./vanillaExtract.d.ts" />
2
2
  /// <reference path="./http.d.ts" />
3
+ /// <reference path="./async.d.ts" />
3
4
  /// <reference path="./fixIcelandicLocale.d.ts" />
5
+ /// <reference path="./remix/http.d.ts" />
4
6
  /// <reference path="./CookieHubConsent.d.tsx" />
7
+ /// <reference path="./remix/Wait.d.tsx" />
5
8
  /// <reference path="./next/SiteImprove.d.tsx" />
6
9
  /// <reference path="./next/http.d.tsx" />
7
10
 
@@ -0,0 +1,49 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ type WaitPropsBase<T> = {
3
+ /**
4
+ * The value you want to wait for before rendering
5
+ */
6
+ for: Promise<T> | T;
7
+ /**
8
+ * A function to render the children when the value is resolved.
9
+ *
10
+ * (If the promise resolved to an object with a truthy `$error` property,
11
+ * then the `$error` is thrown and this function skipped.)
12
+ */
13
+ children: (data: Exclude<T, {
14
+ $error: string | number | true | object;
15
+ }>) => ReactNode;
16
+ };
17
+ type WaitFallbacks = {
18
+ /**
19
+ * Custom loading/spinner component.
20
+ */
21
+ meanwhile?: ReactNode;
22
+ /**
23
+ * Custom error component if the promise is rejected or if it resolves to an
24
+ * object with an `error` property.
25
+ */
26
+ error?: ReactNode;
27
+ };
28
+ export type WaitProps<T> = WaitPropsBase<T> & WaitFallbacks;
29
+ /**
30
+ * A function component that wraps `@reykjavik/webtools/remix/Wait` to provide
31
+ * custom properties for `meanwhile` and `error` fallbacks, and/or other
32
+ * behaviors.
33
+ *
34
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#type-waitcomponent
35
+ */
36
+ export type WaitComponent<CustomProps extends Record<string, unknown> = Record<never, never>> = (<T>(props: WaitPropsBase<T> & CustomProps) => ReactElement) & {
37
+ displayName?: string;
38
+ };
39
+ /**
40
+ * Wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
41
+ * component, to provide a more ergonomic API.
42
+ *
43
+ * If the awaited promise (`props.for`) resolves to an object with a truthy
44
+ * `$error` property, the `$error` will be thrown.
45
+ *
46
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#wait-component
47
+ */
48
+ export declare const Wait: WaitComponent<WaitFallbacks>;
49
+ export {};
@@ -0,0 +1,21 @@
1
+ import React, { Suspense } from 'react';
2
+ import { Await } from '@remix-run/react';
3
+ /**
4
+ * Wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
5
+ * component, to provide a more ergonomic API.
6
+ *
7
+ * If the awaited promise (`props.for`) resolves to an object with a truthy
8
+ * `$error` property, the `$error` will be thrown.
9
+ *
10
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#wait-component
11
+ */
12
+ export const Wait = (props) => (React.createElement(Suspense, { fallback: props.meanwhile || 'Loading...' },
13
+ React.createElement(Await, { resolve: props.for, errorElement: props.error || 'An error occurred.' }, (value) => {
14
+ if (value && // eslint-disable-line @typescript-eslint/no-unnecessary-condition
15
+ typeof value === 'object' &&
16
+ '$error' in value &&
17
+ value.$error) {
18
+ throw value.$error;
19
+ }
20
+ return props.children(value);
21
+ })));
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Detects if the request is a client fetch, or an initial/full-page load.
3
+ * Useful for deciding whether to defer data fetching or not.
4
+ *
5
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#isclientfetch
6
+ */
7
+ export declare const isClientFetch: (request: Request) => boolean;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Detects if the request is a client fetch, or an initial/full-page load.
3
+ * Useful for deciding whether to defer data fetching or not.
4
+ *
5
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#isclientfetch
6
+ */
7
+ export const isClientFetch = (request) =>
8
+ // For info about this detection method:
9
+ // - https://github.com/remix-run/remix/discussions/5583
10
+ // - https://github.com/sergiodxa/remix-utils/discussions/311#discussioncomment-8572497
11
+ (request.headers.get('Sec-Fetch-Dest') || '') === 'empty';
package/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  /// <reference path="./vanillaExtract.d.ts" />
2
2
  /// <reference path="./http.d.ts" />
3
+ /// <reference path="./async.d.ts" />
3
4
  /// <reference path="./fixIcelandicLocale.d.ts" />
5
+ /// <reference path="./remix/http.d.ts" />
4
6
  /// <reference path="./CookieHubConsent.d.tsx" />
7
+ /// <reference path="./remix/Wait.d.tsx" />
5
8
  /// <reference path="./next/SiteImprove.d.tsx" />
6
9
  /// <reference path="./next/http.d.tsx" />
7
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/webtools",
3
- "version": "0.1.20",
3
+ "version": "0.1.22",
4
4
  "description": "Misc. JS/TS helpers used by Reykjavík City's web dev teams.",
5
5
  "main": "index.js",
6
6
  "repository": "ssh://git@github.com:reykjavikcity/webtools.git",
@@ -14,12 +14,16 @@
14
14
  "@reykjavik/hanna-utils": "^0.2.3"
15
15
  },
16
16
  "peerDependencies": {
17
+ "@remix-run/react": "^2.6.0",
17
18
  "@vanilla-extract/css": "^1.14.1",
18
19
  "next": ">=11",
19
20
  "react": ">=16.8.0",
20
21
  "react-dom": ">=16.8.0"
21
22
  },
22
23
  "peerDependenciesMeta": {
24
+ "@remix-run/react": {
25
+ "optional": true
26
+ },
23
27
  "@vanilla-extract/css": {
24
28
  "optional": true
25
29
  },
@@ -52,14 +56,26 @@
52
56
  "import": "./esm/http.js",
53
57
  "require": "./http.js"
54
58
  },
59
+ "./async": {
60
+ "import": "./esm/async.js",
61
+ "require": "./async.js"
62
+ },
55
63
  "./fixIcelandicLocale": {
56
64
  "import": "./esm/fixIcelandicLocale.js",
57
65
  "require": "./fixIcelandicLocale.js"
58
66
  },
67
+ "./remix/http": {
68
+ "import": "./esm/remix/http.js",
69
+ "require": "./remix/http.js"
70
+ },
59
71
  "./CookieHubConsent": {
60
72
  "import": "./esm/CookieHubConsent.js",
61
73
  "require": "./CookieHubConsent.js"
62
74
  },
75
+ "./remix/Wait": {
76
+ "import": "./esm/remix/Wait.js",
77
+ "require": "./remix/Wait.js"
78
+ },
63
79
  "./next/SiteImprove": {
64
80
  "import": "./esm/next/SiteImprove.js",
65
81
  "require": "./next/SiteImprove.js"
@@ -0,0 +1,49 @@
1
+ import { ReactElement, ReactNode } from 'react';
2
+ type WaitPropsBase<T> = {
3
+ /**
4
+ * The value you want to wait for before rendering
5
+ */
6
+ for: Promise<T> | T;
7
+ /**
8
+ * A function to render the children when the value is resolved.
9
+ *
10
+ * (If the promise resolved to an object with a truthy `$error` property,
11
+ * then the `$error` is thrown and this function skipped.)
12
+ */
13
+ children: (data: Exclude<T, {
14
+ $error: string | number | true | object;
15
+ }>) => ReactNode;
16
+ };
17
+ type WaitFallbacks = {
18
+ /**
19
+ * Custom loading/spinner component.
20
+ */
21
+ meanwhile?: ReactNode;
22
+ /**
23
+ * Custom error component if the promise is rejected or if it resolves to an
24
+ * object with an `error` property.
25
+ */
26
+ error?: ReactNode;
27
+ };
28
+ export type WaitProps<T> = WaitPropsBase<T> & WaitFallbacks;
29
+ /**
30
+ * A function component that wraps `@reykjavik/webtools/remix/Wait` to provide
31
+ * custom properties for `meanwhile` and `error` fallbacks, and/or other
32
+ * behaviors.
33
+ *
34
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#type-waitcomponent
35
+ */
36
+ export type WaitComponent<CustomProps extends Record<string, unknown> = Record<never, never>> = (<T>(props: WaitPropsBase<T> & CustomProps) => ReactElement) & {
37
+ displayName?: string;
38
+ };
39
+ /**
40
+ * Wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
41
+ * component, to provide a more ergonomic API.
42
+ *
43
+ * If the awaited promise (`props.for`) resolves to an object with a truthy
44
+ * `$error` property, the `$error` will be thrown.
45
+ *
46
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#wait-component
47
+ */
48
+ export declare const Wait: WaitComponent<WaitFallbacks>;
49
+ export {};
package/remix/Wait.js ADDED
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.Wait = void 0;
27
+ const react_1 = __importStar(require("react"));
28
+ const react_2 = require("@remix-run/react");
29
+ /**
30
+ * Wrapper around [Remix's `Await`](https://remix.run/docs/en/2/components/await)
31
+ * component, to provide a more ergonomic API.
32
+ *
33
+ * If the awaited promise (`props.for`) resolves to an object with a truthy
34
+ * `$error` property, the `$error` will be thrown.
35
+ *
36
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#wait-component
37
+ */
38
+ const Wait = (props) => (react_1.default.createElement(react_1.Suspense, { fallback: props.meanwhile || 'Loading...' },
39
+ react_1.default.createElement(react_2.Await, { resolve: props.for, errorElement: props.error || 'An error occurred.' }, (value) => {
40
+ if (value && // eslint-disable-line @typescript-eslint/no-unnecessary-condition
41
+ typeof value === 'object' &&
42
+ '$error' in value &&
43
+ value.$error) {
44
+ throw value.$error;
45
+ }
46
+ return props.children(value);
47
+ })));
48
+ exports.Wait = Wait;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Detects if the request is a client fetch, or an initial/full-page load.
3
+ * Useful for deciding whether to defer data fetching or not.
4
+ *
5
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#isclientfetch
6
+ */
7
+ export declare const isClientFetch: (request: Request) => boolean;
package/remix/http.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isClientFetch = void 0;
4
+ /**
5
+ * Detects if the request is a client fetch, or an initial/full-page load.
6
+ * Useful for deciding whether to defer data fetching or not.
7
+ *
8
+ * @see https://github.com/reykjavikcity/webtools/blob/v0.1/README-remix.md#isclientfetch
9
+ */
10
+ const isClientFetch = (request) =>
11
+ // For info about this detection method:
12
+ // - https://github.com/remix-run/remix/discussions/5583
13
+ // - https://github.com/sergiodxa/remix-utils/discussions/311#discussioncomment-8572497
14
+ (request.headers.get('Sec-Fetch-Dest') || '') === 'empty';
15
+ exports.isClientFetch = isClientFetch;