limitless_ui 1.0.0-dev.2
limitless_ui: ^1.0.0-dev.2 copied to clipboard
Reusable AngularDart UI components and directives for web applications.
limitless_ui #
Reusable AngularDart UI components, directives, and browser helpers for applications built on the Limitless visual language and Bootstrap-based CSS: https://cdn.jsdelivr.net/gh/SXNhcXVl/limitless@4.0/dist/css/all.min.css https://cdn.jsdelivr.net/gh/SXNhcXVl/limitless@4.0/dist/icons/phosphor/2.0.3/styles.min.css.
This package is browser-only. It depends on dart:html, ngdart, ngforms and ngrouter.
Contract overview #
limitless_ui is a generic AngularDart UI library, but part of its higher-level data UI is built on top of essential_core, which was extracted as a reusable and framework-agnostic foundation.
This means the package remains generic, but it is not fully standalone for every component family.
This library has two explicit usage layers:
- Generic UI layer: components such as alerts, buttons, modals, tabs, toast, tooltip, popover, checkbox, radio, toggle, rating, file upload and the base
li-inputcan be consumed withlimitless_uialone. - Shared-foundation layer:
li-datatable,li-datatable-select,li-select,li-multi-select,li-typeaheadand treeview-related components were designed to reuse generic models and utilities fromessential_core. This is part of the documented contract of the library, not an accidental implementation detail.
For consumers using the data-oriented components above, essential_core should be understood as shared infrastructure, not as an application-specific dependency.
Some form value accessors in this package rely on internal ngforms APIs and behavior because AngularDart does not expose all hooks needed by the library through stable public APIs. Because of that, keep ngdart, ngforms and ngrouter tightly pinned, prefer exact or very narrow version constraints in consuming apps, avoid automatic upgrades, and always run focused tests for the value accessors first during framework upgrades.
Demo page: https://insinfo.github.io/limitless_ui/
Publication status #
The package is prepared for publication and currently versioned as 1.0.0-dev.2, because it still depends on AngularDart pre-release packages:
ngdart: ^8.0.0-dev.4ngforms: ^5.0.0-dev.3ngrouter: ^4.0.0-dev.3
For applications consuming limitless_ui, treat these versions as compatibility-critical. In particular:
- keep
ngdart,ngformsandngrouterwell pinned in the application; - prefer exact versions or very narrow ranges for these packages in consumers of
limitless_ui; - do not enable automated dependency upgrades for these packages without manual validation;
- when testing an upgrade, run the value accessor tests first, because they are usually the first area to break.
Publication metadata is configured in pubspec.yaml and CI is defined in .github/workflows/ci.yml.
Installation #
Generic usage #
dependencies:
limitless_ui: ^1.0.0-dev.2
When using data-oriented components backed by essential_core #
If the application will use li-datatable, li-datatable-select, li-select, li-multi-select, li-typeahead or treeview components, install both packages. In this setup, essential_core is the shared data/model foundation reused by the UI layer:
dependencies:
limitless_ui: ^1.0.0-dev.2
essential_core: ^1.0.0
For local development:
dependencies:
limitless_ui:
path: ../limitless_ui
essential_core:
path: ../essential_core
Import #
Generic UI imports #
import 'package:limitless_ui/limitless_ui.dart';
Imports for data-oriented APIs #
Use limitless_ui for the widgets and essential_core for the shared data structures used by the higher-level components:
import 'package:limitless_ui/limitless_ui.dart';
import 'package:essential_core/essential_core.dart';
Theme and icons #
The package follows the Limitless visual language, but some visual affordances are provided by the theme CSS rather than component Dart code.
The demo application loads Limitless CSS plus the Phosphor icon font in example/web/index.html. One practical detail is the dropdown caret: if your theme renders .dropdown-toggle::after with a glyph that does not exist in the loaded icon font, the caret will appear broken.
For this repository, the canonical icon mapping is always the Phosphor stylesheet loaded from https://cdn.jsdelivr.net/gh/SXNhcXVl/limitless@4.0/dist/icons/phosphor/2.0.3/styles.min.css.
Do not treat the icon content values embedded inside https://cdn.jsdelivr.net/gh/SXNhcXVl/limitless@4.0/dist/css/all.min.css as the source of truth. That theme bundle still contains selectors authored against an older Phosphor codepoint map, so components that depend on pseudo-element icons may need local content overrides to stay visually correct with the newer 2.0.3 font file actually loaded by the demo.
LiWizardComponent is one of those cases: the wizard step icons are overridden in lib/src/components/wizard/wizard_component.scss so the current, done and error states resolve against Phosphor 2.0.3 instead of the older codepoints baked into all.min.css.
The demo fixes that with a global override in example/web/style.scss:
.dropdown-toggle::after {
font-family: var(--icon-font-family), "Phosphor" !important;
content: "\e9fe";
}
\e9fe is the ph-caret-down glyph from the Phosphor font bundle used by the demo.
AngularDart stylesheets #
- In this repository, component styles are authored in
.scssand compiled bysass_builder. - In
@Component(styleUrls: ...), always reference the generated.csspath, not.scss. - Do not create or commit manual duplicate
.cssfiles next to component.scsssources just to satisfystyleUrls. - If a component has
toast_component.scss, the correct AngularDart annotation isstyleUrls: ['toast_component.css'].
Included modules #
- Inputs: checkbox, radio, toggle, rating, file upload, currency input, date picker, time picker, date range picker, color picker, select, multi-select, typeahead.
- Data display: datatable, datatable select, tree view, highlight.
- Structure: accordion, collapse, buttons, carousel, modal, tabs, nav, wizard, breadcrumbs, pagination, offcanvas, floating action button.
- Overlay and menus: dropdown, dropdown menu, tooltip, popover, sweet alert, notification toast.
- Navigation helpers: scrollspy service and directives.
- Utilities: HTML directives, form value accessors, pipes, PDF generator and XLSX generator.
Utility directives and pipes #
Beyond visual components, the package also exposes small browser-oriented helpers that are now available from the public barrel in lib/limitless_ui.dart:
CpfMaskDirective: applies thexxx.xxx.xxx-xxmask while the user types.CnpjMaskDirective: applies thexx.xxx.xxx/xxxx-xxmask while the user types.CpfFormatterPipe: formats CPF values asXXX.XXX.XXX-XXor returns digits only.CpfHiddenPipe: keeps only the first or last four CPF characters visible.HideStringPipe: preserves a visible prefix and masks the rest of a string.TextMaskDirective,OnlyNumberDirectiveandCustomHrefDirective: helpers for generic masked inputs, digit-only fields and attribute-driven href synchronization.CustomNumberValueAccessor,DateTimeValueAccessorandMinMaxDirective: form helpers for<input type="number">,<input type="datetime-local">and constrained numeric inputs.
In ngdart 8, template pipes are invoked through $pipe instead of the legacy value | pipeName syntax. That applies both to built-in pipes such as date and to custom pipes registered in the component.
Example:
import 'package:limitless_ui/limitless_ui.dart';
import 'package:ngdart/angular.dart';
@Component(
selector: 'demo-form-helpers',
template: '''
<input cpfMask [(ngModel)]="cpf">
<input type="number" min="1" max="10" [(ngModel)]="quantity">
<input type="datetime-local" [(ngModel)]="scheduledAt">
<p>{{ $pipe.date(scheduledAt, 'medium') }}</p>
<p>{{ $pipe.cpfFormatter(cpf) }}</p>
<p>{{ $pipe.cpfHidden(cpf, 'asteriskStart') }}</p>
<p>{{ $pipe.hideString(note, 3, '#') }}</p>
''',
directives: [coreDirectives, formDirectives, CpfMaskDirective],
pipes: [commonPipes, CpfFormatterPipe, CpfHiddenPipe, HideStringPipe],
)
class DemoFormHelpersComponent {
String cpf = '';
String quantity = '1';
DateTime? scheduledAt;
String note = 'abcdef';
}
Generic vs essential_core-backed components #
Use these groups as the practical adoption boundary:
- Generic components: alerts, buttons, accordion, collapse, modal, tabs, nav, tooltip, popover, toast, scrollspy, checkbox, radio, toggle, rating, file upload, date picker, date range picker, currency helpers and the base
li-input. essential_core-backed components:li-datatable,li-datatable-select,li-select,li-multi-select,li-typeahead,li-treeview-select,LiTreeViewComponentand related data/selection helpers.
The second group reuses Filters, DataFrame, tree data structures, and related contracts from essential_core.
Public API highlights #
The barrel export in lib/limitless_ui.dart exposes these API families:
- Accordion:
LiAccordionComponent,LiAccordionItemComponent,LiAccordionDirective,LiAccordionItemDirective,LiAccordionBodyDirective,LiAccordionBodyTemplateDirective,LiAccordionButtonDirective,LiAccordionToggleDirective,LiAccordionCollapseDirective,LiAccordionHeaderDirective,LiAccordionHeaderHostDirective. - Collapse:
LiCollapseDirective,LiCollapseController,LiCollapseConfig. - Dropdown:
LiDropdownDirective,LiDropdownMenuDirective,LiDropdownAnchorDirective,LiDropdownToggleDirective,LiDropdownItemDirective,LiDropdownButtonItemDirective,LiDropdownConfig. - Dropdown menu:
LiDropdownMenuComponent,LiDropdownMenuOption. - Breadcrumbs:
LiBreadcrumbComponent,LiBreadcrumbItemDirective,LiBreadcrumbStartDirective,LiBreadcrumbEndDirective. - Nav:
LiNavDirective,LiNavItemDirective,LiNavLinkDirective,LiNavOutletDirective,LiNavContentDirective,LiNavConfig. - Pagination:
LiPaginationComponent,liPaginationDirectives,LiPaginationEllipsisDirective,LiPaginationFirstDirective,LiPaginationLastDirective,LiPaginationNextDirective,LiPaginationNumberDirective,LiPaginationPagesDirective,LiPaginationPreviousDirective. - Wizard:
LiWizardComponent,LiWizardStepComponent,LiWizardStepChange,LiWizardStepHeaderContext,LiWizardActionsContext,liWizardDirectives. - Offcanvas:
LiOffcanvasComponent,LiOffcanvasService,LiOffcanvasRef,LiOffcanvasDismissReason,LiOffcanvasHeaderDirective,LiOffcanvasFooterDirective. - Popover and tooltip:
LiPopoverComponent,LiPopoverDirective,LiPopoverConfig,LiTooltipComponent,LiTooltipDirective,LiTooltipConfig. - Scrollspy:
LiScrollSpyService,LiScrollSpyDirective,LiScrollSpyFragmentDirective,LiScrollSpyItemDirective,LiScrollSpyMenuDirective,LiScrollSpyConfig. - Modal:
LiModalComponentwith lazy content support. - Toast:
LiToastComponent,LiToastStackComponent,LiToastService. - Color picker:
LiColorPickerComponent,LiColorPickerEvent, plus palette inputs, selection-history inputs, andpickerShow,pickerHide,pickerChange,pickerMove,pickerDragStart,pickerDragStopstreams. - Floating action button:
LiFabComponent,LiFabAction,LiFabShortcut,LiFabTriggerDirective,LiFabActionDirective. - Typeahead:
LiTypeaheadComponent,LiTypeaheadItem,LiTypeaheadSelectItemEvent,LiTypeaheadConfig,LiTypeaheadHighlightComponent. - Selection controls and upload:
LiCheckboxComponent,LiRadioComponent,LiToggleComponent,LiRatingComponent,LiRatingConfig,LiFileUploadComponent,LiFileSelectDirective,LiFileDropDirective,LiFileType. - Treeview:
LiTreeViewComponent,LiTreeviewSelectComponent,LiTreeViewPageLoader,TreeViewLoadRequest,TreeViewLoadResult,LiTreeviewSelectNodeDirective,LiTreeviewSelectTriggerDirective.
Recent additions in 1.0.0-dev.2 #
- Added first-class dropdown, nav, popover and scrollspy modules to the public API.
- Expanded accordion into a fuller directive set for host-driven and template-driven compositions.
- Added collapse as a reusable directive/config pair.
- Added injectable config objects for tooltip, popover, dropdown, nav and scrollspy.
- Added
lazyContentsupport toli-modalso heavy projected content can be created only while the modal is open. - Expanded the demo app with pages for dropdown, nav, popover and scrollspy, plus richer modal, tooltip, accordion and datatable examples.
- Added tests for the new surface area, including accordion lazy rendering, modal lazy content and overlay/navigation components.
- Added the new toast component family, toast stack service flow and a dedicated demo page.
- Refined the toast demo presentation, including a more compact rounded toast variant.
- Added CI coverage for toast browser tests and documented the AngularDart
.scssto.cssstylesheet convention used in this repository. - Added
li-typeaheadfor autocomplete-style selection with local filtering, keyboard navigation andngModel. - Expanded
li-typeaheadwith async search, rich result markup, a separate highlight component and injectable defaults viaLiTypeaheadConfig. - Added
li-checkbox,li-radio,li-toggle,li-ratingandli-file-upload, plus low-level file select/drop directives. - Expanded the demo app with dedicated pages for selection controls, rating, file upload, breadcrumbs, pagination, offcanvas, floating action button, highlight and color picker.
- Added
li-treeview-selectfor dropdown selection over hierarchical data. - Expanded
li-treeview-selectwith lazy page loading, remote search term forwarding throughTreeViewLoadRequest.searchTerm,multiple,labelBuilder,canSelectNodeand projected templates for trigger and node rendering. - Renamed legacy package paths from
br_currency_inputtocurrency_inputand aligned select/multi-select internals with the current public API layout. - Added
li-breadcrumb,li-pagination,li-offcanvas,li-fabandli-color-pickerto the public API and demo application. - Expanded
li-color-pickerwith palette support, selection history, toggleable palette-only mode andLiColorPickerEventstreams. - Extended
li-tabsxwith Limitless 4-alignedunderline,overlineandsolidvariants, plus richer demo coverage for justified layouts, projected headers,lazyLoadanddestroyOnHide. - Added
li-wizardandli-wizard-stepfor guided multi-step flows styled against the native Limitless 4.wizardmarkup. - Expanded
li-wizardwith[headerTemplate]and[actionsTemplate], plusLiWizardStepHeaderContextandLiWizardActionsContextso hosts can customize labels and footer actions without breaking the native step icons. - Refined the example shell with sidebar filtering, navbar route search powered by
li-typeahead, a shared breadcrumb component based onli-breadcrumb, richer tabs examples, a dedicated scrollbar stylesheet and a wizard/form wizard page. - Updated the demo i18n bootstrap to detect the browser locale and default to Portuguese only when the language starts with
pt, falling back to English otherwise. - Added an AngularDart documentation link to the example overview and bundled
site_ngdartfor local and GitHub Pages publication. - Expanded the package documentation with toast usage, stack placement, AngularDart stylesheet guidance, dependency/contract notes and richer component coverage.
- Expanded browser and integration coverage for breadcrumbs, offcanvas, pagination, typeahead, treeview select, selection controls, rating, file upload flows, directives, form integrations and wizard navigation.
- Stabilized focus-sensitive browser scenarios across input, offcanvas and multi-select interactions.
- Added GitHub Actions Pages deployment plus
scripts/prepare-pages.ps1to publish a combined artifact with the example app andsite_ngdart. - Improved GitHub Pages path rewriting and pretty URL generation for repository-prefixed
site_ngdarthosting.
Quick examples #
Alert #
@Component(
selector: 'demo-alert',
template: '''
<li-alert
variant="warning"
iconMode="block"
iconClass="ph-warning-circle"
[dismissible]="true">
Alert content
</li-alert>
''',
directives: [coreDirectives, LiAlertComponent],
)
class DemoAlertComponent {}
Datatable #
li-datatable covers the library's most common administrative data flow: field search, pagination, sorting, row selection, export, responsive mobile collapse, and switching between table and grid views without duplicating the data source.
The component revolves around three objects:
Filters: limit, offset, search, and sorting for the current request.DataFrame<T>: the returned collection plustotalRecords.DatatableSettings: columns and visual behavior for the table or grid.
Most useful features:
- column-targeted search with
searchInFields; - events such as
(dataRequest),(searchRequest), and(limitChange)for server-driven flows; - columns with
enableSorting,sortingBy,hideOnMobile,textAlign,nowrap,width, and custom classes; - per-cell styling with
cellStyleResolverand per-row styling withrowStyleResolver; - grid mode with
gridMode,gridTemplateColumns,gridGap, andcustomCardBuilder; - built-in XLSX and PDF export support.
final filters = Filters(limit: 10, offset: 0);
final settings = DatatableSettings(
colsDefinitions: <DatatableCol>[
DatatableCol(
key: 'feature',
title: 'Feature',
sortingBy: 'feature',
enableSorting: true,
),
DatatableCol(
key: 'owner',
title: 'Owner',
hideOnMobile: true,
),
DatatableCol(
key: 'status',
title: 'Status',
enableSorting: true,
sortingBy: 'status',
hideOnMobile: true,
),
],
);
final searchFields = <DatatableSearchField>[
DatatableSearchField(
label: 'Feature',
field: 'feature',
operator: 'like',
),
DatatableSearchField(
label: 'Status',
field: 'status',
operator: '=',
),
];
<li-datatable
[data]="usersFrame"
[settings]="settings"
[dataTableFilter]="filters"
[searchInFields]="searchFields"
[responsiveCollapse]="true"
[searchPlaceholder]="'Type to search'"
(dataRequest)="loadUsers($event)"
(limitChange)="loadUsers($event)"
(searchRequest)="loadUsers($event)">
</li-datatable>
For denser visual layouts, the same dataset can be reused in grid mode:
final gridSettings = DatatableSettings(
colsDefinitions: <DatatableCol>[
DatatableCol(key: 'feature', title: 'Feature', width: '240px'),
DatatableCol(key: 'owner', title: 'Owner'),
DatatableCol(key: 'status', title: 'Status'),
],
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
gridGap: '1rem',
);
<li-datatable
[data]="usersFrame"
[settings]="gridSettings"
[dataTableFilter]="filters"
[gridMode]="true"
[showCheckboxToSelectRow]="false"
[disableRowClick]="true">
</li-datatable>
Column, row, and grid customization:
final advancedSettings = DatatableSettings(
colsDefinitions: <DatatableCol>[
DatatableCol(
key: 'status',
title: 'Status',
width: '160px',
textAlign: 'center',
nowrap: true,
cellStyleResolver: (itemMap, itemInstance) {
final status = itemMap['status']?.toString() ?? '';
return status == 'Blocked'
? 'color: #b91c1c; font-weight: 700;'
: 'color: #0f766e; font-weight: 700;';
},
),
],
rowStyleResolver: (itemMap, itemInstance) {
if (itemMap['health'] == 'Critical') {
return 'background-color: rgba(239, 68, 68, 0.08);';
}
return null;
},
customCardBuilder: (itemMap, itemInstance, row) {
final root = DivElement()..classes.add('my-card');
root.text = itemMap['feature']?.toString() ?? '';
return root;
},
);
Best practices:
- keep
Filters,DatatableSettings, andsearchInFieldsstable instead of recreating them in getters; - use
hideOnMobileon secondary columns to feed the responsive collapse path; - reserve
customCardBuilderfor grids that genuinely need to diverge from the default layout; - for heavy content, prefer loading the datatable on demand inside a lazy accordion body or modal with
lazyContent.
The most complete demo is in example/lib/src/pages/datatable/datatable_page.dart and example/lib/src/pages/datatable/datatable_page.html.
Datatable Select #
li-datatable-select is the right fit when a simple select is not enough because users need to search, paginate, and sort before choosing an item. It combines a form-select-style trigger with an internal li-modal that hosts a li-datatable.
Main flow:
- the host provides
Filters,DataFrame<T>, andDatatableSettings; - the component emits
(dataRequest)whenever the internal table needs data; - clicking a row selects the item, updates the trigger label, and closes the modal;
- the value can be controlled with
[(ngModel)]or(currentValueChange).
Most relevant inputs and features:
labelKeyandvalueKeyto separate the visible label from the persisted value;searchInFieldsfor the search selector inside the modal;modalSize,title,placeholder,disabled, andfullScreenOnMobile;- public methods such as
clear(),setSelectedItem(...), andselectedLabel.
<li-datatable-select
[settings]="personSettings"
[dataTableFilter]="personFilter"
[data]="personFrame"
[searchInFields]="personSearchFields"
labelKey="name"
valueKey="id"
title="Selecionar pessoa"
placeholder="Clique para selecionar..."
(dataRequest)="loadPeople($event)"
(currentValueChange)="onPersonChanged($event)">
</li-datatable-select>
<li-datatable-select
[settings]="personSettings"
[dataTableFilter]="personFilter"
[data]="personFrame"
[searchInFields]="personSearchFields"
labelKey="name"
valueKey="id"
[(ngModel)]="selectedPersonId"
(dataRequest)="loadPeople($event)">
</li-datatable-select>
Best practices:
- keep
Filters,DatatableSettings, andsearchInFieldsstable; - handle data loading in the parent, just as you would for a regular datatable;
- use
valueKeyto persist only IDs instead of the full map when the field belongs to a form; - use
@ViewChildonly for focused programmatic actions such asclear()orsetSelectedItem(...).
The reference demo is in example/lib/src/pages/datatable_select/datatable_select_page.dart and example/lib/src/pages/datatable_select/datatable_select_page.html.
Select and Multi-Select #
li-select and li-multi-select cover adjacent but distinct scenarios:
li-select: a single choice with inline search, support fordataSourceor projected options, andngModelintegration;li-multi-select: multiple selected values, typically rendered as badges in the trigger.
li-select accepts List<Map<String, dynamic>> or DataFrame through [dataSource], and it also supports manual projection with li-option. The main keys are labelKey, valueKey, and disabledKey. The component is searchable by default, uses a Popper-based overlay, and already avoids loops by ignoring semantically identical dataSource updates.
<li-select
[dataSource]="users"
labelKey="name"
valueKey="id"
placeholder="Selecione"
[(ngModel)]="selectedUserId">
</li-select>
li-multi-select follows the same idea, but ngModel becomes a List<dynamic>:
<li-multi-select
[dataSource]="channelOptions"
labelKey="label"
valueKey="id"
[(ngModel)]="selectedChannels">
</li-multi-select>
You can also project options manually:
<li-multi-select [(ngModel)]="targets">
<li-multi-option value="portal">Portal</li-multi-option>
<li-multi-option value="api">API</li-multi-option>
<li-multi-option value="batch">Batch</li-multi-option>
</li-multi-select>
Best practices:
- do not recreate
dataSourcein getters used by the template; - keep lists stable and update only
ngModel; - for very large collections, handle search and pagination in the parent component;
- prefer
li-datatable-selectwhen the choice requires a table, columns, and structured search.
References:
- lib/src/components/select/custom_select.dart
- example/lib/src/pages/multi_select/multi_select_page.dart
- example/lib/src/pages/multi_select/multi_select_page.html
Typeahead #
li-typeahead sits between li-select and li-datatable-select: local or async search with suggestions, configurable highlighting, keyboard navigation, and [(ngModel)] integration.
Main features:
dataSourcewith a stableListorDataFramesearchCallbackfor remote search returning aFutureor immediate listminLength,maxResults, anddebounceMsopenOnFocus,editable,selectOnExact, andshowHintinputFormatterandresultFormatterfor object listsresultMarkupBuilderfor rich result markupLiTypeaheadConfigfor local defaults andLiTypeaheadHighlightComponentfor reusable highlighting
<li-typeahead
[searchCallback]="remoteCitySearch"
[inputFormatter]="cityInputFormatter"
[resultMarkupBuilder]="remoteResultMarkup"
[debounceMs]="220"
placeholder="Search for a city"
[(ngModel)]="selectedCity">
</li-typeahead>
For map-based lists:
<li-typeahead
[dataSource]="cities"
labelKey="name"
valueKey="code"
[inputFormatter]="cityInputFormatter"
[resultFormatter]="cityResultFormatter"
[(ngModel)]="selectedCityCode">
</li-typeahead>
For local defaults via config:
@Component(
selector: 'typeahead-config-host',
providers: [ClassProvider(LiTypeaheadConfig)],
template: '''
<li-typeahead [dataSource]="options"></li-typeahead>
''',
)
class TypeaheadConfigHostComponent {
TypeaheadConfigHostComponent(LiTypeaheadConfig config) {
config.minLength = 0;
config.openOnFocus = true;
config.showHint = true;
}
}
Best practices:
- keep
dataSourcestable in the parent component - prefer
searchCallbackwhen the remote API already exposes filtered search - use
editable=falsewhen the final value must come only from the list - prefer
li-datatable-selectwhen the choice requires a table, pagination, or sorting
The dedicated demo is in example/lib/src/pages/typeahead/typeahead_page.dart and example/lib/src/pages/typeahead/typeahead_page.html.
Treeview Select #
li-treeview-select covers hierarchical selection in a dropdown when a flat select loses too much context. It works with a static tree via [data] or incremental loading via [pageLoader].
Main features:
- single or multiple selection with
[(ngModel)] pageLoaderwithTreeViewLoadRequest(parent, offset, limit, searchTerm)labelBuilderto customize the default labelcanSelectNodeto enforce per-item selection rulestemplate[liTreeviewSelectNode]andtemplate[liTreeviewSelectTrigger]for custom renderingcloseOnSelect,showClearButton,searchable, andopenOnFocus
<li-treeview-select
[pageLoader]="loadTreeChunk"
[pageSize]="20"
[multiple]="true"
[closeOnSelect]="false"
[labelBuilder]="buildNodeLabel"
[canSelectNode]="canSelectLeafNode"
[(ngModel)]="selectedValues">
<template liTreeviewSelectTrigger let-ctx>
<span *ngIf="ctx.selectedNodes.isEmpty">{{ ctx.placeholder }}</span>
<span *ngIf="ctx.selectedNodes.isNotEmpty">
{{ ctx.selectedNodes.length }} item(s)
</span>
</template>
<template liTreeviewSelectNode let-ctx>
<strong>{{ ctx.node.treeViewNodeLabel }}</strong>
<small>{{ ctx.node.value }}</small>
</template>
</li-treeview-select>
Best practices:
- use
[data]when the tree is already loaded in memory - use
[pageLoader]for large catalogs or deep hierarchies - keep remote search on the backend through
request.searchTerm - use
canSelectNodefor rules such as leaf-only selection
References:
- lib/src/components/treeview/treeview_select_component.dart
- example/lib/src/pages/treeview/treeview_page.dart
- example/lib/src/pages/treeview/treeview_page.html
Tabs #
li-tabsx organizes content into sections without changing routes. It supports type="tabs", type="highlight", type="underline", type="overline", type="solid", or type="pills", horizontal or side placement, [justified], disabled tabs, [lazyLoad], [destroyOnHide], and projected headers with template li-tabx-header.
<li-tabsx type="underline" [justified]="true">
<li-tabx header="Summary" [active]="true">
<div class="p-3">Content</div>
</li-tabx>
<li-tabx header="Activity"></li-tabx>
<li-tabx [disabled]="true" header="Disabled"></li-tabx>
</li-tabsx>
Use tabs for documentation, segmented forms, and administrative panels. highlight, underline, overline, and solid map directly to native Limitless 4 tab styles, while pills remains useful for side navigation. Use [lazyLoad] when inactive panes are expensive to create and [destroyOnHide] when they should leave the DOM after tab changes. When tab content becomes heavy or deeply nested, move it into subcomponents.
The demo shows side pills, Limitless 4 variants, disabled tabs, and custom headers in example/lib/src/pages/tabs/tabs_page.dart and example/lib/src/pages/tabs/tabs_page.html.
Wizard #
li-wizard covers guided multi-step flows with native Limitless 4 .wizard markup, projected li-wizard-step bodies, [(activeIndex)], linear navigation, clickable visited steps, guard callbacks through [beforeChange] and [beforeFinish], custom step labels through [headerTemplate], and custom footer actions through [actionsTemplate].
<template #wizardHeader let-ctx>
<span class="d-block">{{ ctx.step.title }}</span>
<small class="text-muted">{{ ctx.isDone ? 'Completed' : 'Current flow' }}</small>
</template>
<template #wizardActions let-ctx>
<div class="d-flex justify-content-between gap-3 flex-wrap">
<span>Step {{ ctx.activeIndex + 1 }} of {{ ctx.stepCount }}</span>
<div class="d-flex gap-2">
<button *ngIf="ctx.hasPrevious" type="button" [class]="ctx.previousButtonClass" (click)="ctx.goPrevious()">
{{ ctx.previousLabel }}
</button>
<button *ngIf="!ctx.isLastStep" type="button" [class]="ctx.nextButtonClass" (click)="ctx.goNext()">
{{ ctx.nextLabel }}
</button>
<button *ngIf="ctx.isLastStep" type="button" [class]="ctx.finishButtonClass" (click)="ctx.finish()">
{{ ctx.finishLabel }}
</button>
</div>
</div>
</template>
<li-wizard
[(activeIndex)]="currentStep"
[beforeChange]="canMoveToStep"
[beforeFinish]="canFinishWizard"
[actionsTemplate]="wizardActions"
(finish)="completeWizard(
$event,
)">
<li-wizard-step title="Account" [headerTemplate]="wizardHeader">
<div class="p-3">Account details</div>
</li-wizard-step>
<li-wizard-step
title="Profile"
subtitle="Optional details"
[headerTemplate]="wizardHeader">
<div class="p-3">Profile details</div>
</li-wizard-step>
<li-wizard-step title="Review">
<div class="p-3">Review and submit</div>
</li-wizard-step>
</li-wizard>
LiWizardStepHeaderContext exposes step, index, displayIndex, isCurrent, isDone, hasError, and isDisabled. LiWizardActionsContext exposes goPrevious(), goNext(), finish(), hasPrevious, isLastStep, activeIndex, stepCount, and the current labels/classes for the footer buttons. The wizard keeps rendering the numeric marker internally, so custom headers do not break the native Limitless current/done/error icon states.
The dedicated wizard/form wizard example is in example/lib/src/pages/wizard/wizard_page.dart and example/lib/src/pages/wizard/wizard_page.html.
Color Picker #
li-color-picker integrates with [(ngModel)] and can be used as a compact swatch trigger or a richer picker with alpha, palette rows, selection history, and event streams for open, close, change, move, drag start, and drag stop.
<li-color-picker
[(ngModel)]="brandColor"
[showPalette]="true"
[showSelectionPalette]="true"
[maxSelectionPalette]="6"
[togglePaletteOnly]="true"
(pickerChange)="onColorChange(
$event,
)">
</li-color-picker>
Use palette to provide curated swatches, showSelectionPalette to keep a short history of recent selections, and hideAfterPaletteSelect when the interaction should behave like a fast swatch picker.
Date Picker #
li-date-picker covers simple date selection with direct [(ngModel)] integration, plus range constraints and locale switching.
Main features:
minDateandmaxDateto constrain the allowed range;localefor formats such aspt_BRanden_US;placeholder,value, anddisabled;- straightforward integration into AngularDart forms without an extra wrapper.
<li-date-picker
[(ngModel)]="selectedDate"
[minDate]="minDate"
[maxDate]="maxDate"
locale="en_US"
[placeholder]="'Select a date'">
</li-date-picker>
The demo covers four useful scenarios: default usage, restricted date ranges, English locale, and a disabled field. References:
- example/lib/src/pages/date_picker/date_picker_page.dart
- example/lib/src/pages/date_picker/date_picker_page.html
Modal with lazy content #
<li-modal
title-text="Heavy report"
size="xtra-large"
[lazyContent]="true"
[dialogScrollable]="true">
<li-datatable
[data]="reportFrame"
[settings]="reportSettings"
[dataTableFilter]="filters"
(dataRequest)="loadReport($event)">
</li-datatable>
</li-modal>
This pattern is useful for expensive content such as datatables, large forms, or projected content that should not exist in the DOM until the dialog opens.
Toast #
li-toast covers the inline declarative case. It renders the toast markup, exposes show(), hide(), and isOpen, and supports header, body, helperText, badgeText, iconClass, autohide, delay, dismissible, pauseOnHover, and rounded.
<li-toast
header="Processing completed"
body="The operation completed successfully."
helperText="now"
iconClass="ph-check-circle"
[autohide]="false">
</li-toast>
For global overlay notifications, the package also exposes LiToastService plus li-toast-stack:
<li-toast-stack [service]="toastService" placement="top-end"></li-toast-stack>
final toastService = LiToastService();
toastService.show(
header: 'Update available',
body: 'A new item is waiting for review.',
badgeText: 'Update',
iconClass: 'ph-bell-ringing',
toastClass: 'border-primary',
headerClass: 'bg-primary text-white border-primary',
autohide: false,
);
placement accepts top-end, top-start, bottom-end, bottom-start, top-center, and bottom-center.
Best practices:
- use
li-toastwhen the toast is part of the page layout itself; - use
LiToastServiceplusli-toast-stackfor global messages; - keep
autohide: falseonly for messages that require human action; - when you need richer layout, project custom markup inside
li-toast.
The dedicated demo is in example/lib/src/pages/toast/toast_page.dart and example/lib/src/pages/toast/toast_page.html.
Popover #
The package exposes two popover layers:
- imperative helpers such as
SimplePopover.showWarning(...)andSweetAlertPopover.showPopover(...); - a declarative API with
LiPopoverComponentandLiPopoverDirective.
Use a popover when the content needs to be richer than a tooltip but still does not justify a modal. The declarative component supports click, hover, manual control via @ViewChild, TemplateRef, container="body", and positioning hooks.
<button
class="btn btn-outline-primary"
[liPopover]="'More context without leaving the screen'"
popoverTitle="Details"
triggers="click">
Open popover
</button>
When the content grows too large, move to a modal, drawer, or expandable card. The richest demo is in example/lib/src/pages/popover/popover_page.dart.
Dropdown Menu #
li-dropdown-menu is a compact action menu driven by an option list. It fits overflow buttons, per-card actions, and small toolbar menus without the complexity of the full dropdown module.
Each item is a LiDropdownMenuOption with:
valuelabeliconClassdescriptiondisableddivider
The component also supports triggerLabel, triggerIconClass, triggerClass,
menuClass, placement, rounded, showCaret, and closeOnSelect.
final options = <LiDropdownMenuOption>[
const LiDropdownMenuOption(
value: 'edit',
label: 'Edit',
iconClass: 'ph-pencil-simple',
),
const LiDropdownMenuOption(
value: 'archive',
label: 'Archive',
description: 'Remove from the main listing',
),
];
<li-dropdown-menu
[options]="options"
value="edit"
triggerLabel="Actions"
placement="dropend"
(valueChange)="onAction($event)">
</li-dropdown-menu>
The menu closes on outside click, Escape, or selection depending on closeOnSelect. Main reference: lib/src/components/dropdown_menu/dropdown_menu_component.dart.
Scrollspy #
<div liScrollSpy [spyOn]="scrollContainer">
<section liScrollSpyFragment="overview">...</section>
<section liScrollSpyFragment="api">...</section>
</div>
<nav liScrollSpyMenu>
<a liScrollSpyItem="overview">Overview</a>
<a liScrollSpyItem="api">API</a>
</nav>
Use LiScrollSpyService directly when you need imperative control over fragment observation or scrolling.
Currency formatting #
final brFormatter = CurrencyInputFormatter(
locale: 'pt_BR',
currencyCode: 'BRL',
);
final usdFormatter = CurrencyInputFormatter(
locale: 'en_US',
currencyCode: 'USD',
);
final cents = brFormatter.minorUnitsFromText('1.234,56');
final displayBr = brFormatter.formatForDisplay(cents);
final displayUsd = usdFormatter.formatForDisplay(123456);
Development #
Install dependencies:
dart pub get
Run static analysis:
dart analyze
Run VM-safe tests:
dart test test/currency_input_formatter_test.dart test/lite_xlsx_test.dart test/tine_pdf_test.dart
Run browser and AngularDart tests in Chrome:
dart run build_runner test -- -p chrome -j 1 test/alerts/alert_component_test.dart test/alerts/li_alert_component_test.dart test/progress_component_test.dart test/datatable/li_datatable_component_test.dart test/accordion/li_accordion_directive_test.dart test/dropdown/li_dropdown_directive_test.dart test/modal/li_modal_component_test.dart test/nav/li_nav_directive_test.dart test/popover/li_popover_component_test.dart test/scrollspy/li_scrollspy_directive_test.dart test/typeahead/li_typeahead_component_test.dart test/toast/li_toast_component_test.dart test/tooltip/li_tooltip_directive_test.dart test/wizard/li_wizard_component_test.dart
When validating dependency upgrades for ngdart, ngforms, or ngrouter, add focused runs for the form value accessors and input bindings before broader test suites. Those accessors depend on internal ngforms APIs and behavior due to framework limitations, and they are usually the first compatibility boundary to break.
Recommended first-pass upgrade command:
dart run build_runner test -- -p chrome -j 1 test/input/li_input_component_test.dart test/multi_select/li_multi_select_focus_test.dart
Validate the package before publishing:
dart pub publish --dry-run
AngularDart Template Performance #
- Do not expose getters used by templates that recreate lists, maps, style objects, or view models on every change-detection pass.
- In particular, avoid patterns like a getter returning a fresh collection consumed by
*ngFor, because AngularDart will treat the result as changed and can rebuild rich component trees continuously. - Prefer stable references:
finalfields, cached lazy fields, or explicit recomputation only when the source input actually changes. - Avoid binding dynamically recreated objects in templates such as
[style]="...", inline maps, or other expressions that allocate on every pass unless the value is intentionally memoized. - The popover example hit this exact failure mode when
palettePopoversrecreated 11 items in a getter, which caused the page to churn and freeze the browser.
AngularDart Change Detection Notes #
- In this repository, do not treat
ChangeDetectorRef.markForCheck()as a generic fix for async rendering bugs on components using the defaultChangeDetectionStrategy.checkAlways. - Practical rule:
markForCheck()is dependable mainly when the relevant host tree isChangeDetectionStrategy.onPush. On default-strategy pages, it may not resolve lazy async rendering by itself. - For async content inside deferred UI such as accordion bodies, tabs, and modals, prefer one of these options:
- use
ChangeDetectionStrategy.onPushintentionally and then rely onmarkForCheck(); - force a synchronous refresh only in a narrow, justified point;
- or change the flow so data is available before the deferred child component is created.
- use
- Always validate the rendered DOM after the async update. If the UI still only refreshes after an extra click, the change-detection issue is not solved.
- Specific lesson learned in this repository: a lazy accordion body that projects async content from the host must not be wrapped by an
onPushaccordion item unless that projection path is explicitly handled. The datatable demo only started rendering correctly after removingChangeDetectionStrategy.onPushfrom lib/src/components/accordion/accordion_item_component.dart, because the projected lazy body needed to receive host-side async updates after first render.
Demo application #
The demo app under example now includes dedicated routes for accordion, breadcrumbs, color picker, datatable, dropdown, fab, file upload, modal, nav, offcanvas, pagination, popover, rating, scrollspy, tabs, toast, treeview, typeahead, tooltip, wizard, and selection-control examples.
Use the demo app as the reference for real template usage, especially for lazy accordion bodies, lazy modal content, scrollspy menus, and overlay components that depend on browser geometry.
Release checklist #
dart analyze- VM-safe tests
- Chrome/browser tests
dart pub publish --dry-run- clean git state before publishing
Notes #
- The demo application is in example.
- The package is not intended for Flutter or server-side Dart.
essential_coreis the shared generic foundation reused by the data-oriented component family of this package:li-datatable,li-datatable-select,li-select,li-multi-select,li-typeaheadand treeview-related components.- Some value accessors are intentionally coupled to internal
ngformsAPIs and behavior due to framework limitations, so framework package upgrades must be deliberate, pinned, and test-driven.