@nexterajs/ui 0.0.1 → 0.1.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.
Files changed (63) hide show
  1. package/README.md +279 -34
  2. package/fesm2022/nexterajs-ui-charts.mjs +1096 -0
  3. package/fesm2022/nexterajs-ui-charts.mjs.map +1 -0
  4. package/fesm2022/nexterajs-ui-core.mjs +835 -11
  5. package/fesm2022/nexterajs-ui-core.mjs.map +1 -1
  6. package/fesm2022/nexterajs-ui-display.mjs +1142 -0
  7. package/fesm2022/nexterajs-ui-display.mjs.map +1 -0
  8. package/fesm2022/nexterajs-ui-dropdowns.mjs +621 -69
  9. package/fesm2022/nexterajs-ui-dropdowns.mjs.map +1 -1
  10. package/fesm2022/nexterajs-ui-file-uploader.mjs +576 -11
  11. package/fesm2022/nexterajs-ui-file-uploader.mjs.map +1 -1
  12. package/fesm2022/nexterajs-ui-forms.mjs +2702 -452
  13. package/fesm2022/nexterajs-ui-forms.mjs.map +1 -1
  14. package/fesm2022/nexterajs-ui-grid.mjs +2350 -336
  15. package/fesm2022/nexterajs-ui-grid.mjs.map +1 -1
  16. package/fesm2022/nexterajs-ui-intl.mjs +117 -15
  17. package/fesm2022/nexterajs-ui-intl.mjs.map +1 -1
  18. package/fesm2022/nexterajs-ui-navigation.mjs +1054 -0
  19. package/fesm2022/nexterajs-ui-navigation.mjs.map +1 -0
  20. package/fesm2022/nexterajs-ui-notify.mjs +353 -0
  21. package/fesm2022/nexterajs-ui-notify.mjs.map +1 -0
  22. package/fesm2022/nexterajs-ui-overlay.mjs +1671 -0
  23. package/fesm2022/nexterajs-ui-overlay.mjs.map +1 -0
  24. package/fesm2022/nexterajs-ui-pivot.mjs +191 -136
  25. package/fesm2022/nexterajs-ui-pivot.mjs.map +1 -1
  26. package/fesm2022/nexterajs-ui-shell.mjs +542 -0
  27. package/fesm2022/nexterajs-ui-shell.mjs.map +1 -0
  28. package/fesm2022/nexterajs-ui-styling.mjs +568 -3
  29. package/fesm2022/nexterajs-ui-styling.mjs.map +1 -1
  30. package/fesm2022/nexterajs-ui-tree-view.mjs +150 -18
  31. package/fesm2022/nexterajs-ui-tree-view.mjs.map +1 -1
  32. package/fesm2022/nexterajs-ui-validation.mjs +283 -97
  33. package/fesm2022/nexterajs-ui-validation.mjs.map +1 -1
  34. package/fesm2022/nexterajs-ui.mjs +6 -0
  35. package/fesm2022/nexterajs-ui.mjs.map +1 -1
  36. package/package.json +37 -2
  37. package/styles/_dropdown.scss +150 -1
  38. package/styles/_editor.scss +75 -21
  39. package/styles/_icons.scss +92 -0
  40. package/styles/_mixins.scss +104 -1
  41. package/styles/_scrollbar.scss +134 -0
  42. package/styles/_status.scss +21 -0
  43. package/styles/_toast.scss +243 -0
  44. package/styles/_tokens.scss +467 -2
  45. package/styles/_widget.scss +14 -3
  46. package/styles/index.scss +10 -4
  47. package/types/nexterajs-ui-charts.d.ts +446 -0
  48. package/types/nexterajs-ui-core.d.ts +515 -30
  49. package/types/nexterajs-ui-display.d.ts +720 -0
  50. package/types/nexterajs-ui-dropdowns.d.ts +318 -57
  51. package/types/nexterajs-ui-file-uploader.d.ts +240 -8
  52. package/types/nexterajs-ui-forms.d.ts +772 -43
  53. package/types/nexterajs-ui-grid.d.ts +878 -135
  54. package/types/nexterajs-ui-intl.d.ts +39 -4
  55. package/types/nexterajs-ui-navigation.d.ts +531 -0
  56. package/types/nexterajs-ui-notify.d.ts +149 -0
  57. package/types/nexterajs-ui-overlay.d.ts +703 -0
  58. package/types/nexterajs-ui-pivot.d.ts +91 -70
  59. package/types/nexterajs-ui-shell.d.ts +294 -0
  60. package/types/nexterajs-ui-styling.d.ts +163 -3
  61. package/types/nexterajs-ui-tree-view.d.ts +56 -4
  62. package/types/nexterajs-ui-validation.d.ts +110 -22
  63. package/types/nexterajs-ui.d.ts +6 -0
