@dmytrokhylko/tb-cdu-ui 0.1.5 → 0.2.3

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,63 +1,90 @@
1
- # TbUi
1
+ # @dmytrokhylko/tb-cdu-ui
2
2
 
3
- This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 20.3.0.
3
+ ThingsBoard shared UI components library base table, loading overlay, action button, and data source utilities.
4
4
 
5
- ## Code scaffolding
6
-
7
- Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
5
+ ## Installation
8
6
 
9
7
  ```bash
10
- ng generate component component-name
8
+ npm install @dmytrokhylko/tb-cdu-ui
11
9
  ```
12
10
 
13
- For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
14
-
15
- ```bash
16
- ng generate --help
17
- ```
11
+ ## Package structure
18
12
 
19
- ## Building
13
+ The library is published as a single npm package with multiple entry points:
20
14
 
21
- To build the library, run:
15
+ | Entry point | Import path | Description |
16
+ |---|---|---|
17
+ | **Primary** | `@dmytrokhylko/tb-cdu-ui` | Re-exports everything from `core` and `widgets` |
18
+ | **Core** | `@dmytrokhylko/tb-cdu-ui/core` | UI components, models, and data source utilities |
19
+ | **Widgets** | `@dmytrokhylko/tb-cdu-ui/widgets` | Ready-made widgets built on top of `core` |
22
20
 
23
- ```bash
24
- ng build tb-ui
21
+ ```ts
22
+ import { BaseTableComponent } from '@dmytrokhylko/tb-cdu-ui/core';
25
23
  ```
26
24
 
27
- This command will compile your project, and the build artifacts will be placed in the `dist/` directory.
25
+ ## Components
28
26
 
29
- ### Publishing the Library
27
+ | Component | Selector | Docs |
28
+ |---|---|---|
29
+ | `BaseTableComponent` | `<tb-cd-base-table>` | [core/base-table](./core/base-table/README.md) |
30
+ | `LoadingOverlayComponent` | `<tb-cd-loading-overlay>` | — |
31
+ | `ActionButtonComponent` | `<tb-cd-action-button>` | — |
30
32
 
31
- Once the project is built, you can publish your library by following these steps:
33
+ ## Adapters
32
34
 
33
- 1. Navigate to the `dist` directory:
34
- ```bash
35
- cd dist/tb-ui
36
- ```
35
+ | Adapter | Description |
36
+ |---|---|
37
+ | `TbSubscriptionDatasource` | Drives a table from a ThingsBoard widget subscription (non-paginated) |
38
+ | `TbSubscriptionDatasourcePaginated` | Drives a table from a ThingsBoard widget subscription with server-side pagination |
39
+ | `collectSubscriptionData` | Maps raw `IWidgetSubscription` data to a typed entity array |
37
40
 
38
- 2. Run the `npm publish` command to publish your library to the npm registry:
39
- ```bash
40
- npm publish
41
- ```
41
+ ## Project layout
42
42
 
43
- ## Running unit tests
43
+ ```
44
+ tb-cdu-ui/
45
+ package.json # npm package manifest + dev dependencies
46
+ ng-package.json # ng-packagr primary entry point
47
+ public-api.ts # primary barrel (re-exports core + widgets)
48
+ core/ # @dmytrokhylko/tb-cdu-ui/core
49
+ ng-package.json
50
+ public-api.ts
51
+ action-button/
52
+ adapters/ # ThingsBoard subscription adapters
53
+ base-table/
54
+ loading-overlay/
55
+ models/
56
+ styles/
57
+ widgets/ # @dmytrokhylko/tb-cdu-ui/widgets
58
+ ng-package.json
59
+ public-api.ts
60
+ ```
44
61
 
45
- To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
62
+ Secondary entry points are discovered automatically by ng-packagr from directories containing `ng-package.json`. The directory name relative to the library root determines the import path suffix (e.g. `core/` → `@dmytrokhylko/tb-cdu-ui/core`).
46
63
 
47
- ```bash
48
- ng test
49
- ```
64
+ ## Peer dependencies
50
65
 
51
- ## Running end-to-end tests
66
+ - `@angular/common`, `@angular/core`, `@angular/forms` `^20.0.0`
67
+ - `@angular/cdk`, `@angular/material` `^20.0.0`
68
+ - `@ngx-translate/core` `^17.0.0`
69
+ - `thingsboard` `^4.0.0`
70
+ - `rxjs` `^7.8.0`
52
71
 
53
- For end-to-end (e2e) testing, run:
72
+ ## Development
54
73
 
55
74
  ```bash
