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 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,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
@@ -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.5",
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"
@@ -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
  }