@@ -0,0 +1,294 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { InjectionToken, Signal, TemplateRef } from '@angular/core';
3
+ import { Params } from '@angular/router';
4
+ import { NxeStatusFamily } from '@nexterajs/ui/display';
5
+ import { NxeMessageKey } from '@nexterajs/ui/core';
6
+
7
+ /**
8
+ * One entry of the side navigation.
9
+ *
10
+ * - A **link** has `route` (router) or `href` (plain anchor).
11
+ * - A **parent** has `children`: it is a disclosure button, not a link. Its children appear under it
12
+ * when it is open, and it opens by itself when one of them is the current page.
13
+ * - A **group** has `heading: true` and `children`: a non-interactive heading, with its items in a
14
+ * labelled list under it. Groups sit at the top level; items inside them nest as deep as needed.
15
+ * - An entry with none of those is a plain button; `itemClick` reports it.
16
+ */
17
+ interface NxeNavItem {
18
+ /** Unique within the tree. Keys the open state and the DOM ids. */
19
+ readonly id: string;
20
+ /** What the entry says. Translate it. */
21
+ readonly label: string;
22
+ /**
23
+ * The icon, named for `<i class="nxe-icon nxe-icon-NAME">`, or, when the app supplies an
24
+ * `ng-template[nxeSidenavIcon]`, any key that template maps to an icon. An entry without one
25
+ * shows the first letter of its label so a collapsed rail is still usable.
26
+ */
27
+ readonly icon?: string;
28
+ /** Router link: a URL (`'/buyer/inbox'`) or commands (`['/buyer/price-lists', id]`). */
29
+ readonly route?: string | unknown[];
30
+ /** Query parameters for `route`. When set they take part in the active match. */
31
+ readonly queryParams?: Params;
32
+ /** A plain `href`, for a link that leaves the app. Never shown as the current page. */
33
+ readonly href?: string;
34
+ /** A count, or a signal of one (read in the template, so it updates without a new tree). */
35
+ readonly badge?: number | Signal<number | null | undefined>;
36
+ /** Colour of the badge; `danger` by default. */
37
+ readonly badgeFamily?: NxeStatusFamily;
38
+ /** Spoken name of the badge ("5 items need action"); defaults to the number. */
39
+ readonly badgeLabel?: string;
40
+ /** Children of a parent entry, or the items of a group. */
41
+ readonly children?: readonly NxeNavItem[];
42
+ /** Make this a group heading; its `children` are the group's items. */
43
+ readonly heading?: boolean;
44
+ /** Not interactive, not focusable, announced as disabled. */
45
+ readonly disabled?: boolean;
46
+ /**
47
+ * Match the URL exactly. Without it an entry is the current page for its URL and everything
48
+ * below it (`/buyer/purchase-orders` for `/buyer/purchase-orders/42`). Give the home entry
49
+ * (`/buyer`) `exact: true`, or it is current everywhere.
50
+ */
51
+ readonly exact?: boolean;
52
+ }
53
+ /** `docked`: beside the content, expanded or a rail. `drawer`: off-canvas, over the content. */
54
+ type NxeShellMode = 'docked' | 'drawer';
55
+ /**
56
+ * What `nxe-topbar` and `nxe-sidenav` need from the `nxe-shell` around them, through
57
+ * {@link NXE_SHELL}. Both also work without a shell; they then do not toggle anything.
58
+ */
59
+ interface NxeShellState {
60
+ readonly mode: Signal<NxeShellMode>;
61
+ /** The sidenav is a narrow icon rail (docked and collapsed). */
62
+ readonly rail: Signal<boolean>;
63
+ /** Docked: the user's collapsed choice. */
64
+ readonly collapsed: Signal<boolean>;
65
+ /** The drawer is on screen (drawer mode and open). */
66
+ readonly drawerShown: Signal<boolean>;
67
+ /** DOM id of the projected `nxe-sidenav`, for `aria-controls`. */
68
+ readonly sidenavId: Signal<string | null>;
69
+ setCollapsed(collapsed: boolean): void;
70
+ /** Hamburger: open or close the drawer, or collapse or expand the dock. */
71
+ toggleNavigation(): void;
72
+ closeDrawer(): void;
73
+ }
74
+ /** Provided by `nxe-shell` to the topbar and sidenav projected into it. */
75
+ declare const NXE_SHELL: InjectionToken<NxeShellState>;
76
+ /**
77
+ * `<ng-template nxeSidenavIcon let-item>` inside `nxe-sidenav` draws the icon of every entry that
78
+ * has an `icon`, given the entry. Use it to map `icon` keys to your own SVGs:
79
+ *
80
+ * ```html
81
+ * <nxe-sidenav [items]="items">
82
+ * <ng-template nxeSidenavIcon let-item>
83
+ * <svg viewBox="0 0 24 24"><path [attr.d]="paths[item.icon]" /></svg>
84
+ * </ng-template>
85
+ * </nxe-sidenav>
86
+ * ```
87
+ */
88
+ declare class NxeSidenavIconDirective {
89
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NxeSidenavIconDirective, never>;
90
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<NxeSidenavIconDirective, "ng-template[nxeSidenavIcon]", never, {}, {}, never, never, true, never>;
91
+ }
92
+
93
+ /**
94
+ * `nxe-shell` is the page frame of an application: an optional banner, the topbar, the sidenav and
95
+ * the main content.
96
+ *
97
+ * ```html
98
+ * <nxe-shell [(collapsed)]="collapsed">
99
+ * <sb-impersonation-banner nxeShellBanner />
100
+ * <nxe-topbar>...</nxe-topbar>
101
+ * <nxe-sidenav [items]="items" />
102
+ * <router-outlet />
103
+ * </nxe-shell>
104
+ * ```
105
+ *
106
+ * **Slots.** `[nxeShellBanner]` (full width above the topbar, for a banner that must stay on screen),
107
+ * an `nxe-topbar`, an `nxe-sidenav`, and everything else, which goes in the `<main>` landmark. A
108
+ * "Skip to content" link, the first tab stop, jumps to that `<main>`.
109
+ *
110
+ * **Responsive.** The shell measures its own width. At or above `breakpoint` (960px by default, the
111
+ * design system's `md`) the sidenav is docked beside the content, expanded or, when `collapsed`, an
112
+ * icon rail. Below it the sidenav is an off-canvas drawer over the content with a scrim: the
113
+ * topbar's toggle button opens it, focus is trapped inside while it is open, Esc and a click on the
114
+ * scrim close it, a navigation closes it, and focus returns to the control that opened it.
115
+ * Widening the shell past the breakpoint closes the drawer.
116
+ *
117
+ * The shell is as tall as `--nxe-shell-height` (default `100dvh`): give it the height of the space
118
+ * it fills, and the content scrolls inside `<main>`.
119
+ */
120
+ declare class NxeShellComponent implements NxeShellState {
121
+ private readonly host;
122
+ private readonly messages;
123
+ private readonly router;
124
+ private readonly injector;
125
+ private readonly focusTrapFactory;
126
+ /** Docked sidenav shows only icons. Two-way: `[(collapsed)]`. */
127
+ readonly collapsed: _angular_core.ModelSignal<boolean>;
128
+ /** The off-canvas drawer is open (only meaningful below the breakpoint). Two-way: `[(drawerOpen)]`. */
129
+ readonly drawerOpen: _angular_core.ModelSignal<boolean>;
130
+ /** Shell width, in CSS px, from which the sidenav is docked rather than a drawer. */
131
+ readonly breakpoint: _angular_core.InputSignalWithTransform<number, unknown>;
132
+ /** Skip-link target id; unique per shell. */
133
+ protected readonly mainId: string;
134
+ private readonly sidenav;
135
+ /** Own width, kept by a ResizeObserver; the window's until the first measurement. */
136
+ private readonly width;
137
+ readonly mode: _angular_core.Signal<NxeShellMode>;
138
+ readonly rail: _angular_core.Signal<boolean>;
139
+ readonly drawerShown: _angular_core.Signal<boolean>;
140
+ readonly sidenavId: _angular_core.Signal<string | null>;
141
+ private trap;
142
+ private returnFocusTo;
143
+ constructor();
144
+ setCollapsed(collapsed: boolean): void;
145
+ /** The hamburger: the drawer below the breakpoint, the dock's collapsed state above it. */
146
+ toggleNavigation(): void;
147
+ closeDrawer(): void;
148
+ protected t(key: 'shell.skipToContent'): string;
149
+ protected skipToContent(event: Event): void;
150
+ protected onEscape(event: Event): void;
151
+ private onDrawerShown;
152
+ private onDrawerHidden;
153
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NxeShellComponent, never>;
154
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NxeShellComponent, "nxe-shell", never, { "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "drawerOpen": { "alias": "drawerOpen"; "required": false; "isSignal": true; }; "breakpoint": { "alias": "breakpoint"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; "drawerOpen": "drawerOpenChange"; }, ["sidenav"], ["[nxeShellBanner]", "nxe-topbar", "nxe-sidenav", "*"], true, never>;
155
+ }
156
+
157
+ /**
158
+ * `nxe-sidenav` is the application's primary navigation, drawn from a tree of {@link NxeNavItem}.
159
+ *
160
+ * ```html
161
+ * <nxe-sidenav [items]="items" (itemClick)="track($event)">
162
+ * <img nxeSidenavHeader src="logo.svg" alt="Nextera" />
163
+ * </nxe-sidenav>
164
+ * ```
165
+ *
166
+ * **Structure and ARIA.** A `<nav>` landmark holding nested lists, in the pattern the ARIA Authoring
167
+ * Practices call a *disclosure navigation menu*: a link is `<a aria-current="page">` when it is the
168
+ * current page; a parent is a `<button aria-expanded aria-controls>` that shows or hides the list
169
+ * under it; a group heading labels its list (`aria-labelledby`). It is deliberately not
170
+ * `role="tree"`: a tree makes one node focusable and needs the full tree keyboard contract, where
171
+ * every link here is an ordinary tab stop and the arrow keys are a shortcut on top of Tab.
172
+ *
173
+ * **Keyboard.** Tab and Shift+Tab visit every link and parent. On a link or parent, Arrow Down/Up
174
+ * move to the next/previous visible entry, Home/End to the first/last. Arrow Right opens a closed
175
+ * parent, or moves into an open one's first child; Arrow Left closes an open parent, or moves from
176
+ * a child to its parent (Right and Left swap in a right-to-left document). Enter and Space activate.
177
+ *
178
+ * **Current page.** From the router: an entry is current when its URL matches (a prefix, or the
179
+ * whole URL with `exact`), and when several do, the most specific one alone is `aria-current`
180
+ * (`/payments/aging` over `/payments`). The parents of the current page open themselves on
181
+ * navigation; what the user opened or closed by hand holds until the next navigation into it.
182
+ *
183
+ * **Collapsed rail.** With the sidenav collapsed (through `nxe-shell` or the `collapsed` model) only
184
+ * the icons show, each with its label as a native `title` and as visually-hidden text, and a badge
185
+ * pins to the icon. There is no flyout for children (that waits for the menu): activating a parent
186
+ * in the rail expands the rail with that parent open.
187
+ *
188
+ * Inside an `nxe-shell` the shell owns the collapsed state and the off-canvas drawer; on its own the
189
+ * sidenav is a plain docked column.
190
+ */
191
+ declare class NxeSidenavComponent {
192
+ private readonly router;
193
+ private readonly messages;
194
+ private readonly shell;
195
+ /** The sidenav's own element; `nxe-shell` traps focus in it while the drawer is open. */
196
+ readonly element: HTMLElement;
197
+ private readonly navElement;
198
+ /** DOM id, which the topbar's toggle button names in `aria-controls`. */
199
+ readonly id: string;
200
+ /** The navigation tree. */
201
+ readonly items: _angular_core.InputSignal<readonly NxeNavItem[]>;
202
+ /** Accessible name of the `<nav>` landmark; "Main navigation" in the active language by default. */
203
+ readonly label: _angular_core.InputSignal<string>;
204
+ /**
205
+ * Collapsed to the icon rail. Inside an `nxe-shell` the shell's `collapsed` wins and this is
206
+ * left alone; the sidenav writes the user's choice through the shell instead.
207
+ */
208
+ readonly collapsed: _angular_core.ModelSignal<boolean>;
209
+ /** Show the collapse/expand button at the foot of a docked sidenav. Default true. */
210
+ readonly collapsible: _angular_core.InputSignalWithTransform<boolean, unknown>;
211
+ /** An entry was activated: a link followed, a plain button pressed. Parents do not emit. */
212
+ readonly itemClick: _angular_core.OutputEmitterRef<NxeNavItem>;
213
+ /** Draws the icon of every entry that has an `icon`. */
214
+ protected readonly iconTemplate: _angular_core.Signal<TemplateRef<any> | undefined>;
215
+ protected readonly navLabel: _angular_core.Signal<string>;
216
+ protected readonly rail: _angular_core.Signal<boolean>;
217
+ protected readonly drawer: _angular_core.Signal<boolean>;
218
+ protected readonly drawerOpen: _angular_core.Signal<boolean>;
219
+ /** Every entry by id, with its place in the tree and the URL it points at. */
220
+ private readonly nodes;
221
+ /** The router's URL, re-read at every navigation so what is current is a signal. */
222
+ private readonly url;
223
+ /** Id of the one entry that is the current page. */
224
+ private readonly currentId;
225
+ /** Ids of the parents above the current page. They open by themselves. */
226
+ private readonly currentAncestors;
227
+ /**
228
+ * What the user opened or closed by hand. Navigating to a new page forgets the choices about
229
+ * its ancestors, so they open for it, and keeps the rest.
230
+ */
231
+ private readonly manual;
232
+ protected t(key: NxeMessageKey): string;
233
+ protected isCurrent(id: string): boolean;
234
+ /** The current page is somewhere under this parent. */
235
+ protected holdsCurrent(id: string): boolean;
236
+ protected isOpen(id: string): boolean;
237
+ protected hasChildren(item: NxeNavItem): boolean;
238
+ protected badgeValue(item: NxeNavItem): number | null | undefined;
239
+ protected badgeName(item: NxeNavItem): string;
240
+ protected initial(item: NxeNavItem): string;
241
+ /** Toggle a parent; in the rail, expand the rail with the parent open instead. */
242
+ protected onParent(item: NxeNavItem): void;
243
+ protected onActivate(item: NxeNavItem): void;
244
+ protected toggleCollapsed(): void;
245
+ protected closeDrawer(): void;
246
+ protected onKeydown(event: KeyboardEvent, item: NxeNavItem): void;
247
+ /** Links and parent buttons in document order; the ones inside a closed parent are not rendered. */
248
+ private focusables;
249
+ private setOpen;
250
+ private setCollapsed;
251
+ private treeOf;
252
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NxeSidenavComponent, never>;
253
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NxeSidenavComponent, "nxe-sidenav", never, { "items": { "alias": "items"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "collapsed": { "alias": "collapsed"; "required": false; "isSignal": true; }; "collapsible": { "alias": "collapsible"; "required": false; "isSignal": true; }; }, { "collapsed": "collapsedChange"; "itemClick": "itemClick"; }, ["iconTemplate"], ["[nxeSidenavHeader]", "[nxeSidenavFooter]"], true, never>;
254
+ }
255
+
256
+ /**
257
+ * `nxe-topbar` is the bar across the top of an `nxe-shell`: a start, a centre and an end slot.
258
+ *
259
+ * ```html
260
+ * <nxe-topbar>
261
+ * <img nxeTopbarStart src="logo.svg" alt="Nextera" />
262
+ * <sb-search nxeTopbarCenter />
263
+ * <ng-container nxeTopbarEnd>
264
+ * <sb-company-switcher />
265
+ * <sb-notifications />
266
+ * </ng-container>
267
+ * </nxe-topbar>
268
+ * ```
269
+ *
270
+ * The start slot is preceded by a hamburger button: an icon-only `nxe-button` named "Toggle
271
+ * navigation" with `aria-controls` the sidenav and `aria-expanded` the sidenav's state. Inside an
272
+ * `nxe-shell` it opens and closes the drawer below the breakpoint and collapses and expands the dock
273
+ * above it. `menuToggle` also fires, so a topbar used without a shell can drive its own sidenav.
274
+ * `[nxeTopbarEnd]` elements sit at the inline end of the bar, where a company switcher, language
275
+ * toggle, notification bell and user menu go.
276
+ */
277
+ declare class NxeTopbarComponent {
278
+ private readonly messages;
279
+ private readonly shell;
280
+ /** Show the hamburger button. Default true. */
281
+ readonly menuButton: _angular_core.InputSignalWithTransform<boolean, unknown>;
282
+ /** The hamburger was pressed. */
283
+ readonly menuToggle: _angular_core.OutputEmitterRef<void>;
284
+ protected readonly label: _angular_core.Signal<string>;
285
+ protected readonly controls: _angular_core.Signal<string | null>;
286
+ /** Drawer: open. Dock: not collapsed. Without a shell the button says nothing about it. */
287
+ protected readonly expanded: _angular_core.Signal<boolean | null>;
288
+ protected toggle(): void;
289
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<NxeTopbarComponent, never>;
290
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<NxeTopbarComponent, "nxe-topbar", never, { "menuButton": { "alias": "menuButton"; "required": false; "isSignal": true; }; }, { "menuToggle": "menuToggle"; }, never, ["[nxeTopbarStart]", "[nxeTopbarCenter]", "[nxeTopbarEnd]"], true, never>;
291
+ }
292
+
293
+ export { NXE_SHELL, NxeShellComponent, NxeSidenavComponent, NxeSidenavIconDirective, NxeTopbarComponent };
294
+ export type { NxeNavItem, NxeShellMode, NxeShellState };
@@ -1,10 +1,12 @@
1
1
  import { OverlayRef } from '@angular/cdk/overlay';
2
2
  import { Subscription } from 'rxjs';
3
+ import * as i0 from '@angular/core';
4
+ import { EnvironmentProviders, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
3
5
 
4
6
  /**
5
7
  * Marker class carried by every nxe surface — component hosts and CDK overlay panes alike.
6
8
  *
7
- * It is the nxe counterpart of DevExtreme's `.dx-widget` base and is what gives a surface its
9
+ * It is the nxe counterpart of DevExtreme's `.nxe-widget` base and is what gives a surface its
8
10
  * font family/size/weight/line-height (see styling/_widget.scss).
9
11
  *
10
12
  * It matters most on overlays: a CDK pane is appended to `<body>`, so a portaled dropdown
@@ -30,11 +32,169 @@ declare function nxePane(...extra: (string | null | undefined)[]): string[];
30
32
  *
31
33
  * DevExtreme drew dropdowns flush against the editor — no border on the shared edge — and moved
32
34
  * that seam to the other side when the popup flipped up
33
- * (`.dx-overlay-content.dx-dropdowneditor-overlay-flipped { border-top-width:1px; border-bottom-width:0 }`).
35
+ * (`.nxe-overlay-content.nxe-dropdowneditor-overlay-flipped { border-top-width:1px; border-bottom-width:0 }`).
34
36
  * CDK does not emit any equivalent hook, so panels need this to reproduce it.
35
37
  *
36
38
  * Returns the subscription; callers dispose it with the overlay.
37
39
  */
38
40
  declare function trackPaneFlip(overlayRef: OverlayRef): Subscription | undefined;
39
41
 
40
- export { NXE_PANE_FLIPPED_CLASS, NXE_WIDGET_CLASS, nxePane, trackPaneFlip };
42
+ /**
43
+ * Migration aid: re-emit the old `dx-*` class names alongside the library's `nxe-*` ones.
44
+ *
45
+ * The library dropped the `dx-` prefix from its DOM. Applications that still style themselves
46
+ * with rules like `.dx-datagrid .dx-row > td { padding: 4px }` would silently lose that styling,
47
+ * and no stylesheet can fix it: CSS cannot alias a class, so a rule keyed on `.dx-datagrid` will
48
+ * never match an element that only says `nxe-datagrid`. The class has to be back on the element.
49
+ *
50
+ * So this mirrors them at runtime. Provide it once and unmigrated stylesheets keep working
51
+ * unchanged; provide nothing and the library costs nothing, which is the point of it being
52
+ * opt-in. `tools/dx-compat/rewrite-styles.mjs` rewrites an application's own stylesheets when it
53
+ * is ready to drop the flag — this is a bridge, not a destination.
54
+ *
55
+ * ```ts
56
+ * bootstrapApplication(AppComponent, { providers: [provideNxeDxCompat()] });
57
+ * ```
58
+ */
59
+ declare class NxeDxCompatService {
60
+ private readonly document;
61
+ private readonly ngZone;
62
+ private observer?;
63
+ /** Our own writes retrigger the observer; this stops it chasing its own tail. */
64
+ private mirroring;
65
+ start(): void;
66
+ stop(): void;
67
+ private onMutations;
68
+ private mirrorSubtree;
69
+ /** Keep `dx-x` present exactly while `nxe-x` is, so a removed state class is removed here too. */
70
+ private mirrorElement;
71
+ static ɵfac: i0.ɵɵFactoryDeclaration<NxeDxCompatService, never>;
72
+ static ɵprov: i0.ɵɵInjectableDeclaration<NxeDxCompatService>;
73
+ }
74
+ /**
75
+ * Opt into the `dx-*` compatibility classes. See {@link NxeDxCompatService} for why this is a
76
+ * runtime mirror rather than a stylesheet.
77
+ */
78
+ declare function provideNxeDxCompat(): EnvironmentProviders;
79
+
80
+ /** Where the scroll position sits on each axis: -1 at the start, 1 at the end, 0 in between. */
81
+ interface NxeScrollbarPosition {
82
+ x: number;
83
+ y: number;
84
+ }
85
+ /** Scroll geometry of the host element. */
86
+ interface NxeScrollbarGeometry {
87
+ x: number;
88
+ y: number;
89
+ w: number;
90
+ h: number;
91
+ }
92
+ /**
93
+ * The options that map onto this implementation.
94
+ *
95
+ * `wheelPropagation` is accepted and ignored: it existed because the original took the wheel over
96
+ * from the browser and had to decide when to hand it back. Scrolling here is native, so the
97
+ * browser makes that decision itself — which is what the option was trying to restore.
98
+ */
99
+ interface NxeScrollbarOptions {
100
+ suppressScrollX?: boolean;
101
+ suppressScrollY?: boolean;
102
+ wheelPropagation?: boolean;
103
+ /** Rail is hidden below this content overflow, in px. Matches the original's default. */
104
+ minScrollbarLength?: number;
105
+ maxScrollbarLength?: number;
106
+ }
107
+ /**
108
+ * `[nxeScrollbar]` — a container with custom overlay scrollbars.
109
+ *
110
+ * This replaces a Perfect Scrollbar wrapper, and reproduces its DOM and its rendering exactly:
111
+ * two absolutely-positioned rails, each holding a thumb, sized and offset from the host's own
112
+ * scroll geometry. The stylesheet (`@nexterajs/ui/styles/_scrollbar.scss`) is a value-for-value
113
+ * port, so applications that restyle the rails keep their appearance unchanged.
114
+ *
115
+ * **What is different, deliberately:** the original set `overflow: hidden` and reimplemented
116
+ * wheel, touch, keyboard and momentum scrolling in JavaScript. Here the element scrolls natively
117
+ * and only the native scrollbar is hidden — the rails are painted from the `scroll` event. That is
118
+ * less code and better behaviour on touch and trackpads, and it is invisible: a hidden native
119
+ * scrollbar takes no layout width, exactly like the overlay rails it replaces.
120
+ *
121
+ * The host carries `nxe-scrollbar` while enabled, which is the marker callers branch on.
122
+ */
123
+ declare class NxeScrollbarDirective implements OnInit, OnChanges, OnDestroy {
124
+ private readonly elementRef;
125
+ private readonly zone;
126
+ private readonly destroyRef;
127
+ nxeScrollbar: boolean;
128
+ nxeScrollbarOptions: NxeScrollbarOptions | null;
129
+ private options;
130
+ private animation;
131
+ private railX?;
132
+ private railY?;
133
+ private thumbX?;
134
+ private thumbY?;
135
+ private resizeObserver?;
136
+ private mutationObserver?;
137
+ private scrollingXTimer;
138
+ private scrollingYTimer;
139
+ private teardown;
140
+ private initialised;
141
+ get element(): HTMLElement;
142
+ ngOnInit(): void;
143
+ ngOnChanges(changes: SimpleChanges): void;
144
+ ngOnDestroy(): void;
145
+ isEnabled(): boolean;
146
+ /**
147
+ * Recompute rail and thumb geometry. Call after changing content the directive cannot observe.
148
+ *
149
+ * This reproduces the original's arithmetic exactly, down to how it rounds — the requirement was
150
+ * no visual difference, and each of these is worth a pixel:
151
+ * - the container is measured from `getBoundingClientRect()` rounded, not from `clientHeight`;
152
+ * - sizes and offsets are **truncated** (the original ran them through `parseInt`), not rounded;
153
+ * - `scrollTop` is floored before it is used;
154
+ * - the thumb offset is clamped so it cannot overhang the end of the rail.
155
+ */
156
+ update(): void;
157
+ /** Kept for parity with the wrapper this replaces; teardown also happens in ngOnDestroy. */
158
+ destroy(): void;
159
+ isScrollableX(): boolean;
160
+ isScrollableY(): boolean;
161
+ geometry(prefix?: 'scroll' | 'client' | 'offset'): NxeScrollbarGeometry;
162
+ /** -1 at the start of an axis, 1 at its end, 0 anywhere between. */
163
+ position(absolute?: boolean): NxeScrollbarPosition;
164
+ scrollTo(x: number, y?: number, speed?: number): void;
165
+ scrollToX(x: number, speed?: number): void;
166
+ scrollToY(y: number, speed?: number): void;
167
+ scrollToTop(offset?: number, speed?: number): void;
168
+ scrollToBottom(offset?: number, speed?: number): void;
169
+ scrollToLeft(offset?: number, speed?: number): void;
170
+ scrollToRight(offset?: number, speed?: number): void;
171
+ /**
172
+ * Bring the first descendant matching `selector` into view.
173
+ *
174
+ * `ignoreVisible` skips the scroll when the target already sits inside the viewport less the
175
+ * offset, so an item that is merely low on screen is not yanked to the top on every change.
176
+ */
177
+ scrollToElement(selector: string, offset?: number, ignoreVisible?: boolean, speed?: number): void;
178
+ /**
179
+ * Move one axis to `value`, easing over `speed` ms when one is given.
180
+ *
181
+ * The easing bails as soon as the position stops matching what it last wrote, so a user who
182
+ * grabs the wheel mid-flight takes over instead of fighting the animation.
183
+ */
184
+ animateScrolling(axis: 'scrollTop' | 'scrollLeft', value: number, speed?: number): void;
185
+ private init;
186
+ private buildRail;
187
+ private onScroll;
188
+ private bindThumbDrag;
189
+ /** Only the configured bounds, and only when set — the same shape the original's clamp had. */
190
+ private clampThumb;
191
+ private on;
192
+ private dispose;
193
+ private cancelAnimation;
194
+ static ɵfac: i0.ɵɵFactoryDeclaration<NxeScrollbarDirective, never>;
195
+ static ɵdir: i0.ɵɵDirectiveDeclaration<NxeScrollbarDirective, "[nxeScrollbar]", ["nxeScrollbar"], { "nxeScrollbar": { "alias": "nxeScrollbar"; "required": false; }; "nxeScrollbarOptions": { "alias": "nxeScrollbarOptions"; "required": false; }; }, {}, never, never, true, never>;
196
+ static ngAcceptInputType_nxeScrollbar: boolean | string | null | undefined;
197
+ }
198
+
199
+ export { NXE_PANE_FLIPPED_CLASS, NXE_WIDGET_CLASS, NxeDxCompatService, NxeScrollbarDirective, nxePane, provideNxeDxCompat, trackPaneFlip };
200
+ export type { NxeScrollbarGeometry, NxeScrollbarOptions, NxeScrollbarPosition };
@@ -1,4 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
+ import { NxeMessageService } from '@nexterajs/ui/core';
2
3
 
3
4
  /** A node in the assembled tree (children built from the flat `parentIdExpr` list). */
4
5
  interface NxeTreeNode {
@@ -10,6 +11,12 @@ interface NxeTreeNode {
10
11
  hasChildren: boolean;
11
12
  level: number;
12
13
  }
14
+ /** A visible node with the sibling position a flattened tree has to state explicitly. */
15
+ interface NxeTreeRow {
16
+ node: NxeTreeNode;
17
+ posInSet: number;
18
+ setSize: number;
19
+ }
13
20
  /** DevExtreme-shaped `onItemClick` payload (consumers read `event.itemData.<field>`). */
14
21
  interface NxeTreeItemClickEvent {
15
22
  itemData: Record<string, unknown>;
@@ -29,7 +36,10 @@ interface NxeTreeItemClickEvent {
29
36
  * keeps reading `event.itemData.url/urlParams/objectName`.
30
37
  */
31
38
  declare class NxeTreeViewComponent {
32
- readonly dataSource: _angular_core.InputSignal<readonly unknown[]>;
39
+ private readonly elementRef;
40
+ /** The library's runtime-switchable messages. */
41
+ protected readonly messages: NxeMessageService;
42
+ readonly dataSource: _angular_core.InputSignal<readonly unknown[] | null | undefined>;
33
43
  /** Accepted for parity; this component always assembles a tree from the flat list. */
34
44
  readonly dataStructure: _angular_core.InputSignal<"plain" | "tree">;
35
45
  readonly keyExpr: _angular_core.InputSignal<string>;
@@ -44,15 +54,27 @@ declare class NxeTreeViewComponent {
44
54
  readonly searchMode: _angular_core.InputSignal<"contains" | "startswith" | "equals">;
45
55
  /** Accepted for parity (native scrolling is the default here anyway). */
46
56
  readonly useNativeScrolling: _angular_core.InputSignal<boolean>;
47
- readonly width: _angular_core.InputSignal<string | number>;
57
+ readonly width: _angular_core.InputSignal<string | number | undefined>;
48
58
  readonly onItemClick: _angular_core.OutputEmitterRef<NxeTreeItemClickEvent>;
49
59
  readonly expanded: _angular_core.WritableSignal<ReadonlySet<unknown>>;
50
60
  readonly selectedKey: _angular_core.WritableSignal<unknown>;
51
61
  readonly searchText: _angular_core.WritableSignal<string>;
62
+ private readonly searchInput;
52
63
  /** DevExtreme `.instance` facade — `expandAll()` is the only method used. */
53
64
  get instance(): this;
54
- protected readonly widthStyle: _angular_core.Signal<string>;
65
+ protected readonly widthStyle: _angular_core.Signal<string | undefined>;
55
66
  private readonly tree;
67
+ /**
68
+ * The visible nodes, each with its position among its siblings.
69
+ *
70
+ * The tree renders as one flat `<ul>` with indentation rather than nested lists, so a screen
71
+ * reader cannot infer depth or sibling counts from the DOM. `aria-level`, `aria-setsize` and
72
+ * `aria-posinset` are what carry that, which makes them required for a flattened tree rather
73
+ * than optional extras. They are computed during the walk, where the sibling array is already
74
+ * in hand — reconstructing them from the flat list afterwards is possible but much harder to
75
+ * read for no gain.
76
+ */
77
+ protected readonly visibleRows: _angular_core.Signal<NxeTreeRow[]>;
56
78
  /** Flattened list of currently-visible nodes (respects expand state + search). */
57
79
  protected readonly visibleNodes: _angular_core.Signal<NxeTreeNode[]>;
58
80
  expandAll(): void;
@@ -61,9 +83,39 @@ declare class NxeTreeViewComponent {
61
83
  protected toggle(node: NxeTreeNode, event: Event): void;
62
84
  protected onNodeClick(node: NxeTreeNode, event: Event): void;
63
85
  protected onSearch(event: Event): void;
86
+ /**
87
+ * The search box's clear button. dx empties the value and leaves the caret in the box (its
88
+ * clear button swallows the mouse-down so the input never loses focus); refocusing here covers
89
+ * the case where focus had already left the input.
90
+ */
91
+ protected clearSearch(): void;
92
+ /**
93
+ * The node holding the tree's single tab stop.
94
+ *
95
+ * A tree is one stop in the tab order, not one per node, so a roving tabindex is part of the
96
+ * role rather than an extra: `role="tree"` on a list nothing can focus would announce a
97
+ * structure a keyboard user cannot reach.
98
+ */
99
+ protected readonly focusedKey: _angular_core.WritableSignal<unknown>;
100
+ protected isTabStop(node: NxeTreeNode, index: number): boolean;
101
+ /**
102
+ * The node that holds DOM focus right now, or `null` while focus is anywhere else.
103
+ *
104
+ * Deliberately not `focusedKey`: that is the roving tab stop and has to outlive a blur, so Tab
105
+ * comes back to the same node. dx drops `dx-state-focused` the moment focus leaves the node,
106
+ * and the focused look goes with it — the row colour, and the toggle, which follows the row's
107
+ * colour only while focused (`color: unset`, dx.light.css). Keyed off focusin/focusout so the
108
+ * class means exactly what it says.
109
+ */
110
+ private readonly focusedNode;
111
+ protected isFocused(node: NxeTreeNode): boolean;
112
+ protected onNodeFocus(node: NxeTreeNode): void;
113
+ protected onNodeBlur(node: NxeTreeNode): void;
114
+ /** Arrow-key navigation, per the WAI-ARIA tree pattern. */
115
+ protected onKeydown(event: KeyboardEvent): void;
64
116
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<NxeTreeViewComponent, never>;
65
117
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<NxeTreeViewComponent, "nxe-tree-view", never, { "dataSource": { "alias": "dataSource"; "required": false; "isSignal": true; }; "dataStructure": { "alias": "dataStructure"; "required": false; "isSignal": true; }; "keyExpr": { "alias": "keyExpr"; "required": false; "isSignal": true; }; "displayExpr": { "alias": "displayExpr"; "required": false; "isSignal": true; }; "parentIdExpr": { "alias": "parentIdExpr"; "required": false; "isSignal": true; }; "rootValue": { "alias": "rootValue"; "required": false; "isSignal": true; }; "expandedExpr": { "alias": "expandedExpr"; "required": false; "isSignal": true; }; "selectionMode": { "alias": "selectionMode"; "required": false; "isSignal": true; }; "selectByClick": { "alias": "selectByClick"; "required": false; "isSignal": true; }; "searchEnabled": { "alias": "searchEnabled"; "required": false; "isSignal": true; }; "searchMode": { "alias": "searchMode"; "required": false; "isSignal": true; }; "useNativeScrolling": { "alias": "useNativeScrolling"; "required": false; "isSignal": true; }; "width": { "alias": "width"; "required": false; "isSignal": true; }; }, { "onItemClick": "onItemClick"; }, never, never, true, never>;
66
118
  }
67
119
 
68
120
  export { NxeTreeViewComponent };
69
- export type { NxeTreeItemClickEvent, NxeTreeNode };
121
+ export type { NxeTreeItemClickEvent, NxeTreeNode, NxeTreeRow };