@fringeworks/react-utils 0.10.0 → 0.11.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.
Files changed (74) hide show
  1. package/README.ja.md +101 -21
  2. package/README.md +114 -34
  3. package/{_helpers → _internal}/_traverseChildren.cjs +1 -1
  4. package/{_helpers → _internal}/_traverseChildren.d.cts +1 -1
  5. package/{_helpers → _internal}/_traverseChildren.d.mts +1 -1
  6. package/{_helpers → _internal}/_traverseChildren.mjs +1 -1
  7. package/_internal/_useChangeHandler.cjs +48 -0
  8. package/_internal/_useChangeHandler.d.cts +18 -0
  9. package/_internal/_useChangeHandler.d.mts +18 -0
  10. package/_internal/_useChangeHandler.mjs +47 -0
  11. package/{_helpers → _internal}/_useExecutionHandler.cjs +13 -12
  12. package/{_helpers → _internal}/_useExecutionHandler.d.cts +6 -4
  13. package/{_helpers → _internal}/_useExecutionHandler.d.mts +6 -4
  14. package/_internal/_useExecutionHandler.mjs +37 -0
  15. package/hooks/index.cjs +11 -2
  16. package/hooks/index.d.cts +6 -1
  17. package/hooks/index.d.mts +6 -1
  18. package/hooks/index.mjs +9 -3
  19. package/hooks/useChangeEffect/index.cjs +5 -0
  20. package/hooks/useChangeEffect/index.d.cts +3 -0
  21. package/hooks/useChangeEffect/index.d.mts +3 -0
  22. package/hooks/useChangeEffect/index.mjs +3 -0
  23. package/hooks/useChangeEffect/types.cjs +1 -0
  24. package/hooks/useChangeEffect/types.d.cts +11 -0
  25. package/hooks/useChangeEffect/types.d.mts +11 -0
  26. package/hooks/useChangeEffect/types.mjs +1 -0
  27. package/hooks/useChangeEffect/useChangeEffect.cjs +37 -0
  28. package/hooks/useChangeEffect/useChangeEffect.d.cts +27 -0
  29. package/hooks/useChangeEffect/useChangeEffect.d.mts +27 -0
  30. package/hooks/useChangeEffect/useChangeEffect.mjs +36 -0
  31. package/hooks/useChangeLayoutEffect/index.cjs +5 -0
  32. package/hooks/useChangeLayoutEffect/index.d.cts +2 -0
  33. package/hooks/useChangeLayoutEffect/index.d.mts +2 -0
  34. package/hooks/useChangeLayoutEffect/index.mjs +3 -0
  35. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.cjs +24 -0
  36. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.d.cts +14 -0
  37. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.d.mts +14 -0
  38. package/hooks/useChangeLayoutEffect/useChangeLayoutEffect.mjs +23 -0
  39. package/hooks/useExecutionEffect/useExecutionEffect.cjs +8 -6
  40. package/hooks/useExecutionEffect/useExecutionEffect.d.cts +8 -5
  41. package/hooks/useExecutionEffect/useExecutionEffect.d.mts +8 -5
  42. package/hooks/useExecutionEffect/useExecutionEffect.mjs +7 -5
  43. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.cjs +3 -3
  44. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.d.cts +3 -2
  45. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.d.mts +3 -2
  46. package/hooks/useExecutionLayoutEffect/useExecutionLayoutEffect.mjs +2 -2
  47. package/hooks/useExecutionMark/index.cjs +5 -0
  48. package/hooks/useExecutionMark/index.d.cts +3 -0
  49. package/hooks/useExecutionMark/index.d.mts +3 -0
  50. package/hooks/useExecutionMark/index.mjs +3 -0
  51. package/hooks/useExecutionMark/types.cjs +1 -0
  52. package/hooks/useExecutionMark/types.d.cts +15 -0
  53. package/hooks/useExecutionMark/types.d.mts +15 -0
  54. package/hooks/useExecutionMark/types.mjs +1 -0
  55. package/hooks/useExecutionMark/useExecutionMark.cjs +29 -0
  56. package/hooks/useExecutionMark/useExecutionMark.d.cts +19 -0
  57. package/hooks/useExecutionMark/useExecutionMark.d.mts +19 -0
  58. package/hooks/useExecutionMark/useExecutionMark.mjs +28 -0
  59. package/index.cjs +7 -1
  60. package/index.d.cts +6 -1
  61. package/index.d.mts +6 -1
  62. package/index.mjs +5 -2
  63. package/package.json +31 -1
  64. package/utils/forEachContent/forEachContent.cjs +2 -2
  65. package/utils/forEachContent/forEachContent.mjs +1 -1
  66. package/utils/forEachElement/forEachElement.cjs +2 -2
  67. package/utils/forEachElement/forEachElement.mjs +1 -1
  68. package/utils/markAt/types.d.cts +3 -0
  69. package/utils/markAt/types.d.mts +3 -0
  70. package/utils/transformContent/transformContent.cjs +2 -2
  71. package/utils/transformContent/transformContent.mjs +1 -1
  72. package/utils/transformElement/transformElement.cjs +2 -2
  73. package/utils/transformElement/transformElement.mjs +1 -1
  74. package/_helpers/_useExecutionHandler.mjs +0 -36
package/README.ja.md CHANGED
@@ -31,20 +31,23 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
31
31
 
32
32
  ### hook
33
33
 
