@venizia/ardor-react 0.4.8-4 → 0.4.8-6

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.
@@ -16,7 +16,8 @@ export class ResizeObservation {
16
16
  onFrame(batch);
17
17
  }
18
18
  };
19
- const observer = new ResizeObserver((entries) => {
19
+ let observer;
20
+ const onEntries = (entries) => {
20
21
  for (const entry of entries) {
21
22
  latest.set(entry.target, entry);
22
23
  }
@@ -31,9 +32,14 @@ export class ResizeObservation {
31
32
  frame = undefined;
32
33
  flush();
33
34
  });
34
- });
35
+ };
35
36
  const update = (targets) => {
36
37
  const wanted = new Set(children ? targets.flatMap((target) => [target, ...target.children]) : targets);
38
+ // No target and no observer yet: there is nothing to watch, so none is made.
39
+ if (!observer && wanted.size === 0) {
40
+ return;
41
+ }
42
+ observer ??= new ResizeObserver(onEntries);
37
43
  for (const element of watched) {
38
44
  if (!wanted.has(element)) {
39
45
  observer.unobserve(element);
@@ -47,7 +53,7 @@ export class ResizeObservation {
47
53
  }
48
54
  watched = wanted;
49
55
  };
50
- const mutations = children ? new MutationObserver(() => update(current)) : undefined;
56
+ let mutations;
51
57
  return {
52
58
  sync: (targets) => {
53
59
  if (targets.length === current.length &&
@@ -57,13 +63,16 @@ export class ResizeObservation {
57
63
  current = targets;
58
64
  update(targets);
59
65
  // A removed target stays registered on `mutations` until `stop`; its callback only updates the current targets.
60
- for (const target of targets) {
61
- mutations?.observe(target, { childList: true });
66
+ if (children && targets.length > 0) {
67
+ mutations ??= new MutationObserver(() => update(current));
68
+ for (const target of targets) {
69
+ mutations.observe(target, { childList: true });
70
+ }
62
71
  }
63
72
  },
64
73
  stop: () => {
65
74
  mutations?.disconnect();
66
- observer.disconnect();
75
+ observer?.disconnect();
67
76
  if (frame !== undefined) {
68
77
  cancelAnimationFrame(frame);
69
78
  }
@@ -35,14 +35,14 @@ export interface IUseStoredState {
35
35
  * that writes through `BrowserStorage`, and with other tabs. Pass a stable `codec` and `defaultValue`.
36
36
  * A write or a removal that the storage refuses is kept in this hook until the storage next changes.
37
37
  * With `writeDelayMs` the value waits in the same place for the delay: a change of the storage from
38
- * outside drops it, and an unmount or a new key or storage writes it at once.
38
+ * outside drops it, and an unmount, a new key or storage, a `pagehide` event or a hidden page writes it at once.
39
39
  *
40
40
  * @example
41
41
  * ```tsx
42
42
  * import { useStoredState } from '@venizia/ardor';
43
43
  *
44
44
  * export function ThemeToggle() {
45
- * const { value, setValue } = useStoredState({ key: '@app/store/theme', defaultValue: 'light' });
45
+ * const { value, setValue } = useStoredState({ key: '@app/prefs/theme', defaultValue: 'light' });
46
46
  * return <button onClick={() => setValue(value === 'light' ? 'dark' : 'light')}>{value}</button>;
47
47
  * }
48
48
  * ```
@@ -7,14 +7,14 @@ const isUpdater = (next) => typeof next === 'function';
7
7
  * that writes through `BrowserStorage`, and with other tabs. Pass a stable `codec` and `defaultValue`.
8
8
  * A write or a removal that the storage refuses is kept in this hook until the storage next changes.
9
9
  * With `writeDelayMs` the value waits in the same place for the delay: a change of the storage from
10
- * outside drops it, and an unmount or a new key or storage writes it at once.
10
+ * outside drops it, and an unmount, a new key or storage, a `pagehide` event or a hidden page writes it at once.
11
11
  *
12
12
  * @example
13
13
  * ```tsx
14
14
  * import { useStoredState } from '@venizia/ardor';
15
15
  *
16
16
  * export function ThemeToggle() {
17
- * const { value, setValue } = useStoredState({ key: '@app/store/theme', defaultValue: 'light' });
17
+ * const { value, setValue } = useStoredState({ key: '@app/prefs/theme', defaultValue: 'light' });
18
18
  * return <button onClick={() => setValue(value === 'light' ? 'dark' : 'light')}>{value}</button>;
19
19
  * }
20
20
  * ```
@@ -35,14 +35,30 @@ export const useStoredState = (opts) => {
35
35
  const write = React.useCallback((entry) => {
36
36
  keep(BrowserStorage.set(entry) ? undefined : entry);
37
37
  }, [keep]);
38
- // A new key or storage, and an unmount, write the pending value to the key it was set for.
38
+ const isDelayed = writeDelayMs > 0;
39
+ const flush = React.useCallback(() => {
40
+ if (timerRef.current !== undefined && keptRef.current) {
41
+ write(keptRef.current);
42
+ }
43
+ }, [write]);
44
+ // A new key or storage, an unmount, and a page that closes or hides write the pending value to the
45
+ // key it was set for: closing a tab does not unmount.
39
46
  React.useEffect(() => {
40
- return () => {
41
- if (timerRef.current !== undefined && keptRef.current) {
42
- write(keptRef.current);
47
+ const onHide = () => {
48
+ if (document.visibilityState === 'hidden') {
49
+ flush();
43
50
  }
44
51
  };
45
- }, [key, storage, write]);
52
+ if (isDelayed) {
53
+ window.addEventListener('pagehide', flush);
54
+ document.addEventListener('visibilitychange', onHide);
55
+ }
56
+ return () => {
57
+ window.removeEventListener('pagehide', flush);
58
+ document.removeEventListener('visibilitychange', onHide);
59
+ flush();
60
+ };
61
+ }, [key, storage, isDelayed, flush]);
46
62
  const subscribe = React.useCallback((listener) => {
47
63
  return BrowserStorage.subscribe({
48
64
  key,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venizia/ardor-react",
3
- "version": "0.4.8-4",
3
+ "version": "0.4.8-6",
4
4
  "description": "ARDOR React bindings - the application context, the injectable and logger hooks that read it, typed Redux hook factories, and the framework-agnostic UI hooks (debounce, autosave, confirm, clipboard, element size, window dimensions). No react-admin.",
5
5
  "keywords": [
6
6
  "ardor",
@@ -59,7 +59,7 @@
59
59
  "prepublishOnly": "make rebuild"
60
60
  },
61
61
  "dependencies": {
62
- "@venizia/ardor-kernel": "0.4.8-4"
62
+ "@venizia/ardor-kernel": "0.4.8-6"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@happy-dom/global-registrator": "^21.0.4",
@@ -70,10 +70,10 @@
70
70
  "@types/react": "^19",
71
71
  "@types/react-dom": "^19",
72
72
  "@venizia/dev-configs": "^0.4.8-2",
73
- "@venizia/ignis-filter": "^0.4.7-2",
74
- "@venizia/ignis-helpers": "^0.4.8-3",
73
+ "@venizia/ignis-filter": "^0.4.7-3",
74
+ "@venizia/ignis-helpers": "^0.4.8-5",
75
75
  "@venizia/ignis-inversion": "^0.4.7-2",
76
- "@venizia/ignis-kernel": "^0.4.8-3",
76
+ "@venizia/ignis-kernel": "^0.4.8-5",
77
77
  "eslint": "^10.13.0",
78
78
  "oxlint": "1.89.0",
79
79
  "oxlint-tsgolint": "7.0.2004",
@@ -87,10 +87,10 @@
87
87
  },
88
88
  "peerDependencies": {
89
89
  "@reduxjs/toolkit": ">=2.13.0",
90
- "@venizia/ignis-filter": "^0.4.7-2",
91
- "@venizia/ignis-helpers": "^0.4.8-3",
90
+ "@venizia/ignis-filter": "^0.4.7-3",
91
+ "@venizia/ignis-helpers": "^0.4.8-5",
92
92
  "@venizia/ignis-inversion": "^0.4.7-2",
93
- "@venizia/ignis-kernel": "^0.4.8-3",
93
+ "@venizia/ignis-kernel": "^0.4.8-5",
94
94
  "react": ">=19.3.1",
95
95
  "react-redux": ">=9.3.0",
96
96
  "reflect-metadata": ">=0.2.2"
@@ -111,7 +111,7 @@
111
111
  "react",
112
112
  "react-redux"
113
113
  ],
114
- "limit": "3450 B"
114
+ "limit": "3540 B"
115
115
  }
116
116
  ],
117
117
  "engines": {