react-ternary-be-gone 0.1.5 → 0.2.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 +100 -7
- package/dist/Case.js +1 -1
- package/dist/Conditional.js +8 -47
- package/dist/Else.js +1 -1
- package/dist/ElseIf.js +1 -1
- package/dist/For.js +56 -0
- package/dist/If.js +1 -1
- package/dist/Match.js +8 -0
- package/dist/Show.js +30 -0
- package/dist/Switch.js +38 -0
- package/dist/index.d.ts +185 -0
- package/dist/index.js +28 -0
- package/dist/markers.js +31 -9
- package/dist/shared.js +35 -0
- package/package.json +8 -3
package/README.md
CHANGED
|
@@ -10,7 +10,91 @@ npm install react-ternary-be-gone
|
|
|
10
10
|
yarn add react-ternary-be-gone
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
TypeScript definitions are bundled - no separate `@types/` package needed.
|
|
13
14
|
|
|
15
|
+
## Two APIs
|
|
16
|
+
|
|
17
|
+
This package ships two sets of components that cover the same ground:
|
|
18
|
+
|
|
19
|
+
- **`<Show>`, `<For>`, `<Switch>`/`<Match>`** - focused, single-purpose
|
|
20
|
+
primitives. They hand the checked value back through a render prop, so
|
|
21
|
+
TypeScript narrows it exactly like `user && <p>{user.name}</p>` does. They
|
|
22
|
+
use no hooks, so they work inside React Server Components.
|
|
23
|
+
- **`<Conditional>`** - the original all-in-one component (condition,
|
|
24
|
+
iteration, switch/case and if/else-if in one prop bag). Fully supported and
|
|
25
|
+
not going anywhere, but `when`/`each` can't be narrowed by TypeScript
|
|
26
|
+
(one prop bag has to cover five modes), and it uses hooks, so it needs a
|
|
27
|
+
client component boundary.
|
|
28
|
+
|
|
29
|
+
New code is better off with the focused primitives. Existing `<Conditional>`
|
|
30
|
+
code keeps working unchanged.
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
// Focused primitives - narrow, RSC-safe
|
|
34
|
+
import { Show, For, Switch, Match } from 'react-ternary-be-gone';
|
|
35
|
+
|
|
36
|
+
<Show when={user} fallback={<Login />}>
|
|
37
|
+
{(u) => <p>{u.name}</p>}
|
|
38
|
+
</Show>
|
|
39
|
+
|
|
40
|
+
<For each={users} empty={<p>No users.</p>}>
|
|
41
|
+
{(user) => <Row key={user.id} {...user} />}
|
|
42
|
+
</For>
|
|
43
|
+
|
|
44
|
+
<Switch fallback={<p>Unknown.</p>}>
|
|
45
|
+
<Match when={status === 'loading'}><Spinner /></Match>
|
|
46
|
+
<Match when={status === 'error'}><Alert /></Match>
|
|
47
|
+
</Switch>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### `<Show>`
|
|
51
|
+
|
|
52
|
+
Renders `children` when `when` is truthy, `fallback` otherwise. `children`
|
|
53
|
+
may be a plain node, or a function receiving the truthy value - which
|
|
54
|
+
TypeScript narrows to a non-nullish type.
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
<Show when={user} fallback={<p>Not signed in.</p>}>
|
|
58
|
+
{(u) => <p>{u.name}</p>}
|
|
59
|
+
</Show>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `<For>`
|
|
63
|
+
|
|
64
|
+
Iterates `each`, calling `children(item, index, items)` per entry. Supports
|
|
65
|
+
`filter`, `sort`, `limit`, `reverse`, `keyExtractor`, `empty`, `fallback`
|
|
66
|
+
and `wrapper`. A nullish `each` (`each={data?.items}` before a fetch
|
|
67
|
+
resolves) renders `empty` rather than crashing.
|
|
68
|
+
|
|
69
|
+
Unlike `<Conditional each>`, `children` must be a function - that removes a
|
|
70
|
+
whole class of mistake instead of guarding against it.
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
<For each={users} filter={(u) => u.active} sort={sortBy('name')} wrapper="ul">
|
|
74
|
+
{(user) => <li key={user.id}>{user.name}</li>}
|
|
75
|
+
</For>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`<For>` deliberately leaves out `animate`, `debug` and `onRender`; use
|
|
79
|
+
`<Conditional each={...}>` if you need those.
|
|
80
|
+
|
|
81
|
+
### `<Switch>` / `<Match>`
|
|
82
|
+
|
|
83
|
+
Renders the first `<Match>` whose `when` is truthy, or `<Switch>`'s
|
|
84
|
+
`fallback` if none match - an if/else-if/else chain as markup. `<Match>`
|
|
85
|
+
children can also be a function, narrowing `when` the way `<Show>` does.
|
|
86
|
+
|
|
87
|
+
```javascript
|
|
88
|
+
<Switch fallback={<p>Unknown status.</p>}>
|
|
89
|
+
<Match when={status === 'loading'}><Spinner /></Match>
|
|
90
|
+
<Match when={error}>{(e) => <Alert>{e.message}</Alert>}</Match>
|
|
91
|
+
<Match when={data}>{(d) => <Table rows={d.rows} />}</Match>
|
|
92
|
+
</Switch>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`<Match when={...}>` takes a boolean condition per branch. To match one
|
|
96
|
+
value against several cases, use `<Conditional switch={value}>` with
|
|
97
|
+
`<Case when="a">` - a separate feature documented below.
|
|
14
98
|
|
|
15
99
|
## Usage
|
|
16
100
|
|
|
@@ -86,13 +170,13 @@ const status = 'success';
|
|
|
86
170
|
|
|
87
171
|
<Conditional>
|
|
88
172
|
<If when={status === 'loading'}>
|
|
89
|
-
<p>
|
|
173
|
+
<p>Loading...</p>
|
|
90
174
|
</If>
|
|
91
175
|
<ElseIf when={status === 'success'}>
|
|
92
|
-
<p>
|
|
176
|
+
<p>Success!</p>
|
|
93
177
|
</ElseIf>
|
|
94
178
|
<Else>
|
|
95
|
-
<p>
|
|
179
|
+
<p>Status unknown.</p>
|
|
96
180
|
</Else>
|
|
97
181
|
</Conditional>
|
|
98
182
|
```
|
|
@@ -107,7 +191,7 @@ const users = [{ id: 1, name: 'Alice', active: true }, { id: 2, name: 'Bob', act
|
|
|
107
191
|
{(user) => <p>{user.name}</p>}
|
|
108
192
|
</If>
|
|
109
193
|
<Else>
|
|
110
|
-
<p>
|
|
194
|
+
<p>No users found.</p>
|
|
111
195
|
</Else>
|
|
112
196
|
</Conditional>
|
|
113
197
|
```
|
|
@@ -127,9 +211,9 @@ import Conditional, { Case } from 'react-ternary-be-gone';
|
|
|
127
211
|
const value = 'b';
|
|
128
212
|
|
|
129
213
|
<Conditional switch={value}>
|
|
130
|
-
<Case when="a">A
|
|
131
|
-
<Case when="b">B
|
|
132
|
-
<Case default>
|
|
214
|
+
<Case when="a">A selected</Case>
|
|
215
|
+
<Case when="b">B selected</Case>
|
|
216
|
+
<Case default>None selected</Case>
|
|
133
217
|
</Conditional>
|
|
134
218
|
```
|
|
135
219
|
|
|
@@ -450,6 +534,15 @@ A callback function that is called after the component renders. Provides informa
|
|
|
450
534
|
|
|
451
535
|
### Advanced Condition Evaluation Props
|
|
452
536
|
|
|
537
|
+
> **Deprecated.** These still work and aren't scheduled for removal, but a
|
|
538
|
+
> plain expression in `when` is shorter and reads better:
|
|
539
|
+
> `when={a > b}` instead of `gt={{ value: a, target: b }}`. They're marked
|
|
540
|
+
> `@deprecated` in the TypeScript definitions, so editors will flag them.
|
|
541
|
+
>
|
|
542
|
+
> Note that these props did nothing at all before v0.1.4 - the condition was
|
|
543
|
+
> ignored and children always rendered. If you wrote code against the broken
|
|
544
|
+
> behaviour, check it.
|
|
545
|
+
|
|
453
546
|
These props allow for more complex conditional checks without needing to define a separate `when` prop. Only one of these props should be used at a time.
|
|
454
547
|
|
|
455
548
|
#### `gt` (Greater Than)
|
package/dist/Case.js
CHANGED
|
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _markers = require("./markers");
|
|
8
|
-
var _default = exports.default = (0, _markers.createMarker)('Case', _markers.CASE);
|
|
8
|
+
var _default = exports.default = (0, _markers.createMarker)('Case', _markers.CASE, 'Conditional');
|
package/dist/Conditional.js
CHANGED
|
@@ -6,47 +6,8 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _react = _interopRequireWildcard(require("react"));
|
|
8
8
|
var _markers = require("./markers");
|
|
9
|
+
var _shared = require("./shared");
|
|
9
10
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
10
|
-
// Pure helper: apply filter/sort/reverse/limit to an `each` array without
|
|
11
|
-
// mutating the caller's array (Array.prototype.sort/reverse mutate in place,
|
|
12
|
-
// so every step works off a copy).
|
|
13
|
-
const processArray = (array, {
|
|
14
|
-
filter,
|
|
15
|
-
sort,
|
|
16
|
-
reverse,
|
|
17
|
-
limit
|
|
18
|
-
}) => {
|
|
19
|
-
if (!Array.isArray(array)) return [];
|
|
20
|
-
let result = [...array];
|
|
21
|
-
if (typeof filter === 'function') result = result.filter(filter);
|
|
22
|
-
if (typeof sort === 'function') result = result.sort(sort);
|
|
23
|
-
if (reverse) result = result.reverse();
|
|
24
|
-
if (typeof limit === 'number') result = result.slice(0, limit);
|
|
25
|
-
return result;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
// Pure helper: render one React.Fragment per item, guarding the render-prop
|
|
29
|
-
// call so static children (a plain element instead of a function) don't
|
|
30
|
-
// crash when they reach an iteration path.
|
|
31
|
-
const renderIterableChildren = (items, children, keyExtractor) => items.map((item, index) => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
|
|
32
|
-
key: keyExtractor ? keyExtractor(item, index) : index
|
|
33
|
-
}, typeof children === 'function' ? children(item, index, items) : children));
|
|
34
|
-
|
|
35
|
-
// `React.Children.toArray` flattens arrays produced by `.map()`, but an
|
|
36
|
-
// author-written `<>...</>` around a set of branches is itself a single
|
|
37
|
-
// Fragment element in the tree - its contents need an explicit unwrap or
|
|
38
|
-
// `<If>`/`<ElseIf>`/`<Else>`/`<Case>` inside one are never seen as branches.
|
|
39
|
-
const flattenBranches = children => {
|
|
40
|
-
const result = [];
|
|
41
|
-
_react.default.Children.toArray(children).forEach(child => {
|
|
42
|
-
if (child && child.type === _react.default.Fragment) {
|
|
43
|
-
result.push(...flattenBranches(child.props.children));
|
|
44
|
-
} else {
|
|
45
|
-
result.push(child);
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
return result;
|
|
49
|
-
};
|
|
50
11
|
const Conditional = props => {
|
|
51
12
|
const {
|
|
52
13
|
when,
|
|
@@ -167,7 +128,7 @@ const Conditional = props => {
|
|
|
167
128
|
|
|
168
129
|
// Array processing
|
|
169
130
|
const processedArray = (0, _react.useMemo)(() => {
|
|
170
|
-
const result = processArray(each, {
|
|
131
|
+
const result = (0, _shared.processArray)(each, {
|
|
171
132
|
filter,
|
|
172
133
|
sort,
|
|
173
134
|
reverse,
|
|
@@ -220,7 +181,7 @@ const Conditional = props => {
|
|
|
220
181
|
// while `status` is legitimately undefined before data loads, the same
|
|
221
182
|
// reasoning as the `each` presence check above.
|
|
222
183
|
if ('switch' in props) {
|
|
223
|
-
const caseChildren = flattenBranches(children).filter(child => child && (0, _markers.markerKindOf)(child) === _markers.CASE);
|
|
184
|
+
const caseChildren = (0, _markers.flattenBranches)(children).filter(child => child && (0, _markers.markerKindOf)(child) === _markers.CASE);
|
|
224
185
|
// `.find()` stops at the first hit, matching how a real `switch` picks
|
|
225
186
|
// the first matching branch instead of the last one.
|
|
226
187
|
const matchedCase = caseChildren.find(child => !child.props.default && child.props.when === switchValue);
|
|
@@ -235,7 +196,7 @@ const Conditional = props => {
|
|
|
235
196
|
}
|
|
236
197
|
|
|
237
198
|
// If-ElseIf-Else logic
|
|
238
|
-
const branches = flattenBranches(children).filter(child => child && [_markers.IF, _markers.ELSE_IF, _markers.ELSE].includes((0, _markers.markerKindOf)(child)));
|
|
199
|
+
const branches = (0, _markers.flattenBranches)(children).filter(child => child && [_markers.IF, _markers.ELSE_IF, _markers.ELSE].includes((0, _markers.markerKindOf)(child)));
|
|
239
200
|
if (branches.length > 0) {
|
|
240
201
|
let rendered = fallback;
|
|
241
202
|
for (const branch of branches) {
|
|
@@ -256,13 +217,13 @@ const Conditional = props => {
|
|
|
256
217
|
const matches = kind === _markers.ELSE || Boolean(branchWhen);
|
|
257
218
|
if (!matches) continue;
|
|
258
219
|
if ('each' in branch.props) {
|
|
259
|
-
const items = processArray(branchEach, {
|
|
220
|
+
const items = (0, _shared.processArray)(branchEach, {
|
|
260
221
|
filter: branchFilter,
|
|
261
222
|
sort: branchSort,
|
|
262
223
|
reverse: branchReverse,
|
|
263
224
|
limit: branchLimit
|
|
264
225
|
});
|
|
265
|
-
rendered = items.length === 0 ? branchEmpty || branchFallback || null : /*#__PURE__*/_react.default.createElement(BranchWrapper, null, renderIterableChildren(items, branchChildren, branchKeyExtractor));
|
|
226
|
+
rendered = items.length === 0 ? branchEmpty || branchFallback || null : /*#__PURE__*/_react.default.createElement(BranchWrapper, null, (0, _shared.renderIterableChildren)(items, branchChildren, branchKeyExtractor));
|
|
266
227
|
} else {
|
|
267
228
|
rendered = /*#__PURE__*/_react.default.createElement(BranchWrapper, null, branchChildren);
|
|
268
229
|
}
|
|
@@ -281,7 +242,7 @@ const Conditional = props => {
|
|
|
281
242
|
if (processedArray.length === 0) {
|
|
282
243
|
return empty || fallback;
|
|
283
244
|
}
|
|
284
|
-
const elements = renderIterableChildren(processedArray, children, keyExtractor);
|
|
245
|
+
const elements = (0, _shared.renderIterableChildren)(processedArray, children, keyExtractor);
|
|
285
246
|
// React.Fragment rejects unknown props like `className`, so the default
|
|
286
247
|
// wrapper is swapped for a real host element whenever `animate` needs
|
|
287
248
|
// somewhere to put the class.
|
|
@@ -295,7 +256,7 @@ const Conditional = props => {
|
|
|
295
256
|
if (hasCondition && hasIteration) {
|
|
296
257
|
if (!evaluateCondition) return fallback;
|
|
297
258
|
if (processedArray.length === 0) return empty || fallback;
|
|
298
|
-
const elements = renderIterableChildren(processedArray, children, keyExtractor);
|
|
259
|
+
const elements = (0, _shared.renderIterableChildren)(processedArray, children, keyExtractor);
|
|
299
260
|
return /*#__PURE__*/_react.default.createElement(Wrapper, null, elements);
|
|
300
261
|
}
|
|
301
262
|
return /*#__PURE__*/_react.default.createElement(Wrapper, null, children);
|
package/dist/Else.js
CHANGED
|
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _markers = require("./markers");
|
|
8
|
-
var _default = exports.default = (0, _markers.createMarker)('Else', _markers.ELSE);
|
|
8
|
+
var _default = exports.default = (0, _markers.createMarker)('Else', _markers.ELSE, 'Conditional');
|
package/dist/ElseIf.js
CHANGED
|
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _markers = require("./markers");
|
|
8
|
-
var _default = exports.default = (0, _markers.createMarker)('ElseIf', _markers.ELSE_IF);
|
|
8
|
+
var _default = exports.default = (0, _markers.createMarker)('ElseIf', _markers.ELSE_IF, 'Conditional');
|
package/dist/For.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _shared = require("./shared");
|
|
9
|
+
var _markers = require("./markers");
|
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
11
|
+
/**
|
|
12
|
+
* Iterates `each`, rendering `children(item, index, items)` for each entry.
|
|
13
|
+
*
|
|
14
|
+
* Unlike `<Conditional each={x}>`, `children` here must be a function - not
|
|
15
|
+
* also a static ReactNode. That removes an entire class of bug (a render-prop
|
|
16
|
+
* call landing on something that isn't a function) by construction instead
|
|
17
|
+
* of by guarding against it.
|
|
18
|
+
*
|
|
19
|
+
* `each={undefined}`/`each={null}` (e.g. `each={data?.items}` before a
|
|
20
|
+
* fetch resolves) are treated as an empty list, rendering `empty` (or
|
|
21
|
+
* `fallback`) rather than crashing.
|
|
22
|
+
*
|
|
23
|
+
* <For each={users} empty={<p>No users.</p>}>
|
|
24
|
+
* {(user) => <p key={user.id}>{user.name}</p>}
|
|
25
|
+
* </For>
|
|
26
|
+
*
|
|
27
|
+
* No hooks, no browser-only APIs: safe to call from a Server Component.
|
|
28
|
+
*/
|
|
29
|
+
const For = ({
|
|
30
|
+
each,
|
|
31
|
+
children,
|
|
32
|
+
fallback = null,
|
|
33
|
+
empty = null,
|
|
34
|
+
keyExtractor = (item, index) => index,
|
|
35
|
+
filter = null,
|
|
36
|
+
sort = null,
|
|
37
|
+
limit = null,
|
|
38
|
+
reverse = false,
|
|
39
|
+
wrapper: Wrapper = _react.default.Fragment
|
|
40
|
+
}) => {
|
|
41
|
+
if (process.env.NODE_ENV !== 'production' && typeof children !== 'function') {
|
|
42
|
+
(0, _markers.warnOnce)('[react-ternary-be-gone] <For> expects `children` to be a function: ' + '(item, index, items) => ReactNode.');
|
|
43
|
+
}
|
|
44
|
+
const items = (0, _shared.processArray)(each, {
|
|
45
|
+
filter,
|
|
46
|
+
sort,
|
|
47
|
+
reverse,
|
|
48
|
+
limit
|
|
49
|
+
});
|
|
50
|
+
if (items.length === 0) {
|
|
51
|
+
return empty || fallback;
|
|
52
|
+
}
|
|
53
|
+
const elements = (0, _shared.renderIterableChildren)(items, children, keyExtractor);
|
|
54
|
+
return /*#__PURE__*/_react.default.createElement(Wrapper, null, elements);
|
|
55
|
+
};
|
|
56
|
+
var _default = exports.default = For;
|
package/dist/If.js
CHANGED
|
@@ -5,4 +5,4 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
var _markers = require("./markers");
|
|
8
|
-
var _default = exports.default = (0, _markers.createMarker)('If', _markers.IF);
|
|
8
|
+
var _default = exports.default = (0, _markers.createMarker)('If', _markers.IF, 'Conditional');
|
package/dist/Match.js
ADDED
package/dist/Show.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Renders `children` when `when` is truthy, `fallback` otherwise.
|
|
9
|
+
*
|
|
10
|
+
* Prefer this over `<Conditional when={x}>` when the branch needs the value
|
|
11
|
+
* itself: `children` may be a function that receives `when`, which
|
|
12
|
+
* TypeScript narrows to a non-nullish type in the .d.ts - exactly what
|
|
13
|
+
* `x && <Foo x={x} />` gives you for free and `<Conditional>` cannot,
|
|
14
|
+
* because it never hands the value back.
|
|
15
|
+
*
|
|
16
|
+
* <Show when={user} fallback={<Login />}>
|
|
17
|
+
* {(u) => <p>{u.name}</p>}
|
|
18
|
+
* </Show>
|
|
19
|
+
*
|
|
20
|
+
* No hooks, no browser-only APIs: safe to call from a Server Component.
|
|
21
|
+
*/
|
|
22
|
+
const Show = ({
|
|
23
|
+
when,
|
|
24
|
+
children,
|
|
25
|
+
fallback = null
|
|
26
|
+
}) => {
|
|
27
|
+
if (!when) return fallback;
|
|
28
|
+
return typeof children === 'function' ? children(when) : children;
|
|
29
|
+
};
|
|
30
|
+
var _default = exports.default = Show;
|
package/dist/Switch.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _markers = require("./markers");
|
|
8
|
+
/**
|
|
9
|
+
* Renders the first `<Match when={...}>` whose condition is truthy, or
|
|
10
|
+
* `fallback` if none match - a chain of if/else-if/else expressed as
|
|
11
|
+
* markup instead of nested ternaries.
|
|
12
|
+
*
|
|
13
|
+
* <Switch fallback={<p>Unknown.</p>}>
|
|
14
|
+
* <Match when={status === 'loading'}><Spinner /></Match>
|
|
15
|
+
* <Match when={status === 'error'}>{() => <Alert>{error}</Alert>}</Match>
|
|
16
|
+
* </Switch>
|
|
17
|
+
*
|
|
18
|
+
* `when` here is a boolean condition per branch, same as `<If>/<ElseIf>`.
|
|
19
|
+
* For matching one value against several cases, `<Conditional switch={value}>`
|
|
20
|
+
* with `<Case when="a">` remains the right tool - it's a different, unrelated
|
|
21
|
+
* feature that happens to share the word "switch".
|
|
22
|
+
*
|
|
23
|
+
* No hooks, no browser-only APIs: safe to call from a Server Component.
|
|
24
|
+
*/
|
|
25
|
+
const Switch = ({
|
|
26
|
+
children,
|
|
27
|
+
fallback = null
|
|
28
|
+
}) => {
|
|
29
|
+
const branches = (0, _markers.flattenBranches)(children).filter(child => child && (0, _markers.markerKindOf)(child) === _markers.MATCH);
|
|
30
|
+
const winner = branches.find(child => Boolean(child.props.when));
|
|
31
|
+
if (!winner) return fallback;
|
|
32
|
+
const {
|
|
33
|
+
when,
|
|
34
|
+
children: branchChildren
|
|
35
|
+
} = winner.props;
|
|
36
|
+
return typeof branchChildren === 'function' ? branchChildren(when) : branchChildren;
|
|
37
|
+
};
|
|
38
|
+
var _default = exports.default = Switch;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
|
|
3
|
+
/** Every value JavaScript treats as falsy, minus `NaN` (not its own type). */
|
|
4
|
+
type Falsy = false | 0 | '' | null | undefined;
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Show / For / Switch / Match
|
|
8
|
+
//
|
|
9
|
+
// The typed, narrowing-friendly primitives. Prefer these in new code -
|
|
10
|
+
// `<Conditional>` below stays fully supported, but `when`/`each` on it are
|
|
11
|
+
// typed as `unknown`/`unknown[]` because one prop bag has to cover five
|
|
12
|
+
// different modes, so TypeScript can't narrow anything inside its children.
|
|
13
|
+
// Show/For/Match hand the checked value back through a render-prop instead,
|
|
14
|
+
// which narrows exactly like `value && children(value)` does natively.
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
export interface ShowProps<T> {
|
|
18
|
+
when: T | Falsy;
|
|
19
|
+
/** A plain node, or a function receiving the truthy, narrowed `when`. */
|
|
20
|
+
children: React.ReactNode | ((value: Exclude<T, Falsy>) => React.ReactNode);
|
|
21
|
+
fallback?: React.ReactNode;
|
|
22
|
+
}
|
|
23
|
+
/** Renders `children` when `when` is truthy, `fallback` otherwise. */
|
|
24
|
+
export function Show<T>(props: ShowProps<T>): React.ReactElement | null;
|
|
25
|
+
|
|
26
|
+
export interface ForProps<T> {
|
|
27
|
+
each: readonly T[] | null | undefined;
|
|
28
|
+
children: (item: T, index: number, items: readonly T[]) => React.ReactNode;
|
|
29
|
+
fallback?: React.ReactNode;
|
|
30
|
+
/** Rendered when `each` is empty; falls back to `fallback` if omitted. */
|
|
31
|
+
empty?: React.ReactNode;
|
|
32
|
+
keyExtractor?: (item: T, index: number) => React.Key;
|
|
33
|
+
filter?: (item: T) => boolean;
|
|
34
|
+
sort?: (a: T, b: T) => number;
|
|
35
|
+
limit?: number;
|
|
36
|
+
reverse?: boolean;
|
|
37
|
+
/** Host element or component to wrap the rendered items in. @default React.Fragment */
|
|
38
|
+
wrapper?: React.ElementType;
|
|
39
|
+
}
|
|
40
|
+
/** Renders `children(item, index, items)` for each entry of `each`. */
|
|
41
|
+
export function For<T>(props: ForProps<T>): React.ReactElement | null;
|
|
42
|
+
|
|
43
|
+
export interface SwitchProps {
|
|
44
|
+
/** `<Match>` elements (optionally inside a fragment or produced by `.map()`). */
|
|
45
|
+
children: React.ReactNode;
|
|
46
|
+
fallback?: React.ReactNode;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Renders the first child `<Match>` whose `when` is truthy, or `fallback`.
|
|
50
|
+
* A chain of if/else-if/else expressed as markup instead of nested ternaries.
|
|
51
|
+
*/
|
|
52
|
+
export function Switch(props: SwitchProps): React.ReactElement | null;
|
|
53
|
+
|
|
54
|
+
export interface MatchProps<T> {
|
|
55
|
+
when: T | Falsy;
|
|
56
|
+
children: React.ReactNode | ((value: Exclude<T, Falsy>) => React.ReactNode);
|
|
57
|
+
}
|
|
58
|
+
/** Only meaningful as a direct (or fragment-wrapped) child of `<Switch>`. */
|
|
59
|
+
export function Match<T>(props: MatchProps<T>): null;
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Conditional - the original, all-in-one API. Fully supported; Show/For/
|
|
63
|
+
// Switch/Match cover the same ground with real narrowing where Conditional
|
|
64
|
+
// cannot offer it, because `when`/`each` here have to stay typed loosely
|
|
65
|
+
// enough to cover all five of Conditional's modes in one prop bag.
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
|
|
68
|
+
export interface Comparison<T> {
|
|
69
|
+
value: T;
|
|
70
|
+
target: T;
|
|
71
|
+
}
|
|
72
|
+
export interface StringComparison {
|
|
73
|
+
value: unknown;
|
|
74
|
+
target: string;
|
|
75
|
+
}
|
|
76
|
+
export interface MatchComparison {
|
|
77
|
+
value: unknown;
|
|
78
|
+
pattern: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface ConditionalProps<T = unknown> {
|
|
82
|
+
when?: unknown;
|
|
83
|
+
each?: readonly T[] | null;
|
|
84
|
+
children?:
|
|
85
|
+
| React.ReactNode
|
|
86
|
+
| ((item: T, index: number, items: readonly T[]) => React.ReactNode);
|
|
87
|
+
fallback?: React.ReactNode;
|
|
88
|
+
/** Rendered when `each` is empty; falls back to `fallback` if omitted. */
|
|
89
|
+
empty?: React.ReactNode;
|
|
90
|
+
loading?: boolean;
|
|
91
|
+
/** Rendered while `loading` is true, taking priority over `fallback`. */
|
|
92
|
+
loadingFallback?: React.ReactNode;
|
|
93
|
+
error?: unknown;
|
|
94
|
+
/** Rendered instead of the default "Error: ..." message when `error` is set. */
|
|
95
|
+
errorFallback?: React.ReactNode | ((error: unknown) => React.ReactNode);
|
|
96
|
+
keyExtractor?: (item: T, index: number) => React.Key;
|
|
97
|
+
filter?: (item: T) => boolean;
|
|
98
|
+
sort?: (a: T, b: T) => number;
|
|
99
|
+
limit?: number;
|
|
100
|
+
reverse?: boolean;
|
|
101
|
+
/** Adds the `conditional-animated` class; define that class yourself. */
|
|
102
|
+
animate?: boolean;
|
|
103
|
+
/** Host element or component to wrap the rendered output in. @default React.Fragment */
|
|
104
|
+
wrapper?: React.ElementType;
|
|
105
|
+
debug?: boolean;
|
|
106
|
+
onRender?: (info: {
|
|
107
|
+
condition: boolean;
|
|
108
|
+
itemCount: number;
|
|
109
|
+
hasCondition: boolean;
|
|
110
|
+
hasIteration: boolean;
|
|
111
|
+
}) => void;
|
|
112
|
+
/** @deprecated Use `when={value > target}` instead - see `<Show>`. */
|
|
113
|
+
gt?: Comparison<number>;
|
|
114
|
+
/** @deprecated Use `when={value < target}` instead - see `<Show>`. */
|
|
115
|
+
lt?: Comparison<number>;
|
|
116
|
+
/** @deprecated Use `when={value === target}` instead - see `<Show>`. */
|
|
117
|
+
eq?: Comparison<unknown>;
|
|
118
|
+
/** @deprecated Use `when={value !== target}` instead - see `<Show>`. */
|
|
119
|
+
ne?: Comparison<unknown>;
|
|
120
|
+
/** @deprecated Use `when={String(value).includes(target)}` instead - see `<Show>`. */
|
|
121
|
+
includes?: StringComparison;
|
|
122
|
+
/** @deprecated Use `when={String(value).startsWith(target)}` instead - see `<Show>`. */
|
|
123
|
+
startsWith?: StringComparison;
|
|
124
|
+
/** @deprecated Use `when={String(value).endsWith(target)}` instead - see `<Show>`. */
|
|
125
|
+
endsWith?: StringComparison;
|
|
126
|
+
/** @deprecated Use `when={new RegExp(pattern).test(String(value))}` instead - see `<Show>`. */
|
|
127
|
+
match?: MatchComparison;
|
|
128
|
+
/** Value to match against child `<Case when={...}>` elements. */
|
|
129
|
+
switch?: unknown;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* All-in-one conditional rendering / iteration / switch / if-else-if
|
|
133
|
+
* component. See `<Show>`, `<For>`, and `<Switch>`/`<Match>` for narrower,
|
|
134
|
+
* TypeScript-narrowing-friendly alternatives covering the same ground.
|
|
135
|
+
*/
|
|
136
|
+
export default function Conditional<T = unknown>(
|
|
137
|
+
props: ConditionalProps<T>
|
|
138
|
+
): React.ReactElement | null;
|
|
139
|
+
|
|
140
|
+
export interface CaseProps {
|
|
141
|
+
when?: unknown;
|
|
142
|
+
/** Renders when no sibling `<Case when={...}>` matched the `switch` value. */
|
|
143
|
+
default?: boolean;
|
|
144
|
+
children?: React.ReactNode;
|
|
145
|
+
}
|
|
146
|
+
/** Only meaningful as a direct (or fragment-wrapped) child of `<Conditional switch={...}>`. */
|
|
147
|
+
export function Case(props: CaseProps): null;
|
|
148
|
+
|
|
149
|
+
export interface BranchProps<T = unknown> {
|
|
150
|
+
when?: unknown;
|
|
151
|
+
each?: readonly T[] | null;
|
|
152
|
+
children?:
|
|
153
|
+
| React.ReactNode
|
|
154
|
+
| ((item: T, index: number, items: readonly T[]) => React.ReactNode);
|
|
155
|
+
filter?: (item: T) => boolean;
|
|
156
|
+
sort?: (a: T, b: T) => number;
|
|
157
|
+
limit?: number;
|
|
158
|
+
reverse?: boolean;
|
|
159
|
+
wrapper?: React.ElementType;
|
|
160
|
+
keyExtractor?: (item: T, index: number) => React.Key;
|
|
161
|
+
empty?: React.ReactNode;
|
|
162
|
+
fallback?: React.ReactNode;
|
|
163
|
+
}
|
|
164
|
+
/** Only meaningful as a direct (or fragment-wrapped) child of a bare `<Conditional>`. */
|
|
165
|
+
export function If<T = unknown>(props: BranchProps<T>): null;
|
|
166
|
+
/** Only meaningful as a direct (or fragment-wrapped) child of a bare `<Conditional>`. */
|
|
167
|
+
export function ElseIf<T = unknown>(props: BranchProps<T>): null;
|
|
168
|
+
/** Only meaningful as a direct (or fragment-wrapped) child of a bare `<Conditional>`. */
|
|
169
|
+
export function Else<T = unknown>(props: Omit<BranchProps<T>, 'when'>): null;
|
|
170
|
+
|
|
171
|
+
// ---------------------------------------------------------------------------
|
|
172
|
+
// useConditionalHelpers
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
|
|
175
|
+
export interface ConditionalHelpers {
|
|
176
|
+
isEmpty(array: readonly unknown[] | null | undefined): { when: boolean };
|
|
177
|
+
isNotEmpty(array: readonly unknown[] | null | undefined): { when: boolean };
|
|
178
|
+
hasLength(array: readonly unknown[] | null | undefined, length: number): { when: boolean };
|
|
179
|
+
isEven(num: number): { when: boolean };
|
|
180
|
+
isOdd(num: number): { when: boolean };
|
|
181
|
+
sortBy<T>(field: keyof T, order?: 'asc' | 'desc'): (a: T, b: T) => number;
|
|
182
|
+
filterBy<T>(field: keyof T, value: T[keyof T]): (item: T) => boolean;
|
|
183
|
+
unique<T>(array: readonly T[] | null | undefined, key: keyof T | ((item: T) => unknown)): T[];
|
|
184
|
+
}
|
|
185
|
+
export function useConditionalHelpers(): ConditionalHelpers;
|
package/dist/index.js
CHANGED
|
@@ -21,12 +21,36 @@ Object.defineProperty(exports, "ElseIf", {
|
|
|
21
21
|
return _ElseIf.default;
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
|
+
Object.defineProperty(exports, "For", {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () {
|
|
27
|
+
return _For.default;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
24
30
|
Object.defineProperty(exports, "If", {
|
|
25
31
|
enumerable: true,
|
|
26
32
|
get: function () {
|
|
27
33
|
return _If.default;
|
|
28
34
|
}
|
|
29
35
|
});
|
|
36
|
+
Object.defineProperty(exports, "Match", {
|
|
37
|
+
enumerable: true,
|
|
38
|
+
get: function () {
|
|
39
|
+
return _Match.default;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(exports, "Show", {
|
|
43
|
+
enumerable: true,
|
|
44
|
+
get: function () {
|
|
45
|
+
return _Show.default;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
Object.defineProperty(exports, "Switch", {
|
|
49
|
+
enumerable: true,
|
|
50
|
+
get: function () {
|
|
51
|
+
return _Switch.default;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
30
54
|
exports.default = void 0;
|
|
31
55
|
Object.defineProperty(exports, "useConditionalHelpers", {
|
|
32
56
|
enumerable: true,
|
|
@@ -39,6 +63,10 @@ var _Case = _interopRequireDefault(require("./Case"));
|
|
|
39
63
|
var _If = _interopRequireDefault(require("./If"));
|
|
40
64
|
var _ElseIf = _interopRequireDefault(require("./ElseIf"));
|
|
41
65
|
var _Else = _interopRequireDefault(require("./Else"));
|
|
66
|
+
var _Show = _interopRequireDefault(require("./Show"));
|
|
67
|
+
var _For = _interopRequireDefault(require("./For"));
|
|
68
|
+
var _Switch = _interopRequireDefault(require("./Switch"));
|
|
69
|
+
var _Match = _interopRequireDefault(require("./Match"));
|
|
42
70
|
var _useConditionalHelpers = require("./useConditionalHelpers");
|
|
43
71
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
44
72
|
var _default = exports.default = _Conditional.default;
|
package/dist/markers.js
CHANGED
|
@@ -3,10 +3,13 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.warnOnce = exports.markerKindOf = exports.createMarker = exports.IF = exports.ELSE_IF = exports.ELSE = exports.CASE = void 0;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
//
|
|
6
|
+
exports.warnOnce = exports.markerKindOf = exports.flattenBranches = exports.createMarker = exports.MATCH = exports.IF = exports.ELSE_IF = exports.ELSE = exports.CASE = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
// Branch markers (`If`, `ElseIf`, `Else`, `Case`, `Match`) are never rendered
|
|
10
|
+
// by React when they are used correctly: their parent (`Conditional` or
|
|
11
|
+
// `Switch`) reads their props and renders the winning branch itself. They
|
|
12
|
+
// exist only so that JSX can express the shape.
|
|
10
13
|
//
|
|
11
14
|
// Identifying them by reference (`child.type === If`) breaks as soon as two
|
|
12
15
|
// copies of this package end up in node_modules, so each marker carries a
|
|
@@ -16,6 +19,7 @@ const IF = exports.IF = 'if';
|
|
|
16
19
|
const ELSE_IF = exports.ELSE_IF = 'elseif';
|
|
17
20
|
const ELSE = exports.ELSE = 'else';
|
|
18
21
|
const CASE = exports.CASE = 'case';
|
|
22
|
+
const MATCH = exports.MATCH = 'match';
|
|
19
23
|
const TAG = '$$rtbgMarker';
|
|
20
24
|
const warned = new Set();
|
|
21
25
|
const warnOnce = message => {
|
|
@@ -24,12 +28,12 @@ const warnOnce = message => {
|
|
|
24
28
|
console.warn(message);
|
|
25
29
|
};
|
|
26
30
|
exports.warnOnce = warnOnce;
|
|
27
|
-
const createMarker = (name, kind) => {
|
|
28
|
-
// Rendering nothing is the safe default: a marker that slipped outside
|
|
29
|
-
//
|
|
31
|
+
const createMarker = (name, kind, parentName) => {
|
|
32
|
+
// Rendering nothing is the safe default: a marker that slipped outside its
|
|
33
|
+
// parent must not leak the content it was meant to gate.
|
|
30
34
|
const Marker = () => {
|
|
31
35
|
if (process.env.NODE_ENV !== 'production') {
|
|
32
|
-
warnOnce(`[react-ternary-be-gone] <${name}> only works as a child of
|
|
36
|
+
warnOnce(`[react-ternary-be-gone] <${name}> only works as a child of <${parentName}>. ` + 'It rendered nothing.');
|
|
33
37
|
}
|
|
34
38
|
return null;
|
|
35
39
|
};
|
|
@@ -39,4 +43,22 @@ const createMarker = (name, kind) => {
|
|
|
39
43
|
};
|
|
40
44
|
exports.createMarker = createMarker;
|
|
41
45
|
const markerKindOf = element => element && element.type && element.type[TAG] || null;
|
|
42
|
-
|
|
46
|
+
|
|
47
|
+
// `React.Children.toArray` flattens arrays produced by `.map()`, but an
|
|
48
|
+
// author-written `<>...</>` around a set of branches is itself a single
|
|
49
|
+
// Fragment element in the tree - its contents need an explicit unwrap or
|
|
50
|
+
// branch markers inside one are never seen by the scanner that looks for
|
|
51
|
+
// them.
|
|
52
|
+
exports.markerKindOf = markerKindOf;
|
|
53
|
+
const flattenBranches = children => {
|
|
54
|
+
const result = [];
|
|
55
|
+
_react.default.Children.toArray(children).forEach(child => {
|
|
56
|
+
if (child && child.type === _react.default.Fragment) {
|
|
57
|
+
result.push(...flattenBranches(child.props.children));
|
|
58
|
+
} else {
|
|
59
|
+
result.push(child);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return result;
|
|
63
|
+
};
|
|
64
|
+
exports.flattenBranches = flattenBranches;
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.renderIterableChildren = exports.processArray = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
9
|
+
// Pure helper: apply filter/sort/reverse/limit to an `each` array without
|
|
10
|
+
// mutating the caller's array (Array.prototype.sort/reverse mutate in place,
|
|
11
|
+
// so every step works off a copy). Shared by `Conditional`'s `each` mode,
|
|
12
|
+
// its If/ElseIf/Else branches, and `<For>`.
|
|
13
|
+
const processArray = (array, {
|
|
14
|
+
filter,
|
|
15
|
+
sort,
|
|
16
|
+
reverse,
|
|
17
|
+
limit
|
|
18
|
+
}) => {
|
|
19
|
+
if (!Array.isArray(array)) return [];
|
|
20
|
+
let result = [...array];
|
|
21
|
+
if (typeof filter === 'function') result = result.filter(filter);
|
|
22
|
+
if (typeof sort === 'function') result = result.sort(sort);
|
|
23
|
+
if (reverse) result = result.reverse();
|
|
24
|
+
if (typeof limit === 'number') result = result.slice(0, limit);
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Pure helper: render one React.Fragment per item, guarding the render-prop
|
|
29
|
+
// call so static children (a plain element instead of a function) don't
|
|
30
|
+
// crash when they reach an iteration path.
|
|
31
|
+
exports.processArray = processArray;
|
|
32
|
+
const renderIterableChildren = (items, children, keyExtractor) => items.map((item, index) => /*#__PURE__*/_react.default.createElement(_react.default.Fragment, {
|
|
33
|
+
key: keyExtractor ? keyExtractor(item, index) : index
|
|
34
|
+
}, typeof children === 'function' ? children(item, index, items) : children));
|
|
35
|
+
exports.renderIterableChildren = renderIterableChildren;
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-ternary-be-gone",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A React component designed to simplify conditional rendering and list iteration, providing a more readable and maintainable alternative to ternary operators and verbose conditional logic.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
6
7
|
"files": [
|
|
7
8
|
"dist"
|
|
8
9
|
],
|
|
9
10
|
"scripts": {
|
|
10
11
|
"test": "vitest run",
|
|
11
12
|
"test:watch": "vitest",
|
|
12
|
-
"
|
|
13
|
-
"
|
|
13
|
+
"typecheck": "tsc -p test-types",
|
|
14
|
+
"build": "babel src --out-dir dist --out-file-extension .js && cp src/index.d.ts dist/index.d.ts",
|
|
15
|
+
"prepublishOnly": "npm test && npm run typecheck && npm run build"
|
|
14
16
|
},
|
|
15
17
|
"peerDependencies": {
|
|
16
18
|
"react": ">=16.8.0"
|
|
@@ -43,10 +45,13 @@
|
|
|
43
45
|
"@babel/preset-env": "^8.0.0",
|
|
44
46
|
"@babel/preset-react": "^8.0.0",
|
|
45
47
|
"@testing-library/react": "^16.3.3",
|
|
48
|
+
"@types/react": "^19.3.0",
|
|
49
|
+
"@types/react-dom": "^19.3.0",
|
|
46
50
|
"@vitejs/plugin-react": "^5.0.0",
|
|
47
51
|
"jsdom": "^30.1.1",
|
|
48
52
|
"react": "^19.3.0",
|
|
49
53
|
"react-dom": "^19.3.0",
|
|
54
|
+
"typescript": "^7.0.2",
|
|
50
55
|
"vitest": "^5.0.1"
|
|
51
56
|
}
|
|
52
57
|
}
|