34
- | hook | 説明 |
35
- | --------------------------------------------- | ---------------------------------------------------------------------- |
36
- | `useCombinedRefs(...refs)` | 複数の ref にまとめて値を渡す callback ref を返す |
37
- | `useConstant(factory)` | 初回のレンダリングで作った値を、アンマウントされるまで返し続ける |
38
- | `useExecutionAt(options?)` | Execution Request プロパティに渡す値と、実行のきっかけになる関数を返す |
39
- | `useExecutionEffect(request, callback)` | Execution Request プロパティで要求されたときに、処理を実行する |
40
- | `useExecutionLayoutEffect(request, callback)` | `useExecutionEffect` の、画面に表示する前に実行する版 |
41
- | `useIsHydrated()` | ハイドレーションが終わっているかを返す |
42
- | `useIsMounted()` | マウントされているかどうかを判定する関数を返す |
43
- | `useLatestRef(value)` | 最新の値を保持する参照を返す |
44
- | `usePreviousValue(value, options?)` | 前回の値を返す |
45
- | `useRerender()` | 再レンダリングだけを起こす関数を返す |
46
- | `useStableCallback(callback)` | 参照が変わらず、最新のコールバックを呼ぶ関数を返す |
47
- | `useStableLength(arr)` | 配列の要素数がレンダリングをまたいで変わらないことを確認する |
34
+ | hook | 説明 |
35
+ | ----------------------------------------------- | ------------------------------------------------------------------------ |
36
+ | `useChangeEffect(dependencies, callback)` | `xxxOn` のプロパティで受け取った依存の値が変わったときに、処理を実行する |
37
+ | `useChangeLayoutEffect(dependencies, callback)` | `useChangeEffect` の、画面に表示する前に実行する版 |
38
+ | `useCombinedRefs(...refs)` | 複数の ref にまとめて値を渡す callback ref を返す |
39
+ | `useConstant(factory)` | 初回のレンダリングで作った値を、アンマウントされるまで返し続ける |
40
+ | `useExecutionAt(options?)` | Execution Request プロパティに渡す値と、実行のきっかけになる関数を返す |
41
+ | `useExecutionEffect(request, callback)` | Execution Request プロパティで要求されたときに、処理を実行する |
42
+ | `useExecutionLayoutEffect(request, callback)` | `useExecutionEffect` の、画面に表示する前に実行する版 |
43
+ | `useExecutionMark()` | payload の無い Execution Request と、要求する関数を返す |
44
+ | `useIsHydrated()` | ハイドレーションが終わっているかを返す |
45
+ | `useIsMounted()` | マウントされているかどうかを判定する関数を返す |
46
+ | `useLatestRef(value)` | 最新の値を保持する参照を返す |
47
+ | `usePreviousValue(value, options?)` | 前回の値を返す |
48
+ | `useRerender()` | 再レンダリングだけを起こす関数を返す |
49
+ | `useStableCallback(callback)` | 参照が変わらず、最新のコールバックを呼ぶ関数を返す |
50
+ | `useStableLength(arr)` | 配列の要素数がレンダリングをまたいで変わらないことを確認する |
48
51
 
49
52
  ### 関数
50
53
 
@@ -61,6 +64,59 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
61
64
  | `transformContent(children, callback)` | Fragment を透過し、エレメント・文字列・数値を変換した配列を返す |
62
65
  | `transformElement(children, callback)` | Fragment を透過し、エレメントを変換した配列を返す |
63
66
 
67
+ ### `useChangeEffect`
68
+
69
+ ```ts
70
+ useChangeEffect(
71
+ dependencies: ChangeDependencies | undefined,
72
+ callback: () => void,
73
+ ): void
74
+ ```
75
+
76
+ `xxxOn` のプロパティ(例: `refreshOn`)を受け取る側の hook です。受け取った依存の値(Change Dependencies)が変わったときに、`callback` を実行します。処理は画面に表示した後(`useEffect`)に実行します。
77
+
78
+ 測り直しや再計算など、結果が入力だけで決まる操作を、利用者が渡した値の変化に合わせて実行するために使います。回数や payload に意味がある命令は、Execution Request(`useExecutionAt`・`useExecutionEffect`)を使います。
79
+
80
+ - 1段目のプロパティを `Object.is` で比べ、どれかが変わったときに実行します。プロパティが増えた・減った場合も変化とみなします。
81
+ - 比べるのは1段目だけです。プロパティの値がオブジェクトの場合は参照で比べるため、その場で作ったオブジェクト(`{ style }` など)を入れると、毎回変化とみなします。
82
+ - 依存の値そのものは、その場で書いたオブジェクトを渡してかまいません。
83
+ - 初回(マウントしたとき)は実行しません。
84
+ - `dependencies` が `undefined` の場合は、プロパティの無いオブジェクトとして扱います。省略できるプロパティをそのまま渡せます。
85
+ - `callback` は最新のものを呼びます。`callback` の参照が変わっても実行しません。
86
+ - Execution Request(`useExecutionAt` の値)をプロパティに入れると、要求のたびに実行します。
87
+
88
+ ```tsx
89
+ // 渡す側
90
+ <WrappedComponent refreshOn={{ className, theme }} />;
91
+
92
+ // 受け取る側
93
+ type Props = { refreshOn?: ChangeDependencies };
94
+
95
+ function WrappedComponent({ refreshOn }: Props) {
96
+ const instance = useConstant(() => createInstance());
97
+ useChangeEffect(refreshOn, () => {
98
+ instance.refresh();
99
+ });
100
+ // ...
101
+ }
102
+ ```
103
+
104
+ | 引数 | 型 | 説明 |
105
+ | -------------- | --------------------------------- | ------------------------------------ |
106
+ | `dependencies` | `ChangeDependencies \| undefined` | 受け取った依存の値 |
107
+ | `callback` | `() => void` | 依存の値が変わったときに実行する処理 |
108
+
109
+ ### `useChangeLayoutEffect`
110
+
111
+ ```ts
112
+ useChangeLayoutEffect(
113
+ dependencies: ChangeDependencies | undefined,
114
+ callback: () => void,
115
+ ): void
116
+ ```
117
+
118
+ `useChangeEffect` の、処理を画面に表示する前(`useLayoutEffect`)に実行する版です。測り直しなど、結果を画面に表示する前に反映したい処理に使います。依存の値の扱いと引数は `useChangeEffect` と同じです。
119
+
64
120
  ### `useCombinedRefs`
