@aweebit/react-essentials 0.10.0 → 0.10.1

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
@@ -5,7 +5,9 @@
5
5
  - [useEventListener()](#useeventlistener)
6
6
  - [useReducerWithDeps()](#usereducerwithdeps)
7
7
  - [useStateWithDeps()](#usestatewithdeps)
8
+ - [contextualize()](#contextualize)
8
9
  - [createSafeContext()](#createsafecontext)
10
+ - [wrapJSX()](#wrapjsx)
9
11
 
10
12
  ### Requirements
11
13
 
@@ -18,7 +20,7 @@
18
20
  const useEventListener: UseEventListener;
19
21
  ```
20
22
 
21
- Defined in: [hooks/useEventListener.ts:135](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L135)
23
+ Defined in: [hooks/useEventListener.ts:135](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L135)
22
24
 
23
25
  Adds `handler` as a listener for the event `eventName` of `target` with the
24
26
  provided `options` applied
@@ -30,7 +32,7 @@ function useEventListener(eventName, handler, options?): void;
30
32
  function useEventListener(target, eventName, handler, options?): void;
31
33
  ```
32
34
 
33
- For the full definition of the hook's type, see [`UseEventListener`](#useeventlistener).
35
+ For the full definition of the hook's type, see [`UseEventListener`](#useeventlistener-1).
34
36
 
35
37
  If `target` is not provided, `window` is used instead.
36
38
 
@@ -55,7 +57,7 @@ useEventListener(buttonRef, 'click', () => console.log('click'));
55
57
 
56
58
  ### See
57
59
 
58
- [`UseEventListener`](#useeventlistener)
60
+ [`UseEventListener`](#useeventlistener-1)
59
61
 
60
62
  ---
61
63
 
@@ -69,7 +71,7 @@ function useReducerWithDeps<S, A>(
69
71
  ): [S, ActionDispatch<A>];
70
72
  ```
71
73
 
72
- Defined in: [hooks/useReducerWithDeps.ts:59](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useReducerWithDeps.ts#L59)
74
+ Defined in: [hooks/useReducerWithDeps.ts:59](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useReducerWithDeps.ts#L59)
73
75
 
74
76
  `useReducer` hook with an additional dependency array `deps` that resets the
75
77
  state to `initialState` when dependencies change
@@ -212,7 +214,7 @@ function useStateWithDeps<S>(
212
214
  ): [S, Dispatch<SetStateAction<S>>];
213
215
  ```
214
216
 
215
- Defined in: [hooks/useStateWithDeps.ts:62](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useStateWithDeps.ts#L62)
217
+ Defined in: [hooks/useStateWithDeps.ts:62](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useStateWithDeps.ts#L62)
216
218
 
217
219
  `useState` hook with an additional dependency array `deps` that resets the
218
220
  state to `initialState` when dependencies change
@@ -336,17 +338,113 @@ Dependencies that reset the state to `initialState`
336
338
 
337
339
  ---
338
340
 
341
+ ## contextualize()
342
+
343
+ ```ts
344
+ function contextualize(jsx): ContextualizePipe;
345
+ ```
346
+
347
+ Defined in: [misc/contextualize.tsx:79](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/misc/contextualize.tsx#L79)
348
+
349
+ An alternative way to provide context values to component trees that avoids
350
+ ever-increasing indentation
351
+
352
+ A context-specific version of the more general [`wrapJSX`](#wrapjsx) function.
353
+
354
+ ### Example
355
+
356
+ ```tsx
357
+ // Before:
358
+ return (
359
+ <CourseIdContext.Provider value={courseId}>
360
+ <DeckIdContext.Provider value={deckId}>
361
+ <FlashcardsContext.Provider value={flashcards}>
362
+ <EventHandlersContext.Provider value={eventHandlers}>
363
+ <Header />
364
+ <Main />
365
+ <Footer />
366
+ </EventHandlersContext.Provider>
367
+ </FlashcardsContext.Provider>
368
+ </DeckIdContext.Provider>
369
+ </CourseIdContext.Provider>
370
+ );
371
+
372
+ // After:
373
+ const jsx = (
374
+ <>
375
+ <Header />
376
+ <Main />
377
+ <Footer />
378
+ </>
379
+ );
380
+
381
+ return contextualize(jsx)
382
+ .with(EventHandlersContext, eventHandlers)
383
+ .with(FlashcardsContext, flashcards)
384
+ .with(DeckIdContext, deckId)
385
+ .with(CourseIdContext, courseId)
386
+ .end();
387
+ ```
388
+
389
+ ### Parameters
390
+
391
+ <table>
392
+ <thead>
393
+ <tr>
394
+ <th>Parameter</th>
395
+ <th>Type</th>
396
+ <th>Description</th>
397
+ </tr>
398
+ </thead>
399
+ <tbody>
400
+ <tr>
401
+ <td>
402
+
403
+ `jsx`
404
+
405
+ </td>
406
+ <td>
407
+
408
+ `Element`
409
+
410
+ </td>
411
+ <td>
412
+
413
+ The JSX to contextualize
414
+
415
+ </td>
416
+ </tr>
417
+ </tbody>
418
+ </table>
419
+
420
+ ### Returns
421
+
422
+ [`ContextualizePipe`](#contextualizepipe)
423
+
424
+ An object with the following properties:
425
+
426
+ - `with`: a function that accepts a context `Context` and a value `value` for
427
+ it as arguments and returns
428
+ `contextualize(<Context.Provider value={value}>{jsx}</Context.Provider>)`
429
+ - `end`: a function that returns `jsx`
430
+
431
+ ### See
432
+
433
+ [`ContextualizePipe`](#contextualizepipe)
434
+
435
+ ---
436
+
339
437
  ## createSafeContext()
340
438
 
341
439
  ```ts
342
- function createSafeContext<T>(): <DisplayName>(
343
- displayName,
344
- ) => { [K in `${string}Context`]: Context<T> } & {
440
+ function createSafeContext<T>(): <DisplayName>(displayName) => {
441
+ [K in `${string}Context`]: Context<T>;
442
+ } & {
345
443
  [K in `use${string}`]: () => T;
346
444
  };
347
445
  ```
348
446
 
349
- Defined in: [misc/createSafeContext.ts:62](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/misc/createSafeContext.ts#L62)
447
+ Defined in: [misc/createSafeContext.ts:62](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/misc/createSafeContext.ts#L62)
350
448
 
351
449
  For a given type `T`, returns a function that produces both a context of that
352
450
  type and a hook that returns the current context value if one was provided,
@@ -366,7 +464,7 @@ enum Direction {
366
464
  Right,
367
465
  }
368
466
 
369
- // Before
467
+ // Before:
370
468
  const DirectionContext = createContext<Direction | undefined>(undefined);
371
469
  DirectionContext.displayName = 'DirectionContext';
372
470
 
@@ -383,7 +481,7 @@ const useDirection = () => {
383
481
  return direction;
384
482
  };
385
483
 
386
- // After
484
+ // After:
387
485
  const { DirectionContext, useDirection } =
388
486
  createSafeContext<Direction>()('Direction'); // That's it :)
389
487
 
@@ -485,6 +583,100 @@ A function that accepts a single string argument `displayName` (e.g.
485
583
 
486
584
  ---
487
585
 
586
+ ## wrapJSX()
587
+
588
+ ```ts
589
+ function wrapJSX(jsx): JSXWrapPipe;
590
+ ```
591
+
592
+ Defined in: [misc/wrapJSX.tsx:93](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/misc/wrapJSX.tsx#L93)
593
+
594
+ An alternative way to compose JSX that avoids ever-increasing indentation
595
+
596
+ A more general version of the context-specific [`contextualize`](#contextualize)
597
+ function.
598
+
599
+ ### Example
600
+
601
+ ```tsx
602
+ // Before:
603
+ createRoot(document.getElementById('root')!).render(
604
+ <StrictMode>
605
+ <I18nextProvider i18n={i18n}>
606
+ <QueryClientProvider client={queryClient}>
607
+ <NuqsAdapter>
608
+ <ThemeProvider theme={theme}>
609
+ <ToasterProvider>
610
+ <App />
611
+ </ToasterProvider>
612
+ </ThemeProvider>
613
+ </NuqsAdapter>
614
+ </QueryClientProvider>
615
+ </I18nextProvider>
616
+ </StrictMode>,
617
+ );
618
+
619
+ // After:
620
+ createRoot(document.getElementById('root')!).render(
621
+ wrapJSX(<App />)
622
+ .with(ToasterProvider)
623
+ .with(ThemeProvider, { theme })
624
+ .with(NuqsAdapter)
625
+ .with(QueryClientProvider, { client: queryClient })
626
+ .with(I18nextProvider, { i18n })
627
+ .with(StrictMode)
628
+ .end(),
629
+ );
630
+ ```
631
+
632
+ ### Parameters
633
+
634
+ <table>
635
+ <thead>
636
+ <tr>
637
+ <th>Parameter</th>
638
+ <th>Type</th>
639
+ <th>Description</th>
640
+ </tr>
641
+ </thead>
642
+ <tbody>
643
+ <tr>
644
+ <td>
645
+
646
+ `jsx`
647
+
648
+ </td>
649
+ <td>
650
+
651
+ `Element`
652
+
653
+ </td>
654
+ <td>
655
+
656
+ The JSX to wrap
657
+
658
+ </td>
659
+ </tr>
660
+ </tbody>
661
+ </table>
662
+
663
+ ### Returns
664
+
665
+ [`JSXWrapPipe`](#jsxwrappipe)
666
+
667
+ An object with the following properties:
668
+
669
+ - `with`: a function that accepts a component `Component` and props `props`
670
+ for it as arguments and returns
671
+ `wrapJSX(<Component {...props}>{jsx}</Component>)`
672
+ - `end`: a function that returns `jsx`
673
+
674
+ ### See
675
+
676
+ [`JSXWrapPipe`](#jsxwrappipe)
677
+
678
+ ---
679
+
488
680
  ## UseEventListener
489
681
 
490
682
  ```ts
@@ -493,13 +685,13 @@ type UseEventListener = UseEventListenerWithImplicitWindowTarget &
493
685
  UseEventListenerWithAnyExplicitTarget;
494
686
  ```
495
687
 
496
- Defined in: [hooks/useEventListener.ts:12](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L12)
688
+ Defined in: [hooks/useEventListener.ts:12](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L12)
497
689
 
498
- The type of [`useEventListener`](#useeventlistener-1)
690
+ The type of [`useEventListener`](#useeventlistener)
499
691
 
500
692
  ### See
501
693
 
502
- [`useEventListener`](#useeventlistener-1),
694
+ [`useEventListener`](#useeventlistener),
503
695
  [`UseEventListenerWithImplicitWindowTarget`](#useeventlistenerwithimplicitwindowtarget),
504
696
  [`UseEventListenerWithExplicitGlobalTarget`](#useeventlistenerwithexplicitglobaltarget),
505
697
  [`UseEventListenerWithAnyExplicitTarget`](#useeventlistenerwithanyexplicittarget)
@@ -512,7 +704,7 @@ The type of [`useEventListener`](#useeventlistener-1)
512
704
  type UseEventListenerWithImplicitWindowTarget = <K>(...args) => void;
513
705
  ```
514
706
 
515
- Defined in: [hooks/useEventListener.ts:21](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L21)
707
+ Defined in: [hooks/useEventListener.ts:21](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L21)
516
708
 
517
709
  ### Type Parameters
518
710
 
@@ -564,7 +756,7 @@ Defined in: [hooks/useEventListener.ts:21](https://github.com/aweebit/react-esse
564
756
 
565
757
  ### See
566
758
 
567
- [`useEventListener`](#useeventlistener-1),
759
+ [`useEventListener`](#useeventlistener),
568
760
  [`UseEventListenerWithImplicitWindowTargetArgs`](#useeventlistenerwithimplicitwindowtargetargs)
569
761
 
570
762
  ---
@@ -580,11 +772,11 @@ type UseEventListenerWithExplicitGlobalTarget =
580
772
  UseEventListenerWithExplicitTarget<MathMLElement, MathMLElementEventMap>;
581
773
  ```
582
774
 
583
- Defined in: [hooks/useEventListener.ts:32](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L32)
775
+ Defined in: [hooks/useEventListener.ts:32](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L32)
584
776
 
585
777
  ### See
586
778
 
587
- [`useEventListener`](#useeventlistener-1),
779
+ [`useEventListener`](#useeventlistener),
588
780
  [`UseEventListenerWithExplicitTarget`](#useeventlistenerwithexplicittarget)
589
781
 
590
782
  ---
@@ -597,7 +789,7 @@ type UseEventListenerWithExplicitTarget<Target, EventMap> = <T, K>(
597
789
  ) => void;
598
790
  ```
599
791
 
600
- Defined in: [hooks/useEventListener.ts:44](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L44)
792
+ Defined in: [hooks/useEventListener.ts:44](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L44)
601
793
 
602
794
  ### Type Parameters
603
795
 
@@ -682,7 +874,7 @@ Defined in: [hooks/useEventListener.ts:44](https://github.com/aweebit/react-esse
682
874
 
683
875
  ### See
684
876
 
685
- [`useEventListener`](#useeventlistener-1),
877
+ [`useEventListener`](#useeventlistener),
686
878
  [`UseEventListenerWithExplicitTargetArgs`](#useeventlistenerwithexplicittargetargs)
687
879
 
688
880
  ---
@@ -696,11 +888,11 @@ type UseEventListenerWithAnyExplicitTarget = UseEventListenerWithExplicitTarget<
696
888
  >;
697
889
  ```
698
890
 
699
- Defined in: [hooks/useEventListener.ts:56](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L56)
891
+ Defined in: [hooks/useEventListener.ts:56](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L56)
700
892
 
701
893
  ### See
702
894
 
703
- [`useEventListener`](#useeventlistener-1),
895
+ [`useEventListener`](#useeventlistener),
704
896
  [`UseEventListenerWithExplicitTarget`](#useeventlistenerwithexplicittarget)
705
897
 
706
898
  ---
@@ -717,7 +909,7 @@ type UseEventListenerWithImplicitWindowTargetArgs<K> =
717
909
  : never;
718
910
  ```
719
911
 
720
- Defined in: [hooks/useEventListener.ts:64](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L64)
912
+ Defined in: [hooks/useEventListener.ts:64](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L64)
721
913
 
722
914
  ### Type Parameters
723
915
 
@@ -740,7 +932,7 @@ Defined in: [hooks/useEventListener.ts:64](https://github.com/aweebit/react-esse
740
932
 
741
933
  ### See
742
934
 
743
- [`useEventListener`](#useeventlistener-1),
935
+ [`useEventListener`](#useeventlistener),
744
936
  [`UseEventListenerWithExplicitTargetArgs`](#useeventlistenerwithexplicittargetargs)
745
937
 
746
938
  ---
@@ -762,7 +954,7 @@ type UseEventListenerWithExplicitTargetArgs<EventMap, T, K> = [
762
954
  ];
763
955
  ```
764
956
 
765
- Defined in: [hooks/useEventListener.ts:78](https://github.com/aweebit/react-essentials/blob/v0.10.0/src/hooks/useEventListener.ts#L78)
957
+ Defined in: [hooks/useEventListener.ts:78](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/hooks/useEventListener.ts#L78)
766
958
 
767
959
  ### Type Parameters
768
960
 
@@ -799,4 +991,256 @@ Defined in: [hooks/useEventListener.ts:78](https://github.com/aweebit/react-esse
799
991
 
800
992
  ### See
801
993
 
802
- [`useEventListener`](#useeventlistener-1)
994
+ [`useEventListener`](#useeventlistener)
995
+
996
+ ---
997
+
998
+ ## ContextualizePipe
999
+
1000
+ ```ts
1001
+ type ContextualizePipe = {
1002
+ with: ContextualizeWith;
1003
+ end: () => React.JSX.Element;
1004
+ };
1005
+ ```
1006
+
1007
+ Defined in: [misc/contextualize.tsx:13](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/misc/contextualize.tsx#L13)
1008
+
1009
+ The return type of [`contextualize`](#contextualize)
1010
+
1011
+ ### See
1012
+
1013
+ [`contextualize`](#contextualize),
1014
+ [`ContextualizeWith`](#contextualizewith)
1015
+
1016
+ ### Properties
1017
+
1018
+ <table>
1019
+ <thead>
1020
+ <tr>
1021
+ <th>Property</th>
1022
+ <th>Type</th>
1023
+ </tr>
1024
+ </thead>
1025
+ <tbody>
1026
+ <tr>
1027
+ <td>
1028
+
1029
+ <a id="with"></a> `with`
1030
+
1031
+ </td>
1032
+ <td>
1033
+
1034
+ [`ContextualizeWith`](#contextualizewith)
1035
+
1036
+ </td>
1037
+ </tr>
1038
+ <tr>
1039
+ <td>
1040
+
1041
+ <a id="end"></a> `end`
1042
+
1043
+ </td>
1044
+ <td>
1045
+
1046
+ () => `React.JSX.Element`
1047
+
1048
+ </td>
1049
+ </tr>
1050
+ </tbody>
1051
+ </table>
1052
+
1053
+ ---
1054
+
1055
+ ## ContextualizeWith()
1056
+
1057
+ ```ts
1058
+ type ContextualizeWith = <T>(Context, value) => ContextualizePipe;
1059
+ ```
1060
+
1061
+ Defined in: [misc/contextualize.tsx:23](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/misc/contextualize.tsx#L23)
1062
+
1063
+ ### Type Parameters
1064
+
1065
+ <table>
1066
+ <thead>
1067
+ <tr>
1068
+ <th>Type Parameter</th>
1069
+ </tr>
1070
+ </thead>
1071
+ <tbody>
1072
+ <tr>
1073
+ <td>
1074
+
1075
+ `T`
1076
+
1077
+ </td>
1078
+ </tr>
1079
+ </tbody>
1080
+ </table>
1081
+
1082
+ ### Parameters
1083
+
1084
+ <table>
1085
+ <thead>
1086
+ <tr>
1087
+ <th>Parameter</th>
1088
+ <th>Type</th>
1089
+ </tr>
1090
+ </thead>
1091
+ <tbody>
1092
+ <tr>
1093
+ <td>
1094
+
1095
+ `Context`
1096
+
1097
+ </td>
1098
+ <td>
1099
+
1100
+ `Context`\<`T`\>
1101
+
1102
+ </td>
1103
+ </tr>
1104
+ <tr>
1105
+ <td>
1106
+
1107
+ `value`
1108
+
1109
+ </td>
1110
+ <td>
1111
+
1112
+ `NoInfer`\<`T`\>
1113
+
1114
+ </td>
1115
+ </tr>
1116
+ </tbody>
1117
+ </table>
1118
+
1119
+ ### Returns
1120
+
1121
+ [`ContextualizePipe`](#contextualizepipe)
1122
+
1123
+ ### See
1124
+
1125
+ [`contextualize`](#contextualize),
1126
+ [`ContextualizePipe`](#contextualizepipe)
1127
+
1128
+ ---
1129
+
1130
+ ## JSXWrapPipe
1131
+
1132
+ ```ts
1133
+ type JSXWrapPipe = {
1134
+ with: WrapJSXWith;
1135
+ end: () => React.JSX.Element;
1136
+ };
1137
+ ```
1138
+
1139
+ Defined in: [misc/wrapJSX.tsx:17](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/misc/wrapJSX.tsx#L17)
1140
+
1141
+ The return type of [`wrapJSX`](#wrapjsx)
1142
+
1143
+ ### See
1144
+
1145
+ [`wrapJSX`](#wrapjsx),
1146
+ [`WrapJSXWith`](#wrapjsxwith)
1147
+
1148
+ ### Properties
1149
+
1150
+ <table>
1151
+ <thead>
1152
+ <tr>
1153
+ <th>Property</th>
1154
+ <th>Type</th>
1155
+ </tr>
1156
+ </thead>
1157
+ <tbody>
1158
+ <tr>
1159
+ <td>
1160
+
1161
+ <a id="with-1"></a> `with`
1162
+
1163
+ </td>
1164
+ <td>
1165
+
1166
+ [`WrapJSXWith`](#wrapjsxwith)
1167
+
1168
+ </td>
1169
+ </tr>
1170
+ <tr>
1171
+ <td>
1172
+
1173
+ <a id="end-1"></a> `end`
1174
+
1175
+ </td>
1176
+ <td>
1177
+
1178
+ () => `React.JSX.Element`
1179
+
1180
+ </td>
1181
+ </tr>
1182
+ </tbody>
1183
+ </table>
1184
+
1185
+ ---
1186
+
1187
+ ## WrapJSXWith()
1188
+
1189
+ ```ts
1190
+ type WrapJSXWith = <C>(...args) => JSXWrapPipe;
1191
+ ```
1192
+
1193
+ Defined in: [misc/wrapJSX.tsx:27](https://github.com/aweebit/react-essentials/blob/v0.10.1/src/misc/wrapJSX.tsx#L27)
1194
+
1195
+ ### Type Parameters
1196
+
1197
+ <table>
1198
+ <thead>
1199
+ <tr>
1200
+ <th>Type Parameter</th>
1201
+ </tr>
1202
+ </thead>
1203
+ <tbody>
1204
+ <tr>
1205
+ <td>
1206
+
1207
+ `C` _extends_ keyof `JSX.IntrinsicElements` \| `JSXElementConstructor`\<`any`\>
1208
+
1209
+ </td>
1210
+ </tr>
1211
+ </tbody>
1212
+ </table>
1213
+
1214
+ ### Parameters
1215
+
1216
+ <table>
1217
+ <thead>
1218
+ <tr>
1219
+ <th>Parameter</th>
1220
+ <th>Type</th>
1221
+ </tr>
1222
+ </thead>
1223
+ <tbody>
1224
+ <tr>
1225
+ <td>
1226
+
1227
+ ...`args`
1228
+
1229
+ </td>
1230
+ <td>
1231
+
1232
+ \[`C`, `...(Record<never, unknown> extends Omit<ComponentProps<C>, "children"> ? [props?: React.JSX.IntrinsicAttributes & Omit<ComponentProps<C>, "children">] : [props: React.JSX.IntrinsicAttributes & Omit<ComponentProps<C>, "children">])`\]
1233
+
1234
+ </td>
1235
+ </tr>
1236
+ </tbody>
1237
+ </table>
1238
+
1239
+ ### Returns
1240
+
1241
+ [`JSXWrapPipe`](#jsxwrappipe)
1242
+
1243
+ ### See
1244
+
1245
+ [`wrapJSX`](#wrapjsx),
1246
+ [`JSXWrapPipe`](#jsxwrappipe)
@@ -0,0 +1,71 @@
1
+ import type { Context, default as React } from 'react';
2
+ /**
3
+ * The return type of {@linkcode contextualize}
4
+ *
5
+ * @see
6
+ * {@linkcode contextualize},
7
+ * {@linkcode ContextualizeWith}
8
+ */
9
+ export type ContextualizePipe = {
10
+ with: ContextualizeWith;
11
+ end: () => React.JSX.Element;
12
+ };
13
+ /**
14
+ * @see
15
+ * {@linkcode contextualize},
16
+ * {@linkcode ContextualizePipe}
17
+ */
18
+ export type ContextualizeWith = <T>(Context: Context<T>, value: NoInfer<T>) => ContextualizePipe;
19
+ /**
20
+ * An alternative way to provide context values to component trees that avoids
21
+ * ever-increasing indentation
22
+ *
23
+ * A context-specific version of the more general {@linkcode wrapJSX} function.
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * // Before:
28
+ * return (
29
+ * <CourseIdContext.Provider value={courseId}>
30
+ * <DeckIdContext.Provider value={deckId}>
31
+ * <FlashcardsContext.Provider value={flashcards}>
32
+ * <EventHandlersContext.Provider value={eventHandlers}>
33
+ * <Header />
34
+ * <Main />
35
+ * <Footer />
36
+ * </EventHandlersContext.Provider>
37
+ * </FlashcardsContext.Provider>
38
+ * </DeckIdContext.Provider>
39
+ * </CourseIdContext.Provider>
40
+ * );
41
+ *
42
+ * // After:
43
+ * const jsx = (
44
+ * <>
45
+ * <Header />
46
+ * <Main />
47
+ * <Footer />
48
+ * </>
49
+ * );
50
+ *
51
+ * return contextualize(jsx)
52
+ * .with(EventHandlersContext, eventHandlers)
53
+ * .with(FlashcardsContext, flashcards)
54
+ * .with(DeckIdContext, deckId)
55
+ * .with(CourseIdContext, courseId)
56
+ * .end();
57
+ * ```
58
+ *
59
+ * @param jsx The JSX to contextualize
60
+ *
61
+ * @returns An object with the following properties:
62
+ * - `with`: a function that accepts a context `Context` and a value `value` for
63
+ * it as arguments and returns
64
+ * `contextualize(<Context.Provider value={value}>{jsx}</Context.Provider>)`
65
+ * - `end`: a function that returns `jsx`
66
+ *
67
+ * @see
68
+ * {@linkcode ContextualizePipe}
69
+ */
70
+ export declare function contextualize(jsx: React.JSX.Element): ContextualizePipe;
71
+ //# sourceMappingURL=contextualize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextualize.d.ts","sourceRoot":"","sources":["../../src/misc/contextualize.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC;AAKvD;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,iBAAiB,CAAC;IACxB,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;CAC9B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,EAChC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EACnB,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,KACd,iBAAiB,CAAC;AAEvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,iBAAiB,CAWvE"}
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * An alternative way to provide context values to component trees that avoids
4
+ * ever-increasing indentation
5
+ *
6
+ * A context-specific version of the more general {@linkcode wrapJSX} function.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // Before:
11
+ * return (
12
+ * <CourseIdContext.Provider value={courseId}>
13
+ * <DeckIdContext.Provider value={deckId}>
14
+ * <FlashcardsContext.Provider value={flashcards}>
15
+ * <EventHandlersContext.Provider value={eventHandlers}>
16
+ * <Header />
17
+ * <Main />
18
+ * <Footer />
19
+ * </EventHandlersContext.Provider>
20
+ * </FlashcardsContext.Provider>
21
+ * </DeckIdContext.Provider>
22
+ * </CourseIdContext.Provider>
23
+ * );
24
+ *
25
+ * // After:
26
+ * const jsx = (
27
+ * <>
28
+ * <Header />
29
+ * <Main />
30
+ * <Footer />
31
+ * </>
32
+ * );
33
+ *
34
+ * return contextualize(jsx)
35
+ * .with(EventHandlersContext, eventHandlers)
36
+ * .with(FlashcardsContext, flashcards)
37
+ * .with(DeckIdContext, deckId)
38
+ * .with(CourseIdContext, courseId)
39
+ * .end();
40
+ * ```
41
+ *
42
+ * @param jsx The JSX to contextualize
43
+ *
44
+ * @returns An object with the following properties:
45
+ * - `with`: a function that accepts a context `Context` and a value `value` for
46
+ * it as arguments and returns
47
+ * `contextualize(<Context.Provider value={value}>{jsx}</Context.Provider>)`
48
+ * - `end`: a function that returns `jsx`
49
+ *
50
+ * @see
51
+ * {@linkcode ContextualizePipe}
52
+ */
53
+ export function contextualize(jsx) {
54
+ return {
55
+ with(Context, value) {
56
+ return contextualize(_jsx(Context.Provider, { value: value, children: jsx }));
57
+ },
58
+ end() {
59
+ return jsx;
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=contextualize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextualize.js","sourceRoot":"","sources":["../../src/misc/contextualize.tsx"],"names":[],"mappings":";AA2BA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,UAAU,aAAa,CAAC,GAAsB;IAClD,OAAO;QACL,IAAI,CAAI,OAAmB,EAAE,KAAQ;YACnC,OAAO,aAAa,CAClB,KAAC,OAAO,CAAC,QAAQ,IAAC,KAAK,EAAE,KAAK,YAAG,GAAG,GAAoB,CACzD,CAAC;QACJ,CAAC;QACD,GAAG;YACD,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -18,7 +18,7 @@ import type { ArgumentFallback } from '../utils.js';
18
18
  * Right,
19
19
  * }
20
20
  *
21
- * // Before
21
+ * // Before:
22
22
  * const DirectionContext = createContext<Direction | undefined>(undefined);
23
23
  * DirectionContext.displayName = 'DirectionContext';
24
24
  *
@@ -35,7 +35,7 @@ import type { ArgumentFallback } from '../utils.js';
35
35
  * return direction;
36
36
  * };
37
37
  *
38
- * // After
38
+ * // After:
39
39
  * const { DirectionContext, useDirection } =
40
40
  * createSafeContext<Direction>()('Direction'); // That's it :)
41
41
  *
@@ -18,7 +18,7 @@ const moValueSymbol = Symbol('noValue');
18
18
  * Right,
19
19
  * }
20
20
  *
21
- * // Before
21
+ * // Before:
22
22
  * const DirectionContext = createContext<Direction | undefined>(undefined);
23
23
  * DirectionContext.displayName = 'DirectionContext';
24
24
  *
@@ -35,7 +35,7 @@ const moValueSymbol = Symbol('noValue');
35
35
  * return direction;
36
36
  * };
37
37
  *
38
- * // After
38
+ * // After:
39
39
  * const { DirectionContext, useDirection } =
40
40
  * createSafeContext<Direction>()('Direction'); // That's it :)
41
41
  *
@@ -1,2 +1,4 @@
1
+ export * from './contextualize.js';
1
2
  export * from './createSafeContext.js';
3
+ export * from './wrapJSX.js';
2
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/misc/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/misc/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC"}
@@ -1,2 +1,4 @@
1
+ export * from './contextualize.js';
1
2
  export * from './createSafeContext.js';
3
+ export * from './wrapJSX.js';
2
4
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/misc/index.ts"],"names":[],"mappings":"AAAA,cAAc,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/misc/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,wBAAwB,CAAC;AACvC,cAAc,cAAc,CAAC"}
@@ -0,0 +1,76 @@
1
+ import type { ComponentProps, JSXElementConstructor, default as React } from 'react';
2
+ /**
3
+ * The return type of {@linkcode wrapJSX}
4
+ *
5
+ * @see
6
+ * {@linkcode wrapJSX},
7
+ * {@linkcode WrapJSXWith}
8
+ */
9
+ export type JSXWrapPipe = {
10
+ with: WrapJSXWith;
11
+ end: () => React.JSX.Element;
12
+ };
13
+ /**
14
+ * @see
15
+ * {@linkcode wrapJSX},
16
+ * {@linkcode JSXWrapPipe}
17
+ */
18
+ export type WrapJSXWith = <C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>>(...args: [
19
+ Component: C,
20
+ ...(Record<never, unknown> extends Omit<ComponentProps<C>, 'children'> ? [
21
+ props?: React.JSX.IntrinsicAttributes & Omit<ComponentProps<C>, 'children'>
22
+ ] : [
23
+ props: React.JSX.IntrinsicAttributes & Omit<ComponentProps<C>, 'children'>
24
+ ])
25
+ ]) => JSXWrapPipe;
26
+ /**
27
+ * An alternative way to compose JSX that avoids ever-increasing indentation
28
+ *
29
+ * A more general version of the context-specific {@linkcode contextualize}
30
+ * function.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // Before:
35
+ * createRoot(document.getElementById('root')!).render(
36
+ * <StrictMode>
37
+ * <I18nextProvider i18n={i18n}>
38
+ * <QueryClientProvider client={queryClient}>
39
+ * <NuqsAdapter>
40
+ * <ThemeProvider theme={theme}>
41
+ * <ToasterProvider>
42
+ * <App />
43
+ * </ToasterProvider>
44
+ * </ThemeProvider>
45
+ * </NuqsAdapter>
46
+ * </QueryClientProvider>
47
+ * </I18nextProvider>
48
+ * </StrictMode>,
49
+ * );
50
+ *
51
+ * // After:
52
+ * createRoot(document.getElementById('root')!).render(
53
+ * wrapJSX(<App />)
54
+ * .with(ToasterProvider)
55
+ * .with(ThemeProvider, { theme })
56
+ * .with(NuqsAdapter)
57
+ * .with(QueryClientProvider, { client: queryClient })
58
+ * .with(I18nextProvider, { i18n })
59
+ * .with(StrictMode)
60
+ * .end(),
61
+ * );
62
+ * ```
63
+ *
64
+ * @param jsx The JSX to wrap
65
+ *
66
+ * @returns An object with the following properties:
67
+ * - `with`: a function that accepts a component `Component` and props `props`
68
+ * for it as arguments and returns
69
+ * `wrapJSX(<Component {...props}>{jsx}</Component>)`
70
+ * - `end`: a function that returns `jsx`
71
+ *
72
+ * @see
73
+ * {@linkcode JSXWrapPipe}
74
+ */
75
+ export declare function wrapJSX(jsx: React.JSX.Element): JSXWrapPipe;
76
+ //# sourceMappingURL=wrapJSX.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapJSX.d.ts","sourceRoot":"","sources":["../../src/misc/wrapJSX.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,OAAO,IAAI,KAAK,EACjB,MAAM,OAAO,CAAC;AAKf;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG;IACxB,IAAI,EAAE,WAAW,CAAC;IAClB,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;CAC9B,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAErB,CAAC,CAAC,SAAS,MAAM,GAAG,CAAC,iBAAiB,GAAG,qBAAqB,CAAC,GAAG,CAAC,EACjE,GAAG,IAAI,EAAE;IACP,SAAS,EAAE,CAAC;IACZ,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC,GAClE;QACE,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,mBAAmB,GACnC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC;KACtC,GACD;QACE,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,mBAAmB,GAClC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,UAAU,CAAC;KACtC,CAAC;CACP,KACE,WAAW,CAAC;AAEnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,GAAG,WAAW,CAc3D"}
@@ -0,0 +1,61 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * An alternative way to compose JSX that avoids ever-increasing indentation
4
+ *
5
+ * A more general version of the context-specific {@linkcode contextualize}
6
+ * function.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * // Before:
11
+ * createRoot(document.getElementById('root')!).render(
12
+ * <StrictMode>
13
+ * <I18nextProvider i18n={i18n}>
14
+ * <QueryClientProvider client={queryClient}>
15
+ * <NuqsAdapter>
16
+ * <ThemeProvider theme={theme}>
17
+ * <ToasterProvider>
18
+ * <App />
19
+ * </ToasterProvider>
20
+ * </ThemeProvider>
21
+ * </NuqsAdapter>
22
+ * </QueryClientProvider>
23
+ * </I18nextProvider>
24
+ * </StrictMode>,
25
+ * );
26
+ *
27
+ * // After:
28
+ * createRoot(document.getElementById('root')!).render(
29
+ * wrapJSX(<App />)
30
+ * .with(ToasterProvider)
31
+ * .with(ThemeProvider, { theme })
32
+ * .with(NuqsAdapter)
33
+ * .with(QueryClientProvider, { client: queryClient })
34
+ * .with(I18nextProvider, { i18n })
35
+ * .with(StrictMode)
36
+ * .end(),
37
+ * );
38
+ * ```
39
+ *
40
+ * @param jsx The JSX to wrap
41
+ *
42
+ * @returns An object with the following properties:
43
+ * - `with`: a function that accepts a component `Component` and props `props`
44
+ * for it as arguments and returns
45
+ * `wrapJSX(<Component {...props}>{jsx}</Component>)`
46
+ * - `end`: a function that returns `jsx`
47
+ *
48
+ * @see
49
+ * {@linkcode JSXWrapPipe}
50
+ */
51
+ export function wrapJSX(jsx) {
52
+ return {
53
+ with(Component, props = {}) {
54
+ return wrapJSX(_jsx(Component, { ...props, children: jsx }));
55
+ },
56
+ end() {
57
+ return jsx;
58
+ },
59
+ };
60
+ }
61
+ //# sourceMappingURL=wrapJSX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wrapJSX.js","sourceRoot":"","sources":["../../src/misc/wrapJSX.tsx"],"names":[],"mappings":";AA2CA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,MAAM,UAAU,OAAO,CAAC,GAAsB;IAC5C,OAAO;QACL,IAAI,CACF,SAEiC,EACjC,QAAgB,EAAE;YAElB,OAAO,OAAO,CAAC,KAAC,SAAS,OAAK,KAAK,YAAG,GAAG,GAAa,CAAC,CAAC;QAC1D,CAAC;QACD,GAAG;YACD,OAAO,GAAG,CAAC;QACb,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aweebit/react-essentials",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "type": "module",
5
5
  "repository": "github:aweebit/react-essentials",
6
6
  "main": "dist/index.js",
@@ -10,7 +10,7 @@
10
10
  "prettier": "npm run prettier:base -- .",
11
11
  "lint": "eslint --max-warnings=0",
12
12
  "build": "rimraf dist && tsc -b -f",
13
- "doc": "rimraf README.md && typedoc",
13
+ "doc": "rimraf README.md && typedoc && node scripts/fixReadmeLinks.js && npm run prettier:base -- README.md",
14
14
  "prepack": "npm run build && npm run doc",
15
15
  "prepare": "husky"
16
16
  },
@@ -29,6 +29,7 @@
29
29
  },
30
30
  "devDependencies": {
31
31
  "@eslint/js": "^9.36.0",
32
+ "@types/node": "^22.18.6",
32
33
  "@types/react": "^18.3.24",
33
34
  "eslint": "^9.36.0",
34
35
  "eslint-config-prettier": "^10.1.8",
@@ -0,0 +1,23 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+
4
+ const rootDir = join(import.meta.dirname, '..');
5
+
6
+ const headerPath = join(rootDir, 'README-header.md');
7
+ const readmePath = join(rootDir, 'README.md');
8
+
9
+ const header = await readFile(headerPath, 'utf8');
10
+ const readme = await readFile(readmePath, 'utf8');
11
+
12
+ const newReadme =
13
+ header +
14
+ readme
15
+ .slice(header.length)
16
+ .replaceAll(
17
+ /\(#useeventlistener((?:-1)?)\)/g,
18
+ (match, /** @type {string} */ suffix) => {
19
+ return `(#useeventlistener${suffix ? '' : '-1'})`;
20
+ },
21
+ );
22
+
23
+ await writeFile(readmePath, newReadme);
@@ -0,0 +1,90 @@
1
+ import type { Context, default as React } from 'react';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4
+ import type { wrapJSX } from './wrapJSX.js';
5
+
6
+ /**
7
+ * The return type of {@linkcode contextualize}
8
+ *
9
+ * @see
10
+ * {@linkcode contextualize},
11
+ * {@linkcode ContextualizeWith}
12
+ */
13
+ export type ContextualizePipe = {
14
+ with: ContextualizeWith;
15
+ end: () => React.JSX.Element;
16
+ };
17
+
18
+ /**
19
+ * @see
20
+ * {@linkcode contextualize},
21
+ * {@linkcode ContextualizePipe}
22
+ */
23
+ export type ContextualizeWith = <T>(
24
+ Context: Context<T>,
25
+ value: NoInfer<T>,
26
+ ) => ContextualizePipe;
27
+
28
+ /**
29
+ * An alternative way to provide context values to component trees that avoids
30
+ * ever-increasing indentation
31
+ *
32
+ * A context-specific version of the more general {@linkcode wrapJSX} function.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * // Before:
37
+ * return (
38
+ * <CourseIdContext.Provider value={courseId}>
39
+ * <DeckIdContext.Provider value={deckId}>
40
+ * <FlashcardsContext.Provider value={flashcards}>
41
+ * <EventHandlersContext.Provider value={eventHandlers}>
42
+ * <Header />
43
+ * <Main />
44
+ * <Footer />
45
+ * </EventHandlersContext.Provider>
46
+ * </FlashcardsContext.Provider>
47
+ * </DeckIdContext.Provider>
48
+ * </CourseIdContext.Provider>
49
+ * );
50
+ *
51
+ * // After:
52
+ * const jsx = (
53
+ * <>
54
+ * <Header />
55
+ * <Main />
56
+ * <Footer />
57
+ * </>
58
+ * );
59
+ *
60
+ * return contextualize(jsx)
61
+ * .with(EventHandlersContext, eventHandlers)
62
+ * .with(FlashcardsContext, flashcards)
63
+ * .with(DeckIdContext, deckId)
64
+ * .with(CourseIdContext, courseId)
65
+ * .end();
66
+ * ```
67
+ *
68
+ * @param jsx The JSX to contextualize
69
+ *
70
+ * @returns An object with the following properties:
71
+ * - `with`: a function that accepts a context `Context` and a value `value` for
72
+ * it as arguments and returns
73
+ * `contextualize(<Context.Provider value={value}>{jsx}</Context.Provider>)`
74
+ * - `end`: a function that returns `jsx`
75
+ *
76
+ * @see
77
+ * {@linkcode ContextualizePipe}
78
+ */
79
+ export function contextualize(jsx: React.JSX.Element): ContextualizePipe {
80
+ return {
81
+ with<T>(Context: Context<T>, value: T) {
82
+ return contextualize(
83
+ <Context.Provider value={value}>{jsx}</Context.Provider>,
84
+ );
85
+ },
86
+ end() {
87
+ return jsx;
88
+ },
89
+ };
90
+ }
@@ -21,7 +21,7 @@ const moValueSymbol = Symbol('noValue');
21
21
  * Right,
22
22
  * }
23
23
  *
24
- * // Before
24
+ * // Before:
25
25
  * const DirectionContext = createContext<Direction | undefined>(undefined);
26
26
  * DirectionContext.displayName = 'DirectionContext';
27
27
  *
@@ -38,7 +38,7 @@ const moValueSymbol = Symbol('noValue');
38
38
  * return direction;
39
39
  * };
40
40
  *
41
- * // After
41
+ * // After:
42
42
  * const { DirectionContext, useDirection } =
43
43
  * createSafeContext<Direction>()('Direction'); // That's it :)
44
44
  *
package/src/misc/index.ts CHANGED
@@ -1 +1,3 @@
1
+ export * from './contextualize.js';
1
2
  export * from './createSafeContext.js';
3
+ export * from './wrapJSX.js';
@@ -0,0 +1,107 @@
1
+ import type {
2
+ ComponentProps,
3
+ JSXElementConstructor,
4
+ default as React,
5
+ } from 'react';
6
+
7
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
+ import type { contextualize } from './contextualize.js';
9
+
10
+ /**
11
+ * The return type of {@linkcode wrapJSX}
12
+ *
13
+ * @see
14
+ * {@linkcode wrapJSX},
15
+ * {@linkcode WrapJSXWith}
16
+ */
17
+ export type JSXWrapPipe = {
18
+ with: WrapJSXWith;
19
+ end: () => React.JSX.Element;
20
+ };
21
+
22
+ /**
23
+ * @see
24
+ * {@linkcode wrapJSX},
25
+ * {@linkcode JSXWrapPipe}
26
+ */
27
+ export type WrapJSXWith =
28
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
+ <C extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>>(
30
+ ...args: [
31
+ Component: C,
32
+ ...(Record<never, unknown> extends Omit<ComponentProps<C>, 'children'>
33
+ ? [
34
+ props?: React.JSX.IntrinsicAttributes &
35
+ Omit<ComponentProps<C>, 'children'>,
36
+ ]
37
+ : [
38
+ props: React.JSX.IntrinsicAttributes &
39
+ Omit<ComponentProps<C>, 'children'>,
40
+ ]),
41
+ ]
42
+ ) => JSXWrapPipe;
43
+
44
+ /**
45
+ * An alternative way to compose JSX that avoids ever-increasing indentation
46
+ *
47
+ * A more general version of the context-specific {@linkcode contextualize}
48
+ * function.
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * // Before:
53
+ * createRoot(document.getElementById('root')!).render(
54
+ * <StrictMode>
55
+ * <I18nextProvider i18n={i18n}>
56
+ * <QueryClientProvider client={queryClient}>
57
+ * <NuqsAdapter>
58
+ * <ThemeProvider theme={theme}>
59
+ * <ToasterProvider>
60
+ * <App />
61
+ * </ToasterProvider>
62
+ * </ThemeProvider>
63
+ * </NuqsAdapter>
64
+ * </QueryClientProvider>
65
+ * </I18nextProvider>
66
+ * </StrictMode>,
67
+ * );
68
+ *
69
+ * // After:
70
+ * createRoot(document.getElementById('root')!).render(
71
+ * wrapJSX(<App />)
72
+ * .with(ToasterProvider)
73
+ * .with(ThemeProvider, { theme })
74
+ * .with(NuqsAdapter)
75
+ * .with(QueryClientProvider, { client: queryClient })
76
+ * .with(I18nextProvider, { i18n })
77
+ * .with(StrictMode)
78
+ * .end(),
79
+ * );
80
+ * ```
81
+ *
82
+ * @param jsx The JSX to wrap
83
+ *
84
+ * @returns An object with the following properties:
85
+ * - `with`: a function that accepts a component `Component` and props `props`
86
+ * for it as arguments and returns
87
+ * `wrapJSX(<Component {...props}>{jsx}</Component>)`
88
+ * - `end`: a function that returns `jsx`
89
+ *
90
+ * @see
91
+ * {@linkcode JSXWrapPipe}
92
+ */
93
+ export function wrapJSX(jsx: React.JSX.Element): JSXWrapPipe {
94
+ return {
95
+ with(
96
+ Component:
97
+ | keyof React.JSX.IntrinsicElements
98
+ | JSXElementConstructor<object>,
99
+ props: object = {},
100
+ ) {
101
+ return wrapJSX(<Component {...props}>{jsx}</Component>);
102
+ },
103
+ end() {
104
+ return jsx;
105
+ },
106
+ };
107
+ }