@devjuliovilla/jv-ui 1.5.5 → 1.6.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 CHANGED
@@ -1,268 +1,313 @@
1
- # @devjuliovilla/jv-ui
2
-
3
- Accessibility-first Angular component library built with signals and standalone APIs. Includes components, forms, data grids, dialogs, layout primitives, services, and i18n infrastructure.
4
-
5
- ## Installation
6
-
7
- ```bash
8
- npm install @devjuliovilla/jv-ui
9
- ```
10
-
11
- Import the theme CSS in your `angular.json` or `styles` array:
12
- ```json
13
- "styles": ["@devjuliovilla/jv-ui/styles/jv-theme.css"]
14
- ```
15
-
16
- ## Quick start
17
-
18
- ```typescript
19
- import { ApplicationConfig } from '@angular/core';
20
- import { provideJvUi } from '@devjuliovilla/jv-ui';
21
-
22
- export const appConfig: ApplicationConfig = {
23
- providers: [provideJvUi({ locale: 'en' })],
24
- };
25
- ```
26
-
27
- ```typescript
28
- import { Component } from '@angular/core';
29
- import { JvButtonComponent } from '@devjuliovilla/jv-ui';
30
-
31
- @Component({
32
- selector: 'app-root',
33
- standalone: true,
34
- imports: [JvButtonComponent],
35
- template: '<jv-button variant="primary">Hello</jv-button>',
36
- })
37
- export class App {}
38
- ```
39
-
40
- ## Components
41
-
42
- | Category | Components |
43
- |---|---|
44
- | Buttons | `JvButton`, `JvButtonGroup`, `JvIconButton` |
45
- | Forms | `JvInput`, `JvSelect`, `JvCheckbox`, `JvRadio`, `JvSwitch`, `JvFormContainer` |
46
- | Data Grid | `JvGrid` — full-featured data table (see below) |
47
- | Containers | `JvCard`, `JvSection`, `JvDivider` |
48
- | Feedback | `JvAlert`, `JvBadge`, `JvLoader`, `JvToast` |
49
- | Dialogs | `JvDialog`, `JvConfirmDialog` |
50
- | Layout | `JvDashboardShell`, `JvSidebar`, `JvTopbar`, `JvBreadcrumb`, `JvPage` |
51
- | Auth Pages | `JvLoginPage`, `JvForgotPasswordPage`, `JvChangePasswordPage` |
52
- | Icon | `JvIcon` — Lucide-based icon component with 1500+ auto-generated icons |
53
- | Pagination | `JvPagination` — standalone pagination with i18n |
54
-
55
- ## Grid Features
56
-
57
- | Feature | Description |
58
- |---|---|
59
- | Sorting | Client-side multi-column sorting with sort icons and `aria-sort` |
60
- | Search | Global text search across visible columns |
61
- | Pagination | Configurable page size, page range buttons, first/last/prev/next |
62
- | Selection | Checkbox row selection with `selectedIds` / `selectionChange` |
63
- | Row actions | Custom action buttons per row |
64
- | Density modes | Compact, normal, comfortable |
65
- | Sticky columns | Pin columns to left or right |
66
- | Column resizing | Drag column header borders to resize |
67
- | Column reordering | Drag & drop column headers |
68
- | Grouped headers | Multi-row header with `colspan` via `children` |
69
- | Per-column filters | Filter input row with operators |
70
- | Inline editing | Double-click or Enter to edit: text, number, select, boolean |
71
- | Virtual scrolling | Render only visible rows for large datasets |
72
- | CSV/Excel export | Built-in toolbar buttons, no external deps |
73
- | Server-side mode | Delegate sort/filter/page to parent component |
74
- | TrackBy | Custom trackBy key or function |
75
- | i18n | All labels configurable via translation dictionary |
76
-
77
- ### Grid API
78
-
79
- ```typescript
80
- import { JvGridComponent } from '@devjuliovilla/jv-ui';
81
- ```
82
-
83
- **Inputs:**
84
-
85
- | Input | Type | Default | Description |
86
- |---|---|---|---|
87
- | `data` | `T[]` | `[]` | Row data array |
88
- | `columns` | `JvGridColumn<T>[]` | `[]` | Column definitions |
89
- | `actions` | `JvGridAction<T>[]` | `[]` | Row action button configs |
90
- | `options` | `Partial<JvGridOptions>` | `{}` | Feature toggles and config |
91
- | `trackBy` | `keyof T \| Function \| null` | `null` | Row identity |
92
- | `selectedIds` | `(string \| number)[]` | `[]` | Controlled selection IDs |
93
-
94
- **Outputs:**
95
-
96
- | Output | Type |
97
- |---|---|
98
- | `rowClick` | `T` |
99
- | `rowDoubleClick` | `T` |
100
- | `actionClick` | `{ actionId: string; row: T }` |
101
- | `selectionChange` | `(string \| number)[]` |
102
- | `pageChange` | `{ pageIndex: number; pageSize: number }` |
103
- | `searchChange` | `string` |
104
- | `sortChange` | `{ columnKey: string; direction: 'asc' \| 'desc' \| null }` |
105
- | `columnFilter` | `{ columnKey: string; value: string }` |
106
- | `columnResize` | `{ columnKey: string; width: string }` |
107
- | `columnReorder` | `{ columnKey: string; newIndex: number }` |
108
- | `rowEdit` | `{ row: T; column: JvGridColumn<T>; value: unknown }` |
109
-
110
- **JvGridColumn options:**
111
-
112
- | Property | Type | Description |
113
- |---|---|---|
114
- | `key` | `keyof T \| string` | Data field key |
115
- | `header` | `string` | Column display header |
116
- | `sortable` | `boolean` | Enable column sorting |
117
- | `searchable` | `boolean` | Include in global search |
118
- | `filterable` | `boolean` | Show per-column filter input |
119
- | `hidden` | `boolean` | Hide column from display |
120
- | `sticky` | `'left' \| 'right'` | Pin column position |
121
- | `editable` | `boolean` | Allow inline cell editing |
122
- | `editType` | `'text' \| 'number' \| 'select' \| 'boolean'` | Editor type |
123
- | `editOptions` | `{ label: string; value: unknown }[]` | Options for select editor |
124
- | `width` | `string` | Column width (e.g. '8rem') |
125
- | `align` | `'start' \| 'center' \| 'end'` | Text alignment |
126
- | `type` | `'text' \| 'number' \| 'currency' \| 'date' \| 'datetime' \| 'boolean'` | Data type for formatting |
127
- | `format` | `(value, row) => string` | Custom display formatter |
128
- | `children` | `JvGridColumn<T>[]` | Child columns for grouped headers |
129
- | `resizable` | `boolean` | Allow column resize |
130
- | `cellClass` | `string` | CSS class for cells |
131
- | `headerClass` | `string` | CSS class for header |
132
-
133
- **JvGridOptions:**
134
-
135
- | Option | Type | Default | Description |
136
- |---|---|---|---|
137
- | `searchable` | `boolean` | `true` | Show search bar |
138
- | `sortable` | `boolean` | `true` | Enable column sorting |
139
- | `pageable` | `boolean` | `true` | Enable pagination |
140
- | `selectable` | `boolean` | `false` | Show selection checkboxes |
141
- | `pageSize` | `number` | `20` | Default page size |
142
- | `pageSizeOptions` | `number[]` | `[10, 20, 50, 100]` | Page size selector options |
143
- | `density` | `'compact' \| 'normal' \| 'comfortable'` | `'normal'` | Row density |
144
- | `loading` | `boolean` | `false` | Show loading spinner |
145
- | `emptyMessage` | `string` | — | Empty state message |
146
- | `exportable` | `boolean` | `false` | Show CSV/Excel export buttons |
147
- | `serverSide` | `boolean` | `false` | Disable client-side processing |
148
- | `totalItems` | `number` | `0` | Total items for server-side mode |
149
- | `stickyColumns` | `boolean` | `false` | Enable sticky column support |
150
- | `resizableColumns` | `boolean` | `false` | Enable column resizing |
151
- | `reorderableColumns` | `boolean` | `false` | Enable column reordering |
152
- | `editable` | `boolean` | `false` | Enable inline editing |
153
- | `virtualScroll` | `boolean` | `false` | Enable virtual scrolling |
154
- | `virtualScrollRowHeight` | `number` | `48` | Row height for virtual scroll |
155
- | `columnFilters` | `boolean` | `false` | Show filter row below headers |
156
- | `rowDoubleClick` | `boolean` | `false` | Enable row double-click event |
157
- | `ariaLabel` | `string` | — | Accessible label for the grid |
158
-
159
- ### Grid example
160
-
161
- ```typescript
162
- import { Component } from '@angular/core';
163
- import { JvGridComponent, JvGridColumn, JvGridOptions } from '@devjuliovilla/jv-ui';
164
-
165
- interface Product {
166
- id: number;
167
- name: string;
168
- price: number;
169
- status: string;
170
- }
171
-
172
- @Component({
173
- selector: 'app-products',
174
- standalone: true,
175
- imports: [JvGridComponent],
176
- template: `
177
- <jv-grid
178
- [data]="products"
179
- [columns]="columns"
180
- [options]="{ selectable: true, exportable: true, pageSize: 10 }"
181
- [trackBy]="'id'"
182
- [selectedIds]="selectedIds"
183
- (selectionChange)="selectedIds = $event"
184
- />
185
- `,
186
- })
187
- export class ProductsComponent {
188
- products: Product[] = [ /* ... */ ];
189
- selectedIds: (string | number)[] = [];
190
- columns: JvGridColumn<Product>[] = [
191
- { key: 'id', header: 'ID', width: '4rem' },
192
- { key: 'name', header: 'Product', sortable: true, filterable: true },
193
- { key: 'price', header: 'Price', type: 'number', align: 'end', format: (v) => '$' + v },
194
- { key: 'status', header: 'Status', sortable: true },
195
- ];
196
- }
197
- ```
198
-
199
- ## Pagination API
200
-
201
- ```typescript
202
- import { JvPaginationComponent } from '@devjuliovilla/jv-ui';
203
- ```
204
-
205
- | Input | Type | Default | Description |
206
- |---|---|---|---|
207
- | `totalItems` | `number` | `0` | Total items |
208
- | `pageSize` | `number` | `20` | Items per page |
209
- | `pageSizeOptions` | `number[]` | `[10, 20, 50, 100]` | Page size options |
210
- | `pageIndex` | `number` | `0` | Current page (0-based) |
211
- | `maxVisiblePages` | `number` | `5` | Max page buttons to show |
212
-
213
- | Output | Type |
214
- |---|---|
215
- | `pageChange` | `{ pageIndex: number; pageSize: number }` |
216
-
217
- ## Icon System
218
-
219
- ```typescript
220
- import { JvIconComponent, JV_LUCIDE_ICON_REGISTRY } from '@devjuliovilla/jv-ui';
221
- ```
222
-
223
- | Input | Type | Default | Description |
224
- |---|---|---|---|
225
- | `name` | `string` | | Icon name (from auto-generated Lucide registry) |
226
- | `size` | `number` | `24` | Width and height in pixels |
227
- | `strokeWidth` | `number` | `2` | SVG stroke width |
228
- | `decorative` | `boolean` | `true` | `aria-hidden="true"` when decorative |
229
- | `ariaLabel` | `string` | | Accessible label when not decorative |
230
-
231
- Icons are auto-generated from [lucide-static](https://www.npmjs.com/package/lucide-static) via:
232
-
233
- ```bash
234
- node tools/generate-icons.mjs
235
- ```
236
-
237
- This reads all SVG files from `lucide-static/icons` and produces `jv-icon-registry.ts` with the complete set (~1500 icons). Run it after updating `lucide-static` to keep icons in sync.
238
-
239
- ## Services
240
-
241
- | Service | Import Path | Description |
242
- |---|---|---|
243
- | `JvThemeService` | `@devjuliovilla/jv-ui` | Light / dark / high-contrast theme switching |
244
- | `JvTranslationService` | `@devjuliovilla/jv-ui` | Dictionary-based i18n (en/es) |
245
- | `JvToastService` | `@devjuliovilla/jv-ui` | Toast notification system |
246
- | `JvLoaderService` | `@devjuliovilla/jv-ui` | Full-screen loader with request counter |
247
- | `JvDialogService` | `@devjuliovilla/jv-ui` | Programmatic confirm dialogs |
248
- | `JvAnnouncementService` | `@devjuliovilla/jv-ui` | Screen-reader live-region announcements |
249
-
250
- ## Accessibility
251
-
252
- - WCAG 2.2 AA target
253
- - Keyboard navigation for all interactive components
254
- - Focus management with visible focus indicators
255
- - Screen reader announcements (loading, empty states, page changes)
256
- - `aria-sort` on sortable grid headers
257
- - `aria-selected` on selectable grid rows
258
- - `aria-label` and `aria-labelledby` on interactive controls
259
- - Supports `prefers-reduced-motion`
260
-
261
- ## Requirements
262
-
263
- - Angular 21.2+
264
- - TypeScript 5.9+
265
-
266
- ## License
267
-
268
- MIT
1
+ # @devjuliovilla/jv-ui
2
+
3
+ Accessibility-first Angular component library built with signals and standalone APIs. Includes components, forms, data grids, dialogs, layout primitives, services, and i18n infrastructure.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @devjuliovilla/jv-ui
9
+ ```
10
+
11
+ Import the theme CSS in your `angular.json` or `styles` array:
12
+ ```json
13
+ "styles": ["@devjuliovilla/jv-ui/styles/jv-theme.css"]
14
+ ```
15
+
16
+ ## Quick start
17
+
18
+ ```typescript
19
+ import { ApplicationConfig } from '@angular/core';
20
+ import { provideJvUi } from '@devjuliovilla/jv-ui';
21
+
22
+ export const appConfig: ApplicationConfig = {
23
+ providers: [provideJvUi({ locale: 'en' })],
24
+ };
25
+ ```
26
+
27
+ ```typescript
28
+ import { Component } from '@angular/core';
29
+ import { JvButtonComponent } from '@devjuliovilla/jv-ui';
30
+
31
+ @Component({
32
+ selector: 'app-root',
33
+ standalone: true,
34
+ imports: [JvButtonComponent],
35
+ template: '<jv-button variant="primary">Hello</jv-button>',
36
+ })
37
+ export class App {}
38
+ ```
39
+
40
+ ## Components
41
+
42
+ | Category | Components |
43
+ |---|---|
44
+ | Buttons | `JvButton`, `JvButtonGroup`, `JvIconButton` |
45
+ | Forms | `JvInput`, `JvTextarea`, `JvSelect`, `JvCheckbox`, `JvRadio`, `JvSwitch`, `JvFormContainer` |
46
+ | Data Grid | `JvGrid` — full-featured data table (see below) |
47
+ | Containers | `JvCard`, `JvSection`, `JvDivider` |
48
+ | Feedback | `JvAlert`, `JvBadge`, `JvLoader`, `JvToast` |
49
+ | Dialogs | `JvDialog`, `JvConfirmDialog` |
50
+ | Layout | `JvDashboardShell`, `JvSidebar`, `JvTopbar`, `JvBreadcrumb`, `JvPage` |
51
+ | Auth Pages | `JvLoginPage`, `JvForgotPasswordPage`, `JvChangePasswordPage` |
52
+ | Icon | `JvIcon` — Lucide-based icon component with 1500+ auto-generated icons |
53
+ | Pagination | `JvPagination` — standalone pagination with i18n |
54
+
55
+ ## Grid Features
56
+
57
+ | Feature | Description |
58
+ |---|---|
59
+ | Sorting | Client-side multi-column sorting with sort icons and `aria-sort` |
60
+ | Search | Global text search across visible columns |
61
+ | Pagination | Configurable page size, page range buttons, first/last/prev/next |
62
+ | Selection | Checkbox row selection with `selectedIds` / `selectionChange` |
63
+ | Row actions | Custom action buttons per row |
64
+ | Density modes | Compact, normal, comfortable |
65
+ | Sticky columns | Pin columns to left or right |
66
+ | Column resizing | Drag column header borders to resize |
67
+ | Column reordering | Drag & drop column headers |
68
+ | Grouped headers | Multi-row header with `colspan` via `children` |
69
+ | Per-column filters | Filter input row with operators |
70
+ | Inline editing | Double-click or Enter to edit: text, number, select, boolean |
71
+ | Virtual scrolling | Render only visible rows for large datasets |
72
+ | CSV/Excel export | Built-in toolbar buttons, no external deps |
73
+ | Server-side mode | Delegate sort/filter/page to parent component |
74
+ | TrackBy | Custom trackBy key or function |
75
+ | i18n | All labels configurable via translation dictionary |
76
+
77
+ ### Grid API
78
+
79
+ ```typescript
80
+ import { JvGridComponent } from '@devjuliovilla/jv-ui';
81
+ ```
82
+
83
+ **Inputs:**
84
+
85
+ | Input | Type | Default | Description |
86
+ |---|---|---|---|
87
+ | `data` | `T[]` | `[]` | Row data array |
88
+ | `columns` | `JvGridColumn<T>[]` | `[]` | Column definitions |
89
+ | `actions` | `JvGridAction<T>[]` | `[]` | Row action button configs |
90
+ | `options` | `Partial<JvGridOptions>` | `{}` | Feature toggles and config |
91
+ | `trackBy` | `keyof T \| Function \| null` | `null` | Row identity |
92
+ | `selectedIds` | `(string \| number)[]` | `[]` | Controlled selection IDs |
93
+
94
+ **Outputs:**
95
+
96
+ | Output | Type |
97
+ |---|---|
98
+ | `rowClick` | `T` |
99
+ | `rowDoubleClick` | `T` |
100
+ | `actionClick` | `{ actionId: string; row: T }` |
101
+ | `selectionChange` | `(string \| number)[]` |
102
+ | `pageChange` | `{ pageIndex: number; pageSize: number }` |
103
+ | `searchChange` | `string` |
104
+ | `sortChange` | `{ columnKey: string; direction: 'asc' \| 'desc' \| null }` |
105
+ | `columnFilter` | `{ columnKey: string; value: string }` |
106
+ | `columnResize` | `{ columnKey: string; width: string }` |
107
+ | `columnReorder` | `{ columnKey: string; newIndex: number }` |
108
+ | `rowEdit` | `{ row: T; column: JvGridColumn<T>; value: unknown }` |
109
+
110
+ **JvGridColumn options:**
111
+
112
+ | Property | Type | Description |
113
+ |---|---|---|
114
+ | `key` | `keyof T \| string` | Data field key |
115
+ | `header` | `string` | Column display header |
116
+ | `sortable` | `boolean` | Enable column sorting |
117
+ | `searchable` | `boolean` | Include in global search |
118
+ | `filterable` | `boolean` | Show per-column filter input |
119
+ | `hidden` | `boolean` | Hide column from display |
120
+ | `sticky` | `'left' \| 'right'` | Pin column position |
121
+ | `editable` | `boolean` | Allow inline cell editing |
122
+ | `editType` | `'text' \| 'number' \| 'select' \| 'boolean'` | Editor type |
123
+ | `editOptions` | `{ label: string; value: unknown }[]` | Options for select editor |
124
+ | `width` | `string` | Column width (e.g. '8rem') |
125
+ | `align` | `'start' \| 'center' \| 'end'` | Text alignment |
126
+ | `type` | `'text' \| 'number' \| 'currency' \| 'date' \| 'datetime' \| 'boolean'` | Data type for formatting |
127
+ | `format` | `(value, row) => string` | Custom display formatter |
128
+ | `children` | `JvGridColumn<T>[]` | Child columns for grouped headers |
129
+ | `resizable` | `boolean` | Allow column resize |
130
+ | `cellClass` | `string` | CSS class for cells |
131
+ | `headerClass` | `string` | CSS class for header |
132
+
133
+ **JvGridOptions:**
134
+
135
+ | Option | Type | Default | Description |
136
+ |---|---|---|---|
137
+ | `searchable` | `boolean` | `true` | Show search bar |
138
+ | `sortable` | `boolean` | `true` | Enable column sorting |
139
+ | `pageable` | `boolean` | `true` | Enable pagination |
140
+ | `selectable` | `boolean` | `false` | Show selection checkboxes |
141
+ | `pageSize` | `number` | `20` | Default page size |
142
+ | `pageSizeOptions` | `number[]` | `[10, 20, 50, 100]` | Page size selector options |
143
+ | `density` | `'compact' \| 'normal' \| 'comfortable'` | `'normal'` | Row density |
144
+ | `loading` | `boolean` | `false` | Show loading spinner |
145
+ | `emptyMessage` | `string` | — | Empty state message |
146
+ | `exportable` | `boolean` | `false` | Show CSV/Excel export buttons |
147
+ | `serverSide` | `boolean` | `false` | Disable client-side processing |
148
+ | `totalItems` | `number` | `0` | Total items for server-side mode |
149
+ | `stickyColumns` | `boolean` | `false` | Enable sticky column support |
150
+ | `resizableColumns` | `boolean` | `false` | Enable column resizing |
151
+ | `reorderableColumns` | `boolean` | `false` | Enable column reordering |
152
+ | `editable` | `boolean` | `false` | Enable inline editing |
153
+ | `virtualScroll` | `boolean` | `false` | Enable virtual scrolling |
154
+ | `virtualScrollRowHeight` | `number` | `48` | Row height for virtual scroll |
155
+ | `columnFilters` | `boolean` | `false` | Show filter row below headers |
156
+ | `rowDoubleClick` | `boolean` | `false` | Enable row double-click event |
157
+ | `ariaLabel` | `string` | — | Accessible label for the grid |
158
+
159
+ ### Grid example
160
+
161
+ ```typescript
162
+ import { Component } from '@angular/core';
163
+ import { JvGridComponent, JvGridColumn, JvGridOptions } from '@devjuliovilla/jv-ui';
164
+
165
+ interface Product {
166
+ id: number;
167
+ name: string;
168
+ price: number;
169
+ status: string;
170
+ }
171
+
172
+ @Component({
173
+ selector: 'app-products',
174
+ standalone: true,
175
+ imports: [JvGridComponent],
176
+ template: `
177
+ <jv-grid
178
+ [data]="products"
179
+ [columns]="columns"
180
+ [options]="{ selectable: true, exportable: true, pageSize: 10 }"
181
+ [trackBy]="'id'"
182
+ [selectedIds]="selectedIds"
183
+ (selectionChange)="selectedIds = $event"
184
+ />
185
+ `,
186
+ })
187
+ export class ProductsComponent {
188
+ products: Product[] = [ /* ... */ ];
189
+ selectedIds: (string | number)[] = [];
190
+ columns: JvGridColumn<Product>[] = [
191
+ { key: 'id', header: 'ID', width: '4rem' },
192
+ { key: 'name', header: 'Product', sortable: true, filterable: true },
193
+ { key: 'price', header: 'Price', type: 'number', align: 'end', format: (v) => '$' + v },
194
+ { key: 'status', header: 'Status', sortable: true },
195
+ ];
196
+ }
197
+ ```
198
+
199
+ ## Pagination API
200
+
201
+ ```typescript
202
+ import { JvPaginationComponent } from '@devjuliovilla/jv-ui';
203
+ ```
204
+
205
+ | Input | Type | Default | Description |
206
+ |---|---|---|---|
207
+ | `totalItems` | `number` | `0` | Total items |
208
+ | `pageSize` | `number` | `20` | Items per page |
209
+ | `pageSizeOptions` | `number[]` | `[10, 20, 50, 100]` | Page size options |
210
+ | `pageIndex` | `number` | `0` | Current page (0-based) |
211
+ | `maxVisiblePages` | `number` | `5` | Max page buttons to show |
212
+
213
+ | Output | Type |
214
+ |---|---|
215
+ | `pageChange` | `{ pageIndex: number; pageSize: number }` |
216
+
217
+ ## Textarea API
218
+
219
+ ```typescript
220
+ import { JvTextareaComponent } from '@devjuliovilla/jv-ui';
221
+ ```
222
+
223
+ | Input | Type | Default | Description |
224
+ |---|---|---|---|
225
+ | `placeholder` | `string` | `''` | Placeholder text |
226
+ | `required` | `boolean` | `false` | Mark field as required |
227
+ | `invalid` | `boolean` | `false` | Show invalid state |
228
+ | `readonly` | `boolean` | `false` | Make textarea read-only |
229
+ | `rows` | `number` | `3` | Visible number of rows |
230
+ | `cols` | `number` | — | Visible width in columns |
231
+ | `maxlength` | `number` | — | Maximum character length |
232
+ | `describedBy` | `string` | `''` | ID of description/error element for aria-describedby |
233
+ | `inputId` | `string` | `jv-textarea-{n}` | Auto-generated unique ID |
234
+
235
+ Implements `ControlValueAccessor` — compatible with `[(ngModel)]` and reactive forms via `formControlName`.
236
+
237
+ ### Example
238
+
239
+ ```typescript
240
+ import { Component } from '@angular/core';
241
+ import { JvTextareaComponent } from '@devjuliovilla/jv-ui';
242
+ import { ReactiveFormsModule, FormBuilder } from '@angular/forms';
243
+
244
+ @Component({
245
+ selector: 'app-profile',
246
+ standalone: true,
247
+ imports: [ReactiveFormsModule, JvTextareaComponent],
248
+ template: `
249
+ <jv-textarea
250
+ formControlName="bio"
251
+ placeholder="Tell us about yourself..."
252
+ [rows]="4"
253
+ [maxlength]="500"
254
+ />
255
+ `,
256
+ })
257
+ export class ProfileComponent {
258
+ form = inject(FormBuilder).group({ bio: [''] });
259
+ }
260
+ ```
261
+
262
+ ## Icon System
263
+
264
+ ```typescript
265
+ import { JvIconComponent, JV_LUCIDE_ICON_REGISTRY } from '@devjuliovilla/jv-ui';
266
+ ```
267
+
268
+ | Input | Type | Default | Description |
269
+ |---|---|---|---|
270
+ | `name` | `string` | — | Icon name (from auto-generated Lucide registry) |
271
+ | `size` | `number` | `24` | Width and height in pixels |
272
+ | `strokeWidth` | `number` | `2` | SVG stroke width |
273
+ | `decorative` | `boolean` | `true` | `aria-hidden="true"` when decorative |
274
+ | `ariaLabel` | `string` | — | Accessible label when not decorative |
275
+
276
+ Icons are auto-generated from [lucide-static](https://www.npmjs.com/package/lucide-static) via:
277
+
278
+ ```bash
279
+ node tools/generate-icons.mjs
280
+ ```
281
+
282
+ This reads all SVG files from `lucide-static/icons` and produces `jv-icon-registry.ts` with the complete set (~1500 icons). Run it after updating `lucide-static` to keep icons in sync.
283
+
284
+ ## Services
285
+
286
+ | Service | Import Path | Description |
287
+ |---|---|---|
288
+ | `JvThemeService` | `@devjuliovilla/jv-ui` | Light / dark / high-contrast theme switching |
289
+ | `JvTranslationService` | `@devjuliovilla/jv-ui` | Dictionary-based i18n (en/es) |
290
+ | `JvToastService` | `@devjuliovilla/jv-ui` | Toast notification system |
291
+ | `JvLoaderService` | `@devjuliovilla/jv-ui` | Full-screen loader with request counter |
292
+ | `JvDialogService` | `@devjuliovilla/jv-ui` | Programmatic confirm dialogs |
293
+ | `JvAnnouncementService` | `@devjuliovilla/jv-ui` | Screen-reader live-region announcements |
294
+
295
+ ## Accessibility
296
+
297
+ - WCAG 2.2 AA target
298
+ - Keyboard navigation for all interactive components
299
+ - Focus management with visible focus indicators
300
+ - Screen reader announcements (loading, empty states, page changes)
301
+ - `aria-sort` on sortable grid headers
302
+ - `aria-selected` on selectable grid rows
303
+ - `aria-label` and `aria-labelledby` on interactive controls
304
+ - Supports `prefers-reduced-motion`
305
+
306
+ ## Requirements
307
+
308
+ - Angular 21.2+
309
+ - TypeScript 5.9+
310
+
311
+ ## License
312
+
313
+ MIT