react-ternary-be-gone 0.1.4 → 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Ibrahim Sanduvaç
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
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>Yükleniyor...</p>
173
+ <p>Loading...</p>
90
174
  </If>
91
175
  <ElseIf when={status === 'success'}>
92
- <p>Başarılı!</p>
176
+ <p>Success!</p>
93
177
  </ElseIf>
94
178
  <Else>
95
- <p>Durum bilinmiyor.</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>Kullanıcı yok.</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 seçildi</Case>
131
- <Case when="b">B seçildi</Case>
132
- <Case default>Hiçbiri seçilmedi</Case>
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');
@@ -6,48 +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 _jsxRuntime = require("react/jsx-runtime");
9
+ var _shared = require("./shared");
10
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); }
11
- // Pure helper: apply filter/sort/reverse/limit to an `each` array without
12
- // mutating the caller's array (Array.prototype.sort/reverse mutate in place,
13
- // so every step works off a copy).
14
- const processArray = (array, {
15
- filter,
16
- sort,
17
- reverse,
18
- limit
19
- }) => {
20
- if (!Array.isArray(array)) return [];
21
- let result = [...array];
22
- if (typeof filter === 'function') result = result.filter(filter);
23
- if (typeof sort === 'function') result = result.sort(sort);
24
- if (reverse) result = result.reverse();
25
- if (typeof limit === 'number') result = result.slice(0, limit);
26
- return result;
27
- };
28
-
29
- // Pure helper: render one React.Fragment per item, guarding the render-prop
30
- // call so static children (a plain element instead of a function) don't
31
- // crash when they reach an iteration path.
32
- const renderIterableChildren = (items, children, keyExtractor) => items.map((item, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_react.default.Fragment, {
33
- children: typeof children === 'function' ? children(item, index, items) : children
34
- }, keyExtractor ? keyExtractor(item, index) : index));
35
-
36
- // `React.Children.toArray` flattens arrays produced by `.map()`, but an
37
- // author-written `<>...</>` around a set of branches is itself a single
38
- // Fragment element in the tree - its contents need an explicit unwrap or
39
- // `<If>`/`<ElseIf>`/`<Else>`/`<Case>` inside one are never seen as branches.
40
- const flattenBranches = children => {
41
- const result = [];
42
- _react.default.Children.toArray(children).forEach(child => {
43
- if (child && child.type === _react.default.Fragment) {
44
- result.push(...flattenBranches(child.props.children));
45
- } else {
46
- result.push(child);
47
- }
48
- });
49
- return result;
50
- };
51
11
  const Conditional = props => {
52
12
  const {
53
13
  when,
@@ -168,7 +128,7 @@ const Conditional = props => {
168
128
 
169
129
  // Array processing
170
130
  const processedArray = (0, _react.useMemo)(() => {
171
- const result = processArray(each, {
131
+ const result = (0, _shared.processArray)(each, {
172
132
  filter,
173
133
  sort,
174
134
  reverse,
@@ -201,10 +161,9 @@ const Conditional = props => {
201
161
 
202
162
  // Loading state
203
163
  if (loading) {
204
- return loadingFallback || fallback || /*#__PURE__*/(0, _jsxRuntime.jsx)("div", {
205
- className: "conditional-loading",
206
- children: "Loading..."
207
- });
164
+ return loadingFallback || fallback || /*#__PURE__*/_react.default.createElement("div", {
165
+ className: "conditional-loading"
166
+ }, "Loading...");
208
167
  }
209
168
 
210
169
  // Error state
@@ -212,10 +171,9 @@ const Conditional = props => {
212
171
  if (errorFallback) {
213
172
  return typeof errorFallback === 'function' ? errorFallback(error) : errorFallback;
214
173
  }
215
- return /*#__PURE__*/(0, _jsxRuntime.jsxs)("div", {
216
- className: "conditional-error",
217
- children: ["Error: ", error]
218
- });
174
+ return /*#__PURE__*/_react.default.createElement("div", {
175
+ className: "conditional-error"
176
+ }, "Error: ", error);
219
177
  }
220
178
 
221
179
  // Switch-case logic. Presence, not strict inequality to `undefined`, is
@@ -223,26 +181,22 @@ const Conditional = props => {
223
181
  // while `status` is legitimately undefined before data loads, the same
224
182
  // reasoning as the `each` presence check above.
225
183
  if ('switch' in props) {
226
- 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);
227
185
  // `.find()` stops at the first hit, matching how a real `switch` picks
228
186
  // the first matching branch instead of the last one.
229
187
  const matchedCase = caseChildren.find(child => !child.props.default && child.props.when === switchValue);
230
188
  const defaultCase = caseChildren.find(child => child.props.default);
231
189
  if (matchedCase) {
232
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapper, {
233
- children: matchedCase.props.children
234
- });
190
+ return /*#__PURE__*/_react.default.createElement(Wrapper, null, matchedCase.props.children);
235
191
  }
236
192
  if (defaultCase) {
237
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapper, {
238
- children: defaultCase.props.children
239
- });
193
+ return /*#__PURE__*/_react.default.createElement(Wrapper, null, defaultCase.props.children);
240
194
  }
241
195
  return fallback;
242
196
  }
243
197
 
244
198
  // If-ElseIf-Else logic
245
- 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)));
246
200
  if (branches.length > 0) {
247
201
  let rendered = fallback;
248
202
  for (const branch of branches) {
@@ -263,19 +217,15 @@ const Conditional = props => {
263
217
  const matches = kind === _markers.ELSE || Boolean(branchWhen);
264
218
  if (!matches) continue;
265
219
  if ('each' in branch.props) {
266
- const items = processArray(branchEach, {
220
+ const items = (0, _shared.processArray)(branchEach, {
267
221
  filter: branchFilter,
268
222
  sort: branchSort,
269
223
  reverse: branchReverse,
270
224
  limit: branchLimit
271
225
  });
272
- rendered = items.length === 0 ? branchEmpty || branchFallback || null : /*#__PURE__*/(0, _jsxRuntime.jsx)(BranchWrapper, {
273
- children: renderIterableChildren(items, branchChildren, branchKeyExtractor)
274
- });
226
+ rendered = items.length === 0 ? branchEmpty || branchFallback || null : /*#__PURE__*/_react.default.createElement(BranchWrapper, null, (0, _shared.renderIterableChildren)(items, branchChildren, branchKeyExtractor));
275
227
  } else {
276
- rendered = /*#__PURE__*/(0, _jsxRuntime.jsx)(BranchWrapper, {
277
- children: branchChildren
278
- });
228
+ rendered = /*#__PURE__*/_react.default.createElement(BranchWrapper, null, branchChildren);
279
229
  }
280
230
  break;
281
231
  }
@@ -284,9 +234,7 @@ const Conditional = props => {
284
234
 
285
235
  // Only condition
286
236
  if (hasCondition && !hasIteration) {
287
- return evaluateCondition ? /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapper, {
288
- children: children
289
- }) : fallback;
237
+ return evaluateCondition ? /*#__PURE__*/_react.default.createElement(Wrapper, null, children) : fallback;
290
238
  }
291
239
 
292
240
  // Only iteration
@@ -294,30 +242,23 @@ const Conditional = props => {
294
242
  if (processedArray.length === 0) {
295
243
  return empty || fallback;
296
244
  }
297
- const elements = renderIterableChildren(processedArray, children, keyExtractor);
245
+ const elements = (0, _shared.renderIterableChildren)(processedArray, children, keyExtractor);
298
246
  // React.Fragment rejects unknown props like `className`, so the default
299
247
  // wrapper is swapped for a real host element whenever `animate` needs
300
248
  // somewhere to put the class.
301
249
  const AnimatedWrapper = animate && Wrapper === _react.default.Fragment ? 'div' : Wrapper;
302
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(AnimatedWrapper, {
303
- ...(animate ? {
304
- className: 'conditional-animated'
305
- } : null),
306
- children: elements
307
- });
250
+ return /*#__PURE__*/_react.default.createElement(AnimatedWrapper, animate ? {
251
+ className: 'conditional-animated'
252
+ } : null, elements);
308
253
  }
309
254
 
310
255
  // Both condition and iteration
311
256
  if (hasCondition && hasIteration) {
312
257
  if (!evaluateCondition) return fallback;
313
258
  if (processedArray.length === 0) return empty || fallback;
314
- const elements = renderIterableChildren(processedArray, children, keyExtractor);
315
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapper, {
316
- children: elements
317
- });
259
+ const elements = (0, _shared.renderIterableChildren)(processedArray, children, keyExtractor);
260
+ return /*#__PURE__*/_react.default.createElement(Wrapper, null, elements);
318
261
  }
319
- return /*#__PURE__*/(0, _jsxRuntime.jsx)(Wrapper, {
320
- children: children
321
- });
262
+ return /*#__PURE__*/_react.default.createElement(Wrapper, null, children);
322
263
  };
323
264
  var _default = exports.default = Conditional;
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
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.default = void 0;
7
+ var _markers = require("./markers");
8
+ var _default = exports.default = (0, _markers.createMarker)('Match', _markers.MATCH, 'Switch');
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;
@@ -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
- // Branch markers (`If`, `ElseIf`, `Else`, `Case`) are never rendered by React
8
- // when they are used correctly: `Conditional` reads their props and renders the
9
- // winning branch itself. They exist only so that JSX can express the shape.
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 a
29
- // `Conditional` must not leak the content it was meant to gate.
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 <Conditional>. ` + 'It rendered nothing.');
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
- exports.markerKindOf = markerKindOf;
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.1.4",
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
- "build": "babel src --out-dir dist --out-file-extension .js",
13
- "prepublishOnly": "npm test && npm run build"
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"
@@ -21,8 +23,15 @@
21
23
  },
22
24
  "keywords": [
23
25
  "react",
24
- "no",
25
- "ternary"
26
+ "conditional-rendering",
27
+ "control-flow",
28
+ "jsx",
29
+ "ternary",
30
+ "v-if",
31
+ "ng-if",
32
+ "v-for",
33
+ "switch-case",
34
+ "if-else"
26
35
  ],
27
36
  "author": "Ibrahim Sanduvaç",
28
37
  "license": "MIT",
@@ -36,10 +45,13 @@
36
45
  "@babel/preset-env": "^8.0.0",
37
46
  "@babel/preset-react": "^8.0.0",
38
47
  "@testing-library/react": "^16.3.3",
48
+ "@types/react": "^19.3.0",
49
+ "@types/react-dom": "^19.3.0",
39
50
  "@vitejs/plugin-react": "^5.0.0",
40
51
  "jsdom": "^30.1.1",
41
52
  "react": "^19.3.0",
42
53
  "react-dom": "^19.3.0",
54
+ "typescript": "^7.0.2",
43
55
  "vitest": "^5.0.1"
44
56
  }
45
57
  }