65
121
 
66
122
  ```ts
@@ -101,7 +157,7 @@ useExecutionAt<P = void>(
101
157
 
102
158
  命令的な API を持つ部品をラップしたコンポーネントで、プロパティを通じてそのメソッドを実行するための hook です。Execution Request プロパティ(`undoAt` のように `At` で終わるプロパティ)に渡す値と、実行のきっかけになる関数を返します。
103
159
 
104
- - `request.at` は初期状態では `0` で、`execute` を呼ぶたびに増加します。受け取る側は `useExecutionEffect` / `useExecutionLayoutEffect` で処理を実行します。
160
+ - `request.at` は初期状態では `0` で、`execute` を呼ぶたびに増加します。受け取る側は `useExecutionEffect` / `useExecutionLayoutEffect` で処理を実行します。payload が要らない場合は `useExecutionMark` を使います。
105
161
  - `execute` の参照は常に同じです。
106
162
 
107
163
  ```tsx
@@ -119,15 +175,17 @@ const [undoAt, undo] = useExecutionAt<string>();
119
175
 
120
176
  ```ts
121
177
  useExecutionEffect<P = void>(
122
- request: ExecutionRequest<P> | undefined,
178
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
123
179
  callback: (payload: P | undefined) => void,
124
180
  ): void
125
181
  ```
126
182
 
127
- `useExecutionAt` と対になる、受け取る側の hook です。Execution Request プロパティで要求されたときに、要求の `payload` を渡して `callback` を実行します。処理は画面に表示した後(`useEffect`)に実行します。
183
+ `useExecutionAt`・`useExecutionMark` と対になる、受け取る側の hook です。Execution Request プロパティで要求されたときに、要求の `payload` を渡して `callback` を実行します。処理は画面に表示した後(`useEffect`)に実行します。
128
184
 
129
- - `request.at` が変わったときだけ実行します。`payload` は比べません。
130
- - マウントより前に出た要求は実行しません。マウントしたときにすでに渡されていた要求も実行しません。
185
+ - 印(`request.at`、または `ExecutionMark` の値)が変わったときだけ実行します。`payload` は比べません。
186
+ - マウントしたときに渡されていた要求は実行しません(マウントしたときの印を基準にします)。
187
+ - 印が `0`(まだ要求していない)の場合は実行しません。
188
+ - `ExecutionMark` を受け取った場合、`callback` の `payload` は `undefined` です。印は `markAt()` で作ったものでなくてもかまいません(要求のたびに異なる値であればよい)。
131
189
  - 次のレンダリングまでに複数回要求した場合は、最後の要求だけを実行します。
132
190
  - `callback` は最新のものを呼びます。`callback` の参照が変わっても実行しません。
133
191
  - `request` が `undefined` の場合は、要求なしとして扱います。省略できるプロパティをそのまま渡せます。
