@fluixi/jsx 1.0.0-alpha.53

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 ADDED
@@ -0,0 +1,857 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/fluixi/assets/main/logos/128x128.png" alt="Fluixi" width="120" height="120" />
3
+ </p>
4
+
5
+ # @fluixi/jsx
6
+
7
+ **A fine-grained JSX runtime — native JSX and lit-html templates, no virtual DOM.**
8
+
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-22c55e.svg)](./LICENSE)
10
+ ![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6?logo=typescript&logoColor=white)
11
+ ![Registry](https://img.shields.io/badge/registry-GitHub%20Packages-181717?logo=github)
12
+ ![JSX](https://img.shields.io/badge/JSX-runtime-f7df1e?logo=javascript&logoColor=black)
13
+
14
+ ---
15
+
16
+ A comprehensive JSX runtime with fine-grained reactivity, inspired by SolidJS. This package provides a modern JSX implementation that supports both native JSX and lit-html templates, with seamless integration with signal and store systems for optimal performance.
17
+
18
+ ## Features
19
+
20
+ - 🎯 **Fine-Grained Reactivity**: Surgical DOM updates without virtual DOM overhead
21
+ - 🔄 **Dual-Mode JSX**: Use native JSX or lit-html templates interchangeably
22
+ - 🚀 **Optimal Performance**: Only updates what actually changed
23
+ - 🧩 **Control Flow**: Built-in Show, For, Switch, Portal components
24
+ - 📦 **Signal & Store Support**: Works with any reactive system
25
+ - 🎨 **Lit-HTML Integration**: Full compatibility with lit-html directives
26
+ - 🔧 **Compiler Support**: Includes transformation utilities for build-time optimization
27
+ - 📘 **Full TypeScript**: Complete type safety with JSX intrinsic elements
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ npm install @fluixi/jsx @fluixi/dom
33
+ # or
34
+ pnpm add @fluixi/jsx @fluixi/dom
35
+ # or
36
+ yarn add @fluixi/jsx @fluixi/dom
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ ### Configure TypeScript
42
+
43
+ Add to your `tsconfig.json`:
44
+
45
+ ```json
46
+ {
47
+ "compilerOptions": {
48
+ "jsx": "react-jsx",
49
+ "jsxImportSource": "@fluixi/jsx",
50
+ "types": ["@fluixi/jsx"]
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### Basic Usage
56
+
57
+ ```tsx
58
+ import { render } from '@fluixi/jsx';
59
+ import { createSignal } from '@fluixi/reactive/signal';
60
+
61
+ function Counter() {
62
+ const [count, setCount] = createSignal(0);
63
+
64
+ return (
65
+ <div>
66
+ <p>Count: {count()}</p>
67
+ <button onClick={() => setCount(count() + 1)}>
68
+ Increment
69
+ </button>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ render(<Counter />, document.getElementById('app')!);
75
+ ```
76
+
77
+ ### With Reactive Values
78
+
79
+ ```tsx
80
+ import { createSignal } from '@fluixi/reactive/signal';
81
+
82
+ function App() {
83
+ const [name, setName] = createSignal('World');
84
+
85
+ return (
86
+ <div>
87
+ {/* Reactive text - updates automatically */}
88
+ <h1>Hello, {name()}!</h1>
89
+
90
+ {/* Reactive attribute */}
91
+ <input
92
+ value={name()}
93
+ onInput={(e) => setName(e.target.value)}
94
+ />
95
+ </div>
96
+ );
97
+ }
98
+ ```
99
+
100
+ ## JSX Syntax
101
+
102
+ ### Elements
103
+
104
+ ```tsx
105
+ // Native elements
106
+ <div className="container">Content</div>
107
+
108
+ // Self-closing
109
+ <img src="image.jpg" alt="Image" />
110
+
111
+ // Components
112
+ <MyComponent prop="value" />
113
+ ```
114
+
115
+ ### Reactive Attributes
116
+
117
+ ```tsx
118
+ import { createSignal } from '@fluixi/reactive/signal';
119
+
120
+ const [color, setColor] = createSignal('red');
121
+ const [size, setSize] = createSignal(16);
122
+
123
+ <div
124
+ style={{
125
+ color: color(), // Reactive
126
+ fontSize: `${size()}px` // Reactive
127
+ }}
128
+ className={`text-${color()}`} // Reactive
129
+ data-color={color()} // Reactive
130
+ >
131
+ Content
132
+ </div>
133
+ ```
134
+
135
+ ### Event Handlers
136
+
137
+ ```tsx
138
+ // Click handler
139
+ <button onClick={() => console.log('clicked')}>
140
+ Click me
141
+ </button>
142
+
143
+ // Input handler
144
+ <input onInput={(e) => console.log(e.target.value)} />
145
+
146
+ // Custom events
147
+ <div onCustomEvent={(e) => handleCustom(e)} />
148
+ ```
149
+
150
+ ### Children
151
+
152
+ ```tsx
153
+ // Text children
154
+ <div>Hello World</div>
155
+
156
+ // Element children
157
+ <div>
158
+ <span>Child 1</span>
159
+ <span>Child 2</span>
160
+ </div>
161
+
162
+ // Reactive children
163
+ <div>{count()}</div>
164
+
165
+ // Array children
166
+ <div>
167
+ {items().map(item => <div key={item.id}>{item.name}</div>)}
168
+ </div>
169
+
170
+ // Mixed children
171
+ <div>
172
+ Static text
173
+ {dynamic()}
174
+ <span>Element</span>
175
+ </div>
176
+ ```
177
+
178
+ ### Fragments
179
+
180
+ ```tsx
181
+ // Fragment shorthand
182
+ <>
183
+ <div>First</div>
184
+ <div>Second</div>
185
+ </>
186
+
187
+ // Named fragment
188
+ <Fragment>
189
+ <div>First</div>
190
+ <div>Second</div>
191
+ </Fragment>
192
+ ```
193
+
194
+ ## Control Flow Components
195
+
196
+ ### Show
197
+
198
+ Conditional rendering with optional fallback.
199
+
200
+ ```tsx
201
+ import { Show } from '@fluixi/jsx';
202
+
203
+ function Profile() {
204
+ const [user, setUser] = createSignal(null);
205
+
206
+ return (
207
+ <Show
208
+ when={user()}
209
+ fallback={<div>Loading...</div>}
210
+ >
211
+ {(u) => (
212
+ <div>
213
+ <h1>{u.name}</h1>
214
+ <p>{u.email}</p>
215
+ </div>
216
+ )}
217
+ </Show>
218
+ );
219
+ }
220
+ ```
221
+
222
+ ### For
223
+
224
+ Keyed list rendering with optimal updates.
225
+
226
+ ```tsx
227
+ import { For } from '@fluixi/jsx';
228
+
229
+ function TodoList() {
230
+ const [todos, setTodos] = createSignal([
231
+ { id: 1, text: 'Learn JSX', done: false },
232
+ { id: 2, text: 'Build app', done: false },
233
+ ]);
234
+
235
+ return (
236
+ <ul>
237
+ <For each={todos()}>
238
+ {(todo, index) => (
239
+ <li>
240
+ <input
241
+ type="checkbox"
242
+ checked={todo.done}
243
+ onChange={(e) => updateTodo(index(), e.target.checked)}
244
+ />
245
+ {todo.text}
246
+ </li>
247
+ )}
248
+ </For>
249
+ </ul>
250
+ );
251
+ }
252
+ ```
253
+
254
+ ### Index
255
+
256
+ Index-based list rendering (use when items change but positions don't).
257
+
258
+ ```tsx
259
+ import { Index } from '@fluixi/jsx';
260
+
261
+ function NumberList() {
262
+ const [numbers] = createSignal([1, 2, 3, 4, 5]);
263
+
264
+ return (
265
+ <ul>
266
+ <Index each={numbers()}>
267
+ {(num, index) => (
268
+ <li>#{index}: {num()}</li>
269
+ )}
270
+ </Index>
271
+ </ul>
272
+ );
273
+ }
274
+ ```
275
+
276
+ ### Switch/Match
277
+
278
+ Multi-way conditional rendering.
279
+
280
+ ```tsx
281
+ import { Switch, Match } from '@fluixi/jsx';
282
+
283
+ function StatusView() {
284
+ const [status, setStatus] = createSignal('loading');
285
+
286
+ return (
287
+ <Switch fallback={<div>Unknown status</div>}>
288
+ <Match when={status() === 'loading'}>
289
+ <div>Loading...</div>
290
+ </Match>
291
+ <Match when={status() === 'success'}>
292
+ <div>Success!</div>
293
+ </Match>
294
+ <Match when={status() === 'error'}>
295
+ <div>Error occurred</div>
296
+ </Match>
297
+ </Switch>
298
+ );
299
+ }
300
+ ```
301
+
302
+ ### Portal
303
+
304
+ Render content in a different DOM location.
305
+
306
+ ```tsx
307
+ import { Portal } from '@fluixi/jsx';
308
+
309
+ function Modal({ isOpen, children }) {
310
+ return (
311
+ <Show when={isOpen()}>
312
+ <Portal mount={document.body}>
313
+ <div class="modal-backdrop">
314
+ <div class="modal">
315
+ {children}
316
+ </div>
317
+ </div>
318
+ </Portal>
319
+ </Show>
320
+ );
321
+ }
322
+ ```
323
+
324
+ ### Dynamic
325
+
326
+ Dynamically render components based on runtime conditions.
327
+
328
+ ```tsx
329
+ import { Dynamic } from '@fluixi/jsx';
330
+
331
+ function DynamicComponent() {
332
+ const [component, setComponent] = createSignal('div');
333
+
334
+ return (
335
+ <Dynamic
336
+ component={component()}
337
+ className="dynamic"
338
+ >
339
+ Content
340
+ </Dynamic>
341
+ );
342
+ }
343
+ ```
344
+
345
+ ### ErrorBoundary
346
+
347
+ Catch and handle errors in component trees.
348
+
349
+ ```tsx
350
+ import { ErrorBoundary } from '@fluixi/jsx';
351
+
352
+ function App() {
353
+ return (
354
+ <ErrorBoundary
355
+ fallback={(err, reset) => (
356
+ <div>
357
+ <h1>Error: {err.message}</h1>
358
+ <button onClick={reset}>Retry</button>
359
+ </div>
360
+ )}
361
+ >
362
+ <RiskyComponent />
363
+ </ErrorBoundary>
364
+ );
365
+ }
366
+ ```
367
+
368
+ ## Lit-HTML Integration
369
+
370
+ Use lit-html templates alongside JSX.
371
+
372
+ ### With signal directive
373
+
374
+ ```tsx
375
+ import { html } from 'lit';
376
+ import { signal } from '@fluixi/jsx';
377
+
378
+ function Component() {
379
+ const [count, setCount] = createSignal(0);
380
+
381
+ return html`
382
+ <div>
383
+ <p>Count: ${signal(count)}</p>
384
+ <button @click=${() => setCount(count() + 1)}>
385
+ Increment
386
+ </button>
387
+ </div>
388
+ `;
389
+ }
390
+ ```
391
+
392
+ ### Helper utilities
393
+
394
+ ```tsx
395
+ import { template, templateSVG, $, $if } from '@fluixi/jsx';
396
+
397
+ function Component() {
398
+ const [show, setShow] = createSignal(true);
399
+ const [name, setName] = createSignal('World');
400
+
401
+ // Auto-wrapped template
402
+ return template`
403
+ <div>
404
+ ${$if(show, `Hello, ${$(name)}!`, 'Hidden')}
405
+ </div>
406
+ `;
407
+ }
408
+ ```
409
+
410
+ ## Component Patterns
411
+
412
+ ### Function Components
413
+
414
+ ```tsx
415
+ interface Props {
416
+ name: string;
417
+ age: number;
418
+ }
419
+
420
+ function Greeting(props: Props) {
421
+ return <div>Hello, {props.name} ({props.age})</div>;
422
+ }
423
+ ```
424
+
425
+ ### With Children
426
+
427
+ ```tsx
428
+ interface CardProps {
429
+ title: string;
430
+ children: JSX.Element;
431
+ }
432
+
433
+ function Card(props: CardProps) {
434
+ return (
435
+ <div class="card">
436
+ <h2>{props.title}</h2>
437
+ <div class="card-body">
438
+ {props.children}
439
+ </div>
440
+ </div>
441
+ );
442
+ }
443
+
444
+ // Usage
445
+ <Card title="My Card">
446
+ <p>Card content here</p>
447
+ </Card>
448
+ ```
449
+
450
+ ### With Signals
451
+
452
+ ```tsx
453
+ function Counter(props: { initial?: number }) {
454
+ const [count, setCount] = createSignal(props.initial ?? 0);
455
+
456
+ return (
457
+ <div>
458
+ <p>Count: {count()}</p>
459
+ <button onClick={() => setCount(c => c + 1)}>+</button>
460
+ <button onClick={() => setCount(c => c - 1)}>-</button>
461
+ </div>
462
+ );
463
+ }
464
+ ```
465
+
466
+ ### Memoized Components
467
+
468
+ ```tsx
469
+ import { memo } from '@fluixi/jsx';
470
+
471
+ const ExpensiveComponent = memo((props: { data: any }) => {
472
+ // Expensive rendering logic
473
+ return <div>{/* ... */}</div>;
474
+ });
475
+ ```
476
+
477
+ ### Component Helpers
478
+
479
+ ```tsx
480
+ import { jsxComponent, defineComponent } from '@fluixi/jsx';
481
+
482
+ // Simple component wrapper
483
+ const MyComponent = jsxComponent((props: Props) => {
484
+ return <div>{/* ... */}</div>;
485
+ });
486
+
487
+ // With name for debugging
488
+ const NamedComponent = defineComponent('MyComponent', (props: Props) => {
489
+ return <div>{/* ... */}</div>;
490
+ });
491
+ ```
492
+
493
+ ## Refs
494
+
495
+ ### Element Refs
496
+
497
+ ```tsx
498
+ import { createRef } from '@fluixi/jsx';
499
+
500
+ function Component() {
501
+ const inputRef = createRef<HTMLInputElement>();
502
+
503
+ const focusInput = () => {
504
+ inputRef.current?.focus();
505
+ };
506
+
507
+ return (
508
+ <div>
509
+ <input ref={inputRef} />
510
+ <button onClick={focusInput}>Focus</button>
511
+ </div>
512
+ );
513
+ }
514
+ ```
515
+
516
+ ### Callback Refs
517
+
518
+ ```tsx
519
+ function Component() {
520
+ let divElement: HTMLDivElement | null = null;
521
+
522
+ const handleRef = (el: HTMLDivElement | null) => {
523
+ divElement = el;
524
+ if (el) {
525
+ console.log('Element mounted:', el);
526
+ }
527
+ };
528
+
529
+ return <div ref={handleRef}>Content</div>;
530
+ }
531
+ ```
532
+
533
+ ### Forward Ref
534
+
535
+ ```tsx
536
+ import { forwardRef } from '@fluixi/jsx';
537
+
538
+ const FancyInput = forwardRef<HTMLInputElement, { label: string }>(
539
+ (props, ref) => {
540
+ return (
541
+ <div>
542
+ <label>{props.label}</label>
543
+ <input ref={ref} />
544
+ </div>
545
+ );
546
+ }
547
+ );
548
+
549
+ // Usage
550
+ const inputRef = createRef<HTMLInputElement>();
551
+ <FancyInput label="Name" ref={inputRef} />
552
+ ```
553
+
554
+ ## Styling
555
+
556
+ ### Inline Styles
557
+
558
+ ```tsx
559
+ // Object style
560
+ <div style={{ color: 'red', fontSize: '16px' }}>
561
+ Styled text
562
+ </div>
563
+
564
+ // String style
565
+ <div style="color: red; font-size: 16px;">
566
+ Styled text
567
+ </div>
568
+
569
+ // Reactive styles
570
+ <div style={{ color: color() }}>
571
+ Dynamic color
572
+ </div>
573
+ ```
574
+
575
+ ### CSS Classes
576
+
577
+ ```tsx
578
+ // String
579
+ <div className="btn btn-primary">Button</div>
580
+
581
+ // Array
582
+ <div className={['btn', isActive() && 'active'].filter(Boolean)}>
583
+ Button
584
+ </div>
585
+
586
+ // Object (with classMap from lit)
587
+ import { classMap } from 'lit/directives/class-map.js';
588
+
589
+ <div className={classMap({ btn: true, active: isActive() })}>
590
+ Button
591
+ </div>
592
+
593
+ // Reactive
594
+ <div className={`btn btn-${type()}`}>Button</div>
595
+ ```
596
+
597
+ ## Advanced Usage
598
+
599
+ ### Batch Updates
600
+
601
+ ```tsx
602
+ import { jsxBatch } from '@fluixi/jsx';
603
+
604
+ function Component() {
605
+ const [a, setA] = createSignal(0);
606
+ const [b, setB] = createSignal(0);
607
+ const [c, setC] = createSignal(0);
608
+
609
+ const updateAll = () => {
610
+ jsxBatch(() => {
611
+ setA(1);
612
+ setB(2);
613
+ setC(3);
614
+ });
615
+ // Only one render cycle
616
+ };
617
+
618
+ return <button onClick={updateAll}>Update All</button>;
619
+ }
620
+ ```
621
+
622
+ ### Server-Side Rendering
623
+
624
+ ```tsx
625
+ import { renderToString } from '@fluixi/jsx';
626
+
627
+ async function handler() {
628
+ const html = await renderToString(<App />);
629
+ return new Response(html, {
630
+ headers: { 'Content-Type': 'text/html' },
631
+ });
632
+ }
633
+ ```
634
+
635
+ ### Hydration
636
+
637
+ ```tsx
638
+ import { hydrate } from '@fluixi/jsx';
639
+
640
+ // Hydrate server-rendered content
641
+ hydrate(<App />, document.getElementById('app')!);
642
+ ```
643
+
644
+ ## Compiler Options
645
+
646
+ The package includes a compiler module for build-time optimizations.
647
+
648
+ ### Transform JSX
649
+
650
+ ```typescript
651
+ import { transform } from '@fluixi/jsx/compiler';
652
+
653
+ const result = transform(code, {
654
+ useLitHTML: true,
655
+ optimize: true,
656
+ delegateEvents: true,
657
+ delegatedEvents: ['click', 'input', 'change'],
658
+ });
659
+
660
+ console.log(result.code);
661
+ console.log(result.metadata);
662
+ ```
663
+
664
+ ### Babel Plugin
665
+
666
+ ```javascript
667
+ // babel.config.js
668
+ module.exports = {
669
+ plugins: [
670
+ ['@fluixi/jsx/compiler/babel', {
671
+ useLitHTML: true,
672
+ optimize: true,
673
+ }]
674
+ ]
675
+ };
676
+ ```
677
+
678
+ ### TypeScript Transformer
679
+
680
+ ```typescript
681
+ import { createTypeScriptTransformer } from '@fluixi/jsx/compiler';
682
+
683
+ const transformer = createTypeScriptTransformer({
684
+ optimize: true,
685
+ });
686
+ ```
687
+
688
+ ## Performance Tips
689
+
690
+ 1. **Use `For` for lists**: Keyed reconciliation is faster than mapping
691
+ 2. **Memoize expensive components**: Use `memo()` wrapper
692
+ 3. **Batch updates**: Use `jsxBatch()` for multiple state changes
693
+ 4. **Avoid inline object creation**: Extract to variables
694
+ 5. **Use `Index` for stable lists**: When order doesn't change
695
+
696
+ ```tsx
697
+ // ❌ Bad - creates new object every render
698
+ <div style={{ color: 'red' }}>Text</div>
699
+
700
+ // ✅ Good - object is stable
701
+ const style = { color: 'red' };
702
+ <div style={style}>Text</div>
703
+
704
+ // ❌ Bad - new function every render
705
+ <button onClick={() => handleClick(id)}>Click</button>
706
+
707
+ // ✅ Good - stable function
708
+ const handleClick = () => handleClick(id);
709
+ <button onClick={handleClick}>Click</button>
710
+ ```
711
+
712
+ ## TypeScript
713
+
714
+ ### Component Types
715
+
716
+ ```typescript
717
+ import type { JSXElement, JSXComponent } from '@fluixi/jsx';
718
+
719
+ // Function component type
720
+ const Component: JSXComponent<{ name: string }> = (props) => {
721
+ return <div>{props.name}</div>;
722
+ };
723
+
724
+ // Element type
725
+ function render(): JSXElement {
726
+ return <div>Content</div>;
727
+ }
728
+ ```
729
+
730
+ ### Props Interface
731
+
732
+ ```typescript
733
+ import type { JSXProps } from '@fluixi/jsx';
734
+
735
+ interface MyProps extends JSXProps {
736
+ title: string;
737
+ count: number;
738
+ onUpdate?: (value: number) => void;
739
+ }
740
+
741
+ function MyComponent(props: MyProps) {
742
+ return <div>{props.title}: {props.count}</div>;
743
+ }
744
+ ```
745
+
746
+ ### Intrinsic Elements
747
+
748
+ ```typescript
749
+ // All HTML elements are typed
750
+ <div>Text</div>
751
+ <button onClick={() => {}}>Button</button>
752
+ <input type="text" value="text" />
753
+
754
+ // With proper event types
755
+ <button onClick={(e: MouseEvent) => console.log(e)}>
756
+ Click
757
+ </button>
758
+
759
+ <input onInput={(e: InputEvent) => {
760
+ const target = e.target as HTMLInputElement;
761
+ console.log(target.value);
762
+ }} />
763
+ ```
764
+
765
+ ## Browser Support
766
+
767
+ - Modern browsers (Chrome, Firefox, Safari, Edge)
768
+ - ES2020+ required
769
+ - No IE11 support
770
+
771
+ ## Debugging
772
+
773
+ ### Development Mode
774
+
775
+ ```typescript
776
+ import { createReactiveJSX } from '@fluixi/jsx';
777
+
778
+ const jsx = createReactiveJSX({
779
+ signalSystem: mySignalSystem,
780
+ development: true, // Enables warnings and checks
781
+ });
782
+ ```
783
+
784
+ ### Component Names
785
+
786
+ Use `defineComponent` for better debugging:
787
+
788
+ ```tsx
789
+ import { defineComponent } from '@fluixi/jsx';
790
+
791
+ const MyComponent = defineComponent('MyComponent', (props) => {
792
+ return <div>{props.children}</div>;
793
+ });
794
+ ```
795
+
796
+ ## Migration Guide
797
+
798
+ ### From React
799
+
800
+ ```tsx
801
+ // React
802
+ import React, { useState } from 'react';
803
+
804
+ function Counter() {
805
+ const [count, setCount] = useState(0);
806
+ return <div onClick={() => setCount(count + 1)}>{count}</div>;
807
+ }
808
+
809
+ // This package
810
+ import { createSignal } from '@fluixi/reactive/signal';
811
+
812
+ function Counter() {
813
+ const [count, setCount] = createSignal(0);
814
+ return <div onClick={() => setCount(count() + 1)}>{count()}</div>;
815
+ }
816
+ // Note: Signals are functions!
817
+ ```
818
+
819
+ ### From Solid
820
+
821
+ ```tsx
822
+ // Very similar! Main difference is import paths
823
+ import { createSignal, Show, For } from 'solid-js';
824
+ // becomes
825
+ import { createSignal } from '@fluixi/reactive/signal';
826
+ import { Show, For } from '@fluixi/jsx';
827
+ ```
828
+
829
+ ## Examples
830
+
831
+ Check the examples directory for complete applications:
832
+
833
+ - Counter app
834
+ - Todo list
835
+ - Data fetching
836
+ - Forms
837
+ - Routing
838
+
839
+ ## Contributing
840
+
841
+ Contributions welcome! Please see the main repository for guidelines.
842
+
843
+ ## License
844
+
845
+ MIT
846
+
847
+ ## Related Packages
848
+
849
+ - `@fluixi/dom` - Core reactive DOM runtime
850
+ - `@fluixi/reactive` - Signal and store implementations
851
+ - `lit` - Template literals for HTML
852
+
853
+ ## Resources
854
+
855
+ - [SolidJS Documentation](https://www.solidjs.com/)
856
+ - [Lit Documentation](https://lit.dev/)
857
+ - [Fine-Grained Reactivity](https://dev.to/ryansolid/a-hands-on-introduction-to-fine-grained-reactivity-3ndf)