56
- ng e2e
75
+ npm ci # install dependencies
76
+ npm run build # production build → dist/tb-cdu-ui/
77
+ npm run watch # development build with watch
78
+ npm run format # format source files with Prettier
79
+ npm run format:check # check formatting without writing
80
+ npm test # run unit tests
57
81
  ```
58
82
 
59
- Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
83
+ ### Publishing
60
84
 
61
- ## Additional Resources
85
+ Releases are published automatically to npm when a `v*` tag is pushed to `master`:
62
86
 
63
- For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
87
+ ```bash
88
+ git tag v1.0.0
89
+ git push origin master --tags
90
+ ```
@@ -0,0 +1,254 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { TemplateRef, AfterViewInit, ElementRef, Signal, Injector } from '@angular/core';
3
+ import { SelectionModel } from '@angular/cdk/collections';
4
+ import { FormControl } from '@angular/forms';
5
+ import { PageEvent } from '@angular/material/paginator';
6
+ import { SortDirection, Sort } from '@angular/material/sort';
7
+ import { Direction, PageLink, DataKeyType, EntityFilter, widgetType, KeyFilter, EntityId, DataEntry } from '@shared/public-api';
8
+ import { EntityColumn } from '@home/components/public-api';
9
+ import { ProgressSpinnerMode } from '@angular/material/progress-spinner';
10
+ import { IWidgetSubscription } from '@core/public-api';
11
+ import { WidgetContext } from '@home/models/widget-component.models';
12
+
13
+ declare class ActionButtonComponent {
14
+ icon: _angular_core.InputSignal<string | null>;
15
+ label: _angular_core.InputSignal<string | null>;
16
+ backgroundColor: _angular_core.InputSignal<string | null>;
17
+ textColor: _angular_core.InputSignal<string | null>;
18
+ clicked: _angular_core.OutputEmitterRef<void>;
19
+ protected readonly resolvedBackgroundColor: _angular_core.Signal<string | null>;
20
+ protected readonly resolvedTextColor: _angular_core.Signal<string | null>;
21
+ protected handleClick(): void;
22
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<ActionButtonComponent, never>;
23
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<ActionButtonComponent, "tb-cd-action-button", never, { "icon": { "alias": "icon"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "backgroundColor": { "alias": "backgroundColor"; "required": false; "isSignal": true; }; "textColor": { "alias": "textColor"; "required": false; "isSignal": true; }; }, { "clicked": "clicked"; }, never, never, true, never>;
24
+ }
25
+
26
+ interface EntityColumnExtended<T = unknown> extends EntityColumn {
27
+ nullDisplayValue?: string;
28
+ headerStyle?: Record<string, string>;
29
+ cellTemplate?: TemplateRef<{
30
+ $implicit: unknown;
31
+ entity: T;
32
+ }>;
33
+ }
34
+
35
+ declare enum LoadingPhase {
36
+ IDLE = "idle",
37
+ GENERATING = "generating",
38
+ POLLING = "polling",
39
+ FETCHING = "fetching"
40
+ }
41
+ type LoadingState = {
42
+ phase: LoadingPhase.IDLE;
43
+ } | {
44
+ phase: LoadingPhase.GENERATING;
45
+ } | {
46
+ phase: LoadingPhase.POLLING;
47
+ processed: number;
48
+ total: number;
49
+ } | {
50
+ phase: LoadingPhase.FETCHING;
51
+ };
52
+ declare const getLoadingState: {
53
+ idle: () => LoadingState;
54
+ generating: () => LoadingState;
55
+ polling: (processed: number, total: number) => LoadingState;
56
+ fetching: () => LoadingState;
57
+ };
58
+
59
+ interface TableRowAction<T> {
60
+ id: string;
61
+ icon: string;
62
+ label?: string;
63
+ visible?: (entity: T) => boolean;
64
+ disabled?: (entity: T) => boolean;
65
+ handler?: (entity: T) => void;
66
+ }
67
+ interface TableHeaderAction {
68
+ id: string;
69
+ icon: string;
70
+ label?: string;
71
+ visible?: () => boolean;
72
+ disabled?: () => boolean;
73
+ handler?: () => void;
74
+ }
75
+ interface TableBulkAction<T> {
76
+ id: string;
77
+ icon: string;
78
+ label?: string;
79
+ visible?: () => boolean;
80
+ disabled?: (selected: T[]) => boolean;
81
+ handler?: (selected: T[]) => void;
82
+ }
83
+ interface TableState {
84
+ pageIndex: number;
85
+ pageSize: number;
86
+ sortProperty: string;
87
+ sortDirection: Direction | null;
88
+ textSearch: string;
89
+ }
90
+
91
+ declare class BaseTableComponent<T> implements AfterViewInit {
92
+ dataSource: _angular_core.InputSignal<T[]>;
93
+ columns: _angular_core.InputSignal<EntityColumnExtended<T>[]>;
94
+ displayedColumns: _angular_core.InputSignal<string[]>;
95
+ loadingState: _angular_core.InputSignal<LoadingState>;
96
+ widgetTitle: _angular_core.InputSignal<string>;
97
+ noDataMessage: _angular_core.InputSignal<string>;
98
+ totalElements: _angular_core.InputSignal<number>;
99
+ pageSizeOptions: _angular_core.InputSignal<number[]>;
100
+ selectionEnabled: _angular_core.InputSignal<boolean>;
101
+ rowActions: _angular_core.InputSignal<TableRowAction<T>[]>;
102
+ headerActions: _angular_core.InputSignal<TableHeaderAction[]>;
103
+ bulkActions: _angular_core.InputSignal<TableBulkAction<T>[]>;
104
+ expandable: _angular_core.InputSignal<boolean>;
105
+ expandedRowTemplate: _angular_core.InputSignal<TemplateRef<{
106
+ $implicit: T;
107
+ entity: T;
108
+ }> | null>;
109
+ expansionTrackBy: _angular_core.InputSignal<(entity: T) => unknown>;
110
+ selectionTrackBy: _angular_core.InputSignal<(entity: T) => unknown>;
111
+ clearSelectionTrigger: _angular_core.InputSignal<unknown>;
112
+ pageLink: _angular_core.ModelSignal<PageLink>;
113
+ rowClick: _angular_core.OutputEmitterRef<T>;
114
+ cellClick: _angular_core.OutputEmitterRef<{
115
+ event: Event;
116
+ entity: T;
117
+ column: EntityColumnExtended<T>;
118
+ }>;
119
+ actionClick: _angular_core.OutputEmitterRef<{
120
+ event: Event;
121
+ entity: T;
122
+ action: TableRowAction<T>;
123
+ }>;
124
+ headerActionClick: _angular_core.OutputEmitterRef<{
125
+ event: Event;
126
+ action: TableHeaderAction;
127
+ }>;
128
+ bulkActionClick: _angular_core.OutputEmitterRef<{
129
+ event: Event;
130
+ action: TableBulkAction<T>;
131
+ selected: T[];
132
+ }>;
133
+ selectionChange: _angular_core.OutputEmitterRef<T[]>;
134
+ expansionChange: _angular_core.OutputEmitterRef<T[]>;
135
+ textSearchMode: _angular_core.WritableSignal<boolean>;
136
+ readonly selection: SelectionModel<T>;
137
+ readonly expandedKeys: _angular_core.WritableSignal<Set<unknown>>;
138
+ readonly lastExpandedKey: _angular_core.WritableSignal<unknown>;
139
+ readonly detailRowColumns: _angular_core.Signal<string[]>;
140
+ private readonly selectionCount;
141
+ readonly isLoadingState: _angular_core.Signal<boolean>;
142
+ readonly toolbarMode: _angular_core.Signal<"default" | "search" | "bulk">;
143
+ readonly effectiveDisplayedColumns: _angular_core.Signal<string[]>;
144
+ readonly visibleHeaderActions: _angular_core.Signal<TableHeaderAction[]>;
145
+ readonly visibleBulkActions: _angular_core.Signal<TableBulkAction<T>[]>;
146
+ pageLinkSortDirection: _angular_core.Signal<SortDirection>;
147
+ searchControl: FormControl<string>;
148
+ searchInputField: _angular_core.Signal<ElementRef<HTMLInputElement> | undefined>;
149
+ private readonly table;
150
+ private readonly tableScrollContainer;
151
+ protected readonly LoadingPhase: typeof LoadingPhase;
152
+ private injector;
153
+ private destroyRef;
154
+ constructor();
155
+ ngAfterViewInit(): void;
156
+ onPaginate(event: PageEvent): void;
157
+ onSort(event: Sort): void;
158
+ enterFilterMode(): void;
159
+ exitFilterMode(): void;
160
+ isAllSelected(): boolean;
161
+ isIndeterminate(): boolean;
162
+ masterToggle(): void;
163
+ toggleRow(entity: T): void;
164
+ clearSelection(): void;
165
+ onCellClick(event: Event, entity: T, column: EntityColumnExtended<T>): void;
166
+ onRowClick(event: MouseEvent, entity: T): void;
167
+ isExpanded(entity: T): boolean;
168
+ readonly isExpandedRow: (_: number, row: T) => boolean;
169
+ toggleExpansion(entity: T, rowElement?: HTMLElement): void;
170
+ private scrollExpandedRowUnderHeader;
171
+ private reconcileSelection;
172
+ private reconcileExpansion;
173
+ private emitExpansionChange;
174
+ onExpandToggleClick(event: Event, entity: T): void;
175
+ onActionClick(event: Event, entity: T, action: TableRowAction<T>): void;
176
+ onHeaderActionClick(event: Event, action: TableHeaderAction): void;
177
+ onBulkActionClick(event: Event, action: TableBulkAction<T>): void;
178
+ private clone;
179
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<BaseTableComponent<any>, never>;
180
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<BaseTableComponent<any>, "tb-cd-base-table", never, { "dataSource": { "alias": "dataSource"; "required": true; "isSignal": true; }; "columns": { "alias": "columns"; "required": true; "isSignal": true; }; "displayedColumns": { "alias": "displayedColumns"; "required": true; "isSignal": true; }; "loadingState": { "alias": "loadingState"; "required": true; "isSignal": true; }; "widgetTitle": { "alias": "widgetTitle"; "required": false; "isSignal": true; }; "noDataMessage": { "alias": "noDataMessage"; "required": false; "isSignal": true; }; "totalElements": { "alias": "totalElements"; "required": false; "isSignal": true; }; "pageSizeOptions": { "alias": "pageSizeOptions"; "required": false; "isSignal": true; }; "selectionEnabled": { "alias": "selectionEnabled"; "required": false; "isSignal": true; }; "rowActions": { "alias": "rowActions"; "required": false; "isSignal": true; }; "headerActions": { "alias": "headerActions"; "required": false; "isSignal": true; }; "bulkActions": { "alias": "bulkActions"; "required": false; "isSignal": true; }; "expandable": { "alias": "expandable"; "required": false; "isSignal": true; }; "expandedRowTemplate": { "alias": "expandedRowTemplate"; "required": false; "isSignal": true; }; "expansionTrackBy": { "alias": "expansionTrackBy"; "required": false; "isSignal": true; }; "selectionTrackBy": { "alias": "selectionTrackBy"; "required": false; "isSignal": true; }; "clearSelectionTrigger": { "alias": "clearSelectionTrigger"; "required": false; "isSignal": true; }; "pageLink": { "alias": "pageLink"; "required": false; "isSignal": true; }; }, { "pageLink": "pageLinkChange"; "rowClick": "rowClick"; "cellClick": "cellClick"; "actionClick": "actionClick"; "headerActionClick": "headerActionClick"; "bulkActionClick": "bulkActionClick"; "selectionChange": "selectionChange"; "expansionChange": "expansionChange"; }, never, never, true, never>;
181
+ }
182
+
183
+ declare class LoadingOverlayComponent {
184
+ state: _angular_core.InputSignal<LoadingState>;
185
+ mode: _angular_core.InputSignal<ProgressSpinnerMode>;
186
+ generating: _angular_core.InputSignal<string>;
187
+ processingDeterminate: _angular_core.InputSignal<string>;
188
+ processingIndeterminate: _angular_core.InputSignal<string>;
189
+ fetching: _angular_core.InputSignal<string>;
190
+ protected readonly LoadingPhase: typeof LoadingPhase;
191
+ spinnerMode: _angular_core.Signal<ProgressSpinnerMode>;
192
+ percentage: _angular_core.Signal<number>;
193
+ pollingProcessed: _angular_core.Signal<number>;
194
+ pollingTotal: _angular_core.Signal<number>;
195
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<LoadingOverlayComponent, never>;
196
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<LoadingOverlayComponent, "tb-cd-loading-overlay", never, { "state": { "alias": "state"; "required": true; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "generating": { "alias": "generating"; "required": false; "isSignal": true; }; "processingDeterminate": { "alias": "processingDeterminate"; "required": false; "isSignal": true; }; "processingIndeterminate": { "alias": "processingIndeterminate"; "required": false; "isSignal": true; }; "fetching": { "alias": "fetching"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
197
+ }
198
+
199
+ declare const DEFAULT_PAGE_LINK_PAGE_SIZE = 20;
200
+ declare const DEFAULT_PAGE_SIZES: number[];
201
+
202
+ interface SubscriptionDataKey {
203
+ name: string;
204
+ type: DataKeyType;
205
+ label?: string;
206
+ decimals?: number;
207
+ settings?: Record<string, unknown>;
208
+ }
209
+ interface TbSubscriptionDatasourceConfig<T> {
210
+ ctx: WidgetContext;
211
+ entityFilter: Signal<EntityFilter>;
212
+ dataKeys: SubscriptionDataKey[];
213
+ widgetType: widgetType;
214
+ keyFilters?: KeyFilter[];
215
+ dataMapper: (subscription: IWidgetSubscription) => T[];
216
+ injector: Injector;
217
+ }
218
+ interface TbSubscriptionDatasourcePaginatedConfig<T> extends TbSubscriptionDatasourceConfig<T> {
219
+ columns: EntityColumn[];
220
+ pageLink: Signal<PageLink>;
221
+ }
222
+ declare class TbSubscriptionDatasource<T> {
223
+ private readonly config;
224
+ readonly data: _angular_core.WritableSignal<T[]>;
225
+ readonly loadingState: _angular_core.WritableSignal<LoadingState>;
226
+ private subscription;
227
+ private subscriptionSub;
228
+ constructor(config: TbSubscriptionDatasourceConfig<T>);
229
+ refresh(): void;
230
+ private recreateSubscription;
231
+ private onDataUpdated;
232
+ private cleanup;
233
+ }
234
+ declare class TbSubscriptionDatasourcePaginated<T> {
235
+ private readonly config;
236
+ readonly data: _angular_core.WritableSignal<T[]>;
237
+ readonly totalElements: _angular_core.WritableSignal<number>;
238
+ readonly loadingState: _angular_core.WritableSignal<LoadingState>;
239
+ private subscription;
240
+ private subscriptionSub;
241
+ constructor(config: TbSubscriptionDatasourcePaginatedConfig<T>);
242
+ refresh(): void;
243
+ private recreateSubscription;
244
+ private onDataUpdated;
245
+ private toEntityPageLink;
246
+ private cleanup;
247
+ }
248
+ declare function collectSubscriptionData<T extends {
249
+ id: EntityId;
250
+ name: string;
251
+ }>(subscription: IWidgetSubscription, valueTransformer?: (keyName: string, dataPoint: DataEntry | null) => unknown): T[];
252
+
253
+ export { ActionButtonComponent, BaseTableComponent, DEFAULT_PAGE_LINK_PAGE_SIZE, DEFAULT_PAGE_SIZES, LoadingOverlayComponent, LoadingPhase, TbSubscriptionDatasource, TbSubscriptionDatasourcePaginated, collectSubscriptionData, getLoadingState };
254
+ export type { EntityColumnExtended, LoadingState, SubscriptionDataKey, TableBulkAction, TableHeaderAction, TableRowAction, TableState, TbSubscriptionDatasourceConfig, TbSubscriptionDatasourcePaginatedConfig };