@diniz/webcomponents 1.1.5 → 1.1.6
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 +291 -824
- package/dist/README.md +291 -824
- package/dist/webcomponents.es.js +197 -168
- package/dist/webcomponents.umd.js +57 -57
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,948 +12,415 @@ A lightweight, framework-agnostic web components library built with vanilla Type
|
|
|
12
12
|
🎯 **Tree-shakeable** - Import only what you need
|
|
13
13
|
♿ **Accessible** - ARIA attributes and keyboard navigation
|
|
14
14
|
|
|
15
|
-
## 🚀 Live Demo
|
|
15
|
+
## 🚀 Live Demo & Component Documentation
|
|
16
16
|
|
|
17
|
-
Check out the interactive demo and component
|
|
17
|
+
Check out the interactive demo and explore component implementations:
|
|
18
18
|
|
|
19
19
|
**[View Live Demo →](https://rodiniz.github.io/webcomponents/)**
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
**Component source code and demos are located in the `src/features/` directory:**
|
|
22
|
+
- Button Demo: `src/features/button-demo/`
|
|
23
|
+
- Input Demo: `src/features/input-demo/`
|
|
24
|
+
- Table Demo: `src/features/table-demo/`
|
|
25
|
+
- Form Demo: `src/features/form-demo/`
|
|
26
|
+
- Date Picker Demo: `src/features/date-picker-demo/`
|
|
27
|
+
- And more...
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Each demo includes the component implementation and usage examples. Visit the live demo site to see all components in action.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
# Create a new Vite project with vanilla TypeScript template
|
|
35
|
-
npm create vite@latest my-app -- --template vanilla-ts
|
|
36
|
-
cd my-app
|
|
37
|
-
npm install
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### 2. Install the Library
|
|
31
|
+
## Installation
|
|
41
32
|
|
|
42
33
|
```bash
|
|
43
34
|
npm install @diniz/webcomponents
|
|
44
35
|
```
|
|
45
36
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
In your `src/main.ts` file:
|
|
37
|
+
## Quick Start
|
|
49
38
|
|
|
50
39
|
```typescript
|
|
51
40
|
import '@diniz/webcomponents';
|
|
52
|
-
import '@diniz/webcomponents/dist/style.css';
|
|
53
|
-
|
|
54
|
-
//
|
|
55
|
-
document.
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
<ui-button variant="primary" size="md">Click Me</ui-button>
|
|
59
|
-
<ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
|
|
60
|
-
</div>
|
|
41
|
+
import '@diniz/webcomponents/dist/style.css';
|
|
42
|
+
|
|
43
|
+
// Components are now available
|
|
44
|
+
document.body.innerHTML = `
|
|
45
|
+
<ui-button variant="primary">Click Me</ui-button>
|
|
46
|
+
<ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
|
|
61
47
|
`;
|
|
62
48
|
```
|
|
63
49
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
In your `index.html`:
|
|
67
|
-
|
|
68
|
-
```html
|
|
69
|
-
<!DOCTYPE html>
|
|
70
|
-
<html lang="en">
|
|
71
|
-
<head>
|
|
72
|
-
<meta charset="UTF-8" />
|
|
73
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
74
|
-
<title>My App</title>
|
|
75
|
-
</head>
|
|
76
|
-
<body>
|
|
77
|
-
<div id="app">
|
|
78
|
-
<ui-button variant="primary">Click Me</ui-button>
|
|
79
|
-
<ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
|
|
80
|
-
<ui-table></ui-table>
|
|
81
|
-
</div>
|
|
82
|
-
<script type="module" src="/src/main.ts"></script>
|
|
83
|
-
</body>
|
|
84
|
-
</html>
|
|
85
|
-
```
|
|
50
|
+
## Components
|
|
86
51
|
|
|
87
|
-
|
|
52
|
+
- **ui-button** - Button with variants, sizes, icons
|
|
53
|
+
- **ui-input** - Input with validation
|
|
54
|
+
- **ui-table** - Data table with actions
|
|
55
|
+
- **ui-date-picker** - Date picker
|
|
56
|
+
- **ui-pagination** - Pagination control
|
|
57
|
+
- **ui-select** - Dropdown selection
|
|
58
|
+
- **ui-checkbox** - Checkbox input
|
|
59
|
+
- **ui-modal** - Modal dialog
|
|
60
|
+
- **ui-card** - Card container
|
|
61
|
+
- **ui-tabs** - Tab navigation
|
|
62
|
+
- **ui-stepper** - Step indicator
|
|
63
|
+
- **ui-toast** - Toast notifications
|
|
64
|
+
- **ui-upload** - File upload
|
|
65
|
+
- **ui-layout** - Application layout
|
|
66
|
+
|
|
67
|
+
For detailed documentation on each component, see the demo implementations in `src/features/`.
|
|
88
68
|
|
|
89
|
-
|
|
90
|
-
// Wait for components to be defined
|
|
91
|
-
customElements.whenDefined('ui-button').then(() => {
|
|
92
|
-
const button = document.querySelector('ui-button');
|
|
93
|
-
button?.addEventListener('click', () => {
|
|
94
|
-
console.log('Button clicked!');
|
|
95
|
-
});
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
// Listen to custom events
|
|
99
|
-
const picker = document.querySelector('ui-date-picker');
|
|
100
|
-
picker?.addEventListener('date-change', ((e: CustomEvent) => {
|
|
101
|
-
console.log('Date selected:', e.detail.value);
|
|
102
|
-
}) as EventListener);
|
|
103
|
-
```
|
|
69
|
+
## Core Features
|
|
104
70
|
|
|
105
|
-
###
|
|
71
|
+
### Signals & Reactivity
|
|
106
72
|
|
|
107
|
-
|
|
73
|
+
Create reactive, auto-updating UI with signals. Changes automatically trigger re-renders:
|
|
108
74
|
|
|
109
75
|
```typescript
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
disabled?: boolean;
|
|
116
|
-
}
|
|
76
|
+
import { BaseComponent } from '@diniz/webcomponents';
|
|
77
|
+
|
|
78
|
+
class CounterComponent extends BaseComponent {
|
|
79
|
+
// Create a reactive signal with initial value 0
|
|
80
|
+
private count = this.useSignal(0);
|
|
117
81
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
min?: string;
|
|
122
|
-
max?: string;
|
|
82
|
+
connectedCallback() {
|
|
83
|
+
super.connectedCallback();
|
|
84
|
+
this.render();
|
|
123
85
|
}
|
|
124
|
-
|
|
125
|
-
// Add other component interfaces as needed
|
|
126
|
-
}
|
|
127
86
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
'ui-date-picker': import('@diniz/webcomponents').UIDatePicker;
|
|
132
|
-
// Add other components as needed
|
|
87
|
+
private increment() {
|
|
88
|
+
// Update signal value - automatically triggers re-render
|
|
89
|
+
this.count.set(this.count.get() + 1);
|
|
133
90
|
}
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### 7. Build for Production
|
|
138
|
-
|
|
139
|
-
```bash
|
|
140
|
-
npm run build
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
The build output will be in the `dist` folder, ready to deploy to any static hosting service.
|
|
144
|
-
|
|
145
|
-
### Tree-shaking (Import Only What You Need)
|
|
146
|
-
|
|
147
|
-
You can import individual components to reduce bundle size:
|
|
148
|
-
|
|
149
|
-
```typescript
|
|
150
|
-
// Import only specific components
|
|
151
|
-
import { UIButton } from '@diniz/webcomponents';
|
|
152
|
-
import '@diniz/webcomponents/dist/style.css';
|
|
153
|
-
|
|
154
|
-
// The component is automatically registered
|
|
155
|
-
// Now you can use <ui-button> in your HTML
|
|
156
|
-
```
|
|
157
91
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
**CSS Customization** - Override CSS custom properties to match your theme:
|
|
92
|
+
private reset() {
|
|
93
|
+
this.count.set(0);
|
|
94
|
+
}
|
|
163
95
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
96
|
+
render() {
|
|
97
|
+
const currentCount = this.count.get();
|
|
98
|
+
|
|
99
|
+
this.shadowRoot!.innerHTML = `
|
|
100
|
+
<style>
|
|
101
|
+
:host {
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
gap: 1rem;
|
|
105
|
+
padding: 2rem;
|
|
106
|
+
}
|
|
107
|
+
.count-display {
|
|
108
|
+
font-size: 2rem;
|
|
109
|
+
font-weight: bold;
|
|
110
|
+
color: var(--color-primary, #24ec71);
|
|
111
|
+
}
|
|
112
|
+
</style>
|
|
113
|
+
|
|
114
|
+
<div class="count-display">Count: ${currentCount}</div>
|
|
115
|
+
<button id="increment">Increment</button>
|
|
116
|
+
<button id="reset">Reset</button>
|
|
117
|
+
`;
|
|
118
|
+
|
|
119
|
+
// Connect event listeners to update signals
|
|
120
|
+
this.shadowRoot!.getElementById('increment')?.addEventListener('click',
|
|
121
|
+
() => this.increment()
|
|
122
|
+
);
|
|
123
|
+
this.shadowRoot!.getElementById('reset')?.addEventListener('click',
|
|
124
|
+
() => this.reset()
|
|
125
|
+
);
|
|
126
|
+
}
|
|
176
127
|
}
|
|
177
|
-
```
|
|
178
|
-
**Update `src/main.ts`:**
|
|
179
|
-
```typescript
|
|
180
|
-
import '@diniz/webcomponents';
|
|
181
|
-
import '@diniz/webcomponents/dist/style.css';
|
|
182
128
|
|
|
183
|
-
|
|
184
|
-
<div>
|
|
185
|
-
<h1>My Web Components App</h1>
|
|
186
|
-
<ui-button variant="primary">Click Me</ui-button>
|
|
187
|
-
<ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
|
|
188
|
-
<ui-table id="myTable"></ui-table>
|
|
189
|
-
</div>
|
|
190
|
-
`;
|
|
191
|
-
|
|
192
|
-
// Add some data to the table
|
|
193
|
-
const table = document.getElementById('myTable') as any;
|
|
194
|
-
table.data = {
|
|
195
|
-
columns: [
|
|
196
|
-
{ key: 'name', label: 'Name' },
|
|
197
|
-
{ key: 'role', label: 'Role' }
|
|
198
|
-
],
|
|
199
|
-
rows: [
|
|
200
|
-
{ name: 'Alice', role: 'Admin' },
|
|
201
|
-
{ name: 'Bob', role: 'User' }
|
|
202
|
-
]
|
|
203
|
-
};
|
|
129
|
+
customElements.define('counter-app', CounterComponent);
|
|
204
130
|
```
|
|
205
131
|
|
|
206
|
-
|
|
207
|
-
|
|
132
|
+
**Usage in HTML:**
|
|
208
133
|
```html
|
|
209
|
-
<
|
|
210
|
-
import '@diniz/webcomponents';
|
|
211
|
-
</script>
|
|
212
|
-
|
|
213
|
-
<ui-button variant="primary">Click Me</ui-button>
|
|
214
|
-
<ui-date-picker format="DD/MM/YYYY"></ui-date-picker>
|
|
215
|
-
<ui-table></ui-table>
|
|
134
|
+
<counter-app></counter-app>
|
|
216
135
|
```
|
|
217
136
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
137
|
+
**Key features:**
|
|
138
|
+
- `this.useSignal(value)` - Create a reactive signal
|
|
139
|
+
- `signal.get()` - Read the current value
|
|
140
|
+
- `signal.set(newValue)` - Update value and trigger re-render
|
|
141
|
+
- `this.setState({ ... })` - Update multiple values at once
|
|
142
|
+
- Changes are isolated within component's shadow DOM
|
|
221
143
|
|
|
222
|
-
|
|
144
|
+
### Signals with Separated HTML & TypeScript Files
|
|
223
145
|
|
|
224
|
-
|
|
225
|
-
- 3 variants: `primary`, `secondary`, `ghost`
|
|
226
|
-
- 3 sizes: `sm`, `md`, `lg`
|
|
227
|
-
- Icon support with [Feather Icons](https://feathericons.com/)
|
|
228
|
-
- Icon positioning (left/right)
|
|
229
|
-
- Icon-only buttons
|
|
230
|
-
- Disabled state support
|
|
231
|
-
- Button type support
|
|
232
|
-
- Smooth transitions and hover effects
|
|
146
|
+
When using separate HTML template files, you can achieve automatic reactivity so the HTML updates whenever a signal changes:
|
|
233
147
|
|
|
234
|
-
**
|
|
148
|
+
**counter.html**
|
|
235
149
|
```html
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
- `icon` - Icon name from Feather Icons
|
|
250
|
-
- `icon-position` - Icon position (`left` | `right`, default: `left`)
|
|
251
|
-
- `disabled` - Disable the button
|
|
252
|
-
- `type` - Button type (`button` | `submit` | `reset`)
|
|
253
|
-
|
|
254
|
-
---
|
|
255
|
-
|
|
256
|
-
### 📅 Date Picker (`ui-date-picker`)
|
|
257
|
-
|
|
258
|
-
A customizable date picker with multiple format options and calendar support.
|
|
259
|
-
|
|
260
|
-
**Features:**
|
|
261
|
-
- 5 date formats: `YYYY-MM-DD`, `DD/MM/YYYY`, `MM/DD/YYYY`, `DD-MM-YYYY`, `MM-DD-YYYY`
|
|
262
|
-
- Native calendar picker integration
|
|
263
|
-
- Min/max date constraints
|
|
264
|
-
- Text input with format validation
|
|
265
|
-
- Real-time format conversion
|
|
266
|
-
- Disabled state support
|
|
267
|
-
- Custom events for date changes
|
|
150
|
+
<style>
|
|
151
|
+
:host {
|
|
152
|
+
display: flex;
|
|
153
|
+
flex-direction: column;
|
|
154
|
+
gap: 1rem;
|
|
155
|
+
padding: 2rem;
|
|
156
|
+
}
|
|
157
|
+
.count-display {
|
|
158
|
+
font-size: 2rem;
|
|
159
|
+
font-weight: bold;
|
|
160
|
+
color: var(--color-primary, #24ec71);
|
|
161
|
+
}
|
|
162
|
+
</style>
|
|
268
163
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
min="2026-01-01"
|
|
275
|
-
max="2026-12-31"
|
|
276
|
-
></ui-date-picker>
|
|
277
|
-
|
|
278
|
-
<script>
|
|
279
|
-
const picker = document.querySelector('ui-date-picker');
|
|
280
|
-
|
|
281
|
-
picker.addEventListener('date-change', (e) => {
|
|
282
|
-
console.log('ISO:', e.detail.value);
|
|
283
|
-
console.log('Formatted:', e.detail.formattedValue);
|
|
284
|
-
});
|
|
285
|
-
</script>
|
|
164
|
+
<div class="count-display">
|
|
165
|
+
Count: <span id="countValue">0</span>
|
|
166
|
+
</div>
|
|
167
|
+
<button id="incrementBtn">Increment</button>
|
|
168
|
+
<button id="resetBtn">Reset</button>
|
|
286
169
|
```
|
|
287
170
|
|
|
288
|
-
**
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
- `max` - Maximum date (ISO format)
|
|
293
|
-
- `disabled` - Disable the picker
|
|
294
|
-
- `placeholder` - Placeholder text
|
|
295
|
-
|
|
296
|
-
**Methods:**
|
|
297
|
-
- `getISOValue()` - Get date in ISO format
|
|
298
|
-
- `getFormattedValue()` - Get date in display format
|
|
299
|
-
- `setValue(isoDate)` - Set the date value
|
|
300
|
-
- `clear()` - Clear the date
|
|
301
|
-
|
|
302
|
-
**Events:**
|
|
303
|
-
- `date-change` - Fired when date changes
|
|
304
|
-
- `date-input` - Fired during input
|
|
305
|
-
|
|
306
|
-
---
|
|
307
|
-
|
|
308
|
-
### 📋 Table (`ui-table`)
|
|
309
|
-
|
|
310
|
-
A dynamic data table with customizable columns and alignment.
|
|
311
|
-
|
|
312
|
-
**Features:**
|
|
313
|
-
- Dynamic column configuration
|
|
314
|
-
- Text alignment per column (left, center, right)
|
|
315
|
-
- Responsive layout
|
|
316
|
-
- Automatic row rendering
|
|
317
|
-
- Theme-aware styling
|
|
318
|
-
|
|
319
|
-
**Usage:**
|
|
320
|
-
```html
|
|
321
|
-
<ui-table id="myTable"></ui-table>
|
|
171
|
+
**counter.ts - Automatic Reactivity Approach**
|
|
172
|
+
```typescript
|
|
173
|
+
import { BaseComponent } from '@diniz/webcomponents';
|
|
174
|
+
import template from './counter.html?raw';
|
|
322
175
|
|
|
323
|
-
|
|
324
|
-
|
|
176
|
+
class CounterComponent extends BaseComponent {
|
|
177
|
+
private count = this.useSignal(0);
|
|
325
178
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
};
|
|
337
|
-
</script>
|
|
338
|
-
```
|
|
179
|
+
connectedCallback() {
|
|
180
|
+
super.connectedCallback();
|
|
181
|
+
this.render();
|
|
182
|
+
this.setupEventListeners();
|
|
183
|
+
|
|
184
|
+
// Subscribe to signal changes - re-render when count changes
|
|
185
|
+
this.watchSignal(this.count, () => {
|
|
186
|
+
this.updateCountDisplay();
|
|
187
|
+
});
|
|
188
|
+
}
|
|
339
189
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
- `rows`: Array of objects matching column keys
|
|
190
|
+
private increment() {
|
|
191
|
+
this.count.set(this.count.get() + 1);
|
|
192
|
+
}
|
|
344
193
|
|
|
345
|
-
|
|
194
|
+
private reset() {
|
|
195
|
+
this.count.set(0);
|
|
196
|
+
}
|
|
346
197
|
|
|
347
|
-
|
|
198
|
+
// Single method that updates the display - called whenever signal changes
|
|
199
|
+
private updateCountDisplay() {
|
|
200
|
+
const countValue = this.shadowRoot?.getElementById('countValue');
|
|
201
|
+
if (countValue) {
|
|
202
|
+
countValue.textContent = String(this.count.get());
|
|
203
|
+
}
|
|
204
|
+
}
|
|
348
205
|
|
|
349
|
-
|
|
206
|
+
private setupEventListeners() {
|
|
207
|
+
this.shadowRoot?.getElementById('incrementBtn')?.addEventListener('click',
|
|
208
|
+
() => this.increment()
|
|
209
|
+
);
|
|
210
|
+
this.shadowRoot?.getElementById('resetBtn')?.addEventListener('click',
|
|
211
|
+
() => this.reset()
|
|
212
|
+
);
|
|
213
|
+
}
|
|
350
214
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
- Disabled states for edge pages
|
|
357
|
-
- Custom events for page changes
|
|
358
|
-
- ARIA labels for accessibility
|
|
215
|
+
render() {
|
|
216
|
+
this.shadowRoot!.innerHTML = template;
|
|
217
|
+
this.updateCountDisplay(); // Initial display
|
|
218
|
+
}
|
|
219
|
+
}
|
|
359
220
|
|
|
360
|
-
|
|
361
|
-
```html
|
|
362
|
-
<ui-pagination
|
|
363
|
-
total="250"
|
|
364
|
-
current-page="5"
|
|
365
|
-
page-size="10"
|
|
366
|
-
></ui-pagination>
|
|
367
|
-
|
|
368
|
-
<script>
|
|
369
|
-
const pagination = document.querySelector('ui-pagination');
|
|
370
|
-
|
|
371
|
-
pagination.addEventListener('page-change', (e) => {
|
|
372
|
-
console.log('Page:', e.detail.page);
|
|
373
|
-
console.log('Total Pages:', e.detail.totalPages);
|
|
374
|
-
// Load new data...
|
|
375
|
-
});
|
|
376
|
-
</script>
|
|
221
|
+
customElements.define('counter-app', CounterComponent);
|
|
377
222
|
```
|
|
378
223
|
|
|
379
|
-
**
|
|
380
|
-
- `total` - Total number of items
|
|
381
|
-
- `current-page` - Current page number
|
|
382
|
-
- `page-size` - Items per page (default: 10)
|
|
383
|
-
|
|
384
|
-
**Computed Properties:**
|
|
385
|
-
- `totalPages` - Total number of pages
|
|
386
|
-
|
|
387
|
-
**Events:**
|
|
388
|
-
- `page-change` - Fired when page changes, includes pagination details
|
|
389
|
-
|
|
390
|
-
---
|
|
391
|
-
|
|
392
|
-
### 📝 Input (`ui-input`)
|
|
393
|
-
|
|
394
|
-
Advanced form input with built-in validation and error handling.
|
|
395
|
-
|
|
396
|
-
**Features:**
|
|
397
|
-
- Multiple input types: `text`, `email`, `password`, `number`, `tel`, `url`
|
|
398
|
-
- Built-in validation rules:
|
|
399
|
-
- Email domain validation
|
|
400
|
-
- Password matching
|
|
401
|
-
- Min/max length
|
|
402
|
-
- Regex patterns
|
|
403
|
-
- Custom validators
|
|
404
|
-
- Real-time validation feedback
|
|
405
|
-
- Error message display
|
|
406
|
-
- Touched state tracking
|
|
407
|
-
- Disabled state support
|
|
408
|
-
|
|
409
|
-
**Usage:**
|
|
410
|
-
```html
|
|
411
|
-
<ui-input
|
|
412
|
-
type="email"
|
|
413
|
-
label="Email"
|
|
414
|
-
placeholder="you@example.com"
|
|
415
|
-
required
|
|
416
|
-
validate="emailDomain:company.com"
|
|
417
|
-
></ui-input>
|
|
418
|
-
|
|
419
|
-
<ui-input
|
|
420
|
-
type="password"
|
|
421
|
-
label="Password"
|
|
422
|
-
minlength="8"
|
|
423
|
-
required
|
|
424
|
-
></ui-input>
|
|
425
|
-
```
|
|
224
|
+
**Alternative: Full Re-render on Signal Change**
|
|
426
225
|
|
|
427
|
-
|
|
428
|
-
- `type` - Input type
|
|
429
|
-
- `label` - Label text
|
|
430
|
-
- `placeholder` - Placeholder text
|
|
431
|
-
- `required` - Required field
|
|
432
|
-
- `pattern` - Regex pattern
|
|
433
|
-
- `minlength` / `maxlength` - Length constraints
|
|
434
|
-
- `min` / `max` - Number constraints
|
|
435
|
-
- `error-message` - Custom error message
|
|
436
|
-
- `disabled` - Disable input
|
|
437
|
-
- `name` - Form field name
|
|
438
|
-
- `validate` - Validation rule (e.g., `emailDomain:company.com`)
|
|
439
|
-
|
|
440
|
-
**State:**
|
|
441
|
-
- `value` - Current input value
|
|
442
|
-
- `valid` - Validation state
|
|
443
|
-
- `touched` - Whether field has been interacted with
|
|
444
|
-
- `error` - Current error message
|
|
445
|
-
|
|
446
|
-
---
|
|
447
|
-
|
|
448
|
-
### 🪟 Modal (`ui-modal`)
|
|
449
|
-
|
|
450
|
-
Responsive modal dialog with customizable sizes and behaviors.
|
|
451
|
-
|
|
452
|
-
**Features:**
|
|
453
|
-
- 5 size options: `sm`, `md`, `lg`, `xl`, `full`
|
|
454
|
-
- Auto-close on Escape key (configurable)
|
|
455
|
-
- Auto-close on backdrop click (configurable)
|
|
456
|
-
- Smooth animations (fade in, slide up)
|
|
457
|
-
- Header, body, and footer slots
|
|
458
|
-
- Programmatic open/close API
|
|
459
|
-
- Custom events
|
|
460
|
-
- Body scroll lock when open
|
|
461
|
-
|
|
462
|
-
**Usage:**
|
|
463
|
-
```html
|
|
464
|
-
<ui-button id="openModal">Open Modal</ui-button>
|
|
226
|
+
For simpler components, you can also re-render the entire template when any signal changes:
|
|
465
227
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
<div slot="footer">
|
|
471
|
-
<ui-button id="closeBtn" variant="secondary">Cancel</ui-button>
|
|
472
|
-
<ui-button id="confirmBtn" variant="primary">Confirm</ui-button>
|
|
473
|
-
</div>
|
|
474
|
-
</ui-modal>
|
|
475
|
-
|
|
476
|
-
<script>
|
|
477
|
-
const modal = document.getElementById('myModal');
|
|
478
|
-
const openBtn = document.getElementById('openModal');
|
|
479
|
-
const closeBtn = document.getElementById('closeBtn');
|
|
480
|
-
|
|
481
|
-
openBtn.addEventListener('click', () => modal.open());
|
|
482
|
-
closeBtn.addEventListener('click', () => modal.close());
|
|
228
|
+
```typescript
|
|
229
|
+
class CounterComponent extends BaseComponent {
|
|
230
|
+
private count = this.useSignal(0);
|
|
483
231
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
- `title` - Modal title text
|
|
492
|
-
- `size` - Modal size (`sm` | `md` | `lg` | `xl` | `full`)
|
|
493
|
-
- `open` - Open state attribute
|
|
494
|
-
- `no-close-on-escape` - Disable closing on Escape key
|
|
495
|
-
- `no-close-on-backdrop` - Disable closing on backdrop click
|
|
496
|
-
|
|
497
|
-
**Methods:**
|
|
498
|
-
- `open()` - Open the modal
|
|
499
|
-
- `close()` - Close the modal
|
|
500
|
-
|
|
501
|
-
**Events:**
|
|
502
|
-
- `modal-open` - Fired when modal opens
|
|
503
|
-
- `modal-close` - Fired when modal closes
|
|
504
|
-
|
|
505
|
-
---
|
|
506
|
-
|
|
507
|
-
### 📋 Select (`ui-select`)
|
|
508
|
-
|
|
509
|
-
Customizable dropdown select with search capability.
|
|
232
|
+
connectedCallback() {
|
|
233
|
+
super.connectedCallback();
|
|
234
|
+
this.render();
|
|
235
|
+
this.setupEventListeners();
|
|
236
|
+
// Re-render entire component when signal changes
|
|
237
|
+
this.watchSignal(this.count, () => this.render());
|
|
238
|
+
}
|
|
510
239
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
- Disabled options support
|
|
516
|
-
- Custom placeholder text
|
|
517
|
-
- Change events with full option details
|
|
518
|
-
- Click-outside to close
|
|
519
|
-
- Smooth animations
|
|
520
|
-
- Theme-aware styling
|
|
240
|
+
private increment() {
|
|
241
|
+
this.count.set(this.count.get() + 1);
|
|
242
|
+
// No need to manually update - render() is called automatically
|
|
243
|
+
}
|
|
521
244
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
id="mySelect"
|
|
526
|
-
label="Choose a Country"
|
|
527
|
-
placeholder="Select country..."
|
|
528
|
-
searchable
|
|
529
|
-
></ui-select>
|
|
530
|
-
|
|
531
|
-
<script>
|
|
532
|
-
const select = document.getElementById('mySelect');
|
|
533
|
-
|
|
534
|
-
// Set options
|
|
535
|
-
const options = [
|
|
536
|
-
{ value: 'us', label: 'United States' },
|
|
537
|
-
{ value: 'uk', label: 'United Kingdom' },
|
|
538
|
-
{ value: 'ca', label: 'Canada' },
|
|
539
|
-
{ value: 'au', label: 'Australia', disabled: true }
|
|
540
|
-
];
|
|
541
|
-
|
|
542
|
-
select.setAttribute('options', JSON.stringify(options));
|
|
543
|
-
|
|
544
|
-
// Set initial value
|
|
545
|
-
select.setAttribute('value', 'us');
|
|
546
|
-
|
|
547
|
-
// Listen for changes
|
|
548
|
-
select.addEventListener('select-change', (e) => {
|
|
549
|
-
console.log('Value:', e.detail.value);
|
|
550
|
-
console.log('Option:', e.detail.option);
|
|
551
|
-
});
|
|
552
|
-
</script>
|
|
553
|
-
```
|
|
245
|
+
private reset() {
|
|
246
|
+
this.count.set(0);
|
|
247
|
+
}
|
|
554
248
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
249
|
+
private setupEventListeners() {
|
|
250
|
+
// Re-attach listeners after each render
|
|
251
|
+
this.shadowRoot?.getElementById('incrementBtn')?.addEventListener('click',
|
|
252
|
+
() => this.increment()
|
|
253
|
+
);
|
|
254
|
+
this.shadowRoot?.getElementById('resetBtn')?.addEventListener('click',
|
|
255
|
+
() => this.reset()
|
|
256
|
+
);
|
|
257
|
+
}
|
|
562
258
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
259
|
+
render() {
|
|
260
|
+
const currentCount = this.count.get();
|
|
261
|
+
|
|
262
|
+
this.shadowRoot!.innerHTML = `
|
|
263
|
+
<style>
|
|
264
|
+
:host {
|
|
265
|
+
display: flex;
|
|
266
|
+
flex-direction: column;
|
|
267
|
+
gap: 1rem;
|
|
268
|
+
padding: 2rem;
|
|
269
|
+
}
|
|
270
|
+
.count-display {
|
|
271
|
+
font-size: 2rem;
|
|
272
|
+
font-weight: bold;
|
|
273
|
+
color: var(--color-primary, #24ec71);
|
|
274
|
+
}
|
|
275
|
+
</style>
|
|
276
|
+
|
|
277
|
+
<div class="count-display">Count: ${currentCount}</div>
|
|
278
|
+
<button id="incrementBtn">Increment</button>
|
|
279
|
+
<button id="resetBtn">Reset</button>
|
|
280
|
+
`;
|
|
281
|
+
|
|
282
|
+
this.setupEventListeners();
|
|
283
|
+
}
|
|
569
284
|
}
|
|
570
|
-
```
|
|
571
|
-
|
|
572
|
-
**Events:**
|
|
573
|
-
- `select-change` - Fired when selection changes
|
|
574
|
-
- `detail.value` - Selected value
|
|
575
|
-
- `detail.option` - Full option object
|
|
576
285
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
### ☑️ Checkbox (`ui-checkbox`)
|
|
580
|
-
|
|
581
|
-
Flexible checkbox with indeterminate state support.
|
|
582
|
-
|
|
583
|
-
**Features:**
|
|
584
|
-
- 3 sizes: `sm`, `md`, `lg`
|
|
585
|
-
- Checked/unchecked states
|
|
586
|
-
- Indeterminate state (useful for "select all")
|
|
587
|
-
- Disabled state
|
|
588
|
-
- Label support (attribute or slot)
|
|
589
|
-
- Programmatic API
|
|
590
|
-
- Custom events
|
|
591
|
-
- Smooth animations and transitions
|
|
592
|
-
- Theme-aware styling
|
|
593
|
-
|
|
594
|
-
**Usage:**
|
|
595
|
-
```html
|
|
596
|
-
<!-- Basic usage -->
|
|
597
|
-
<ui-checkbox label="Accept terms"></ui-checkbox>
|
|
598
|
-
<ui-checkbox label="Subscribe" checked></ui-checkbox>
|
|
599
|
-
<ui-checkbox label="Disabled" disabled></ui-checkbox>
|
|
600
|
-
|
|
601
|
-
<!-- With sizes -->
|
|
602
|
-
<ui-checkbox label="Small" size="sm"></ui-checkbox>
|
|
603
|
-
<ui-checkbox label="Medium" size="md"></ui-checkbox>
|
|
604
|
-
<ui-checkbox label="Large" size="lg"></ui-checkbox>
|
|
605
|
-
|
|
606
|
-
<!-- Programmatic usage -->
|
|
607
|
-
<ui-checkbox id="myCheckbox" label="Select All"></ui-checkbox>
|
|
608
|
-
|
|
609
|
-
<script>
|
|
610
|
-
const checkbox = document.getElementById('myCheckbox');
|
|
611
|
-
|
|
612
|
-
// Listen for changes
|
|
613
|
-
checkbox.addEventListener('checkbox-change', (e) => {
|
|
614
|
-
console.log('Checked:', e.detail.checked);
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
// Set states programmatically
|
|
618
|
-
checkbox.setChecked(true);
|
|
619
|
-
checkbox.setIndeterminate(true);
|
|
620
|
-
</script>
|
|
286
|
+
customElements.define('counter-app', CounterComponent);
|
|
621
287
|
```
|
|
622
288
|
|
|
623
|
-
**
|
|
624
|
-
- `
|
|
625
|
-
-
|
|
626
|
-
-
|
|
627
|
-
-
|
|
628
|
-
-
|
|
629
|
-
|
|
630
|
-
**Methods:**
|
|
631
|
-
- `setChecked(checked: boolean)` - Set checked state
|
|
632
|
-
- `setIndeterminate(indeterminate: boolean)` - Set indeterminate state
|
|
633
|
-
|
|
634
|
-
**Events:**
|
|
635
|
-
- `checkbox-change` - Fired when state changes
|
|
636
|
-
- `detail.checked` - New checked state
|
|
289
|
+
**Key improvements:**
|
|
290
|
+
- Use `this.watchSignal(signal, callback)` to subscribe to signal changes
|
|
291
|
+
- When signal updates, callback triggers automatically - no manual DOM updates needed
|
|
292
|
+
- Choose between:
|
|
293
|
+
- **Selective updates** - Only update specific DOM elements (better performance)
|
|
294
|
+
- **Full re-render** - Re-render entire template (simpler logic, less efficient)
|
|
295
|
+
- The HTML automatically stays in sync with signal values
|
|
637
296
|
|
|
638
|
-
|
|
297
|
+
### Enhanced: useSignalHtml for Direct DOM Binding
|
|
639
298
|
|
|
640
|
-
|
|
299
|
+
For even cleaner code, use `useSignalHtml()` to create a signal that automatically updates a specific HTML element:
|
|
641
300
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
**Features:**
|
|
645
|
-
- Workspace navigation
|
|
646
|
-
- Active link highlighting (via routing)
|
|
647
|
-
- Theme-aware styling
|
|
648
|
-
|
|
649
|
-
**Usage:**
|
|
301
|
+
**counter.html**
|
|
650
302
|
```html
|
|
651
|
-
<
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
- Responsive layout
|
|
303
|
+
<style>
|
|
304
|
+
:host {
|
|
305
|
+
display: flex;
|
|
306
|
+
flex-direction: column;
|
|
307
|
+
gap: 1rem;
|
|
308
|
+
padding: 2rem;
|
|
309
|
+
}
|
|
310
|
+
.count-display {
|
|
311
|
+
font-size: 2rem;
|
|
312
|
+
font-weight: bold;
|
|
313
|
+
color: var(--color-primary, #24ec71);
|
|
314
|
+
}
|
|
315
|
+
</style>
|
|
665
316
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
</
|
|
317
|
+
<div class="count-display">
|
|
318
|
+
Count: <span id="countValue">0</span>
|
|
319
|
+
</div>
|
|
320
|
+
<button id="incrementBtn">Increment</button>
|
|
321
|
+
<button id="resetBtn">Reset</button>
|
|
671
322
|
```
|
|
672
323
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
## Core Features
|
|
676
|
-
|
|
677
|
-
### Base Component
|
|
678
|
-
|
|
679
|
-
All components extend `BaseComponent` which provides:
|
|
680
|
-
|
|
681
|
-
**Signal-based Reactivity:**
|
|
324
|
+
**counter.ts - Simplified with useSignalHtml**
|
|
682
325
|
```typescript
|
|
683
|
-
|
|
684
|
-
|
|
326
|
+
import { BaseComponent } from '@diniz/webcomponents';
|
|
327
|
+
import template from './counter.html?raw';
|
|
328
|
+
|
|
329
|
+
class CounterComponent extends BaseComponent {
|
|
330
|
+
// Create signal bound to HTML element with ID 'countValue'
|
|
331
|
+
// Automatically updates the element's textContent when signal changes
|
|
332
|
+
private count = this.useSignalHtml('countValue', 0);
|
|
685
333
|
|
|
686
334
|
connectedCallback() {
|
|
687
335
|
super.connectedCallback();
|
|
688
|
-
|
|
689
|
-
this.
|
|
336
|
+
this.render();
|
|
337
|
+
this.setupEventListeners();
|
|
690
338
|
}
|
|
691
|
-
}
|
|
692
|
-
```
|
|
693
339
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
constructor() {
|
|
698
|
-
super();
|
|
699
|
-
this.state = { user: '' };
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
updateUser() {
|
|
703
|
-
this.setState({ user: 'Alice' }); // Triggers re-render
|
|
340
|
+
private increment() {
|
|
341
|
+
// Just update the signal - HTML updates automatically
|
|
342
|
+
this.count.set(this.count.get() + 1);
|
|
704
343
|
}
|
|
705
|
-
}
|
|
706
|
-
```
|
|
707
344
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
```typescript
|
|
713
|
-
const routes = [
|
|
714
|
-
{
|
|
715
|
-
path: '/',
|
|
716
|
-
layout: 'app-layout',
|
|
717
|
-
load: () => import('./features/home/home-page'),
|
|
718
|
-
component: 'home-page'
|
|
345
|
+
private reset() {
|
|
346
|
+
// Just update the signal - HTML updates automatically
|
|
347
|
+
this.count.set(0);
|
|
719
348
|
}
|
|
720
|
-
];
|
|
721
|
-
```
|
|
722
349
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
350
|
+
private setupEventListeners() {
|
|
351
|
+
this.shadowRoot?.getElementById('incrementBtn')?.addEventListener('click',
|
|
352
|
+
() => this.increment()
|
|
353
|
+
);
|
|
354
|
+
this.shadowRoot?.getElementById('resetBtn')?.addEventListener('click',
|
|
355
|
+
() => this.reset()
|
|
356
|
+
);
|
|
357
|
+
}
|
|
729
358
|
|
|
730
|
-
|
|
731
|
-
|
|
359
|
+
render() {
|
|
360
|
+
this.shadowRoot!.innerHTML = template;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
732
363
|
|
|
733
|
-
|
|
734
|
-
console.log('State changed:', state);
|
|
735
|
-
});
|
|
364
|
+
customElements.define('counter-app', CounterComponent);
|
|
736
365
|
```
|
|
737
366
|
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
367
|
+
**Benefits of `useSignalHtml()`:**
|
|
368
|
+
- No need for `watchSignal()` subscription
|
|
369
|
+
- No need for manual `updateDisplay()` functions
|
|
370
|
+
- Automatically syncs signal value to element's `textContent`
|
|
371
|
+
- One line instead of multiple lines of setup
|
|
372
|
+
- Perfect for data binding in separated HTML/TS architecture
|
|
741
373
|
|
|
374
|
+
**Signature:**
|
|
742
375
|
```typescript
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
// Set base URL for all requests
|
|
746
|
-
http.setBaseURL('https://api.example.com');
|
|
747
|
-
|
|
748
|
-
// Set default headers
|
|
749
|
-
http.setDefaultHeaders({ 'Authorization': 'Bearer token' });
|
|
750
|
-
|
|
751
|
-
// Make requests
|
|
752
|
-
const users = await http.get<User[]>('/users');
|
|
753
|
-
const newUser = await http.post<User>('/users', { name: 'Alice' });
|
|
754
|
-
await http.put(`/users/${id}`, updatedData);
|
|
755
|
-
await http.delete(`/users/${id}`);
|
|
376
|
+
useSignalHtml<T>(elementId: string, initialValue: T): Signal<T>
|
|
756
377
|
```
|
|
757
378
|
|
|
758
|
-
|
|
379
|
+
Returns a signal that automatically updates the specified HTML element whenever the value changes.
|
|
759
380
|
|
|
760
|
-
|
|
761
|
-
// Add auth token to every request
|
|
762
|
-
http.interceptors.request.use((config) => {
|
|
763
|
-
const token = localStorage.getItem('auth_token');
|
|
764
|
-
if (token) {
|
|
765
|
-
config.headers = config.headers || {};
|
|
766
|
-
config.headers['Authorization'] = `Bearer ${token}`;
|
|
767
|
-
}
|
|
768
|
-
return config;
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
// Handle request errors
|
|
772
|
-
http.interceptors.request.use(
|
|
773
|
-
(config) => config,
|
|
774
|
-
(error) => {
|
|
775
|
-
console.error('Request failed:', error);
|
|
776
|
-
throw error;
|
|
777
|
-
}
|
|
778
|
-
);
|
|
779
|
-
```
|
|
381
|
+
This pattern is used throughout the demo components in `src/features/`.
|
|
780
382
|
|
|
781
|
-
|
|
383
|
+
### HTTP Client
|
|
782
384
|
|
|
783
385
|
```typescript
|
|
784
|
-
|
|
785
|
-
http.interceptors.response.use((response) => {
|
|
786
|
-
// Unwrap API response if it's nested
|
|
787
|
-
if (response.data?.result) {
|
|
788
|
-
response.data = response.data.result;
|
|
789
|
-
}
|
|
790
|
-
return response;
|
|
791
|
-
});
|
|
792
|
-
|
|
793
|
-
// Handle errors globally
|
|
794
|
-
http.interceptors.response.use(
|
|
795
|
-
(response) => response,
|
|
796
|
-
(error) => {
|
|
797
|
-
if (error.response?.status === 401) {
|
|
798
|
-
// Handle unauthorized
|
|
799
|
-
localStorage.removeItem('auth_token');
|
|
800
|
-
window.location.href = '/login';
|
|
801
|
-
}
|
|
802
|
-
throw error;
|
|
803
|
-
}
|
|
804
|
-
);
|
|
805
|
-
```
|
|
806
|
-
|
|
807
|
-
**Methods:**
|
|
808
|
-
|
|
809
|
-
- `get<T>(url, config?)` - GET request
|
|
810
|
-
- `post<T>(url, data?, config?)` - POST request
|
|
811
|
-
- `put<T>(url, data?, config?)` - PUT request
|
|
812
|
-
- `patch<T>(url, data?, config?)` - PATCH request
|
|
813
|
-
- `delete<T>(url, config?)` - DELETE request
|
|
814
|
-
- `head<T>(url, config?)` - HEAD request
|
|
815
|
-
|
|
816
|
-
**Configuration:**
|
|
386
|
+
import { http } from '@diniz/webcomponents';
|
|
817
387
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
method?: string;
|
|
821
|
-
headers?: Record<string, string>;
|
|
822
|
-
body?: string | FormData | null;
|
|
823
|
-
timeout?: number; // Default: 30000ms
|
|
824
|
-
}
|
|
388
|
+
const users = await http.get<User[]>('/api/users');
|
|
389
|
+
const newUser = await http.post<User>('/api/users', data);
|
|
825
390
|
```
|
|
826
391
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
- ✅ Request/response interceptors with error handling
|
|
830
|
-
- ✅ Automatic JSON serialization/deserialization
|
|
831
|
-
- ✅ Timeout support (default 30s)
|
|
832
|
-
- ✅ Global headers and base URL configuration
|
|
833
|
-
- ✅ FormData support for file uploads
|
|
834
|
-
- ✅ TypeScript generics for type-safe responses
|
|
835
|
-
- ✅ Automatic error messages with status codes
|
|
392
|
+
### Router & Store
|
|
836
393
|
|
|
837
|
-
|
|
394
|
+
Built-in routing and global state management utilities.
|
|
838
395
|
|
|
839
396
|
## Theming
|
|
840
397
|
|
|
841
|
-
|
|
398
|
+
Customize colors and spacing using CSS custom properties:
|
|
842
399
|
|
|
843
400
|
```css
|
|
844
401
|
:root {
|
|
845
402
|
--color-primary: #24ec71;
|
|
846
|
-
--color-
|
|
847
|
-
--color-ink: #0f172a;
|
|
848
|
-
--color-muted: #f1f5f9;
|
|
849
|
-
--color-border: #e2e8f0;
|
|
403
|
+
--color-secondary: #8b5cf6;
|
|
850
404
|
--radius-md: 12px;
|
|
851
|
-
--radius-pill: 999px;
|
|
852
405
|
}
|
|
853
406
|
```
|
|
854
407
|
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
## Dependencies
|
|
858
|
-
|
|
859
|
-
### Icons
|
|
860
|
-
|
|
861
|
-
This library uses **[Feather Icons](https://feathericons.com/)** for beautiful, minimal SVG icons. Feather provides a consistent set of 286 icons perfect for UI components.
|
|
862
|
-
|
|
863
|
-
```typescript
|
|
864
|
-
import feather from 'feather-icons';
|
|
408
|
+
## Development
|
|
865
409
|
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
410
|
+
```bash
|
|
411
|
+
npm install
|
|
412
|
+
npm run dev # Start dev server
|
|
413
|
+
npm run build # Build for production
|
|
414
|
+
npm run build:lib # Build library distribution
|
|
869
415
|
```
|
|
870
416
|
|
|
871
|
-
[Browse all available Feather icons →](https://feathericons.com/)
|
|
872
|
-
|
|
873
|
-
---
|
|
874
|
-
|
|
875
|
-
## Bundle Size
|
|
876
|
-
|
|
877
|
-
@diniz/webcomponents is extremely lightweight with zero runtime dependencies:
|
|
878
|
-
|
|
879
|
-
| Package | Size (minified) | Size (gzipped) |
|
|
880
|
-
|---------|-----------------|----------------|
|
|
881
|
-
| **@diniz/webcomponents** | ~45KB | ~12KB |
|
|
882
|
-
| Vue 3 + Router | ~185KB | ~65KB |
|
|
883
|
-
| React 18 + Router | ~245KB | ~85KB |
|
|
884
|
-
| Angular 15 | ~500KB+ | ~150KB+ |
|
|
885
|
-
| Svelte | ~60KB | ~15KB |
|
|
886
|
-
|
|
887
|
-
*Sizes are approximate and vary based on included components and tree-shaking effectiveness*
|
|
888
|
-
|
|
889
|
-
**Why Web Components?**
|
|
890
|
-
- ✅ No framework overhead - use with any framework or vanilla JS
|
|
891
|
-
- ✅ Smaller initial bundle size than traditional frameworks
|
|
892
|
-
- ✅ Progressive enhancement - works without JavaScript
|
|
893
|
-
- ✅ Share components across different projects/frameworks
|
|
894
|
-
- ✅ Built-in browser APIs - no external polyfills needed for modern browsers
|
|
895
|
-
|
|
896
|
-
---
|
|
897
|
-
|
|
898
417
|
## Browser Support
|
|
899
418
|
|
|
900
419
|
- ✅ Chrome/Edge (latest)
|
|
901
420
|
- ✅ Firefox (latest)
|
|
902
421
|
- ✅ Safari (latest)
|
|
903
|
-
- ✅ All modern browsers with Custom Elements support
|
|
904
|
-
|
|
905
|
-
---
|
|
906
|
-
|
|
907
|
-
## Development
|
|
908
|
-
|
|
909
|
-
```bash
|
|
910
|
-
# Install dependencies
|
|
911
|
-
npm install
|
|
912
|
-
|
|
913
|
-
# Start dev server
|
|
914
|
-
npm run dev
|
|
915
|
-
|
|
916
|
-
# Build library
|
|
917
|
-
npm run build:lib
|
|
918
|
-
|
|
919
|
-
# Build production app
|
|
920
|
-
npm run build:prod
|
|
921
|
-
```
|
|
922
|
-
|
|
923
|
-
---
|
|
924
|
-
|
|
925
|
-
## Project Structure
|
|
926
|
-
|
|
927
|
-
```
|
|
928
|
-
src/
|
|
929
|
-
├── core/
|
|
930
|
-
│ ├── base-component.ts # Base class with signals
|
|
931
|
-
│ ├── router.ts # Client-side routing
|
|
932
|
-
│ └── store.ts # Global state management
|
|
933
|
-
├── shared/
|
|
934
|
-
│ └── components/ # Reusable UI components
|
|
935
|
-
│ ├── button.ts
|
|
936
|
-
│ ├── checkbox.ts
|
|
937
|
-
│ ├── date-picker.ts
|
|
938
|
-
│ ├── input.ts
|
|
939
|
-
│ ├── modal.ts
|
|
940
|
-
│ ├── pagination.ts
|
|
941
|
-
│ ├── select.ts
|
|
942
|
-
│ └── table.ts
|
|
943
|
-
├── layouts/
|
|
944
|
-
│ └── app-layout.ts # Application shell
|
|
945
|
-
├── features/ # Page components
|
|
946
|
-
└── styles/
|
|
947
|
-
└── theme.css # Global theme variables
|
|
948
|
-
```
|
|
949
|
-
## Contributing
|
|
950
|
-
|
|
951
|
-
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
952
|
-
|
|
953
|
-
---
|
|
954
422
|
|
|
955
423
|
## License
|
|
956
424
|
|
|
957
425
|
MIT © Rodrigo Diniz
|
|
958
426
|
|
|
959
|
-
|