Accessibility-first Angular component library built with signals and standalone APIs. Includes components, forms, data grids, dialogs, layout primitives, services, and i18n infrastructure.
npm install @devjuliovilla/jv-ui
Import the theme CSS in your angular.json or styles array:
"styles": ["@devjuliovilla/jv-ui/styles/jv-theme.css"]
import { ApplicationConfig } from '@angular/core';
import { provideJvUi } from '@devjuliovilla/jv-ui';
export const appConfig: ApplicationConfig = {
providers: [provideJvUi({ locale: 'en' })],
};
import { Component } from '@angular/core';
import { JvButtonComponent } from '@devjuliovilla/jv-ui';
@Component({
selector: 'app-root',
standalone: true,
imports: [JvButtonComponent],
template: '<jv-button variant="primary">Hello</jv-button>',
})
export class App {}
| Category |
Components |
| Buttons |
JvButton, JvButtonGroup, JvIconButton |
| Forms |
JvInput, JvTextarea, JvSelect, JvCheckbox, JvRadio, JvSwitch, JvFormContainer |
| Data Grid |
JvGrid — full-featured data table (see below) |
| Containers |
JvCard, JvSection, JvDivider |
| List |
JvList, JvListItem — list with title, description, icons, badges, and interactive items |
| Feedback |
JvAlert, JvBadge, JvLoader, JvToast |
| Dialogs |
JvDialog, JvConfirmDialog |
| Layout |
JvDashboardShell, JvSidebar, JvTopbar, JvBreadcrumb, JvPage |
| Auth Pages |
JvLoginPage, JvForgotPasswordPage, JvChangePasswordPage |
| Icon |
JvIcon — Lucide-based icon component with 1500+ auto-generated icons |
| Pagination |
JvPagination — standalone pagination with i18n |
| Feature |
Description |
| Sorting |
Client-side multi-column sorting with sort icons and aria-sort |
| Search |
Global text search across visible columns |
| Pagination |
Configurable page size, page range buttons, first/last/prev/next |
| Selection |
Checkbox row selection with selectedIds / selectionChange |
| Row actions |
Custom action buttons per row |
| Density modes |
Compact, normal, comfortable |
| Sticky columns |
Pin columns to left or right |
| Column resizing |
Drag column header borders to resize |
| Column reordering |
Drag & drop column headers |
| Grouped headers |
Multi-row header with colspan via children |
| Per-column filters |
Filter input row with operators |
| Inline editing |
Double-click or Enter to edit: text, number, select, boolean |
| Virtual scrolling |
Render only visible rows for large datasets |
| CSV/Excel export |
Built-in toolbar buttons, no external deps |
| Server-side mode |
Delegate sort/filter/page to parent component |
| TrackBy |
Custom trackBy key or function |
| i18n |
All labels configurable via translation dictionary |
import { JvGridComponent } from '@devjuliovilla/jv-ui';
Inputs:
| Input |
Type |
Default |
Description |
data |
T[] |
[] |
Row data array |
columns |
JvGridColumn<T>[] |
[] |
Column definitions |
actions |
JvGridAction<T>[] |
[] |
Row action button configs |
options |
Partial<JvGridOptions> |
{} |
Feature toggles and config |
trackBy |
keyof T | Function | null |
null |
Row identity |
selectedIds |
(string | number)[] |
[] |
Controlled selection IDs |
Outputs:
| Output |
Type |
rowClick |
T |
rowDoubleClick |
T |
actionClick |
{ actionId: string; row: T } |
selectionChange |
(string | number)[] |
pageChange |
{ pageIndex: number; pageSize: number } |
searchChange |
string |
sortChange |
{ columnKey: string; direction: 'asc' | 'desc' | null } |
columnFilter |
{ columnKey: string; value: string } |
columnResize |
{ columnKey: string; width: string } |
columnReorder |
{ columnKey: string; newIndex: number } |
rowEdit |
{ row: T; column: JvGridColumn<T>; value: unknown } |
JvGridColumn options:
| Property |
Type |
Description |
key |
keyof T | string |
Data field key |
header |
string |
Column display header |
sortable |
boolean |
Enable column sorting |
searchable |
boolean |
Include in global search |
filterable |
boolean |
Show per-column filter input |
hidden |
boolean |
Hide column from display |
sticky |
'left' | 'right' |
Pin column position |
editable |
boolean |
Allow inline cell editing |
editType |
'text' | 'number' | 'select' | 'boolean' |
Editor type |
editOptions |
{ label: string; value: unknown }[] |
Options for select editor |
width |
string |
Column width (e.g. '8rem') |
align |
'start' | 'center' | 'end' |
Text alignment |
type |
'text' | 'number' | 'currency' | 'date' | 'datetime' | 'boolean' |
Data type for formatting |
format |
(value, row) => string |
Custom display formatter |
children |
JvGridColumn<T>[] |
Child columns for grouped headers |
resizable |
boolean |
Allow column resize |
cellClass |
string |
CSS class for cells |
headerClass |
string |
CSS class for header |
JvGridOptions:
| Option |
Type |
Default |
Description |
searchable |
boolean |
true |
Show search bar |
sortable |
boolean |
true |
Enable column sorting |
pageable |
boolean |
true |
Enable pagination |
selectable |
boolean |
false |
Show selection checkboxes |
pageSize |
number |
20 |
Default page size |
pageSizeOptions |
number[] |
[10, 20, 50, 100] |
Page size selector options |
density |
'compact' | 'normal' | 'comfortable' |
'normal' |
Row density |
loading |
boolean |
false |
Show loading spinner |
emptyMessage |
string |
— |
Empty state message |
exportable |
boolean |
false |
Show CSV/Excel export buttons |
serverSide |
boolean |
false |
Disable client-side processing |
totalItems |
number |
0 |
Total items for server-side mode |
stickyColumns |
boolean |
false |
Enable sticky column support |
resizableColumns |
boolean |
false |
Enable column resizing |
reorderableColumns |
boolean |
false |
Enable column reordering |
editable |
boolean |
false |
Enable inline editing |
virtualScroll |
boolean |
false |
Enable virtual scrolling |
virtualScrollRowHeight |
number |
48 |
Row height for virtual scroll |
columnFilters |
boolean |
false |
Show filter row below headers |
rowDoubleClick |
boolean |
false |
Enable row double-click event |
ariaLabel |
string |
— |
Accessible label for the grid |
import { Component } from '@angular/core';
import { JvGridComponent, JvGridColumn, JvGridOptions } from '@devjuliovilla/jv-ui';
interface Product {
id: number;
name: string;
price: number;
status: string;
}
@Component({
selector: 'app-products',
standalone: true,
imports: [JvGridComponent],
template: `
<jv-grid
[data]="products"
[columns]="columns"
[options]="{ selectable: true, exportable: true, pageSize: 10 }"
[trackBy]="'id'"
[selectedIds]="selectedIds"
(selectionChange)="selectedIds = $event"
/>
`,
})
export class ProductsComponent {
products: Product[] = [ ];
selectedIds: (string | number)[] = [];
columns: JvGridColumn<Product>[] = [
{ key: 'id', header: 'ID', width: '4rem' },
{ key: 'name', header: 'Product', sortable: true, filterable: true },
{ key: 'price', header: 'Price', type: 'number', align: 'end', format: (v) => '
import { JvListComponent, JvListItemComponent } from '@devjuliovilla/jv-ui';
JvList inputs:
| Input |
Type |
Default |
Description |
role |
'list' | 'navigation' |
'list' |
ARIA role for the list |
variant |
'bordered' | null |
null |
Outer border + border-radius around the list (items always have bottom border) |
ariaLabel |
string |
'' |
Accessible label |
JvListItem inputs:
| Input |
Type |
Default |
Description |
title |
string (required) |
— |
Primary text |
description |
string |
'' |
Secondary text below title |
leadingIcon |
string | null |
null |
Lucide icon name on the left |
disabled |
boolean |
false |
Gray out and block interaction |
active |
boolean |
false |
Highlight with primary color and left border |
JvListItem outputs:
| Output |
Type |
activated |
void |
Content projection: use the meta attribute selector for badges, icons, or any content on the right side:
<jv-list-item title="Inbox" description="New messages">
<jv-badge tone="primary" meta>3</jv-badge>
<jv-icon name="chevron-right" meta />
</jv-list-item>
import { Component } from '@angular/core';
import { JvListComponent, JvListItemComponent, JvBadgeComponent } from '@devjuliovilla/jv-ui';
@Component({
selector: 'app-nav-list',
standalone: true,
imports: [JvListComponent, JvListItemComponent, JvBadgeComponent],
template: `
<jv-list variant="bordered">
<jv-list-item
title="Dashboard"
description="Main overview"
leadingIcon="layout-dashboard"
[active]="true"
/>
<jv-list-item
title="Notifications"
description="3 pending"
(activated)="onOpen()"
>
<jv-badge tone="primary" meta>3</jv-badge>
</jv-list-item>
<jv-list-item
title="Reports"
description="Coming soon"
[disabled]="true"
/>
</jv-list>
`,
})
export class NavListComponent {
onOpen() { }
}
import { JvPaginationComponent } from '@devjuliovilla/jv-ui';
| Input |
Type |
Default |
Description |
totalItems |
number |
0 |
Total items |
pageSize |
number |
20 |
Items per page |
pageSizeOptions |
number[] |
[10, 20, 50, 100] |
Page size options |
pageIndex |
number |
0 |
Current page (0-based) |
maxVisiblePages |
number |
5 |
Max page buttons to show |
| Output |
Type |
pageChange |
{ pageIndex: number; pageSize: number } |
import { JvTextareaComponent } from '@devjuliovilla/jv-ui';
| Input |
Type |
Default |
Description |
placeholder |
string |
'' |
Placeholder text |
required |
boolean |
false |
Mark field as required |
invalid |
boolean |
false |
Show invalid state |
readonly |
boolean |
false |
Make textarea read-only |
rows |
number |
3 |
Visible number of rows |
cols |
number |
— |
Visible width in columns |
maxlength |
number |
— |
Maximum character length |
describedBy |
string |
'' |
ID of description/error element for aria-describedby |
inputId |
string |
jv-textarea-{n} |
Auto-generated unique ID |
Implements ControlValueAccessor — compatible with [(ngModel)] and reactive forms via formControlName.
import { Component } from '@angular/core';
import { JvTextareaComponent } from '@devjuliovilla/jv-ui';
import { ReactiveFormsModule, FormBuilder } from '@angular/forms';
@Component({
selector: 'app-profile',
standalone: true,
imports: [ReactiveFormsModule, JvTextareaComponent],
template: `
<jv-textarea
formControlName="bio"
placeholder="Tell us about yourself..."
[rows]="4"
[maxlength]="500"
/>
`,
})
export class ProfileComponent {
form = inject(FormBuilder).group({ bio: [''] });
}
import { JvIconComponent, JV_LUCIDE_ICON_REGISTRY } from '@devjuliovilla/jv-ui';
| Input |
Type |
Default |
Description |
name |
string |
— |
Icon name (from auto-generated Lucide registry) |
size |
number |
24 |
Width and height in pixels |
strokeWidth |
number |
2 |
SVG stroke width |
decorative |
boolean |
true |
aria-hidden="true" when decorative |
ariaLabel |
string |
— |
Accessible label when not decorative |
Icons are auto-generated from lucide-static via:
node tools/generate-icons.mjs
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.
import { JvDashboardShellComponent } from '@devjuliovilla/jv-ui';
| Input |
Type |
Default |
Description |
title |
string |
— |
App / brand title |
sidebarSubtitle |
string |
'' |
Subtitle below brand |
brandIcon |
string |
'monitor' |
Brand Lucide icon |
navItems |
JvNavItem[] |
[] |
Navigation items (supports nested children) |
sidebarCollapsed |
boolean |
false |
Desktop sidebar collapsed state |
mobileSidebarOpen |
boolean |
false |
Mobile sidebar open/closed |
topbarTitle |
string |
'' |
Topbar page title |
topbarSubtitle |
string |
'' |
Topbar subtitle |
currentTheme |
JvTheme |
'light' |
Active theme value |
themeOptions |
JvThemeOption[] |
[] |
Theme selector options |
topbarActions |
JvTopbarAction[] |
[] |
Icon buttons in topbar |
showThemeSelector |
boolean |
true |
Show theme pills |
| Output |
Description |
menuClick |
Hamburger button clicked — toggle mobile sidebar |
mobileClose |
Backdrop clicked or navigation selected — close mobile sidebar |
sidebarCollapseToggle |
Collapse toggle clicked — collapse/expand sidebar |
themeChange |
Theme pill clicked |
topbarActionClick |
Topbar action button clicked |
Mobile behavior: at viewports ≤ 900px the sidebar becomes a fixed overlay with slide-in animation (transform: translateX) and a semi-transparent backdrop. The sidebar emits mobileClose when the backdrop is clicked or a nav link is followed.
| Input |
Type |
Default |
Description |
title |
string |
— |
Brand title |
subtitle |
string |
'' |
Brand subtitle |
brandIcon |
string |
'monitor' |
Brand icon |
items |
JvNavItem[] |
[] |
Navigation items |
collapsed |
boolean |
false |
Collapse to icon-only |
mobileOpen |
boolean |
false |
Show on mobile |
ariaLabel |
string |
'Sidebar navigation' |
Accessible label |
| Output |
Description |
collapseToggle |
Collapse button clicked |
mobileClose |
Backdrop or nav item interaction — request close |
The library includes basic responsive behavior:
- Breakpoints defined as CSS custom properties:
--jv-breakpoint-sm (640px), --jv-breakpoint-md (768px), --jv-breakpoint-lg (1024px), --jv-breakpoint-xl (1280px)
- Dashboard shell collapses to single column at ≤ 900px; sidebar becomes a slide-in overlay with backdrop
- Topbar stacks vertically at ≤ 900px
- Grid pagination and toolbar stack at ≤ 640px; table scrolls horizontally with
overflow-x: auto
- Flex-wrap is used extensively for natural overflow handling
- Dialog and Toast constrain width with
min() to never exceed the viewport
| Service |
Import Path |
Description |
JvThemeService |
@devjuliovilla/jv-ui |
Light / dark / high-contrast theme switching |
JvTranslationService |
@devjuliovilla/jv-ui |
Dictionary-based i18n (en/es) |
JvToastService |
@devjuliovilla/jv-ui |
Toast notification system |
JvLoaderService |
@devjuliovilla/jv-ui |
Full-screen loader with request counter |
JvDialogService |
@devjuliovilla/jv-ui |
Programmatic confirm dialogs |
JvAnnouncementService |
@devjuliovilla/jv-ui |
Screen-reader live-region announcements |
- WCAG 2.2 AA target
- Keyboard navigation for all interactive components
- Focus management with visible focus indicators
- Screen reader announcements (loading, empty states, page changes)
aria-sort on sortable grid headers
aria-selected on selectable grid rows
aria-label and aria-labelledby on interactive controls
- Supports
prefers-reduced-motion
- Angular 21.2+
- TypeScript 5.9+
MIT
+ v },
{ key: 'status', header: 'Status', sortable: true },
];
}
__CODE_BLOCK_6__
JvList inputs:
| Input |
Type |
Default |
Description |
| __INLINE_CODE_176__ |
__INLINE_CODE_177__ |
__INLINE_CODE_178__ |
ARIA role for the list |
| __INLINE_CODE_179__ |
__INLINE_CODE_180__ |
__INLINE_CODE_181__ |
Outer border + border-radius around the list (items always have bottom border) |
| __INLINE_CODE_182__ |
__INLINE_CODE_183__ |
__INLINE_CODE_184__ |
Accessible label |
JvListItem inputs:
| Input |
Type |
Default |
Description |
| __INLINE_CODE_185__ |
__INLINE_CODE_186__ (required) |
— |
Primary text |
| __INLINE_CODE_187__ |
__INLINE_CODE_188__ |
__INLINE_CODE_189__ |
Secondary text below title |
| __INLINE_CODE_190__ |
__INLINE_CODE_191__ |
__INLINE_CODE_192__ |
Lucide icon name on the left |
| __INLINE_CODE_193__ |
__INLINE_CODE_194__ |
__INLINE_CODE_195__ |
Gray out and block interaction |
| __INLINE_CODE_196__ |
__INLINE_CODE_197__ |
__INLINE_CODE_198__ |
Highlight with primary color and left border |
JvListItem outputs:
| Output |
Type |
| __INLINE_CODE_199__ |
__INLINE_CODE_200__ |
Content projection: use the __INLINE_CODE_201__ attribute selector for badges, icons, or any content on the right side:
__CODE_BLOCK_7__
__CODE_BLOCK_8__
__CODE_BLOCK_9__
| Input |
Type |
Default |
Description |
| __INLINE_CODE_202__ |
__INLINE_CODE_203__ |
__INLINE_CODE_204__ |
Total items |
| __INLINE_CODE_205__ |
__INLINE_CODE_206__ |
__INLINE_CODE_207__ |
Items per page |
| __INLINE_CODE_208__ |
__INLINE_CODE_209__ |
__INLINE_CODE_210__ |
Page size options |
| __INLINE_CODE_211__ |
__INLINE_CODE_212__ |
__INLINE_CODE_213__ |
Current page (0-based) |
| __INLINE_CODE_214__ |
__INLINE_CODE_215__ |
__INLINE_CODE_216__ |
Max page buttons to show |
| Output |
Type |
| __INLINE_CODE_217__ |
__INLINE_CODE_218__ |
__CODE_BLOCK_10__
| Input |
Type |
Default |
Description |
| __INLINE_CODE_219__ |
__INLINE_CODE_220__ |
__INLINE_CODE_221__ |
Placeholder text |
| __INLINE_CODE_222__ |
__INLINE_CODE_223__ |
__INLINE_CODE_224__ |
Mark field as required |
| __INLINE_CODE_225__ |
__INLINE_CODE_226__ |
__INLINE_CODE_227__ |
Show invalid state |
| __INLINE_CODE_228__ |
__INLINE_CODE_229__ |
__INLINE_CODE_230__ |
Make textarea read-only |
| __INLINE_CODE_231__ |
__INLINE_CODE_232__ |
__INLINE_CODE_233__ |
Visible number of rows |
| __INLINE_CODE_234__ |
__INLINE_CODE_235__ |
— |
Visible width in columns |
| __INLINE_CODE_236__ |
__INLINE_CODE_237__ |
— |
Maximum character length |
| __INLINE_CODE_238__ |
__INLINE_CODE_239__ |
__INLINE_CODE_240__ |
ID of description/error element for aria-describedby |
| __INLINE_CODE_241__ |
__INLINE_CODE_242__ |
__INLINE_CODE_243__ |
Auto-generated unique ID |
Implements __INLINE_CODE_244__ — compatible with __INLINE_CODE_245__ and reactive forms via __INLINE_CODE_246__.
__CODE_BLOCK_11__
__CODE_BLOCK_12__
| Input |
Type |
Default |
Description |
| __INLINE_CODE_247__ |
__INLINE_CODE_248__ |
— |
Icon name (from auto-generated Lucide registry) |
| __INLINE_CODE_249__ |
__INLINE_CODE_250__ |
__INLINE_CODE_251__ |
Width and height in pixels |
| __INLINE_CODE_252__ |
__INLINE_CODE_253__ |
__INLINE_CODE_254__ |
SVG stroke width |
| __INLINE_CODE_255__ |
__INLINE_CODE_256__ |
__INLINE_CODE_257__ |
__INLINE_CODE_258__ when decorative |
| __INLINE_CODE_259__ |
__INLINE_CODE_260__ |
— |
Accessible label when not decorative |
Icons are auto-generated from lucide-static via:
__CODE_BLOCK_13__
This reads all SVG files from __INLINE_CODE_261__ and produces __INLINE_CODE_262__ with the complete set (~1500 icons). Run it after updating __INLINE_CODE_263__ to keep icons in sync.
__CODE_BLOCK_14__
| Input |
Type |
Default |
Description |
| __INLINE_CODE_264__ |
__INLINE_CODE_265__ |
— |
App / brand title |
| __INLINE_CODE_266__ |
__INLINE_CODE_267__ |
__INLINE_CODE_268__ |
Subtitle below brand |
| __INLINE_CODE_269__ |
__INLINE_CODE_270__ |
__INLINE_CODE_271__ |
Brand Lucide icon |
| __INLINE_CODE_272__ |
__INLINE_CODE_273__ |
__INLINE_CODE_274__ |
Navigation items (supports nested children) |
| __INLINE_CODE_275__ |
__INLINE_CODE_276__ |
__INLINE_CODE_277__ |
Desktop sidebar collapsed state |
| __INLINE_CODE_278__ |
__INLINE_CODE_279__ |
__INLINE_CODE_280__ |
Mobile sidebar open/closed |
| __INLINE_CODE_281__ |
__INLINE_CODE_282__ |
__INLINE_CODE_283__ |
Topbar page title |
| __INLINE_CODE_284__ |
__INLINE_CODE_285__ |
__INLINE_CODE_286__ |
Topbar subtitle |
| __INLINE_CODE_287__ |
__INLINE_CODE_288__ |
__INLINE_CODE_289__ |
Active theme value |
| __INLINE_CODE_290__ |
__INLINE_CODE_291__ |
__INLINE_CODE_292__ |
Theme selector options |
| __INLINE_CODE_293__ |
__INLINE_CODE_294__ |
__INLINE_CODE_295__ |
Icon buttons in topbar |
| __INLINE_CODE_296__ |
__INLINE_CODE_297__ |
__INLINE_CODE_298__ |
Show theme pills |
| Output |
Description |
| __INLINE_CODE_299__ |
Hamburger button clicked — toggle mobile sidebar |
| __INLINE_CODE_300__ |
Backdrop clicked or navigation selected — close mobile sidebar |
| __INLINE_CODE_301__ |
Collapse toggle clicked — collapse/expand sidebar |
| __INLINE_CODE_302__ |
Theme pill clicked |
| __INLINE_CODE_303__ |
Topbar action button clicked |
Mobile behavior: at viewports ≤ 900px the sidebar becomes a fixed overlay with slide-in animation (__INLINE_CODE_304__) and a semi-transparent backdrop. The sidebar emits __INLINE_CODE_305__ when the backdrop is clicked or a nav link is followed.
| Input |
Type |
Default |
Description |
| __INLINE_CODE_306__ |
__INLINE_CODE_307__ |
— |
Brand title |
| __INLINE_CODE_308__ |
__INLINE_CODE_309__ |
__INLINE_CODE_310__ |
Brand subtitle |
| __INLINE_CODE_311__ |
__INLINE_CODE_312__ |
__INLINE_CODE_313__ |
Brand icon |
| __INLINE_CODE_314__ |
__INLINE_CODE_315__ |
__INLINE_CODE_316__ |
Navigation items |
| __INLINE_CODE_317__ |
__INLINE_CODE_318__ |
__INLINE_CODE_319__ |
Collapse to icon-only |
| __INLINE_CODE_320__ |
__INLINE_CODE_321__ |
__INLINE_CODE_322__ |
Show on mobile |
| __INLINE_CODE_323__ |
__INLINE_CODE_324__ |
__INLINE_CODE_325__ |
Accessible label |
| Output |
Description |
| __INLINE_CODE_326__ |
Collapse button clicked |
| __INLINE_CODE_327__ |
Backdrop or nav item interaction — request close |
The library includes basic responsive behavior:
- Breakpoints defined as CSS custom properties: __INLINE_CODE_328__ (640px), __INLINE_CODE_329__ (768px), __INLINE_CODE_330__ (1024px), __INLINE_CODE_331__ (1280px)
- Dashboard shell collapses to single column at ≤ 900px; sidebar becomes a slide-in overlay with backdrop
- Topbar stacks vertically at ≤ 900px
- Grid pagination and toolbar stack at ≤ 640px; table scrolls horizontally with __INLINE_CODE_332__
- Flex-wrap is used extensively for natural overflow handling
- Dialog and Toast constrain width with __INLINE_CODE_333__ to never exceed the viewport
| Service |
Import Path |
Description |
| __INLINE_CODE_334__ |
__INLINE_CODE_335__ |
Light / dark / high-contrast theme switching |
| __INLINE_CODE_336__ |
__INLINE_CODE_337__ |
Dictionary-based i18n (en/es) |
| __INLINE_CODE_338__ |
__INLINE_CODE_339__ |
Toast notification system |
| __INLINE_CODE_340__ |
__INLINE_CODE_341__ |
Full-screen loader with request counter |
| __INLINE_CODE_342__ |
__INLINE_CODE_343__ |
Programmatic confirm dialogs |
| __INLINE_CODE_344__ |
__INLINE_CODE_345__ |
Screen-reader live-region announcements |
- WCAG 2.2 AA target
- Keyboard navigation for all interactive components
- Focus management with visible focus indicators
- Screen reader announcements (loading, empty states, page changes)
- __INLINE_CODE_346__ on sortable grid headers
- __INLINE_CODE_347__ on selectable grid rows
- __INLINE_CODE_348__ and __INLINE_CODE_349__ on interactive controls
- Supports __INLINE_CODE_350__
- Angular 21.2+
- TypeScript 5.9+
MIT