@forgeroute/ui 0.4.66 → 0.4.68

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeroute/ui",
3
- "version": "0.4.66",
3
+ "version": "0.4.68",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -0,0 +1,32 @@
1
+ // Independent column scrolling for detail pages (`<ui-sidebar-layout uiFillViewport>`): the viewport does not scroll; the two columns are capped
2
+ // to the room under the page's top block (`--cols-h`, set by the `uiFillViewport` directive) and each scrolls inside its own box.
3
+ //
4
+ // @use 'detail-columns' as dc; // @forgeroute/ui/src is on the stylePreprocessorOptions includePaths
5
+ // @include dc.independent-columns; // or (1200px) for collapseAt="lg"
6
+ //
7
+ // Markup: `.detail-page` wraps the page, `.col-main` is the `layout-main` element, `.col-side` is the `layout-side` element.
8
+ // Only applies while the layout has two columns: `$collapse` = the sidebar-layout `collapseAt` width in px (900 = md, 1200 = lg); collapsed it is
9
+ // one column and the page scrolls as usual. `$bleed` is the room inside the main column's scroll box so a focus ring or shadow at its edge is not clipped.
10
+ @mixin independent-columns($collapse: 900px, $bleed: 4px) {
11
+ @media (min-width: #{$collapse + 1}) {
12
+ .detail-page { padding-bottom: 0; } // the columns end at the bottom of the content area
13
+ .col-main {
14
+ position: relative; // visually hidden / absolutely positioned content (sr-only text, menus) stays inside this scroll box; otherwise it is positioned against the page and stretches the document, which then scrolls
15
+ box-sizing: border-box;
16
+ padding: $bleed;
17
+ margin: -$bleed;
18
+ max-height: calc(var(--cols-h) + #{2 * $bleed});
19
+ overflow-y: auto;
20
+ overscroll-behavior: contain;
21
+ -webkit-overflow-scrolling: touch;
22
+ }
23
+ .col-side {
24
+ display: block;
25
+ position: relative; // absolutely positioned content (visually hidden text, menus) stays inside this scroll box; otherwise it would stretch the library's outer side wrapper and give the column a second scrollbar
26
+ max-height: var(--cols-h);
27
+ overflow-y: auto;
28
+ overscroll-behavior: contain;
29
+ -webkit-overflow-scrolling: touch;
30
+ }
31
+ }
32
+ }
@@ -1591,6 +1591,8 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1591
1591
  */
1592
1592
  private _columnBadgeLabel;
1593
1593
  protected readonly _error: import("@angular/core").WritableSignal<string | null>;
1594
+ /** Phone card list is only a role=list while it contains listitems: skeleton cards or real rows (not error / empty / no-match). */
1595
+ protected readonly _cardListHasItems: Signal<boolean>;
1594
1596
  private readonly _loadFailed;
1595
1597
  /**
1596
1598
  * True from a failed load (the table shows its error state) until a later load SUCCEEDS. Unlike the error state itself it stays true while
@@ -3963,7 +3965,7 @@ export declare class ButtonComponent {
3963
3965
  * `ghost-start`: a ghost button for list / section toggles - a full-width row with its content aligned to the start, transparent
3964
3966
  * background, hover tint and the normal focus ring. Pair it with `ariaExpanded` / `ariaPressed`; the label may contain a heading element.
3965
3967
  */
3966
- readonly variant: import("@angular/core").InputSignal<"primary" | "secondary" | "danger" | "warning" | "link" | "ghost" | "ghost-start" | "tab" | "icon" | "icon-danger">;
3968
+ readonly variant: import("@angular/core").InputSignal<"link" | "primary" | "secondary" | "danger" | "warning" | "ghost" | "ghost-start" | "tab" | "icon" | "icon-danger">;
3967
3969
  /** Renders an anchor to this route instead of a button (use with `variant="link"` for an inline text link). `['/settings']` or `'/settings'`. While `disabled` or `loading` it renders a non-clickable `<span role="link" aria-disabled>` with no href (like `href`). */
3968
3970
  readonly routerLink: import("@angular/core").InputSignal<string | readonly unknown[] | null>;
3969
3971
  /** Query params of the `routerLink` (e.g. `{ section: 'deal-defaults' }`). */
@@ -4009,7 +4011,7 @@ export declare class ButtonComponent {
4009
4011
  readonly color: import("@angular/core").InputSignal<string | undefined>;
4010
4012
  readonly active: import("@angular/core").InputSignal<boolean>;
4011
4013
  readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg" | "xs">;
4012
- readonly type: import("@angular/core").InputSignal<"reset" | "submit" | "button">;
4014
+ readonly type: import("@angular/core").InputSignal<"button" | "reset" | "submit">;
4013
4015
  readonly disabled: import("@angular/core").InputSignal<boolean>;
4014
4016
  readonly loading: import("@angular/core").InputSignal<boolean>;
4015
4017
  readonly fullWidth: import("@angular/core").InputSignal<boolean>;
@@ -4957,7 +4959,7 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
4957
4959
  * `search`, `text`, `none`). Empty (default) = the browser's default for the field, except `type="tel"`, which keeps `tel`.
4958
4960
  * It only picks the keyboard; it does not restrict what can be typed or pasted.
4959
4961
  */
4960
- readonly inputmode: import("@angular/core").InputSignal<"" | "none" | "text" | "search" | "email" | "tel" | "url" | "decimal" | "numeric">;
4962
+ readonly inputmode: import("@angular/core").InputSignal<"" | "search" | "none" | "text" | "email" | "tel" | "url" | "decimal" | "numeric">;
4961
4963
  readonly readonly: import("@angular/core").InputSignal<boolean>;
4962
4964
  readonly hint: import("@angular/core").InputSignal<string>;
4963
4965
  /** Id(s) of an external element that also describes this control (e.g. one shared error text for a group of fields). Space separated; MERGED after the control's own hint / error id in `aria-describedby`, never replacing it. Empty = unchanged. */
@@ -6493,7 +6495,7 @@ export declare class DropdownComponent {
6493
6495
  readonly loader: import("@angular/core").InputSignal<(() => Promise<DropdownItem[]>) | null>;
6494
6496
  readonly active: import("@angular/core").InputSignal<boolean>;
6495
6497
  readonly activeValue: import("@angular/core").InputSignal<string>;
6496
- readonly variant: import("@angular/core").InputSignal<"badge" | "button">;
6498
+ readonly variant: import("@angular/core").InputSignal<"button" | "badge">;
6497
6499
  /** Trigger size for `variant="button"`: `sm` (default, the compact toolbar look) or `md` (same padding and type as a medium `ui-button`). */
6498
6500
  readonly size: import("@angular/core").InputSignal<"sm" | "md">;
6499
6501
  readonly menuWidth: import("@angular/core").InputSignal<string>;
@@ -6526,6 +6528,8 @@ export declare class DropdownComponent {
6526
6528
  private _typeahead;
6527
6529
  private _typeaheadTimer;
6528
6530
  private _destroyed;
6531
+ /** Identifies the current loader request, so a late or stale answer (menu closed / reopened meanwhile) is ignored. */
6532
+ private _loadToken;
6529
6533
  protected readonly triggerId: string;
6530
6534
  protected readonly menuId: string;
6531
6535
  protected readonly _ariaLabel: import("@angular/core").Signal<string | null>;
@@ -7823,6 +7827,10 @@ export declare class PanelInfoListComponent {
7823
7827
  isWeb(href: string): boolean;
7824
7828
  /** Link text: the value, else the email / phone as written. */
7825
7829
  textOf(item: PanelInfoItem): string;
7830
+ /** Plain value, or the dash for an empty / blank one. */
7831
+ valueOf(item: PanelInfoItem): string;
7832
+ /** A disabled header action never runs, even when the click reaches the host element. */
7833
+ runHeaderAction(ha: NonNullable<PanelInfoSection['headerAction']>): void;
7826
7834
  visibleItems(section: PanelInfoSection): PanelInfoItem[];
7827
7835
  static ɵfac: i0.ɵɵFactoryDeclaration<PanelInfoListComponent, never>;
7828
7836
  static ɵcmp: i0.ɵɵComponentDeclaration<PanelInfoListComponent, "ui-panel-info-list", never, {
@@ -8080,6 +8088,11 @@ export declare class AppShellComponent implements OnInit, OnDestroy {
8080
8088
  * when the title is not set.
8081
8089
  */
8082
8090
  readonly pageTitle: import("@angular/core").InputSignal<string>;
8091
+ /**
8092
+ * Show the app's toasts (renders one `<ui-toast />` for the whole shell). Default false so an app that already renders its own `<ui-toast />`
8093
+ * is unchanged; an app that sets this removes its own.
8094
+ */
8095
+ readonly showToast: import("@angular/core").InputSignal<boolean>;
8083
8096
  readonly appName: import("@angular/core").InputSignal<string>;
8084
8097
  readonly user: import("@angular/core").InputSignal<ShellUser | null>;
8085
8098
  readonly readOnly: import("@angular/core").InputSignal<boolean>;
@@ -8097,7 +8110,13 @@ export declare class AppShellComponent implements OnInit, OnDestroy {
8097
8110
  readonly persistKey: import("@angular/core").InputSignal<string>;
8098
8111
  readonly logoutClick: import("@angular/core").OutputEmitterRef<void>;
8099
8112
  protected readonly _sidebarOpen: import("@angular/core").WritableSignal<boolean>;
8113
+ /** `data.pageTitle` of the deepest activated route that sets one ('' when none does). Updated on every NavigationEnd. */
8114
+ protected readonly _routeTitle: import("@angular/core").WritableSignal<string>;
8115
+ /** The title shown in the top bar: the `pageTitle` input when the app sets one (it wins), else the route's `data.pageTitle`. */
8116
+ protected readonly _title: import("@angular/core").Signal<string>;
8100
8117
  protected readonly _collapsed: import("@angular/core").WritableSignal<boolean>;
8118
+ /** The rail (icons-only sidebar) only exists on the desktop layout; on the drawer layout (≤1024px) a saved rail choice is ignored. */
8119
+ protected readonly _drawerLayout: import("@angular/core").WritableSignal<boolean>;
8101
8120
  protected readonly _isRail: import("@angular/core").Signal<boolean>;
8102
8121
  private readonly _collapsedGroups;
8103
8122
  protected readonly _visibleGroups: import("@angular/core").Signal<NavGroup[]>;
@@ -8106,6 +8125,8 @@ export declare class AppShellComponent implements OnInit, OnDestroy {
8106
8125
  private readonly _iconCache;
8107
8126
  _safeIcon(svg: string): SafeHtml;
8108
8127
  _visibleLinks(group: NavGroup): NavLink[];
8128
+ /** Groups can only be collapsed when there is more than one: a lone group stays open so the navigation can never be hidden. */
8129
+ protected _canCollapseGroups(): boolean;
8109
8130
  _isCollapsed(key: string): boolean;
8110
8131
  toggleGroup(key: string): void;
8111
8132
  private _readSavedGroups;
@@ -8114,9 +8135,28 @@ export declare class AppShellComponent implements OnInit, OnDestroy {
8114
8135
  toggleCollapsed(): void;
8115
8136
  private static readonly _COLLAPSE_KEY;
8116
8137
  private static _loadCollapsed;
8138
+ private readonly _host;
8139
+ private readonly _injector;
8140
+ private readonly _document;
8141
+ /** The drawer layout is exactly the complement of the desktop layout the CSS rail rules use (min-width: 1025px): no viewport width falls in between. */
8142
+ private static readonly _DRAWER_MQ;
8143
+ private static readonly _FOCUSABLE;
8144
+ private _sidebarFocusables;
8145
+ private _hamburger;
8117
8146
  toggleSidebar(): void;
8118
- closeSidebar(): void;
8147
+ /** Opens the drawer (drawer layout only; a no-op on desktop) and moves keyboard focus into it, so the links behind the backdrop are not reachable until it closes. */
8148
+ openSidebar(): void;
8149
+ /** Closes the drawer; `returnFocus` puts focus back on the hamburger that opened it (keyboard users keep their place). */
8150
+ closeSidebar(returnFocus?: boolean): void;
8151
+ /** Escape closes an open drawer; Tab stays inside it while it is open. */
8152
+ onShellKeydown(e: KeyboardEvent): void;
8153
+ /** Skip link: focuses the main content (`tabindex="-1"`) without changing the URL (the app uses the router). */
8154
+ skipToContent(e: Event): void;
8119
8155
  ngOnInit(): void;
8156
+ private _drawerMq?;
8157
+ private readonly _onDrawerMqChange;
8158
+ /** The `data.pageTitle` of the deepest activated route that has a non-empty string one (a child route's title wins over its parent's). */
8159
+ private _titleOfRoute;
8120
8160
  ngOnDestroy(): void;
8121
8161
  private _expandActiveGroup;
8122
8162
  static ɵfac: i0.ɵɵFactoryDeclaration<AppShellComponent, never>;
@@ -8131,6 +8171,11 @@ export declare class AppShellComponent implements OnInit, OnDestroy {
8131
8171
  "required": false;
8132
8172
  "isSignal": true;
8133
8173
  };
8174
+ "showToast": {
8175
+ "alias": "showToast";
8176
+ "required": false;
8177
+ "isSignal": true;
8178
+ };
8134
8179
  "appName": {
8135
8180
  "alias": "appName";
8136
8181
  "required": false;
@@ -11170,6 +11215,28 @@ export declare class SidebarLayoutComponent {
11170
11215
  };
11171
11216
  }, {}, never, ["[layout-main]", "[layout-side]"], true, never>;
11172
11217
  }
11218
+ /**
11219
+ * Independent column scrolling for a detail page's `<ui-sidebar-layout uiFillViewport>`.
11220
+ *
11221
+ * Sets `--cols-h` on the host: the room left between the top of the layout and the bottom of the shell's content area (the nearest
11222
+ * `<main>`). Pair it with the `independent-columns` mixin (`@use 'detail-columns' as dc;` from `@forgeroute/ui/src/_detail-columns.scss`),
11223
+ * which caps both columns to `--cols-h` so each scrolls on its own while the viewport does not scroll.
11224
+ *
11225
+ * <ui-sidebar-layout uiFillViewport sideWidth="320px" collapseAt="md">
11226
+ * <div layout-main class="col-main"> ... </div>
11227
+ * <aside layout-side class="col-side"> ... </aside>
11228
+ * </ui-sidebar-layout>
11229
+ *
11230
+ * Re-measured when the content area or the element's parent changes size (window resize, tabs wrapping, a card loading). Pure layout maths
11231
+ * on the element's position inside the scroller's content, so it does not depend on how far the page happens to be scrolled. The height is
11232
+ * never below 240px. With no `<main>` ancestor nothing is set. Not related to `ui-page`'s `fillViewport` input (a page at least one viewport high).
11233
+ */
11234
+ export declare class FillViewportDirective {
11235
+ private readonly el;
11236
+ constructor();
11237
+ static ɵfac: i0.ɵɵFactoryDeclaration<FillViewportDirective, never>;
11238
+ static ɵdir: i0.ɵɵDirectiveDeclaration<FillViewportDirective, "[uiFillViewport]", never, {}, {}, never, never, true, never>;
11239
+ }
11173
11240
  export type ActionBarAlign = 'start' | 'center' | 'end' | 'between';
11174
11241
  /**
11175
11242
  * Bar for page-level actions (Save / Cancel) that stays at the bottom of the scrolling area.