@fringeworks/react-utils 0.8.1 → 0.9.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 (170) hide show
  1. package/README.ja.md +307 -0
  2. package/README.md +307 -3
  3. package/_helpers/_traverseChildren.cjs +32 -0
  4. package/_helpers/_traverseChildren.d.cts +24 -0
  5. package/_helpers/_traverseChildren.d.mts +24 -0
  6. package/_helpers/_traverseChildren.mjs +30 -0
  7. package/hooks/index.cjs +21 -40
  8. package/hooks/index.d.cts +5 -7
  9. package/hooks/index.d.mts +5 -7
  10. package/hooks/index.mjs +1 -14
  11. package/hooks/useCombinedRefs/index.cjs +4 -2
  12. package/hooks/useCombinedRefs/index.d.cts +1 -1
  13. package/hooks/useCombinedRefs/index.d.mts +1 -1
  14. package/hooks/useCombinedRefs/index.mjs +1 -1
  15. package/hooks/useCombinedRefs/useCombinedRefs.cjs +7 -6
  16. package/hooks/useCombinedRefs/useCombinedRefs.d.cts +0 -1
  17. package/hooks/useCombinedRefs/useCombinedRefs.d.mts +2 -4
  18. package/hooks/useConstant/index.cjs +4 -2
  19. package/hooks/useConstant/index.d.cts +1 -1
  20. package/hooks/useConstant/index.d.mts +1 -1
  21. package/hooks/useConstant/index.mjs +1 -1
  22. package/hooks/useConstant/useConstant.cjs +2 -1
  23. package/hooks/useConstant/useConstant.d.mts +2 -3
  24. package/hooks/useExecutionAt/index.cjs +4 -2
  25. package/hooks/useExecutionAt/index.d.cts +1 -1
  26. package/hooks/useExecutionAt/index.d.mts +1 -1
  27. package/hooks/useExecutionAt/index.mjs +1 -1
  28. package/hooks/useExecutionAt/types.cjs +1 -0
  29. package/hooks/useExecutionAt/types.d.cts +5 -7
  30. package/hooks/useExecutionAt/types.d.mts +5 -7
  31. package/hooks/useExecutionAt/useExecutionAt.cjs +4 -3
  32. package/hooks/useExecutionAt/useExecutionAt.d.cts +0 -1
  33. package/hooks/useExecutionAt/useExecutionAt.d.mts +2 -4
  34. package/hooks/useIsMounted/index.cjs +4 -2
  35. package/hooks/useIsMounted/index.d.cts +1 -1
  36. package/hooks/useIsMounted/index.d.mts +1 -1
  37. package/hooks/useIsMounted/index.mjs +1 -1
  38. package/hooks/useIsMounted/useIsMounted.cjs +8 -3
  39. package/hooks/useIsMounted/useIsMounted.d.cts +5 -1
  40. package/hooks/useIsMounted/useIsMounted.d.mts +7 -4
  41. package/hooks/useIsMounted/useIsMounted.mjs +7 -3
  42. package/hooks/useLatestRef/index.cjs +4 -2
  43. package/hooks/useLatestRef/index.d.cts +1 -1
  44. package/hooks/useLatestRef/index.d.mts +1 -1
  45. package/hooks/useLatestRef/index.mjs +1 -1
  46. package/hooks/useLatestRef/types.cjs +1 -0
  47. package/hooks/useLatestRef/types.d.cts +2 -3
  48. package/hooks/useLatestRef/types.d.mts +2 -3
  49. package/hooks/useLatestRef/useLatestRef.cjs +2 -1
  50. package/hooks/useLatestRef/useLatestRef.d.cts +0 -1
  51. package/hooks/useLatestRef/useLatestRef.d.mts +2 -4
  52. package/hooks/usePreviousValue/index.cjs +4 -2
  53. package/hooks/usePreviousValue/index.d.cts +1 -1
  54. package/hooks/usePreviousValue/index.d.mts +1 -1
  55. package/hooks/usePreviousValue/index.mjs +1 -1
  56. package/hooks/usePreviousValue/types.cjs +1 -0
  57. package/hooks/usePreviousValue/types.d.cts +8 -9
  58. package/hooks/usePreviousValue/types.d.mts +8 -9
  59. package/hooks/usePreviousValue/usePreviousValue.cjs +10 -5
  60. package/hooks/usePreviousValue/usePreviousValue.d.cts +6 -2
  61. package/hooks/usePreviousValue/usePreviousValue.d.mts +8 -5
  62. package/hooks/usePreviousValue/usePreviousValue.mjs +8 -4
  63. package/hooks/useStableLength/index.cjs +4 -2
  64. package/hooks/useStableLength/index.d.cts +1 -1
  65. package/hooks/useStableLength/index.d.mts +1 -1
  66. package/hooks/useStableLength/index.mjs +1 -1
  67. package/hooks/useStableLength/useStableLength.cjs +2 -1
  68. package/hooks/useStableLength/useStableLength.d.cts +1 -1
  69. package/hooks/useStableLength/useStableLength.d.mts +2 -3
  70. package/index.cjs +34 -14
  71. package/index.d.cts +25 -3
  72. package/index.d.mts +25 -3
  73. package/index.mjs +19 -3
  74. package/package.json +160 -38
  75. package/types.cjs +1 -0
  76. package/types.d.cts +2 -4
  77. package/types.d.mts +2 -4
  78. package/utils/applyDefaultProps/applyDefaultProps.cjs +6 -2
  79. package/utils/applyDefaultProps/applyDefaultProps.d.cts +6 -3
  80. package/utils/applyDefaultProps/applyDefaultProps.d.mts +8 -5
  81. package/utils/applyDefaultProps/applyDefaultProps.mjs +4 -0
  82. package/utils/applyDefaultProps/index.cjs +4 -2
  83. package/utils/applyDefaultProps/index.d.cts +1 -1
  84. package/utils/applyDefaultProps/index.d.mts +1 -1
  85. package/utils/applyDefaultProps/index.mjs +1 -1
  86. package/utils/applyDefaultProps/types.cjs +1 -0
  87. package/utils/applyDefaultProps/types.d.cts +6 -6
  88. package/utils/applyDefaultProps/types.d.mts +6 -6
  89. package/utils/ensureComponent/ensureComponent.cjs +2 -1
  90. package/utils/ensureComponent/ensureComponent.d.cts +0 -1
  91. package/utils/ensureComponent/ensureComponent.d.mts +2 -4
  92. package/utils/ensureComponent/index.cjs +4 -2
  93. package/utils/ensureComponent/index.d.cts +1 -1
  94. package/utils/ensureComponent/index.d.mts +1 -1
  95. package/utils/ensureComponent/index.mjs +1 -1
  96. package/utils/extendElement/extendElement.cjs +21 -17
  97. package/utils/extendElement/extendElement.d.cts +11 -6
  98. package/utils/extendElement/extendElement.d.mts +12 -8
  99. package/utils/extendElement/extendElement.mjs +19 -16
  100. package/utils/extendElement/index.cjs +4 -2
  101. package/utils/extendElement/index.d.cts +2 -1
  102. package/utils/extendElement/index.d.mts +2 -1
  103. package/utils/extendElement/index.mjs +1 -1
  104. package/utils/extendElement/types.cjs +1 -0
  105. package/utils/extendElement/types.d.cts +16 -0
  106. package/utils/extendElement/types.d.mts +16 -0
  107. package/utils/extendElement/types.mjs +1 -0
  108. package/utils/forEachContent/forEachContent.cjs +5 -4
  109. package/utils/forEachContent/forEachContent.d.cts +1 -2
  110. package/utils/forEachContent/forEachContent.d.mts +3 -5
  111. package/utils/forEachContent/forEachContent.mjs +3 -3
  112. package/utils/forEachContent/index.cjs +4 -2
  113. package/utils/forEachContent/index.d.cts +1 -1
  114. package/utils/forEachContent/index.d.mts +1 -1
  115. package/utils/forEachContent/index.mjs +1 -1
  116. package/utils/forEachElement/forEachElement.cjs +6 -4
  117. package/utils/forEachElement/forEachElement.d.cts +1 -2
  118. package/utils/forEachElement/forEachElement.d.mts +3 -5
  119. package/utils/forEachElement/forEachElement.mjs +4 -3
  120. package/utils/forEachElement/index.cjs +4 -2
  121. package/utils/forEachElement/index.d.cts +1 -1
  122. package/utils/forEachElement/index.d.mts +1 -1
  123. package/utils/forEachElement/index.mjs +1 -1
  124. package/utils/index.cjs +24 -45
  125. package/utils/index.d.cts +11 -5
  126. package/utils/index.d.mts +11 -5
  127. package/utils/index.mjs +1 -16
  128. package/utils/markAt/index.cjs +4 -2
  129. package/utils/markAt/index.d.cts +1 -1
  130. package/utils/markAt/index.d.mts +1 -1
  131. package/utils/markAt/index.mjs +1 -1
  132. package/utils/markAt/markAt.cjs +2 -2
  133. package/utils/markAt/markAt.d.cts +0 -1
  134. package/utils/markAt/markAt.d.mts +2 -4
  135. package/utils/markAt/types.cjs +1 -0
  136. package/utils/markAt/types.d.cts +2 -3
  137. package/utils/markAt/types.d.mts +2 -3
  138. package/utils/setRefCurrent/index.cjs +4 -2
  139. package/utils/setRefCurrent/index.d.cts +1 -1
  140. package/utils/setRefCurrent/index.d.mts +1 -1
  141. package/utils/setRefCurrent/index.mjs +1 -1
  142. package/utils/setRefCurrent/setRefCurrent.cjs +2 -2
  143. package/utils/setRefCurrent/setRefCurrent.d.cts +0 -1
  144. package/utils/setRefCurrent/setRefCurrent.d.mts +2 -4
  145. package/utils/transformContent/index.cjs +4 -2
  146. package/utils/transformContent/index.d.cts +1 -1
  147. package/utils/transformContent/index.d.mts +1 -1
  148. package/utils/transformContent/index.mjs +1 -1
  149. package/utils/transformContent/transformContent.cjs +11 -4
  150. package/utils/transformContent/transformContent.d.cts +7 -2
  151. package/utils/transformContent/transformContent.d.mts +9 -5
  152. package/utils/transformContent/transformContent.mjs +9 -3
  153. package/utils/transformElement/index.cjs +4 -2
  154. package/utils/transformElement/index.d.cts +1 -1
  155. package/utils/transformElement/index.d.mts +1 -1
  156. package/utils/transformElement/index.mjs +1 -1
  157. package/utils/transformElement/transformElement.cjs +12 -4
  158. package/utils/transformElement/transformElement.d.cts +7 -2
  159. package/utils/transformElement/transformElement.d.mts +9 -5
  160. package/utils/transformElement/transformElement.mjs +10 -3
  161. package/_helpers/_traverseContent.cjs +0 -24
  162. package/_helpers/_traverseContent.d.cts +0 -15
  163. package/_helpers/_traverseContent.d.mts +0 -16
  164. package/_helpers/_traverseContent.mjs +0 -22
  165. package/_helpers/_traverseElement.cjs +0 -23
  166. package/_helpers/_traverseElement.d.cts +0 -15
  167. package/_helpers/_traverseElement.d.mts +0 -16
  168. package/_helpers/_traverseElement.mjs +0 -21
  169. package/_virtual/_rolldown/runtime.cjs +0 -43
  170. package/_virtual/_rolldown/runtime.mjs +0 -18
