@diniz/webcomponents 1.1.4 → 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/dist/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 examples:
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
- ## Installation
22
-
23
- ```bash
24
- npm install @diniz/webcomponents
25
- ```
26
-
27
- ## Using with Vite (No Framework)
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
- This library works seamlessly with Vite without requiring any framework. Here's how to set up a vanilla JavaScript/TypeScript project:
29
+ Each demo includes the component implementation and usage examples. Visit the live demo site to see all components in action.
30
30
 
31
- ### 1. Create a New Vite Project
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
- ### 3. Import Components in Your Main File
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'; // Import styles
53
-
54
- // Now you can use the components in your HTML
55
- document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
56
- <div>
57
- <h1>My Web Components App</h1>
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
- ### 4. Use Components in HTML
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
- ### 5. Add Event Listeners (Optional)
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
- ```typescript
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
- ### 6. TypeScript Support
71
+ ### Signals & Reactivity
106
72
 
107
- For full TypeScript support, create a `src/types.d.ts` file:
73
+ Create reactive, auto-updating UI with signals. Changes automatically trigger re-renders:
108
74
 
109
75
  ```typescript
110
- declare module '@diniz/webcomponents' {
111
- export interface UIButton extends HTMLElement {
112
- variant: 'primary' | 'secondary' | 'ghost';
113
- size: 'sm' | 'md' | 'lg';
114
- icon?: string;
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
- export interface UIDatePicker extends HTMLElement {
119
- format: string;
120
- value: string;
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
- declare global {
129
- interface HTMLElementTagNameMap {
130
- 'ui-button': import('@diniz/webcomponents').UIButton;
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
- ### Configuration Tips
159
-
160
- **Vite Config** - No special configuration needed! Web Components work out of the box with Vite.
161
-
162
- **CSS Customization** - Override CSS custom properties to match your theme:
92
+ private reset() {
93
+ this.count.set(0);
94
+ }
163
95
 
164
- ```css
165
- :root {
166
- --color-primary: #3b82f6;
167
- --color-secondary: #8b5cf6;
168
- --color-success: #10b981;
169
- --color-danger: #ef4444;
170
- --color-warning: #f59e0b;
171
- --color-info: #06b6d4;
172
-
173
- --radius-sm: 0.25rem;
174
- --radius-md: 0.375rem;
175
- --radius-lg: 0.5rem;
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
- document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
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
- ### Using via CDN or Direct Import
207
-
132
+ **Usage in HTML:**
208
133
  ```html
209
- <script type="module">
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
- ## Components
219
-
220
- ### 🔘 Button (`ui-button`)
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
- A versatile button component with multiple variants, sizes, and icon support.
144
+ ### Signals with Separated HTML & TypeScript Files
223
145
 
224
- **Features:**
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
- **Usage:**
148
+ **counter.html**
235
149
  ```html
236
- <ui-button variant="primary" size="md">Primary Button</ui-button>
237
- <ui-button variant="secondary" size="sm">Secondary</ui-button>
238
- <ui-button variant="ghost" disabled>Disabled</ui-button>
239
-
240
- <!-- With icons -->
241
- <ui-button variant="primary" icon="check">Save</ui-button>
242
- <ui-button variant="secondary" icon="trash-2" icon-position="right">Delete</ui-button>
243
- <ui-button variant="ghost" icon="settings"></ui-button>
244
- ```
245
-
246
- **Attributes:**
247
- - `variant` - Button style (`primary` | `secondary` | `ghost`)
248
- - `size` - Button size (`sm` | `md` | `lg`)
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
- **Usage:**
270
- ```html
271
- <ui-date-picker
272
- format="DD/MM/YYYY"
273
- value="2026-02-26"
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
- **Attributes:**
289
- - `format` - Date display format
290
- - `value` - Date value in ISO format (YYYY-MM-DD)
291
- - `min` - Minimum date (ISO format)
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
- <script type="module">
324
- const table = document.getElementById('myTable');
176
+ class CounterComponent extends BaseComponent {
177
+ private count = this.useSignal(0);
325
178
 
326
- table.data = {
327
- columns: [
328
- { key: 'name', label: 'Name' },
329
- { key: 'role', label: 'Role' },
330
- { key: 'score', label: 'Score', align: 'right' }
331
- ],
332
- rows: [
333
- { name: 'Alice', role: 'Admin', score: 95 },
334
- { name: 'Bob', role: 'User', score: 87 }
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
- **Properties:**
341
- - `data` - Object with `columns` and `rows`
342
- - `columns`: Array of `{ key, label, align? }`
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
- ### 📄 Pagination (`ui-pagination`)
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
- Smart pagination component with ellipsis for large page counts.
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
- **Features:**
352
- - Automatic page number generation
353
- - Smart ellipsis for large page counts
354
- - Previous/Next navigation
355
- - "Showing X to Y of Z" info display
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
- **Usage:**
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
- **Attributes/Properties:**
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
- **Attributes:**
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
- <ui-modal id="myModal" title="Welcome!" size="md">
467
- <p>This is the modal content.</p>
468
- <p>You can include any HTML here.</p>
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
- modal.addEventListener('modal-close', () => {
485
- console.log('Modal closed');
486
- });
487
- </script>
488
- ```
489
-
490
- **Attributes:**
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
- **Features:**
512
- - JSON-based options configuration
513
- - Searchable dropdown (optional)
514
- - Keyboard navigation
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
- **Usage:**
523
- ```html
524
- <ui-select
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
- **Attributes:**
556
- - `label` - Label text above select
557
- - `placeholder` - Placeholder when no selection
558
- - `options` - JSON string of options array
559
- - `value` - Currently selected value
560
- - `disabled` - Disable the select
561
- - `searchable` - Enable search functionality
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
- **Option Format:**
564
- ```typescript
565
- {
566
- value: string; // The option value
567
- label: string; // Display text
568
- disabled?: boolean; // Optional: disable option
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
- **Attributes:**
624
- - `label` - Label text
625
- - `checked` - Checked state
626
- - `indeterminate` - Indeterminate state
627
- - `disabled` - Disable checkbox
628
- - `size` - Checkbox size (`sm` | `md` | `lg`)
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
- ### 🎯 Sidebar (`app-sidebar`)
299
+ For even cleaner code, use `useSignalHtml()` to create a signal that automatically updates a specific HTML element:
641
300
 
642
- Navigation sidebar component with links.
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
- <app-sidebar></app-sidebar>
652
- ```
653
-
654
- ---
655
-
656
- ### 📐 Layout (`app-layout`)
657
-
658
- Application layout wrapper with navigation and sidebar.
659
-
660
- **Features:**
661
- - Top navigation bar
662
- - Sidebar integration
663
- - Main content area with slot
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
- **Usage:**
667
- ```html
668
- <app-layout>
669
- <your-page-component></your-page-component>
670
- </app-layout>
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
- class MyComponent extends BaseComponent {
684
- private count = this.useSignal(0);
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
- // count.set() automatically triggers re-render
689
- this.count.set(this.count.get() + 1);
336
+ this.render();
337
+ this.setupEventListeners();
690
338
  }
691
- }
692
- ```
693
339
 
694
- **State Management:**
695
- ```typescript
696
- class MyComponent extends BaseComponent<{ user: string }> {
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
- ### Router
709
-
710
- Built-in client-side router with layouts:
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
- ### Store
724
-
725
- Global state management:
726
-
727
- ```typescript
728
- import { store } from './core/store';
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
- store.setState({ theme: 'dark' });
731
- const currentState = store.getState();
359
+ render() {
360
+ this.shadowRoot!.innerHTML = template;
361
+ }
362
+ }
732
363
 
733
- store.subscribe(state => {
734
- console.log('State changed:', state);
735
- });
364
+ customElements.define('counter-app', CounterComponent);
736
365
  ```
737
366
 
738
- ### HTTP Client
739
-
740
- Lightweight HTTP client with interceptor support for API requests:
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
- import { http } from '@diniz/webcomponents';
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
- **Request Interceptors:**
379
+ Returns a signal that automatically updates the specified HTML element whenever the value changes.
759
380
 
760
- ```typescript
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
- **Response Interceptors:**
383
+ ### HTTP Client
782
384
 
783
385
  ```typescript
784
- // Transform response data
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
- ```typescript
819
- interface RequestConfig {
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
- **Features:**
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
- All components use CSS custom properties for easy theming:
398
+ Customize colors and spacing using CSS custom properties:
842
399
 
843
400
  ```css
844
401
  :root {
845
402
  --color-primary: #24ec71;
846
- --color-primary-contrast: #ffffff;
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
- // Use icons in components
867
- <ui-button icon="plus">Add Item</ui-button>
868
- <ui-button icon="trash-2" variant="danger">Delete</ui-button>
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
-