@rethunk/react-load-hooks 0.3.0 → 0.4.0
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/README.md +7 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/loader.d.ts.map +1 -1
- package/dist/loader.js +2 -1
- package/dist/useSize.d.ts +8 -0
- package/dist/useSize.d.ts.map +1 -0
- package/dist/useSize.js +24 -0
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/loader.ts +2 -1
- package/src/useSize.ts +31 -0
package/README.md
CHANGED
|
@@ -32,20 +32,20 @@ A worked example and the contracts: [HUMANS.md](HUMANS.md).
|
|
|
32
32
|
- `useLoaded` and `useDebouncedLoad` fetch on dependency changes and drop superseded answers.
|
|
33
33
|
- `loader` is the same logic without React, for a store or a class.
|
|
34
34
|
- `useStoredState` persists UI state in `localStorage`.
|
|
35
|
-
- `
|
|
35
|
+
- `useSize` tracks an element's width and height before paint; `useWidth` is the width only. `useDebouncedValue` settles a fast-changing value.
|
|
36
36
|
- `sameValue`, `keepUnchanged` and `keepFields` keep the stored value when a fresh backend answer is equal, so subscribers do not re-render.
|
|
37
37
|
- `clockWhen`, `absoluteWhen` and `relativeWhen` label a time through `Intl`: "3 minutes ago" within a week, a date after.
|
|
38
38
|
- `useNowValue` and `useSecondValue` share one timer per period and re-render only on a changed answer.
|
|
39
39
|
|
|
40
40
|
## Documentation
|
|
41
41
|
|
|
42
|
-
| Doc
|
|
43
|
-
|
|
|
44
|
-
| [HUMANS.md](HUMANS.md)
|
|
45
|
-
| [AGENTS.md](AGENTS.md)
|
|
42
|
+
| Doc | Audience |
|
|
43
|
+
| ---------------------------------- | ------------------------------ |
|
|
44
|
+
| [HUMANS.md](HUMANS.md) | Running and using the package |
|
|
45
|
+
| [AGENTS.md](AGENTS.md) | Internals, invariants, and why |
|
|
46
46
|
| [CONTRIBUTING.md](CONTRIBUTING.md) | Process — commits, tests, docs |
|
|
47
|
-
| [SECURITY.md](SECURITY.md)
|
|
48
|
-
| [CHANGELOG.md](CHANGELOG.md)
|
|
47
|
+
| [SECURITY.md](SECURITY.md) | Vulnerability disclosure |
|
|
48
|
+
| [CHANGELOG.md](CHANGELOG.md) | Release notes |
|
|
49
49
|
|
|
50
50
|
## License
|
|
51
51
|
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { useDebouncedLoad } from './useDebouncedLoad.ts';
|
|
|
5
5
|
export { useDebouncedValue } from './useDebouncedValue.ts';
|
|
6
6
|
export { type LoadedOptions, useLoaded } from './useLoaded.ts';
|
|
7
7
|
export { useNowValue, useSecondValue } from './useNow.ts';
|
|
8
|
+
export { useSize } from './useSize.ts';
|
|
8
9
|
export { readStored, useStoredState, writeStored } from './useStoredState.ts';
|
|
9
10
|
export { useWidth } from './useWidth.ts';
|
|
10
11
|
export { absoluteWhen, clockWhen, relativeWhen, type WhenOptions } from './when.ts';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC1D,OAAO,EAAE,KAAK,aAAa,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC9D,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AACtC,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAC7E,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ export { useDebouncedLoad } from './useDebouncedLoad.js';
|
|
|
5
5
|
export { useDebouncedValue } from './useDebouncedValue.js';
|
|
6
6
|
export { useLoaded } from './useLoaded.js';
|
|
7
7
|
export { useNowValue, useSecondValue } from './useNow.js';
|
|
8
|
+
export { useSize } from './useSize.js';
|
|
8
9
|
export { readStored, useStoredState, writeStored } from './useStoredState.js';
|
|
9
10
|
export { useWidth } from './useWidth.js';
|
|
10
11
|
export { absoluteWhen, clockWhen, relativeWhen } from './when.js';
|
package/dist/loader.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAK3C,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC/B,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,4HAA4H;AAC5H,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAOzD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EACtB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,EACE,cAAsB,EACtB,KAAW,GACZ,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAAO;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../src/loader.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAA;AAK3C,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAA;IACzB,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC/B,OAAO,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB;AAED,4HAA4H;AAC5H,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;AAOzD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EACtB,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC,EACjB,EACE,cAAsB,EACtB,KAAW,GACZ,GAAE;IAAE,cAAc,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAA;CAAO;IA8C1E,IAAI,OAAO,cAAc,QAAQ,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI;IAoB/C,8GAA8G;IAC9G,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAOxC,IAAI;EAKP"}
|
package/dist/loader.js
CHANGED
|
@@ -27,7 +27,8 @@ export function loader(sink, { keepAcrossKeys = false, defer = now, } = {}) {
|
|
|
27
27
|
const mineController = new AbortController();
|
|
28
28
|
controller = mineController;
|
|
29
29
|
sink.loading(true);
|
|
30
|
-
|
|
30
|
+
// A load that throws before returning a promise settles through the sink like a rejection.
|
|
31
|
+
return new Promise((resolve) => resolve(load(mineController.signal)))
|
|
31
32
|
.then((value) => {
|
|
32
33
|
if (current()) {
|
|
33
34
|
sink.value(value);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
/** The client size of the element the ref points at, kept current as it resizes; 0 until measured. */
|
|
3
|
+
declare function useSize<T extends HTMLElement>(ref: RefObject<T | null>): {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
};
|
|
7
|
+
export { useSize };
|
|
8
|
+
//# sourceMappingURL=useSize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSize.d.ts","sourceRoot":"","sources":["../src/useSize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,SAAS,EAA6B,MAAM,OAAO,CAAA;AAGjE,sGAAsG;AACtG,iBAAS,OAAO,CAAC,CAAC,SAAS,WAAW,EACpC,GAAG,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GACvB;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAsBnC;AAED,OAAO,EAAE,OAAO,EAAE,CAAA"}
|
package/dist/useSize.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useLayoutEffect, useState } from 'react';
|
|
2
|
+
import { flushSync } from 'react-dom';
|
|
3
|
+
/** The client size of the element the ref points at, kept current as it resizes; 0 until measured. */
|
|
4
|
+
function useSize(ref) {
|
|
5
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
6
|
+
// Measured before paint, so the first frame is laid out for the real size rather than 0.
|
|
7
|
+
useLayoutEffect(() => {
|
|
8
|
+
const el = ref.current;
|
|
9
|
+
if (!el) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const apply = () => setSize((prev) => prev.width === el.clientWidth && prev.height === el.clientHeight
|
|
13
|
+
? prev
|
|
14
|
+
: { width: el.clientWidth, height: el.clientHeight });
|
|
15
|
+
// Rendered before the browser paints the resize: a default-priority update would paint one frame laid out for the
|
|
16
|
+
// old size, with whatever columns or scrollbar that size implies.
|
|
17
|
+
const ro = new ResizeObserver(() => flushSync(apply));
|
|
18
|
+
ro.observe(el);
|
|
19
|
+
apply();
|
|
20
|
+
return () => ro.disconnect();
|
|
21
|
+
}, [ref]);
|
|
22
|
+
return size;
|
|
23
|
+
}
|
|
24
|
+
export { useSize };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rethunk/react-load-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "React hooks for async loads that never land out of order, plus the small state hooks an app builds screens from.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rethunk.Tech, LLC",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"pack:check": "bun scripts/check-pack.ts"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@biomejs/biome": "^2.6.
|
|
32
|
+
"@biomejs/biome": "^2.6.6",
|
|
33
33
|
"@happy-dom/global-registrator": "^21.0.4",
|
|
34
34
|
"@testing-library/dom": "^10.4.2",
|
|
35
35
|
"@testing-library/react": "^16.3.3",
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { useDebouncedLoad } from './useDebouncedLoad.ts'
|
|
|
5
5
|
export { useDebouncedValue } from './useDebouncedValue.ts'
|
|
6
6
|
export { type LoadedOptions, useLoaded } from './useLoaded.ts'
|
|
7
7
|
export { useNowValue, useSecondValue } from './useNow.ts'
|
|
8
|
+
export { useSize } from './useSize.ts'
|
|
8
9
|
export { readStored, useStoredState, writeStored } from './useStoredState.ts'
|
|
9
10
|
export { useWidth } from './useWidth.ts'
|
|
10
11
|
export { absoluteWhen, clockWhen, relativeWhen, type WhenOptions } from './when.ts'
|
package/src/loader.ts
CHANGED
|
@@ -49,7 +49,8 @@ export function loader<T>(
|
|
|
49
49
|
const mineController = new AbortController()
|
|
50
50
|
controller = mineController
|
|
51
51
|
sink.loading(true)
|
|
52
|
-
|
|
52
|
+
// A load that throws before returning a promise settles through the sink like a rejection.
|
|
53
|
+
return new Promise<T>((resolve) => resolve(load(mineController.signal)))
|
|
53
54
|
.then(
|
|
54
55
|
(value) => {
|
|
55
56
|
if (current()) {
|
package/src/useSize.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type RefObject, useLayoutEffect, useState } from 'react'
|
|
2
|
+
import { flushSync } from 'react-dom'
|
|
3
|
+
|
|
4
|
+
/** The client size of the element the ref points at, kept current as it resizes; 0 until measured. */
|
|
5
|
+
function useSize<T extends HTMLElement>(
|
|
6
|
+
ref: RefObject<T | null>,
|
|
7
|
+
): { width: number; height: number } {
|
|
8
|
+
const [size, setSize] = useState({ width: 0, height: 0 })
|
|
9
|
+
// Measured before paint, so the first frame is laid out for the real size rather than 0.
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
const el = ref.current
|
|
12
|
+
if (!el) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
const apply = () =>
|
|
16
|
+
setSize((prev) =>
|
|
17
|
+
prev.width === el.clientWidth && prev.height === el.clientHeight
|
|
18
|
+
? prev
|
|
19
|
+
: { width: el.clientWidth, height: el.clientHeight },
|
|
20
|
+
)
|
|
21
|
+
// Rendered before the browser paints the resize: a default-priority update would paint one frame laid out for the
|
|
22
|
+
// old size, with whatever columns or scrollbar that size implies.
|
|
23
|
+
const ro = new ResizeObserver(() => flushSync(apply))
|
|
24
|
+
ro.observe(el)
|
|
25
|
+
apply()
|
|
26
|
+
return () => ro.disconnect()
|
|
27
|
+
}, [ref])
|
|
28
|
+
return size
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { useSize }
|