@@ -153,13 +211,32 @@ function WrappedImperativeComponent({ undoAt }: Props) {
153
211
 
154
212
  ```ts
155
213
  useExecutionLayoutEffect<P = void>(
156
- request: ExecutionRequest<P> | undefined,
214
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
157
215
  callback: (payload: P | undefined) => void,
158
216
  ): void
159
217
  ```
160
218
 
161
219
  `useExecutionEffect` の、処理を画面に表示する前(`useLayoutEffect`)に実行する版です。スクロールの位置を合わせる、計測し直すなど、結果を画面に表示する前に反映したい処理に使います。要求の扱いと引数は `useExecutionEffect` と同じです。
162
220
 
221
+ ### `useExecutionMark`
222
+
223
+ ```ts
224
+ useExecutionMark(): UseExecutionMarkResult
225
+ ```
226
+
227
+ `useExecutionAt` の payload を持たない版です。payload の無い Execution Request(`ExecutionMark`。実体は数値)と、要求する関数を返します。命令的な部品の `focus()`・`reset()` のような、引数の要らないメソッドをプロパティから実行する場合に使います。
228
+
229
+ - 値は初期状態では `0`(まだ要求していない)で、`execute` を呼ぶたびに `markAt()` で作った新しい値になります。
230
+ - `execute` の参照は常に同じです。
231
+ - この hook を使わずに、`ExecutionMark` を自分で作ってもかまいません(ストアや設定に置いた値など)。その場合は、**要求のたびに前回と異なる値にしてください**。同じ値を続けて渡すと、2回目は要求とみなしません。`markAt()` を使うと、呼ぶたびに異なる値になります。
232
+
233
+ ```tsx
234
+ const [focusAt, focus] = useExecutionMark();
235
+
236
+ <button onClick={focus}>Focus</button>
237
+ <WrappedImperativeComponent focusAt={focusAt} />
238
+ ```
239
+
163
240
  ### `useIsHydrated`
164
241
 
165
242
  ```ts
@@ -361,7 +438,7 @@ HOC の `displayName` などに使う、コンポーネントの名前を返し
361
438
  markAt(): MarkAtResult
362
439
  ```
363
440
 
364
- 呼び出すたびに増加する値を返します。値は `1` から始まり、重複しません。`useExecutionAt` で `request.at` に使っています。
441
+ 呼び出すたびに増加する値を返します。値は `1` から始まり、重複しません。`useExecutionAt` の `request.at` と、`useExecutionMark` の値に使っています。
365
442
 
366
443
  ### `setRefCurrent`
367
444
 
@@ -404,15 +481,18 @@ const items = transformElement(children, (element, index) => (
404
481
  | 型 | 説明 |
405
482
  | ---------------------------- | -------------------------------------------------------------------------------- |
406
483
  | `AppliedProps<P, Ds>` | `applyDefaultProps` の戻り値の型 |
484
+ | `ChangeDependencies` | `xxxOn` のプロパティに渡す依存の値(1段目のプロパティを `Object.is` で比べる) |
407
485
  | `CSSPropertiesWithVars` | CSS 変数(`--name`)を書ける `CSSProperties` |
408
486
  | `ExecutionFunction<P>` | `useExecutionAt` が返す、実行のきっかけになる関数 |
409
487
  | `ExecutionRequest<P>` | `useExecutionAt` が返す、Execution Request プロパティに渡す値(`at`・`payload`) |
410
488
  | `ExtendableProps` | `extendElement` に渡せるエレメントの props(`className`・`style`) |
411
489
  | `LatestRef<T>` | `useLatestRef` が返す参照 |
490
+ | `ExecutionMark` | payload の無い Execution Request(実体は数値。要求のたびに異なる値にする) |
412
491
  | `MarkAtResult` | `markAt` の戻り値の型 |
413
492
  | `SkippedDefaults` | `applyDefaultProps` でスキップされる値(`null`・`undefined`・`false`) |
414
493
  | `UseExecutionAtOptions<P>` | `useExecutionAt` のオプション |
415
494
  | `UseExecutionAtResult<P>` | `useExecutionAt` の戻り値の型 |
495
+ | `UseExecutionMarkResult` | `useExecutionMark` の戻り値の型 |
416
496
  | `UsePreviousValueOptions<T>` | `usePreviousValue` のオプション |
417
497
 
418
498
  ## ライセンス
package/README.md CHANGED
@@ -31,20 +31,23 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
31
31
 
32
32
  ### Hooks
33
33
 
34
- | Hook | Description |
35
- | --------------------------------------------- | ----------------------------------------------------------------------------- |
36
- | `useCombinedRefs(...refs)` | Returns a callback ref that passes the value to all the given refs |
37
- | `useConstant(factory)` | Keeps returning the value created on the first render until unmount |
38
- | `useExecutionAt(options?)` | Returns a value for an Execution Request prop and a function that triggers it |
39
- | `useExecutionEffect(request, callback)` | Runs an operation when it is requested through an Execution Request prop |
40
- | `useExecutionLayoutEffect(request, callback)` | A version of `useExecutionEffect` that runs before the browser paints |
41
- | `useIsHydrated()` | Returns whether hydration has finished |
42
- | `useIsMounted()` | Returns a function that tells whether the component is mounted |
43
- | `useLatestRef(value)` | Returns a ref that holds the latest value |
44
- | `usePreviousValue(value, options?)` | Returns the previous value |
45
- | `useRerender()` | Returns a function that only triggers a re-render |
46
- | `useStableCallback(callback)` | Returns a function with a stable reference that calls the latest callback |
47
- | `useStableLength(arr)` | Ensures that the length of an array does not change across renders |
34
+ | Hook | Description |
35
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------ |
36
+ | `useChangeEffect(dependencies, callback)` | Runs an operation when the dependency values received through an `xxxOn` prop change |
37
+ | `useChangeLayoutEffect(dependencies, callback)` | A version of `useChangeEffect` that runs before the browser paints |
38
+ | `useCombinedRefs(...refs)` | Returns a callback ref that passes the value to all the given refs |
39
+ | `useConstant(factory)` | Keeps returning the value created on the first render until unmount |
40
+ | `useExecutionAt(options?)` | Returns a value for an Execution Request prop and a function that triggers it |
41
+ | `useExecutionEffect(request, callback)` | Runs an operation when it is requested through an Execution Request prop |
42
+ | `useExecutionLayoutEffect(request, callback)` | A version of `useExecutionEffect` that runs before the browser paints |
43
+ | `useExecutionMark()` | Returns an Execution Request without a payload and a function that makes a request |
44
+ | `useIsHydrated()` | Returns whether hydration has finished |
45
+ | `useIsMounted()` | Returns a function that tells whether the component is mounted |
46
+ | `useLatestRef(value)` | Returns a ref that holds the latest value |
47
+ | `usePreviousValue(value, options?)` | Returns the previous value |
48
+ | `useRerender()` | Returns a function that only triggers a re-render |
49
+ | `useStableCallback(callback)` | Returns a function with a stable reference that calls the latest callback |
50
+ | `useStableLength(arr)` | Ensures that the length of an array does not change across renders |
48
51
 
49
52
  ### Functions
50
53
 
@@ -61,6 +64,59 @@ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
61
64
  | `transformContent(children, callback)` | Maps elements, strings, and numbers to an array, looking through Fragments |
62
65
  | `transformElement(children, callback)` | Maps elements to an array, looking through Fragments |
63
66
 
67
+ ### `useChangeEffect`
68
+
69
+ ```ts
70
+ useChangeEffect(
71
+ dependencies: ChangeDependencies | undefined,
72
+ callback: () => void,
73
+ ): void
74
+ ```
75
+
76
+ The receiving hook for `xxxOn` props (such as `refreshOn`). It runs `callback` when the received dependency values (Change Dependencies) change. The operation runs after the browser paints (`useEffect`).
77
+
78
+ Use it to run operations whose result depends only on their inputs, such as measuring or recalculating again, when the values passed by the user change. For commands where the count or the payload matters, use Execution Requests (`useExecutionAt`, `useExecutionEffect`).
79
+
80
+ - It compares the first-level properties with `Object.is` and runs when any of them changes. Added or removed properties also count as a change.
81
+ - Only the first level is compared. Object values are compared by reference, so putting an object created during rendering (such as `{ style }`) counts as a change every time.
82
+ - The dependency object itself may be written inline.
83
+ - It does not run on the first commit (mount).
84
+ - An `undefined` `dependencies` is treated as an object with no properties, so an optional prop can be passed as is.
85
+ - The latest `callback` is called. Changing the reference of `callback` does not run it.
86
+ - Putting an Execution Request (the value of `useExecutionAt`) in a property runs the operation on every request.
87
+
88
+ ```tsx
89
+ // Passing side
90
+ <WrappedComponent refreshOn={{ className, theme }} />;
91
+
92
+ // Receiving side
93
+ type Props = { refreshOn?: ChangeDependencies };
94
+
95
+ function WrappedComponent({ refreshOn }: Props) {
96
+ const instance = useConstant(() => createInstance());
97
+ useChangeEffect(refreshOn, () => {
98
+ instance.refresh();
99
+ });
100
+ // ...
101
+ }
102
+ ```
103
+
104
+ | Parameter | Type | Description |
105
+ | -------------- | --------------------------------- | ------------------------------------------------------ |
106
+ | `dependencies` | `ChangeDependencies \| undefined` | The received dependency values |
107
+ | `callback` | `() => void` | The operation to run when the dependency values change |
108
+
109
+ ### `useChangeLayoutEffect`
110
+
111
+ ```ts
112
+ useChangeLayoutEffect(
113
+ dependencies: ChangeDependencies | undefined,
114
+ callback: () => void,
115
+ ): void
116
+ ```
117
+
118
+ A version of `useChangeEffect` that runs the operation before the browser paints (`useLayoutEffect`). Use it for operations whose result should be reflected before painting, such as measuring again. Dependency values and parameters are handled the same way as `useChangeEffect`.
119
+
64
120
  ### `useCombinedRefs`
65
121
 
66
122
  ```ts
@@ -101,7 +157,7 @@ useExecutionAt<P = void>(
101
157
 
102
158
  A hook for components that wrap a part with an imperative API, letting you call its methods through props. It returns a value to pass to an Execution Request prop (a prop ending with `At`, such as `undoAt`) and a function that triggers it.
103
159
 
104
- - `request.at` is `0` initially and increases every time `execute` is called. The receiving side runs the operation with `useExecutionEffect` / `useExecutionLayoutEffect`.
160
+ - `request.at` is `0` initially and increases every time `execute` is called. The receiving side runs the operation with `useExecutionEffect` / `useExecutionLayoutEffect`. Use `useExecutionMark` when no payload is needed.
105
161
  - `execute` always keeps the same reference.
106
162
 
107
163
  ```tsx
@@ -119,15 +175,17 @@ const [undoAt, undo] = useExecutionAt<string>();
119
175
 
120
176
  ```ts
121
177
  useExecutionEffect<P = void>(
122
- request: ExecutionRequest<P> | undefined,
178
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
123
179
  callback: (payload: P | undefined) => void,
124
180
  ): void
125
181
  ```
126
182
 
127
- The receiving counterpart of `useExecutionAt`. When an operation is requested through an Execution Request prop, it runs `callback` with the request's `payload`. The operation runs after the browser paints (`useEffect`).
183
+ The receiving counterpart of `useExecutionAt` and `useExecutionMark`. When an operation is requested through an Execution Request prop, it runs `callback` with the request's `payload`. The operation runs after the browser paints (`useEffect`).
128
184
 
129
- - It runs only when `request.at` changes. `payload` is not compared.
130
- - Requests made before mount are not run, including a request that was already passed at mount.
185
+ - It runs only when the mark (`request.at`, or the value of an `ExecutionMark`) changes. `payload` is not compared.
186
+ - A request that was already passed at mount is not run (the mark at mount is the baseline).
187
+ - It does not run when the mark is `0` (not requested yet).
188
+ - When it receives an `ExecutionMark`, the `payload` passed to `callback` is `undefined`. The mark does not have to be created by `markAt()` (any value that differs on every request works).
131
189
  - If multiple requests are made before the next render, only the last one is run.
132
190
  - The latest `callback` is called. Changing the reference of `callback` does not run it.
133
191
  - An `undefined` `request` is treated as no request, so an optional prop can be passed as is.
@@ -153,13 +211,32 @@ function WrappedImperativeComponent({ undoAt }: Props) {
153
211
 
154
212
  ```ts
155
213
  useExecutionLayoutEffect<P = void>(
156
- request: ExecutionRequest<P> | undefined,
214
+ request: ExecutionRequest<P> | ExecutionMark | undefined,
157
215
  callback: (payload: P | undefined) => void,
158
216
  ): void
159
217
  ```
160
218
 
161
219
  A version of `useExecutionEffect` that runs the operation before the browser paints (`useLayoutEffect`). Use it for operations whose result should be reflected before painting, such as adjusting the scroll position or measuring again. Requests and parameters are handled the same way as `useExecutionEffect`.
162
220
 
221
+ ### `useExecutionMark`
222
+
223
+ ```ts
224
+ useExecutionMark(): UseExecutionMarkResult
225
+ ```
226
+
227
+ A version of `useExecutionAt` without a payload. It returns an Execution Request without a payload (`ExecutionMark`, which is a number) and a function that makes a request. Use it to run methods that take no arguments, such as `focus()` or `reset()` of an imperative part, through props.
228
+
229
+ - The value is `0` (not requested yet) initially, and becomes a new value created by `markAt()` every time `execute` is called.
230
+ - `execute` always keeps the same reference.
231
+ - You may create an `ExecutionMark` yourself without this hook (for example, a value in a store or configuration). In that case, **make it different from the previous value on every request**. Passing the same value again is not treated as a new request. `markAt()` returns a different value on every call.
232
+
233
+ ```tsx
234
+ const [focusAt, focus] = useExecutionMark();
235
+
236
+ <button onClick={focus}>Focus</button>
237
+ <WrappedImperativeComponent focusAt={focusAt} />
238
+ ```
239
+
163
240
  ### `useIsHydrated`
164
241
 
165
242
  ```ts
@@ -361,7 +438,7 @@ Returns the name of a component, for use in an HOC's `displayName` and the like.
361
438
  markAt(): MarkAtResult
362
439
  ```
363
440
 
364
- Returns a value that increases on every call. Values start from `1` and are never duplicated. `useExecutionAt` uses it for `request.at`.
441
+ Returns a value that increases on every call. Values start from `1` and are never duplicated. `useExecutionAt` uses it for `request.at`, and `useExecutionMark` uses it for its value.
365
442
 
366
443
  ### `setRefCurrent`
367
444
 
@@ -401,19 +478,22 @@ const items = transformElement(children, (element, index) => (
401
478
 
402
479
  ### Types
403
480
 
404
- | Type | Description |
405
- | ---------------------------- | -------------------------------------------------------------------------------------- |
406
- | `AppliedProps<P, Ds>` | The return type of `applyDefaultProps` |
407
- | `CSSPropertiesWithVars` | `CSSProperties` that also accepts CSS variables (`--name`) |
408
- | `ExecutionFunction<P>` | The trigger function returned by `useExecutionAt` |
409
- | `ExecutionRequest<P>` | The value for an Execution Request prop returned by `useExecutionAt` (`at`, `payload`) |
410
- | `ExtendableProps` | The props of an element that `extendElement` accepts (`className`, `style`) |
411
- | `LatestRef<T>` | The ref returned by `useLatestRef` |
412
- | `MarkAtResult` | The return type of `markAt` |
413
- | `SkippedDefaults` | Values skipped by `applyDefaultProps` (`null`, `undefined`, `false`) |
414
- | `UseExecutionAtOptions<P>` | The options of `useExecutionAt` |
415
- | `UseExecutionAtResult<P>` | The return type of `useExecutionAt` |
416
- | `UsePreviousValueOptions<T>` | The options of `usePreviousValue` |
481
+ | Type | Description |
482
+ | ---------------------------- | ------------------------------------------------------------------------------------------------------ |
483
+ | `AppliedProps<P, Ds>` | The return type of `applyDefaultProps` |
484
+ | `ChangeDependencies` | The dependency values passed to an `xxxOn` prop (first-level properties are compared with `Object.is`) |
485
+ | `CSSPropertiesWithVars` | `CSSProperties` that also accepts CSS variables (`--name`) |
486
+ | `ExecutionFunction<P>` | The trigger function returned by `useExecutionAt` |
487
+ | `ExecutionRequest<P>` | The value for an Execution Request prop returned by `useExecutionAt` (`at`, `payload`) |
488
+ | `ExtendableProps` | The props of an element that `extendElement` accepts (`className`, `style`) |
489
+ | `LatestRef<T>` | The ref returned by `useLatestRef` |
490
+ | `ExecutionMark` | An Execution Request without a payload (a number; make it different on every request) |
491
+ | `MarkAtResult` | The return type of `markAt` |
492
+ | `SkippedDefaults` | Values skipped by `applyDefaultProps` (`null`, `undefined`, `false`) |
493
+ | `UseExecutionAtOptions<P>` | The options of `useExecutionAt` |
494
+ | `UseExecutionAtResult<P>` | The return type of `useExecutionAt` |
495
+ | `UseExecutionMarkResult` | The return type of `useExecutionMark` |
496
+ | `UsePreviousValueOptions<T>` | The options of `usePreviousValue` |
417
497
 
418
498
  ## License
419
499
 
@@ -1,7 +1,7 @@
1
1
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
2
  let react = require("react");
3
3
 
4
- //#region src/_helpers/_traverseChildren.ts
4
+ //#region src/_internal/_traverseChildren.ts
5
5
  /**
6
6
  * ReactElement、string、numberのいずれかであるかを判定する
7
7
  * @param child 子要素
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, ReactNode } from "react";
2
- //#region src/_helpers/_traverseChildren.d.ts
2
+ //#region src/_internal/_traverseChildren.d.ts
3
3
  type IndexCursor = {
4
4
  current: number;
5
5
  };
@@ -1,5 +1,5 @@
1
1
  import { ReactElement, ReactNode } from "react";
2
- //#region src/_helpers/_traverseChildren.d.ts
2
+ //#region src/_internal/_traverseChildren.d.ts
3
3
  type IndexCursor = {
4
4
  current: number;
5
5
  };
@@ -1,6 +1,6 @@
1
1
  import { Children, Fragment, isValidElement } from "react";
2
2
 
3
- //#region src/_helpers/_traverseChildren.ts
3
+ //#region src/_internal/_traverseChildren.ts
4
4
  /**
5
5
  * ReactElement、string、numberのいずれかであるかを判定する
6
6
  * @param child 子要素
@@ -0,0 +1,48 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ const require_hooks_useLatestRef_useLatestRef = require("../hooks/useLatestRef/useLatestRef.cjs");
3
+ require("../hooks/useLatestRef/index.cjs");
4
+ let react = require("react");
5
+
6
+ //#region src/_internal/_useChangeHandler.ts
7
+ /**
8
+ * 依存の値が変わったか(1段目のプロパティを `Object.is` で比べる)
9
+ *
10
+ * undefined はプロパティの無いオブジェクトとして扱う
11
+ */
12
+ const isChanged = (prev, next) => {
13
+ if (prev === next) return false;
14
+ const a = prev ?? {};
15
+ const b = next ?? {};
16
+ const keys = Object.keys(a);
17
+ if (keys.length !== Object.keys(b).length) return true;
18
+ return keys.some((key) => !Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key]));
19
+ };
20
+ /**
21
+ * 依存の値(Change Dependencies)を受け取り、変わっていれば処理を実行する関数を返すフック
22
+ *
23
+ * `useChangeEffect`・`useChangeLayoutEffect` に共通の処理。返す関数を、毎回のコミットでエフェクトから呼ぶ。
24
+ *
25
+ * - 前回の処理の後にコミットした依存の値と比べる。初回のコミットの値が最初の基準になるため、初回は実行しない
26
+ * - 比べるのはエフェクトの中(コミットした値どうし)。破棄されたレンダリングの値とは比べない
27
+ * - 同じ値のままエフェクトが再実行されても(StrictMode など)実行しない
28
+ * - コールバックは最新のものを呼ぶ
29
+ *
30
+ * @param dependencies 依存の値。undefined の場合は依存なし
31
+ * @param callback 依存の値が変わったときに実行する処理
32
+ * @returns エフェクトで呼ぶ関数。参照は常に同じ
33
+ */
34
+ function useChangeHandler(dependencies, callback) {
35
+ const dependenciesRef = require_hooks_useLatestRef_useLatestRef.default(dependencies);
36
+ const callbackRef = require_hooks_useLatestRef_useLatestRef.default(callback);
37
+ const committedRef = (0, react.useRef)(dependencies);
38
+ return (0, react.useCallback)(() => {
39
+ const next = dependenciesRef.current;
40
+ if (isChanged(committedRef.current, next)) {
41
+ committedRef.current = next;
42
+ callbackRef.current();
43
+ }
44
+ }, [dependenciesRef, callbackRef]);
45
+ }
46
+
47
+ //#endregion
48
+ exports.default = useChangeHandler;
@@ -0,0 +1,18 @@
1
+ import { ChangeDependencies } from "../hooks/useChangeEffect/types.cjs";
2
+ //#region src/_internal/_useChangeHandler.d.ts
3
+ /**
4
+ * 依存の値(Change Dependencies)を受け取り、変わっていれば処理を実行する関数を返すフック
5
+ *
6
+ * `useChangeEffect`・`useChangeLayoutEffect` に共通の処理。返す関数を、毎回のコミットでエフェクトから呼ぶ。
7
+ *
8
+ * - 前回の処理の後にコミットした依存の値と比べる。初回のコミットの値が最初の基準になるため、初回は実行しない
9
+ * - 比べるのはエフェクトの中(コミットした値どうし)。破棄されたレンダリングの値とは比べない
10
+ * - 同じ値のままエフェクトが再実行されても(StrictMode など)実行しない
11
+ * - コールバックは最新のものを呼ぶ
12
+ *
13
+ * @param dependencies 依存の値。undefined の場合は依存なし
14
+ * @param callback 依存の値が変わったときに実行する処理
15
+ * @returns エフェクトで呼ぶ関数。参照は常に同じ
16
+ */
17
+ declare function useChangeHandler(dependencies: ChangeDependencies | undefined, callback: () => void): () => void;
18
+ export = useChangeHandler;
@@ -0,0 +1,18 @@
1
+ import { ChangeDependencies } from "../hooks/useChangeEffect/types.mjs";
2
+ //#region src/_internal/_useChangeHandler.d.ts
3
+ /**
4
+ * 依存の値(Change Dependencies)を受け取り、変わっていれば処理を実行する関数を返すフック
5
+ *
6
+ * `useChangeEffect`・`useChangeLayoutEffect` に共通の処理。返す関数を、毎回のコミットでエフェクトから呼ぶ。
7
+ *
8
+ * - 前回の処理の後にコミットした依存の値と比べる。初回のコミットの値が最初の基準になるため、初回は実行しない
9
+ * - 比べるのはエフェクトの中(コミットした値どうし)。破棄されたレンダリングの値とは比べない
10
+ * - 同じ値のままエフェクトが再実行されても(StrictMode など)実行しない
11
+ * - コールバックは最新のものを呼ぶ
12
+ *
13
+ * @param dependencies 依存の値。undefined の場合は依存なし
14
+ * @param callback 依存の値が変わったときに実行する処理
15
+ * @returns エフェクトで呼ぶ関数。参照は常に同じ
16
+ */
17
+ export default function useChangeHandler(dependencies: ChangeDependencies | undefined, callback: () => void): () => void;
18
+ //#endregion
@@ -0,0 +1,47 @@
1
+ import useLatestRef from "../hooks/useLatestRef/useLatestRef.mjs";
2
+ import "../hooks/useLatestRef/index.mjs";
3
+ import { useCallback, useRef } from "react";
4
+
5
+ //#region src/_internal/_useChangeHandler.ts
6
+ /**
7
+ * 依存の値が変わったか(1段目のプロパティを `Object.is` で比べる)
8
+ *
9
+ * undefined はプロパティの無いオブジェクトとして扱う
10
+ */
11
+ const isChanged = (prev, next) => {
12
+ if (prev === next) return false;
13
+ const a = prev ?? {};
14
+ const b = next ?? {};
15
+ const keys = Object.keys(a);
16
+ if (keys.length !== Object.keys(b).length) return true;
17
+ return keys.some((key) => !Object.prototype.hasOwnProperty.call(b, key) || !Object.is(a[key], b[key]));
18
+ };
19
+ /**
20
+ * 依存の値(Change Dependencies)を受け取り、変わっていれば処理を実行する関数を返すフック
21
+ *
22
+ * `useChangeEffect`・`useChangeLayoutEffect` に共通の処理。返す関数を、毎回のコミットでエフェクトから呼ぶ。
23
+ *
24
+ * - 前回の処理の後にコミットした依存の値と比べる。初回のコミットの値が最初の基準になるため、初回は実行しない
25
+ * - 比べるのはエフェクトの中(コミットした値どうし)。破棄されたレンダリングの値とは比べない
26
+ * - 同じ値のままエフェクトが再実行されても(StrictMode など)実行しない
27
+ * - コールバックは最新のものを呼ぶ
28
+ *
29
+ * @param dependencies 依存の値。undefined の場合は依存なし
30
+ * @param callback 依存の値が変わったときに実行する処理
31
+ * @returns エフェクトで呼ぶ関数。参照は常に同じ
32
+ */
33
+ function useChangeHandler(dependencies, callback) {
34
+ const dependenciesRef = useLatestRef(dependencies);
35
+ const callbackRef = useLatestRef(callback);
36
+ const committedRef = useRef(dependencies);
37
+ return useCallback(() => {
38
+ const next = dependenciesRef.current;
39
+ if (isChanged(committedRef.current, next)) {
40
+ committedRef.current = next;
41
+ callbackRef.current();
42
+ }
43
+ }, [dependenciesRef, callbackRef]);
44
+ }
45
+
46
+ //#endregion
47
+ export { useChangeHandler as default };
@@ -1,19 +1,21 @@
1
1
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
- const require_hooks_useConstant_useConstant = require("../hooks/useConstant/useConstant.cjs");
3
- require("../hooks/useConstant/index.cjs");
4
- const require_utils_markAt_markAt = require("../utils/markAt/markAt.cjs");
5
2
  const require_hooks_useLatestRef_useLatestRef = require("../hooks/useLatestRef/useLatestRef.cjs");
6
3
  require("../hooks/useLatestRef/index.cjs");
7
4
  let react = require("react");
8
5
 
9
- //#region src/_helpers/_useExecutionHandler.ts
6
+ //#region src/_internal/_useExecutionHandler.ts
7
+ /**
8
+ * 受け取った要求の印(`at`、または ExecutionMark の値)を返す
9
+ */
10
+ const getMark = (request) => typeof request === "number" ? request : request?.at;
10
11
  /**
11
12
  * Execution Request を受け取り、要求があれば処理を実行する関数を返すフック
12
13
  *
13
14
  * `useExecutionEffect`・`useExecutionLayoutEffect` に共通の処理。返す関数をエフェクトで呼ぶ。
14
15
  *
15
- * - 初回のレンダリングで `markAt()` の値を基準にし、`at` がそれより大きい要求だけを実行する。
16
- * `at` は `markAt()` で単調に増えるため、マウントより前に出た要求は実行しない
16
+ * - 初回のレンダリングで受け取った印(`at`、または ExecutionMark の値)を基準にし、印が変わったときだけ実行する。
17
+ * マウントしたときに渡されていた要求は実行しない。印は `markAt()` で作ったものでなくてよい(カウンターなども使える)
18
+ * - 印が `0`(まだ要求していない)や undefined(要求なし)の場合は実行しない
17
19
  * - 一度実行した要求は、エフェクトが再実行されても(StrictMode など)実行しない
18
20
  * - コールバックは最新のものを呼ぶ
19
21
  *
@@ -23,13 +25,12 @@ let react = require("react");
23
25
  */
24
26
  function useExecutionHandler(request, callback) {
25
27
  const callbackRef = require_hooks_useLatestRef_useLatestRef.default(callback);
26
- const baseline = require_hooks_useConstant_useConstant.default(require_utils_markAt_markAt.default);
27
- const handledAtRef = (0, react.useRef)(baseline);
28
+ const handledRef = (0, react.useRef)(getMark(request));
28
29
  return (0, react.useCallback)(() => {
29
- if (request && request.at > handledAtRef.current) {
30
- handledAtRef.current = request.at;
31
- callbackRef.current(request.payload);
32
- }
30
+ const mark = getMark(request);
31
+ if (mark === void 0 || mark === 0 || Object.is(mark, handledRef.current)) return;
32
+ handledRef.current = mark;
33
+ callbackRef.current(typeof request === "number" ? void 0 : request?.payload);
33
34
  }, [request, callbackRef]);
34
35
  }
35
36