@arcmantle/lit-jsx 1.0.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 +768 -0
- package/dist/compiler/attribute-processor.d.ts +128 -0
- package/dist/compiler/attribute-processor.d.ts.map +1 -0
- package/dist/compiler/attribute-processor.js +380 -0
- package/dist/compiler/attribute-processor.js.map +1 -0
- package/dist/compiler/babel-preset.d.ts +6 -0
- package/dist/compiler/babel-preset.d.ts.map +1 -0
- package/dist/compiler/babel-preset.js +27 -0
- package/dist/compiler/babel-preset.js.map +1 -0
- package/dist/compiler/builder.d.ts +22 -0
- package/dist/compiler/builder.d.ts.map +1 -0
- package/dist/compiler/builder.js +62 -0
- package/dist/compiler/builder.js.map +1 -0
- package/dist/compiler/compiler-utils.d.ts +81 -0
- package/dist/compiler/compiler-utils.d.ts.map +1 -0
- package/dist/compiler/compiler-utils.js +410 -0
- package/dist/compiler/compiler-utils.js.map +1 -0
- package/dist/compiler/config.d.ts +77 -0
- package/dist/compiler/config.d.ts.map +1 -0
- package/dist/compiler/config.js +78 -0
- package/dist/compiler/config.js.map +1 -0
- package/dist/compiler/postprocess.d.ts +5 -0
- package/dist/compiler/postprocess.d.ts.map +1 -0
- package/dist/compiler/postprocess.js +3 -0
- package/dist/compiler/postprocess.js.map +1 -0
- package/dist/compiler/preprocess.d.ts +5 -0
- package/dist/compiler/preprocess.d.ts.map +1 -0
- package/dist/compiler/preprocess.js +28 -0
- package/dist/compiler/preprocess.js.map +1 -0
- package/dist/compiler/transform-jsx.d.ts +5 -0
- package/dist/compiler/transform-jsx.d.ts.map +1 -0
- package/dist/compiler/transform-jsx.js +25 -0
- package/dist/compiler/transform-jsx.js.map +1 -0
- package/dist/compiler/transpiler.d.ts +48 -0
- package/dist/compiler/transpiler.d.ts.map +1 -0
- package/dist/compiler/transpiler.js +463 -0
- package/dist/compiler/transpiler.js.map +1 -0
- package/dist/compiler/vite-plugin.d.ts +38 -0
- package/dist/compiler/vite-plugin.d.ts.map +1 -0
- package/dist/compiler/vite-plugin.js +96 -0
- package/dist/compiler/vite-plugin.js.map +1 -0
- package/dist/runtime/choose-component.d.ts +39 -0
- package/dist/runtime/choose-component.d.ts.map +1 -0
- package/dist/runtime/choose-component.js +40 -0
- package/dist/runtime/choose-component.js.map +1 -0
- package/dist/runtime/compiler-ctors.d.ts +21 -0
- package/dist/runtime/compiler-ctors.d.ts.map +1 -0
- package/dist/runtime/compiler-ctors.js +21 -0
- package/dist/runtime/compiler-ctors.js.map +1 -0
- package/dist/runtime/for-component.d.ts +25 -0
- package/dist/runtime/for-component.d.ts.map +1 -0
- package/dist/runtime/for-component.js +35 -0
- package/dist/runtime/for-component.js.map +1 -0
- package/dist/runtime/literal-map.d.ts +22 -0
- package/dist/runtime/literal-map.d.ts.map +1 -0
- package/dist/runtime/literal-map.js +29 -0
- package/dist/runtime/literal-map.js.map +1 -0
- package/dist/runtime/rest-directive.d.ts +28 -0
- package/dist/runtime/rest-directive.d.ts.map +1 -0
- package/dist/runtime/rest-directive.js +49 -0
- package/dist/runtime/rest-directive.js.map +1 -0
- package/dist/runtime/show-component.d.ts +33 -0
- package/dist/runtime/show-component.d.ts.map +1 -0
- package/dist/runtime/show-component.js +30 -0
- package/dist/runtime/show-component.js.map +1 -0
- package/dist/runtime/tagged-template.d.ts +12 -0
- package/dist/runtime/tagged-template.d.ts.map +1 -0
- package/dist/runtime/tagged-template.js +12 -0
- package/dist/runtime/tagged-template.js.map +1 -0
- package/dist/runtime/type-helpers.d.ts +80 -0
- package/dist/runtime/type-helpers.d.ts.map +1 -0
- package/dist/runtime/type-helpers.js +85 -0
- package/dist/runtime/type-helpers.js.map +1 -0
- package/dist/shared/jsx-types.d.ts +2139 -0
- package/dist/shared/jsx-types.d.ts.map +1 -0
- package/dist/shared/jsx-types.js +2 -0
- package/dist/shared/jsx-types.js.map +1 -0
- package/dist/shared/jsx-utils.d.ts +30 -0
- package/dist/shared/jsx-utils.d.ts.map +1 -0
- package/dist/shared/jsx-utils.js +58 -0
- package/dist/shared/jsx-utils.js.map +1 -0
- package/dist/shared/mathml-tags.d.ts +12 -0
- package/dist/shared/mathml-tags.d.ts.map +1 -0
- package/dist/shared/mathml-tags.js +215 -0
- package/dist/shared/mathml-tags.js.map +1 -0
- package/dist/shared/svg-tags.d.ts +13 -0
- package/dist/shared/svg-tags.d.ts.map +1 -0
- package/dist/shared/svg-tags.js +95 -0
- package/dist/shared/svg-tags.js.map +1 -0
- package/dist/utils.d.ts +30 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +30 -0
- package/dist/utils.js.map +1 -0
- package/package.json +52 -0
- package/src/compiler/attribute-processor.ts +579 -0
- package/src/compiler/babel-preset.ts +34 -0
- package/src/compiler/builder.ts +86 -0
- package/src/compiler/compiler-utils.ts +789 -0
- package/src/compiler/config.ts +77 -0
- package/src/compiler/postprocess.ts +7 -0
- package/src/compiler/preprocess.ts +40 -0
- package/src/compiler/transform-jsx.ts +36 -0
- package/src/compiler/transpiler.ts +644 -0
- package/src/compiler/vite-plugin.ts +114 -0
- package/src/external.d.ts +9 -0
- package/src/runtime/choose-component.ts +53 -0
- package/src/runtime/compiler-ctors.ts +28 -0
- package/src/runtime/for-component.ts +54 -0
- package/src/runtime/literal-map.ts +37 -0
- package/src/runtime/rest-directive.ts +66 -0
- package/src/runtime/show-component.ts +48 -0
- package/src/runtime/tagged-template.ts +11 -0
- package/src/runtime/type-helpers.ts +91 -0
- package/src/shared/jsx-types.ts +2556 -0
- package/src/shared/jsx-utils.ts +85 -0
- package/src/shared/mathml-tags.ts +235 -0
- package/src/shared/svg-tags.ts +103 -0
- package/src/tsconfig.json +4 -0
- package/src/utils.ts +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,768 @@
|
|
|
1
|
+
# lit-jsx
|
|
2
|
+
|
|
3
|
+
A powerful JSX compiler and Vite plugin that transforms JSX into native Lit templates at compile time with zero runtime overhead.
|
|
4
|
+
|
|
5
|
+
## 🚀 Features
|
|
6
|
+
|
|
7
|
+
jsx-lit brings the familiar JSX syntax to the Lit ecosystem while maintaining the performance and capabilities that make Lit exceptional.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Write familiar JSX
|
|
11
|
+
function TodoItem({ todo, onToggle, onDelete }) {
|
|
12
|
+
return (
|
|
13
|
+
<div classList={{ completed: todo.completed }}>
|
|
14
|
+
<input
|
|
15
|
+
type="checkbox"
|
|
16
|
+
checked={as.prop(todo.completed)}
|
|
17
|
+
disabled={as.bool(todo.readonly)}
|
|
18
|
+
on-change={() => onToggle(todo.id)}
|
|
19
|
+
/>
|
|
20
|
+
<span>{todo.text}</span>
|
|
21
|
+
<button on-click={() => onDelete(todo.id)}>Delete</button>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Compiles to efficient Lit templates
|
|
27
|
+
html`
|
|
28
|
+
<div class=${classMap({ completed: todo.completed })}>
|
|
29
|
+
<input
|
|
30
|
+
type="checkbox"
|
|
31
|
+
.checked=${todo.completed}
|
|
32
|
+
?disabled=${todo.readonly}
|
|
33
|
+
@change=${() => onToggle(todo.id)}
|
|
34
|
+
/>
|
|
35
|
+
<span>${todo.text}</span>
|
|
36
|
+
<button @click=${() => onDelete(todo.id)}>Delete</button>
|
|
37
|
+
</div>
|
|
38
|
+
`
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### ✨ Key Benefits
|
|
42
|
+
|
|
43
|
+
- **⚡ Zero Runtime Overhead**: Pure compile-time transformation to native Lit templates
|
|
44
|
+
- **🎯 Type-Safe**: Full TypeScript support with comprehensive JSX type definitions
|
|
45
|
+
- **🔧 Vite Integration**: Seamless setup with the included Vite plugin
|
|
46
|
+
- **🎨 Lit Ecosystem**: Works with all Lit directives, custom elements, and patterns
|
|
47
|
+
- **🎛️ Flexible Binding**: Fine-grained control over attribute, property, and boolean bindings
|
|
48
|
+
- **🏷️ Dynamic Tags**: Support for conditional element types with static template optimization
|
|
49
|
+
- **📦 Function Components**: Full support for composable function components
|
|
50
|
+
- **🔗 Custom Elements**: Type-safe integration with Lit-based custom elements
|
|
51
|
+
- **🧩 Library Components**: Built-in `For`, `Show`, and `Choose` components for common rendering patterns
|
|
52
|
+
|
|
53
|
+
## 📦 Installation
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install jsx-lit
|
|
57
|
+
# or
|
|
58
|
+
pnpm add jsx-lit
|
|
59
|
+
# or
|
|
60
|
+
yarn add jsx-lit
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## ⚡ Quick Start
|
|
64
|
+
|
|
65
|
+
### 1. Configure Vite
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// vite.config.ts
|
|
69
|
+
import { litJsx } from 'jsx-lit/vite-jsx-preserve';
|
|
70
|
+
import { defineConfig } from 'vite';
|
|
71
|
+
|
|
72
|
+
export default defineConfig({
|
|
73
|
+
plugins: [litJsx()],
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Configure TypeScript
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"compilerOptions": {
|
|
82
|
+
"jsx": "preserve",
|
|
83
|
+
"jsxImportSource": "jsx-lit"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Start Writing JSX
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
import { LitElement } from 'lit';
|
|
92
|
+
import { For, Show, Choose } from 'jsx-lit';
|
|
93
|
+
|
|
94
|
+
export class MyComponent extends LitElement {
|
|
95
|
+
render() {
|
|
96
|
+
return (
|
|
97
|
+
<div>
|
|
98
|
+
<h1>Hello jsx-lit!</h1>
|
|
99
|
+
<p>JSX compiled to Lit templates with utility components</p>
|
|
100
|
+
|
|
101
|
+
<Show when={this.items.length > 0}>
|
|
102
|
+
{(length) => (
|
|
103
|
+
<For each={this.items}>
|
|
104
|
+
{(item, index) => <div>{item}</div>}
|
|
105
|
+
</For>
|
|
106
|
+
)}
|
|
107
|
+
</Show>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 🎯 Core Concepts
|
|
115
|
+
|
|
116
|
+
### Attribute Binding Control
|
|
117
|
+
|
|
118
|
+
jsx-lit provides precise control over how values are bound to elements:
|
|
119
|
+
|
|
120
|
+
#### Default Behavior (Attribute Binding)
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<input value={userInput} />
|
|
124
|
+
// Compiles to: <input value=${userInput} />
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### Property Binding
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
<input value={as.prop(userInput)} />
|
|
131
|
+
// or
|
|
132
|
+
<input value={prop => userInput} />
|
|
133
|
+
// Compiles to: <input .value=${userInput} />
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### Boolean Attribute Binding
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
<input disabled={as.bool(isDisabled)} />
|
|
140
|
+
// or
|
|
141
|
+
<input disabled={bool => isDisabled} />
|
|
142
|
+
// Compiles to: <input ?disabled=${isDisabled} />
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Special Attributes
|
|
146
|
+
|
|
147
|
+
#### classList - Object to Class Mapping
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<div classList={{ active: isActive, disabled: !isEnabled }}>
|
|
151
|
+
// Compiles to: <div class=${classMap({ active: isActive, disabled: !isEnabled })}>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### styleList - Object to Style Mapping
|
|
155
|
+
|
|
156
|
+
```tsx
|
|
157
|
+
<div styleList={{ color: textColor, fontSize: '16px' }}>
|
|
158
|
+
// Compiles to: <div style=${styleMap({ color: textColor, fontSize: '16px' })}>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### Event Handlers
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
<button on-click={handleClick} on-dblclick={handleDoubleClick}>
|
|
165
|
+
// Compiles to: <button @click=${handleClick} @dblclick=${handleDoubleClick}>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### References
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
<input ref={inputRef} />
|
|
172
|
+
// Compiles to: <input ${ref(inputRef)} />
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
#### Element Directives
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
<div directive={myDirective()} />
|
|
179
|
+
// Compiles to: <div ${myDirective()} />
|
|
180
|
+
|
|
181
|
+
// Multiple directives as an array
|
|
182
|
+
<div directive={[directive1(), directive2()]} />
|
|
183
|
+
// Compiles to: <div ${directive1()} ${directive2()} />
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### Spread Attributes
|
|
187
|
+
|
|
188
|
+
```tsx
|
|
189
|
+
<div {...dynamicProps} />
|
|
190
|
+
// Compiles to: <div ${__$rest(dynamicProps)} />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## 🏗️ Component Patterns
|
|
194
|
+
|
|
195
|
+
### Function Components
|
|
196
|
+
|
|
197
|
+
jsx-lit fully supports function components that return JSX:
|
|
198
|
+
|
|
199
|
+
```tsx
|
|
200
|
+
const Button = ({ label, variant = 'primary', disabled, onClick, children }) => (
|
|
201
|
+
<button
|
|
202
|
+
classList={{ [`btn-${variant}`]: true, 'disabled': disabled }}
|
|
203
|
+
disabled={as.bool(disabled)}
|
|
204
|
+
on-click={onClick}
|
|
205
|
+
>
|
|
206
|
+
{label || children}
|
|
207
|
+
</button>
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
// Usage
|
|
211
|
+
<Button
|
|
212
|
+
label="Submit"
|
|
213
|
+
variant="success"
|
|
214
|
+
onClick={handleSubmit}
|
|
215
|
+
disabled={isLoading}
|
|
216
|
+
/>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Function components:
|
|
220
|
+
|
|
221
|
+
- Receive props as a single object parameter
|
|
222
|
+
- Support `children` via the `children` property
|
|
223
|
+
- Compile to efficient function calls
|
|
224
|
+
- Support all JSX features including conditional rendering and loops
|
|
225
|
+
|
|
226
|
+
### Custom Element Integration
|
|
227
|
+
|
|
228
|
+
Use `toJSX()` for type-safe custom element components:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { toJSX } from 'jsx-lit';
|
|
232
|
+
import { LitElement } from 'lit';
|
|
233
|
+
|
|
234
|
+
export class MyButton extends LitElement {
|
|
235
|
+
static tagName = 'my-button';
|
|
236
|
+
static tag = toJSX(MyButton);
|
|
237
|
+
|
|
238
|
+
render() {
|
|
239
|
+
return html`<button><slot></slot></button>`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Usage with type safety
|
|
244
|
+
<MyButton.tag
|
|
245
|
+
class="custom-btn"
|
|
246
|
+
onClick={() => console.log('Clicked!')}
|
|
247
|
+
/>
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Generic Custom Elements
|
|
251
|
+
|
|
252
|
+
For custom elements with generic types, you can create type-safe JSX components using explicit type casting:
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
import { toJSX } from 'jsx-lit';
|
|
256
|
+
import { LitElement } from 'lit';
|
|
257
|
+
|
|
258
|
+
class DataList<T> extends LitElement {
|
|
259
|
+
static tagName = 'data-list';
|
|
260
|
+
// Type casting is required due to TypeScript's inability to forward generics
|
|
261
|
+
static tag = toJSX(this) as <T>(props: JSX.JSXProps<DataList<T>>) => JSX.JSXElement;
|
|
262
|
+
|
|
263
|
+
@property({ type: Array }) items: T[] = [];
|
|
264
|
+
@property() renderItem?: (item: T) => TemplateResult;
|
|
265
|
+
|
|
266
|
+
render() {
|
|
267
|
+
return html`
|
|
268
|
+
<ul>
|
|
269
|
+
${this.items.map(item => html`
|
|
270
|
+
<li>${this.renderItem ? this.renderItem(item) : item}</li>
|
|
271
|
+
`)}
|
|
272
|
+
</ul>
|
|
273
|
+
`;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Usage with explicit type parameter
|
|
278
|
+
<DataList.tag<User>
|
|
279
|
+
items={users}
|
|
280
|
+
renderItem={(user) => `${user.name} (${user.email})`}
|
|
281
|
+
/>
|
|
282
|
+
|
|
283
|
+
// Type inference works for the renderItem callback
|
|
284
|
+
<DataList.tag<Product>
|
|
285
|
+
items={products}
|
|
286
|
+
renderItem={(product) => `${product.name} - $${product.price}`}
|
|
287
|
+
/>
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Note**: The current generic syntax requires explicit type casting due to TypeScript's limitations in forwarding generic parameters through static properties. If TypeScript gains the ability to forward generics in this context in the future, jsx-lit will implement a more seamless syntax.
|
|
291
|
+
|
|
292
|
+
### Dynamic Tag Names
|
|
293
|
+
|
|
294
|
+
jsx-lit supports dynamic element types with the `.tag` property pattern:
|
|
295
|
+
|
|
296
|
+
```tsx
|
|
297
|
+
import { toTag } from 'jsx-lit';
|
|
298
|
+
|
|
299
|
+
function ActionElement({ href, children }) {
|
|
300
|
+
const Tag = toTag(href ? 'a' : 'button');
|
|
301
|
+
|
|
302
|
+
return (
|
|
303
|
+
<Tag.tag href={href} class="action-element">
|
|
304
|
+
{children}
|
|
305
|
+
</Tag.tag>
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**Important**: Dynamic tag names must use the `.tag` property pattern for proper static template compilation.
|
|
311
|
+
|
|
312
|
+
### Library Components
|
|
313
|
+
|
|
314
|
+
jsx-lit provides utility components that enhance common patterns and integrate seamlessly with Lit directives:
|
|
315
|
+
|
|
316
|
+
#### For Component - Declarative List Rendering
|
|
317
|
+
|
|
318
|
+
The `For` component provides a declarative way to render lists with optional keys and separators:
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
import { For } from 'jsx-lit';
|
|
322
|
+
|
|
323
|
+
// Basic list rendering
|
|
324
|
+
<For each={users}>
|
|
325
|
+
{(user, index) => (
|
|
326
|
+
<div class="user-item">
|
|
327
|
+
{index + 1}. {user.name}
|
|
328
|
+
</div>
|
|
329
|
+
)}
|
|
330
|
+
</For>
|
|
331
|
+
|
|
332
|
+
// With key function for efficient updates
|
|
333
|
+
<For each={todos} key={(todo) => todo.id}>
|
|
334
|
+
{(todo, index) => (
|
|
335
|
+
<li classList={{ completed: todo.completed }}>
|
|
336
|
+
{todo.text}
|
|
337
|
+
</li>
|
|
338
|
+
)}
|
|
339
|
+
</For>
|
|
340
|
+
|
|
341
|
+
// With separators between items
|
|
342
|
+
<For each={breadcrumbs} separator={<span> / </span>}>
|
|
343
|
+
{(crumb, index) => (
|
|
344
|
+
<a href={crumb.url}>{crumb.label}</a>
|
|
345
|
+
)}
|
|
346
|
+
</For>
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
The `For` component automatically uses lit-html's optimized directives:
|
|
350
|
+
|
|
351
|
+
- **Without key**: Uses `map` directive for simple iteration
|
|
352
|
+
- **With key**: Uses `repeat` directive for efficient updates when items change
|
|
353
|
+
- **With separator**: Uses `join` directive to insert elements between items
|
|
354
|
+
|
|
355
|
+
#### Show Component - Conditional Rendering
|
|
356
|
+
|
|
357
|
+
The `Show` component provides type-safe conditional rendering with optional fallback:
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
import { Show } from 'jsx-lit';
|
|
361
|
+
|
|
362
|
+
// Simple conditional rendering
|
|
363
|
+
<Show when={user}>
|
|
364
|
+
{(user) => (
|
|
365
|
+
<div class="welcome">
|
|
366
|
+
Welcome back, {user.name}!
|
|
367
|
+
</div>
|
|
368
|
+
)}
|
|
369
|
+
</Show>
|
|
370
|
+
|
|
371
|
+
// With fallback content
|
|
372
|
+
<Show when={currentUser}>
|
|
373
|
+
{[
|
|
374
|
+
(user) => (
|
|
375
|
+
<div class="user-panel">
|
|
376
|
+
<img src={user.avatar} alt={user.name} />
|
|
377
|
+
<span>{user.name}</span>
|
|
378
|
+
</div>
|
|
379
|
+
),
|
|
380
|
+
() => (
|
|
381
|
+
<div class="login-prompt">
|
|
382
|
+
<button>Sign In</button>
|
|
383
|
+
</div>
|
|
384
|
+
)
|
|
385
|
+
]}
|
|
386
|
+
</Show>
|
|
387
|
+
|
|
388
|
+
// Conditional rendering with complex conditions
|
|
389
|
+
<Show when={items.length > 0}>
|
|
390
|
+
{(length) => (
|
|
391
|
+
<div class="item-count">
|
|
392
|
+
Found {length} items
|
|
393
|
+
</div>
|
|
394
|
+
)}
|
|
395
|
+
</Show>
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
The `Show` component uses lit-html's `when` directive internally and provides strong TypeScript inference for the truthy value.
|
|
399
|
+
|
|
400
|
+
#### Choose Component - Multi-Condition Rendering
|
|
401
|
+
|
|
402
|
+
The `Choose` component enables clean switch-like conditional rendering with multiple condition-output pairs:
|
|
403
|
+
|
|
404
|
+
```tsx
|
|
405
|
+
import { Choose } from 'jsx-lit';
|
|
406
|
+
|
|
407
|
+
// Multiple conditions based on a value
|
|
408
|
+
<Choose value={status}>
|
|
409
|
+
{[
|
|
410
|
+
(status) => status === 'loading',
|
|
411
|
+
() => (
|
|
412
|
+
<div class="loading">
|
|
413
|
+
<spinner-icon></spinner-icon>
|
|
414
|
+
Loading...
|
|
415
|
+
</div>
|
|
416
|
+
)
|
|
417
|
+
]}
|
|
418
|
+
{[
|
|
419
|
+
(status) => status === 'error',
|
|
420
|
+
(status) => (
|
|
421
|
+
<div class="error">
|
|
422
|
+
Error: {status}
|
|
423
|
+
</div>
|
|
424
|
+
)
|
|
425
|
+
]}
|
|
426
|
+
{[
|
|
427
|
+
(status) => status === 'success',
|
|
428
|
+
(status) => (
|
|
429
|
+
<div class="success">
|
|
430
|
+
Operation completed successfully!
|
|
431
|
+
</div>
|
|
432
|
+
)
|
|
433
|
+
]}
|
|
434
|
+
{[
|
|
435
|
+
() => true, // Default case
|
|
436
|
+
(status) => (
|
|
437
|
+
<div class="unknown">
|
|
438
|
+
Unknown status: {status}
|
|
439
|
+
</div>
|
|
440
|
+
)
|
|
441
|
+
]}
|
|
442
|
+
</Choose>
|
|
443
|
+
|
|
444
|
+
// Without a value (boolean conditions)
|
|
445
|
+
<Choose>
|
|
446
|
+
{[
|
|
447
|
+
() => user.isAdmin,
|
|
448
|
+
() => <admin-panel></admin-panel>
|
|
449
|
+
]}
|
|
450
|
+
{[
|
|
451
|
+
() => user.isModerator,
|
|
452
|
+
() => <moderator-panel></moderator-panel>
|
|
453
|
+
]}
|
|
454
|
+
{[
|
|
455
|
+
() => true, // Default case
|
|
456
|
+
() => <user-panel></user-panel>
|
|
457
|
+
]}
|
|
458
|
+
</Choose>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
The `Choose` component evaluates conditions in order and renders the first matching case, similar to a switch statement but as an expression.
|
|
462
|
+
|
|
463
|
+
#### Combining Library Components
|
|
464
|
+
|
|
465
|
+
These components work seamlessly together for complex rendering scenarios:
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
import { For, Show, Choose } from 'jsx-lit';
|
|
469
|
+
|
|
470
|
+
@customElement('user-dashboard')
|
|
471
|
+
export class UserDashboard extends LitElement {
|
|
472
|
+
@property({ type: Array }) users = [];
|
|
473
|
+
@property() currentUser = null;
|
|
474
|
+
@property() viewMode = 'list';
|
|
475
|
+
|
|
476
|
+
render() {
|
|
477
|
+
return (
|
|
478
|
+
<div class="dashboard">
|
|
479
|
+
{/* Conditional user greeting */}
|
|
480
|
+
<Show when={this.currentUser}>
|
|
481
|
+
{(user) => (
|
|
482
|
+
<header class="welcome">
|
|
483
|
+
Welcome back, {user.name}!
|
|
484
|
+
</header>
|
|
485
|
+
)}
|
|
486
|
+
</Show>
|
|
487
|
+
|
|
488
|
+
{/* Dynamic view rendering based on mode */}
|
|
489
|
+
<Choose value={this.viewMode}>
|
|
490
|
+
{[
|
|
491
|
+
(mode) => mode === 'grid',
|
|
492
|
+
() => (
|
|
493
|
+
<div class="user-grid">
|
|
494
|
+
<For each={this.users} key={(user) => user.id}>
|
|
495
|
+
{(user) => (
|
|
496
|
+
<div class="user-card">
|
|
497
|
+
<img src={user.avatar} alt={user.name} />
|
|
498
|
+
<h3>{user.name}</h3>
|
|
499
|
+
<p>{user.role}</p>
|
|
500
|
+
</div>
|
|
501
|
+
)}
|
|
502
|
+
</For>
|
|
503
|
+
</div>
|
|
504
|
+
)
|
|
505
|
+
]}
|
|
506
|
+
{[
|
|
507
|
+
(mode) => mode === 'list',
|
|
508
|
+
() => (
|
|
509
|
+
<div class="user-list">
|
|
510
|
+
<For each={this.users} separator={<hr />}>
|
|
511
|
+
{(user, index) => (
|
|
512
|
+
<div class="user-row">
|
|
513
|
+
<span class="user-index">{index + 1}.</span>
|
|
514
|
+
<span class="user-name">{user.name}</span>
|
|
515
|
+
<span class="user-role">{user.role}</span>
|
|
516
|
+
</div>
|
|
517
|
+
)}
|
|
518
|
+
</For>
|
|
519
|
+
</div>
|
|
520
|
+
)
|
|
521
|
+
]}
|
|
522
|
+
{[
|
|
523
|
+
() => true, // Default case
|
|
524
|
+
(mode) => (
|
|
525
|
+
<div class="error">
|
|
526
|
+
Unknown view mode: {mode}
|
|
527
|
+
</div>
|
|
528
|
+
)
|
|
529
|
+
]}
|
|
530
|
+
</Choose>
|
|
531
|
+
|
|
532
|
+
{/* Conditional empty state */}
|
|
533
|
+
<Show when={this.users.length === 0}>
|
|
534
|
+
{() => (
|
|
535
|
+
<div class="empty-state">
|
|
536
|
+
<p>No users found</p>
|
|
537
|
+
<button on-click={this.loadUsers}>Load Users</button>
|
|
538
|
+
</div>
|
|
539
|
+
)}
|
|
540
|
+
</Show>
|
|
541
|
+
</div>
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
## 🔧 Advanced Usage
|
|
548
|
+
|
|
549
|
+
### Lit Directives Integration
|
|
550
|
+
|
|
551
|
+
jsx-lit works seamlessly with all Lit directives:
|
|
552
|
+
|
|
553
|
+
```tsx
|
|
554
|
+
import { when } from 'lit-html/directives/when.js';
|
|
555
|
+
import { repeat } from 'lit-html/directives/repeat.js';
|
|
556
|
+
import { guard } from 'lit-html/directives/guard.js';
|
|
557
|
+
|
|
558
|
+
return (
|
|
559
|
+
<div>
|
|
560
|
+
{when(condition, () => <p>Conditional content</p>)}
|
|
561
|
+
{repeat(items, item => item.id, item => (
|
|
562
|
+
<li key={item.id}>{item.name}</li>
|
|
563
|
+
))}
|
|
564
|
+
{guard([expensiveData], () => (
|
|
565
|
+
<ExpensiveComponent data={expensiveData} />
|
|
566
|
+
))}
|
|
567
|
+
</div>
|
|
568
|
+
);
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### Complex Example: Todo List
|
|
572
|
+
|
|
573
|
+
```tsx
|
|
574
|
+
@customElement('todo-list')
|
|
575
|
+
export class TodoList extends LitElement {
|
|
576
|
+
@property({ type: Array }) items = [];
|
|
577
|
+
@state() private newItemText = '';
|
|
578
|
+
@state() private filter = 'all';
|
|
579
|
+
|
|
580
|
+
private inputRef = createRef();
|
|
581
|
+
|
|
582
|
+
get filteredItems() {
|
|
583
|
+
switch (this.filter) {
|
|
584
|
+
case 'active': return this.items.filter(item => !item.completed);
|
|
585
|
+
case 'completed': return this.items.filter(item => item.completed);
|
|
586
|
+
default: return this.items;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
addItem() {
|
|
591
|
+
if (this.newItemText.trim()) {
|
|
592
|
+
this.items = [...this.items, {
|
|
593
|
+
id: Date.now(),
|
|
594
|
+
text: this.newItemText,
|
|
595
|
+
completed: false
|
|
596
|
+
}];
|
|
597
|
+
this.newItemText = '';
|
|
598
|
+
this.inputRef.value?.focus();
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
render() {
|
|
603
|
+
return (
|
|
604
|
+
<div class="todo-container">
|
|
605
|
+
<h1>Todo List</h1>
|
|
606
|
+
|
|
607
|
+
<div class="add-form">
|
|
608
|
+
<input
|
|
609
|
+
ref={this.inputRef}
|
|
610
|
+
value={as.prop(this.newItemText)}
|
|
611
|
+
placeholder="Add new todo..."
|
|
612
|
+
on-input={(e) => this.newItemText = e.target.value}
|
|
613
|
+
on-keydown={(e) => e.key === 'Enter' && this.addItem()}
|
|
614
|
+
/>
|
|
615
|
+
<button on-click={this.addItem}>Add</button>
|
|
616
|
+
</div>
|
|
617
|
+
|
|
618
|
+
<div class="filters">
|
|
619
|
+
{['all', 'active', 'completed'].map(filterType => (
|
|
620
|
+
<button
|
|
621
|
+
classList={{ active: this.filter === filterType }}
|
|
622
|
+
on-click={() => this.filter = filterType}
|
|
623
|
+
>
|
|
624
|
+
{filterType}
|
|
625
|
+
</button>
|
|
626
|
+
))}
|
|
627
|
+
</div>
|
|
628
|
+
|
|
629
|
+
{when(this.filteredItems.length > 0, () => (
|
|
630
|
+
<ul class="todo-list">
|
|
631
|
+
{repeat(this.filteredItems, item => item.id, item => (
|
|
632
|
+
<TodoItem
|
|
633
|
+
todo={item}
|
|
634
|
+
onToggle={(id) => this.toggleItem(id)}
|
|
635
|
+
onDelete={(id) => this.deleteItem(id)}
|
|
636
|
+
/>
|
|
637
|
+
))}
|
|
638
|
+
</ul>
|
|
639
|
+
), () => (
|
|
640
|
+
<p class="empty-state">No items to show</p>
|
|
641
|
+
))}
|
|
642
|
+
</div>
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## 🎛️ Configuration
|
|
649
|
+
|
|
650
|
+
### Vite Plugin Options
|
|
651
|
+
|
|
652
|
+
```typescript
|
|
653
|
+
import { litJsx } from 'jsx-lit/vite-jsx-preserve';
|
|
654
|
+
|
|
655
|
+
export default defineConfig({
|
|
656
|
+
plugins: [
|
|
657
|
+
litJsx({
|
|
658
|
+
babel: {
|
|
659
|
+
// Babel transform options
|
|
660
|
+
plugins: ['@babel/plugin-proposal-decorators'],
|
|
661
|
+
},
|
|
662
|
+
// Or use a function for dynamic configuration
|
|
663
|
+
babel: (code, id) => ({
|
|
664
|
+
plugins: id.includes('legacy') ? [] : ['modern-plugin'],
|
|
665
|
+
}),
|
|
666
|
+
}),
|
|
667
|
+
],
|
|
668
|
+
});
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## 🚀 Template Types
|
|
672
|
+
|
|
673
|
+
jsx-lit automatically detects and uses the appropriate template type:
|
|
674
|
+
|
|
675
|
+
- **HTML templates**: `html\`...\`` for regular HTML elements
|
|
676
|
+
- **SVG templates**: `svg\`...\`` for SVG elements
|
|
677
|
+
- **MathML templates**: `mathml\`...\`` for MathML elements
|
|
678
|
+
- **Static templates**: `htmlStatic\`...\`` for dynamic tag names
|
|
679
|
+
|
|
680
|
+
## 🎯 Best Practices
|
|
681
|
+
|
|
682
|
+
### When to Use Each Binding Type
|
|
683
|
+
|
|
684
|
+
#### **Attribute Binding (Default)**
|
|
685
|
+
|
|
686
|
+
- Custom attributes and data attributes
|
|
687
|
+
- Values that should appear in HTML as attributes
|
|
688
|
+
- Working with libraries that expect attributes
|
|
689
|
+
|
|
690
|
+
```tsx
|
|
691
|
+
<div data-id={item.id} aria-label={item.description} />
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
#### **Property Binding (`as.prop()` or `prop =>`)**
|
|
695
|
+
|
|
696
|
+
- Standard DOM properties like `value`, `checked`, `selected`
|
|
697
|
+
- Interactive elements that need live property updates
|
|
698
|
+
- Complex object values
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
<input value={as.prop(formData.email)} checked={as.prop(isSelected)} />
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
#### **Boolean Attribute Binding (`as.bool()` or `bool =>`)**
|
|
705
|
+
|
|
706
|
+
- Boolean HTML attributes like `disabled`, `hidden`, `readonly`
|
|
707
|
+
- Accessibility attributes that follow boolean patterns
|
|
708
|
+
- Presence/absence semantics
|
|
709
|
+
|
|
710
|
+
```tsx
|
|
711
|
+
<button disabled={as.bool(isLoading)} hidden={as.bool(!isVisible)} />
|
|
712
|
+
```
|
|
713
|
+
|
|
714
|
+
### Function Component Guidelines
|
|
715
|
+
|
|
716
|
+
- Use descriptive prop names and provide defaults where appropriate
|
|
717
|
+
- Keep components focused and composable
|
|
718
|
+
- Leverage TypeScript for better developer experience
|
|
719
|
+
- Handle `children` appropriately for flexible composition
|
|
720
|
+
|
|
721
|
+
### Dynamic Tag Best Practices
|
|
722
|
+
|
|
723
|
+
- Always use `toTag()` with the `.tag` property pattern
|
|
724
|
+
- Use descriptive variable names for clarity
|
|
725
|
+
- Consider TypeScript for better type safety with HTML elements
|
|
726
|
+
- Document complex dynamic tag logic
|
|
727
|
+
|
|
728
|
+
## 🔗 Ecosystem Integration
|
|
729
|
+
|
|
730
|
+
jsx-lit is designed to work seamlessly with the entire Lit ecosystem:
|
|
731
|
+
|
|
732
|
+
- **Lit Elements**: Full compatibility with LitElement and reactive properties
|
|
733
|
+
- **Lit Directives**: All official and community directives work out of the box
|
|
734
|
+
- **Custom Elements**: Easy integration with any custom elements
|
|
735
|
+
- **Web Components**: Standard web component patterns and lifecycle
|
|
736
|
+
- **TypeScript**: Comprehensive type definitions for the best developer experience
|
|
737
|
+
|
|
738
|
+
## 📚 Migration Guide
|
|
739
|
+
|
|
740
|
+
### From React JSX
|
|
741
|
+
|
|
742
|
+
jsx-lit syntax is very similar to React, with a few key differences:
|
|
743
|
+
|
|
744
|
+
```tsx
|
|
745
|
+
// React
|
|
746
|
+
<button onClick={handler} className="btn" />
|
|
747
|
+
|
|
748
|
+
// jsx-lit
|
|
749
|
+
<button on-click={handler} class="btn" />
|
|
750
|
+
```
|
|
751
|
+
|
|
752
|
+
### From Lit html Templates
|
|
753
|
+
|
|
754
|
+
```tsx
|
|
755
|
+
// Lit html
|
|
756
|
+
html`<div class=${classMap(classes)}>${content}</div>`
|
|
757
|
+
|
|
758
|
+
// jsx-lit
|
|
759
|
+
<div classList={classes}>{content}</div>
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
## 🤝 Contributing
|
|
763
|
+
|
|
764
|
+
jsx-lit is part of the larger Weave project. Contributions are welcome!
|
|
765
|
+
|
|
766
|
+
## 📄 License
|
|
767
|
+
|
|
768
|
+
Apache-2.0
|