package/README.ja.md ADDED
@@ -0,0 +1,307 @@
1
+ # @fringeworks/react-utils
2
+
3
+ `@fringeworks/react-utils` は React のコンポーネントを作るときに使う hook や関数を集めた、誰かにとっては便利なライブラリです。
4
+
5
+ **[English README is available here](./README.md)**
6
+
7
+ ## インストール
8
+
9
+ ```bash
10
+ npm install @fringeworks/react-utils
11
+ # または
12
+ pnpm add @fringeworks/react-utils
13
+ ```
14
+
15
+ React 18 と 19 に対応しています。
16
+
17
+ ## 使い方
18
+
19
+ ルートからまとめて import するほか、機能ごとのパスから import することもできます。
20
+
21
+ ```ts
22
+ // ルートから
23
+ import { applyDefaultProps, useLatestRef } from '@fringeworks/react-utils';
24
+
25
+ // 機能ごとのパスから
26
+ import applyDefaultProps from '@fringeworks/react-utils/utils/applyDefaultProps';
27
+ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
28
+ ```
29
+
30
+ ## API
31
+
32
+ ### hook
33
+
34
+ | hook | 説明 |
35
+ | ----------------------------------- | ---------------------------------------------------------------------- |
36
+ | `useCombinedRefs(...refs)` | 複数の ref にまとめて値を渡す callback ref を返す |
37
+ | `useConstant(factory)` | 初回のレンダリングで作った値を、アンマウントされるまで返し続ける |
38
+ | `useExecutionAt(options?)` | Execution Request プロパティに渡す値と、実行のきっかけになる関数を返す |
39
+ | `useIsMounted()` | マウントされているかどうかを判定する関数を返す |
40
+ | `useLatestRef(value)` | 最新の値を保持する参照を返す |
41
+ | `usePreviousValue(value, options?)` | 前回の値を返す |
42
+ | `useStableLength(arr)` | 配列の要素数がレンダリングをまたいで変わらないことを確認する |
43
+
44
+ ### 関数
45
+
46
+ | 関数 | 説明 |
47
+ | ----------------------------------------------------- | ------------------------------------------------------------------ |
48
+ | `applyDefaultProps(props, ...defaults)` | props の `undefined` / `null` のプロパティにデフォルト値を反映する |
49
+ | `ensureComponent(component)` | 組み込みタグ(`'div'` など)をコンポーネントに変換する |
50
+ | `extendElement(element, overrideProps?, ...children)` | `className` と `style` をマージしてエレメントを複製する |
51
+ | `forEachContent(children, callback)` | Fragment を透過し、エレメント・文字列・数値を順に処理する |
52
+ | `forEachElement(children, callback)` | Fragment を透過し、エレメントを順に処理する |
53
+ | `markAt()` | 呼び出すたびに増加する値を返す |
54
+ | `setRefCurrent(value, ...refs)` | 複数の ref に値を設定し、クリーンアップ関数を返す |
55
+ | `transformContent(children, callback)` | Fragment を透過し、エレメント・文字列・数値を変換した配列を返す |
56
+ | `transformElement(children, callback)` | Fragment を透過し、エレメントを変換した配列を返す |
57
+
58
+ ### `useCombinedRefs`
59
+
60
+ ```ts
61
+ useCombinedRefs<T = unknown>(...refs: Ref<T>[]): RefCallback<T>
62
+ ```
63
+
64
+ 渡したすべての ref に同じ値を設定する callback ref を返します。`forwardRef` で受け取った ref と、コンポーネント内の ref を1つのエレメントに渡したい場合に使います。
65
+
66
+ - 返す関数の参照は、渡す ref がすべて変わらない限り変わりません。
67
+ - 渡す ref の数は、レンダリングをまたいで変えられません。変わった場合はエラーになります。
68
+ - React 19 では、返す callback ref がクリーンアップ関数を返します。callback ref が返したクリーンアップ関数はそのまま呼び、返さなかった callback ref には `null` を渡します。
69
+
70
+ ```tsx
71
+ const Input = forwardRef<HTMLInputElement>((props, ref) => {
72
+ const innerRef = useRef<HTMLInputElement>(null);
73
+ const combinedRef = useCombinedRefs(ref, innerRef);
74
+ return <input {...props} ref={combinedRef} />;
75
+ });
76
+ ```
77
+
78
+ ### `useConstant`
79
+
80
+ ```ts
81
+ useConstant<T>(factory: () => T): T
82
+ ```
83
+
84
+ 初回のレンダリングで `factory` を呼んで作った値を、アンマウントされるまで返し続けます。
85
+
86
+ `useMemo` はパフォーマンスを最適化するための hook で、キャッシュが破棄されないことは保証されていません。状態を持つインスタンスなど、同じものであることが動作の前提になる値は、この hook で保持します。
87
+
88
+ ### `useExecutionAt`
89
+
90
+ ```ts
91
+ useExecutionAt<P = void>(
92
+ options?: UseExecutionAtOptions<P>,
93
+ ): UseExecutionAtResult<P>
94
+ ```
95
+
96
+ 命令的な API を持つ部品をラップしたコンポーネントで、プロパティを通じてそのメソッドを実行するための hook です。Execution Request プロパティ(`undoAt` のように `At` で終わるプロパティ)に渡す値と、実行のきっかけになる関数を返します。
97
+
98
+ - `request.at` は初期状態では `0` で、`execute` を呼ぶたびに増加します。受け取る側は `at` が変わったら処理を実行します。
99
+ - `execute` の参照は常に同じです。
100
+
101
+ ```tsx
102
+ const [undoAt, undo] = useExecutionAt<string>();
103
+
104
+ <button onClick={() => undo('no-123')}>Undo</button>
105
+ <WrappedImperativeComponent undoAt={undoAt} />
106
+ ```
107
+
108
+ | オプション | 型 | 説明 |
109
+ | ----------------- | --- | --------------------------------- |
110
+ | `initialPayload?` | `P` | 初期状態の `request.payload` の値 |
111
+
112
+ ### `useIsMounted`
113
+
114
+ ```ts
115
+ useIsMounted(): () => boolean
116
+ ```
117
+
118
+ マウントされているかどうかを判定する関数を返します。非同期処理が終わったときに、アンマウント済みのコンポーネントを更新しないようにする場合などに使います。
119
+
120
+ - DOM へのコミット時(`useLayoutEffect`)に `true` になり、アンマウント時に `false` になります。
121
+ - レンダリング中に呼んだ場合は、初回のマウントが完了するまで `false` を返します。
122
+ - 返す関数の参照は常に同じです。
123
+
124
+ ### `useLatestRef`
125
+
126
+ ```ts
127
+ useLatestRef<T>(value: T): LatestRef<T>
128
+ ```
129
+
130
+ 最新の値を保持する参照を返します。参照は常に同じオブジェクトなので、依存配列に含めずに、イベントハンドラーやエフェクトの中から最新の値を参照できます。
131
+
132
+ - 値はコミット時(`useInsertionEffect`)に更新されるため、`useLayoutEffect` / `useEffect` の中からも最新の値を参照できます。
133
+ - レンダリング中には参照しないでください(コミット前のため、前回の値になります)。
134
+
135
+ ### `usePreviousValue`
136
+
137
+ ```ts
138
+ usePreviousValue<T = unknown>(
139
+ value: T,
140
+ options?: UsePreviousValueOptions<T>,
141
+ ): T | undefined
142
+ ```
143
+
144
+ 前回の値を返します。値が一度も変わっていない間は `initialValue` を返します。
145
+
146
+ 値が変わったときは、レンダリング中のステート更新で前回値を更新します(余分なコミットやエフェクトの実行は起きません)。そのため、比較が毎回 `false` になる値(レンダリング中に作るオブジェクトや配列など)を渡すと、再レンダリングが止まらなくなります。参照が安定した値を渡すか、`isEqual` を指定してください。
147
+
148
+ | オプション | 型 | 説明 |
149
+ | --------------- | --------------------------------------- | ---------------------------------------------------------------------- |
150
+ | `initialValue?` | `T` | 値が一度も変わっていない間に返す値 |
151
+ | `isEqual?` | `(oldValue: T, newValue: T) => boolean` | 同じ値とみなす場合に `true` を返す比較関数。未指定の場合は `Object.is` |
152
+
153
+ ### `useStableLength`
154
+
155
+ ```ts
156
+ useStableLength(arr: readonly unknown[]): void
157
+ ```
158
+
159
+ 配列の要素数が、初回のレンダリングから変わっていないことを確認します。変わった場合はエラーをスローします。可変長の配列を hook の依存配列に使う場合などに使います。
160
+
161
+ ### `applyDefaultProps`
162
+
163
+ ```ts
164
+ applyDefaultProps<P extends object, const Ds extends readonly (Partial<P> | SkippedDefaults)[]>(
165
+ props: P,
166
+ ...defaults: Ds,
167
+ ): AppliedProps<P, Ds>
168
+ ```
169
+
170
+ `props` のプロパティが `undefined` または `null` で、デフォルト値が `undefined` 以外の場合に、デフォルト値を反映した新しいオブジェクトを返します。元の `props` は変更しません。
171
+
172
+ - `defaults` は複数渡せます。キーごとに見ると `props[key] ?? d1[key] ?? d2[key] ?? ...` と同じように動作し、先の引数ほど優先されます。
173
+ - `null` / `undefined` / `false` を渡した場合はスキップします。`isCompact && compactDefaults` のように、条件付きのデフォルト値を書けます。
174
+ - 戻り値の型では、確実にデフォルト値があるキーから `null` / `undefined` が取り除かれます。
175
+ - `props` に無いキーを `defaults` に書くと型エラーになります。
176
+
177
+ ```ts
178
+ const { size, color } = applyDefaultProps(
179
+ props,
180
+ isCompact && { size: 'small' },
181
+ { size: 'medium', color: 'primary' },
182
+ );
183
+ ```
184
+
185
+ 制限事項:
186
+
187
+ - `defaults` のキーは文字列のものだけを反映します(symbol のキーは無視します)。
188
+ - `props` の型がユニオン型の場合、`defaults` に書けるのはすべての型に共通するキーだけです。
189
+
190
+ ### `ensureComponent`
191
+
192
+ ```ts
193
+ ensureComponent<C extends ElementType>(
194
+ component: C,
195
+ ): ComponentType<ComponentPropsWithRef<C>>
196
+ ```
197
+
198
+ 組み込みタグ(`'div'` など)を、props と ref をそのまま渡すコンポーネントに変換して返します。コンポーネントを渡した場合はそのまま返します。
199
+
200
+ - 同じタグには同じコンポーネントを返します。
201
+ - `displayName` は `ensureComponent(タグ名)` です。
202
+
203
+ ### `extendElement`
204
+
205
+ ```ts
206
+ extendElement<P extends ExtendableProps>(
207
+ element: ReactElement<P>,
208
+ overrideProps?: Partial<P>,
209
+ ...children: ReactNode[]
210
+ ): ReactElement<P>
211
+ ```
212
+
213
+ `overrideProps` を反映してエレメントを複製します(`cloneElement`)。`key` と `ref` は元のエレメントのものを引き継ぎます。
214
+
215
+ | プロパティ | 反映方法 |
216
+ | ----------- | ------------------------------------------------------------------- |
217
+ | `className` | 元の `className` の後ろに空白区切りで追加する(空文字は無視する) |
218
+ | `style` | 元の `style` にプロパティ単位で上書きする(`undefined` は無視する) |
219
+ | それ以外 | 上書きする(値が `undefined` のプロパティは無視する) |
220
+
221
+ | 引数 | 型 | 説明 |
222
+ | ---------------- | ----------------- | ------------------------------------------ |
223
+ | `element` | `ReactElement<P>` | 複製するエレメント |
224
+ | `overrideProps?` | `Partial<P>` | 反映するプロパティ |
225
+ | `...children` | `ReactNode[]` | 子要素。省略した場合は元の子要素を引き継ぐ |
226
+
227
+ ### `forEachContent` / `forEachElement`
228
+
229
+ ```ts
230
+ forEachContent(
231
+ children: ReactNode,
232
+ callback: (node: ReactElement | string | number, index: number) => void,
233
+ ): void
234
+ forEachElement(
235
+ children: ReactNode,
236
+ callback: (node: ReactElement, index: number) => void,
237
+ ): void
238
+ ```
239
+
240
+ Fragment を透過して `children` を順に処理します。`forEachContent` はエレメント・文字列・数値を、`forEachElement` はエレメントだけを処理します。`null` / `undefined` / 真偽値は処理しません。
241
+
242
+ - 配列や iterable も展開します。
243
+ - `index` は、Fragment をまたいだ連番です。
244
+
245
+ ### `markAt`
246
+
247
+ ```ts
248
+ markAt(): MarkAtResult
249
+ ```
250
+
251
+ 呼び出すたびに増加する値を返します。値は `1` から始まり、重複しません。`useExecutionAt` で `request.at` に使っています。
252
+
253
+ ### `setRefCurrent`
254
+
255
+ ```ts
256
+ setRefCurrent<T = unknown>(
257
+ value: T | null,
258
+ ...refs: (RefCallback<T> | RefObject<T | null> | null | undefined)[]
259
+ ): () => void
260
+ ```
261
+
262
+ object ref には `current` を設定し、callback ref は `value` を渡して呼びます。`null` / `undefined` は無視します。
263
+
264
+ 戻り値のクリーンアップ関数を呼ぶと、object ref には `null` を設定します。callback ref は、呼んだときにクリーンアップ関数を返した場合はそれを呼び、返さなかった場合は `null` を渡して呼びます。
265
+
266
+ ### `transformContent` / `transformElement`
267
+
268
+ ```ts
269
+ transformContent<T>(
270
+ children: ReactNode,
271
+ callback: (node: ReactElement | string | number, index: number) => T,
272
+ ): T[]
273
+ transformElement<T>(
274
+ children: ReactNode,
275
+ callback: (node: ReactElement, index: number) => T,
276
+ ): T[]
277
+ ```
278
+
279
+ `forEachContent` / `forEachElement` と同じ順で `children` を処理し、`callback` の戻り値の配列を返します。
280
+
281
+ `Children.map` と異なり、key の付け直しは行いません。別々の Fragment の中に同じ key があると、戻り値の配列の中で key が重複します。戻り値をそのまま描画する場合は、`callback` の中で key を付けてください。
282
+
283
+ ```tsx
284
+ const items = transformElement(children, (element, index) => (
285
+ <li key={index}>{element}</li>
286
+ ));
287
+ ```
288
+
289
+ ### 型
290
+
291
+ | 型 | 説明 |
292
+ | ---------------------------- | -------------------------------------------------------------------------------- |
293
+ | `AppliedProps<P, Ds>` | `applyDefaultProps` の戻り値の型 |
294
+ | `CSSPropertiesWithVars` | CSS 変数(`--name`)を書ける `CSSProperties` |
295
+ | `ExecutionFunction<P>` | `useExecutionAt` が返す、実行のきっかけになる関数 |
296
+ | `ExecutionRequest<P>` | `useExecutionAt` が返す、Execution Request プロパティに渡す値(`at`・`payload`) |
297
+ | `ExtendableProps` | `extendElement` に渡せるエレメントの props(`className`・`style`) |
298
+ | `LatestRef<T>` | `useLatestRef` が返す参照 |
299
+ | `MarkAtResult` | `markAt` の戻り値の型 |
300
+ | `SkippedDefaults` | `applyDefaultProps` でスキップされる値(`null`・`undefined`・`false`) |
301
+ | `UseExecutionAtOptions<P>` | `useExecutionAt` のオプション |
302
+ | `UseExecutionAtResult<P>` | `useExecutionAt` の戻り値の型 |
303
+ | `UsePreviousValueOptions<T>` | `usePreviousValue` のオプション |
304
+
305
+ ## ライセンス
306
+
307
+ MIT
package/README.md CHANGED
@@ -1,3 +1,307 @@
1
- # @fringeworks/react-utils
2
-
3
- `@fringeworks/react-utils` is a library some will find handy, providing utilities for React.
1
+ # @fringeworks/react-utils
2
+
3
+ `@fringeworks/react-utils` is a library some will find handy, offering hooks and functions for building React components.
4
+
5
+ **[日本語のREADMEはこちら](./README.ja.md)**
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @fringeworks/react-utils
11
+ # or
12
+ pnpm add @fringeworks/react-utils
13
+ ```
14
+
15
+ Supports React 18 and 19.
16
+
17
+ ## Usage
18
+
19
+ You can import everything from the root, or import each feature from its own path.
20
+
21
+ ```ts
22
+ // From the root
23
+ import { applyDefaultProps, useLatestRef } from '@fringeworks/react-utils';
24
+
25
+ // From the path of each feature
26
+ import applyDefaultProps from '@fringeworks/react-utils/utils/applyDefaultProps';
27
+ import useLatestRef from '@fringeworks/react-utils/hooks/useLatestRef';
28
+ ```
29
+
30
+ ## API
31
+
32
+ ### Hooks
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
+ | `useIsMounted()` | Returns a function that tells whether the component is mounted |
40
+ | `useLatestRef(value)` | Returns a ref that holds the latest value |
41
+ | `usePreviousValue(value, options?)` | Returns the previous value |
42
+ | `useStableLength(arr)` | Ensures that the length of an array does not change across renders |
43
+
44
+ ### Functions
45
+
46
+ | Function | Description |
47
+ | ----------------------------------------------------- | -------------------------------------------------------------------------- |
48
+ | `applyDefaultProps(props, ...defaults)` | Fills `undefined` / `null` props with default values |
49
+ | `ensureComponent(component)` | Converts a built-in tag (such as `'div'`) into a component |
50
+ | `extendElement(element, overrideProps?, ...children)` | Clones an element, merging `className` and `style` |
51
+ | `forEachContent(children, callback)` | Iterates over elements, strings, and numbers, looking through Fragments |
52
+ | `forEachElement(children, callback)` | Iterates over elements, looking through Fragments |
53
+ | `markAt()` | Returns a value that increases on every call |
54
+ | `setRefCurrent(value, ...refs)` | Sets a value to multiple refs and returns a cleanup function |
55
+ | `transformContent(children, callback)` | Maps elements, strings, and numbers to an array, looking through Fragments |
56
+ | `transformElement(children, callback)` | Maps elements to an array, looking through Fragments |
57
+
58
+ ### `useCombinedRefs`
59
+
60
+ ```ts
61
+ useCombinedRefs<T = unknown>(...refs: Ref<T>[]): RefCallback<T>
62
+ ```
63
+
64
+ Returns a callback ref that sets the same value to all the given refs. Use it to pass both a ref received through `forwardRef` and a ref inside the component to a single element.
65
+
66
+ - The returned function keeps the same reference as long as none of the given refs change.
67
+ - The number of refs must not change across renders. An error is thrown if it does.
68
+ - In React 19, the returned callback ref returns a cleanup function. It calls the cleanup functions returned by callback refs as they are, and passes `null` to the callback refs that returned none.
69
+
70
+ ```tsx
71
+ const Input = forwardRef<HTMLInputElement>((props, ref) => {
72
+ const innerRef = useRef<HTMLInputElement>(null);
73
+ const combinedRef = useCombinedRefs(ref, innerRef);
74
+ return <input {...props} ref={combinedRef} />;
75
+ });
76
+ ```
77
+
78
+ ### `useConstant`
79
+
80
+ ```ts
81
+ useConstant<T>(factory: () => T): T
82
+ ```
83
+
84
+ Calls `factory` on the first render and keeps returning the created value until the component unmounts.
85
+
86
+ `useMemo` is a performance optimization and does not guarantee that its cache is never discarded. Use this hook for values whose identity matters, such as stateful instances.
87
+
88
+ ### `useExecutionAt`
89
+
90
+ ```ts
91
+ useExecutionAt<P = void>(
92
+ options?: UseExecutionAtOptions<P>,
93
+ ): UseExecutionAtResult<P>
94
+ ```
95
+
96
+ 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.
97
+
98
+ - `request.at` is `0` initially and increases every time `execute` is called. The receiving side runs the operation when `at` changes.
99
+ - `execute` always keeps the same reference.
100
+
101
+ ```tsx
102
+ const [undoAt, undo] = useExecutionAt<string>();
103
+
104
+ <button onClick={() => undo('no-123')}>Undo</button>
105
+ <WrappedImperativeComponent undoAt={undoAt} />
106
+ ```
107
+
108
+ | Option | Type | Description |
109
+ | ----------------- | ---- | -------------------------------------- |
110
+ | `initialPayload?` | `P` | The initial value of `request.payload` |
111
+
112
+ ### `useIsMounted`
113
+
114
+ ```ts
115
+ useIsMounted(): () => boolean
116
+ ```
117
+
118
+ Returns a function that tells whether the component is mounted. Use it, for example, to avoid updating an unmounted component when an async task finishes.
119
+
120
+ - It becomes `true` when the DOM is committed (`useLayoutEffect`), and `false` on unmount.
121
+ - When called during rendering, it returns `false` until the first mount completes.
122
+ - The returned function always keeps the same reference.
123
+
124
+ ### `useLatestRef`
125
+
126
+ ```ts
127
+ useLatestRef<T>(value: T): LatestRef<T>
128
+ ```
129
+
130
+ Returns a ref that holds the latest value. Since the ref is always the same object, you can read the latest value from event handlers and effects without adding it to dependency arrays.
131
+
132
+ - The value is updated on commit (`useInsertionEffect`), so it is already up to date inside `useLayoutEffect` / `useEffect`.
133
+ - Do not read it during rendering (it still holds the previous value before the commit).
134
+
135
+ ### `usePreviousValue`
136
+
137
+ ```ts
138
+ usePreviousValue<T = unknown>(
139
+ value: T,
140
+ options?: UsePreviousValueOptions<T>,
141
+ ): T | undefined
142
+ ```
143
+
144
+ Returns the previous value. It returns `initialValue` until the value changes for the first time.
145
+
146
+ When the value changes, the previous value is updated by a state update during rendering (no extra commit or effect run happens). Therefore, passing a value whose comparison is always `false` (such as an object or array created during rendering) causes endless re-rendering. Pass a value with a stable reference, or specify `isEqual`.
147
+
148
+ | Option | Type | Description |
149
+ | --------------- | --------------------------------------- | ------------------------------------------------------------------------------------- |
150
+ | `initialValue?` | `T` | The value returned until the value changes for the first time |
151
+ | `isEqual?` | `(oldValue: T, newValue: T) => boolean` | A comparison that returns `true` for values considered equal. Defaults to `Object.is` |
152
+
153
+ ### `useStableLength`
154
+
155
+ ```ts
156
+ useStableLength(arr: readonly unknown[]): void
157
+ ```
158
+
159
+ Ensures that the length of an array has not changed since the first render, and throws an error if it has. Use it, for example, when using a variable-length array as a dependency array of a hook.
160
+
161
+ ### `applyDefaultProps`
162
+
163
+ ```ts
164
+ applyDefaultProps<P extends object, const Ds extends readonly (Partial<P> | SkippedDefaults)[]>(
165
+ props: P,
166
+ ...defaults: Ds,
167
+ ): AppliedProps<P, Ds>
168
+ ```
169
+
170
+ Returns a new object in which the props that are `undefined` or `null` are filled with default values that are not `undefined`. The original `props` is not modified.
171
+
172
+ - You can pass multiple `defaults`. Per key, it behaves like `props[key] ?? d1[key] ?? d2[key] ?? ...`, so earlier arguments take precedence.
173
+ - `null` / `undefined` / `false` are skipped, so you can write conditional defaults such as `isCompact && compactDefaults`.
174
+ - In the return type, `null` / `undefined` are removed from the keys that definitely have a default value.
175
+ - A key in `defaults` that does not exist in `props` is a type error.
176
+
177
+ ```ts
178
+ const { size, color } = applyDefaultProps(
179
+ props,
180
+ isCompact && { size: 'small' },
181
+ { size: 'medium', color: 'primary' },
182
+ );
183
+ ```
184
+
185
+ Limitations:
186
+
187
+ - Only string keys of `defaults` are applied (symbol keys are ignored).
188
+ - When the type of `props` is a union, `defaults` can only contain keys common to all members.
189
+
190
+ ### `ensureComponent`
191
+
192
+ ```ts
193
+ ensureComponent<C extends ElementType>(
194
+ component: C,
195
+ ): ComponentType<ComponentPropsWithRef<C>>
196
+ ```
197
+
198
+ Converts a built-in tag (such as `'div'`) into a component that passes props and ref through as they are. A component is returned as is.
199
+
200
+ - The same component is returned for the same tag.
201
+ - Its `displayName` is `ensureComponent(tagName)`.
202
+
203
+ ### `extendElement`
204
+
205
+ ```ts
206
+ extendElement<P extends ExtendableProps>(
207
+ element: ReactElement<P>,
208
+ overrideProps?: Partial<P>,
209
+ ...children: ReactNode[]
210
+ ): ReactElement<P>
211
+ ```
212
+
213
+ Clones an element (`cloneElement`) with `overrideProps` applied. The `key` and `ref` of the original element are kept.
214
+
215
+ | Property | How it is applied |
216
+ | ----------- | -------------------------------------------------------------------------------- |
217
+ | `className` | Appended to the original `className`, separated by a space (empty is ignored) |
218
+ | `style` | Overrides the original `style` per property (`undefined` is ignored) |
219
+ | Others | Overrides the original value (properties whose value is `undefined` are ignored) |
220
+
221
+ | Parameter | Type | Description |
222
+ | ---------------- | ----------------- | ---------------------------------------------------------- |
223
+ | `element` | `ReactElement<P>` | The element to clone |
224
+ | `overrideProps?` | `Partial<P>` | The props to apply |
225
+ | `...children` | `ReactNode[]` | The children. When omitted, the original children are kept |
226
+
227
+ ### `forEachContent` / `forEachElement`
228
+
229
+ ```ts
230
+ forEachContent(
231
+ children: ReactNode,
232
+ callback: (node: ReactElement | string | number, index: number) => void,
233
+ ): void
234
+ forEachElement(
235
+ children: ReactNode,
236
+ callback: (node: ReactElement, index: number) => void,
237
+ ): void
238
+ ```
239
+
240
+ Iterates over `children`, looking through Fragments. `forEachContent` handles elements, strings, and numbers, while `forEachElement` handles only elements. `null` / `undefined` / booleans are not handled.
241
+
242
+ - Arrays and iterables are also expanded.
243
+ - `index` is a sequential number across Fragments.
244
+
245
+ ### `markAt`
246
+
247
+ ```ts
248
+ markAt(): MarkAtResult
249
+ ```
250
+
251
+ Returns a value that increases on every call. Values start from `1` and are never duplicated. `useExecutionAt` uses it for `request.at`.
252
+
253
+ ### `setRefCurrent`
254
+
255
+ ```ts
256
+ setRefCurrent<T = unknown>(
257
+ value: T | null,
258
+ ...refs: (RefCallback<T> | RefObject<T | null> | null | undefined)[]
259
+ ): () => void
260
+ ```
261
+
262
+ Sets `current` of object refs and calls callback refs with `value`. `null` / `undefined` are ignored.
263
+
264
+ Calling the returned cleanup function sets `null` to object refs. For callback refs, it calls the cleanup function they returned, or calls them with `null` if they returned none.
265
+
266
+ ### `transformContent` / `transformElement`
267
+
268
+ ```ts
269
+ transformContent<T>(
270
+ children: ReactNode,
271
+ callback: (node: ReactElement | string | number, index: number) => T,
272
+ ): T[]
273
+ transformElement<T>(
274
+ children: ReactNode,
275
+ callback: (node: ReactElement, index: number) => T,
276
+ ): T[]
277
+ ```
278
+
279
+ Processes `children` in the same order as `forEachContent` / `forEachElement`, and returns an array of the values returned by `callback`.
280
+
281
+ Unlike `Children.map`, keys are not reassigned. If different Fragments contain the same key, the key is duplicated in the returned array. When rendering the result as is, set keys inside `callback`.
282
+
283
+ ```tsx
284
+ const items = transformElement(children, (element, index) => (
285
+ <li key={index}>{element}</li>
286
+ ));
287
+ ```
288
+
289
+ ### Types
290
+
291
+ | Type | Description |
292
+ | ---------------------------- | -------------------------------------------------------------------------------------- |
293
+ | `AppliedProps<P, Ds>` | The return type of `applyDefaultProps` |
294
+ | `CSSPropertiesWithVars` | `CSSProperties` that also accepts CSS variables (`--name`) |
295
+ | `ExecutionFunction<P>` | The trigger function returned by `useExecutionAt` |
296
+ | `ExecutionRequest<P>` | The value for an Execution Request prop returned by `useExecutionAt` (`at`, `payload`) |
297
+ | `ExtendableProps` | The props of an element that `extendElement` accepts (`className`, `style`) |
298
+ | `LatestRef<T>` | The ref returned by `useLatestRef` |
299
+ | `MarkAtResult` | The return type of `markAt` |
300
+ | `SkippedDefaults` | Values skipped by `applyDefaultProps` (`null`, `undefined`, `false`) |
301
+ | `UseExecutionAtOptions<P>` | The options of `useExecutionAt` |
302
+ | `UseExecutionAtResult<P>` | The return type of `useExecutionAt` |
303
+ | `UsePreviousValueOptions<T>` | The options of `usePreviousValue` |
304
+
305
+ ## License
306
+
307
+ MIT
@@ -0,0 +1,32 @@
1
+ Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } });
2
+ let react = require("react");
3
+
4
+ //#region src/_helpers/_traverseChildren.ts
5
+ /**
6
+ * ReactElement、string、numberのいずれかであるかを判定する
7
+ * @param child 子要素
8
+ */
9
+ function _isContent(child) {
10
+ return (0, react.isValidElement)(child) || typeof child === "string" || typeof child === "number";
11
+ }
12
+ /**
13
+ * React.Fragmentを透過し、children内の`accept`に一致する子要素を処理する
14
+ *
15
+ * 配列やiterableは`Children.forEach`が展開するため、ここではFragmentだけを展開する。
16
+ *
17
+ * @param children エレメントの子要素
18
+ * @param accept 処理する子要素かどうかを判定する関数
19
+ * @param callback コールバック
20
+ * @param collect コールバックの結果を収集する処理
21
+ * @param cursor Fragmentをまたいでindexを連番にする為のカーソル(内部再帰用)
22
+ */
23
+ function _traverseChildren(children, accept, callback, collect = () => {}, cursor = { current: 0 }) {
24
+ react.Children.forEach(children, (child) => {
25
+ if ((0, react.isValidElement)(child) && child.type === react.Fragment) _traverseChildren(child.props.children, accept, callback, collect, cursor);
26
+ else if (accept(child)) collect(callback(child, cursor.current++));
27
+ });
28
+ }
29
+
30
+ //#endregion
31
+ exports._isContent = _isContent;
32
+ exports.default = _traverseChildren;
@@ -0,0 +1,24 @@
1
+ import { ReactElement, ReactNode } from "react";
2
+ //#region src/_helpers/_traverseChildren.d.ts
3
+ type IndexCursor = {
4
+ current: number;
5
+ };
6
+ /**
7
+ * ReactElement、string、numberのいずれかであるかを判定する
8
+ * @param child 子要素
9
+ */
10
+ export declare function _isContent(child: ReactNode): child is ReactElement | string | number;
11
+ /**
12
+ * React.Fragmentを透過し、children内の`accept`に一致する子要素を処理する
13
+ *
14
+ * 配列やiterableは`Children.forEach`が展開するため、ここではFragmentだけを展開する。
15
+ *
16
+ * @param children エレメントの子要素
17
+ * @param accept 処理する子要素かどうかを判定する関数
18
+ * @param callback コールバック
19
+ * @param collect コールバックの結果を収集する処理
20
+ * @param cursor Fragmentをまたいでindexを連番にする為のカーソル(内部再帰用)
21
+ */
22
+ export default function _traverseChildren<N extends ReactNode, T>(children: ReactNode, accept: (child: ReactNode) => child is N, callback: (node: N, index: number) => T, collect?: (value: T) => void, cursor?: IndexCursor): void;
23
+ //#endregion
24
+ export {};