@forgeroute/ui 0.4.1 → 0.4.2

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.1",
3
+ "version": "0.4.2",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -1,5 +1,6 @@
1
1
  import * as i0 from "@angular/core";
2
2
  import { ChangeDetectorRef, EnvironmentProviders, InjectionToken, OnDestroy, OnInit, Signal, TemplateRef, WritableSignal } from "@angular/core";
3
+ import { UrlTree } from "@angular/router";
3
4
  import { AbstractControl, ControlValueAccessor, FormControl, ValidationErrors, Validator } from "@angular/forms";
4
5
  import { Observable, Subject } from "rxjs";
5
6
  import { HttpClient, HttpInterceptorFn } from "@angular/common/http";
@@ -99,7 +100,38 @@ export interface RowAction {
99
100
  /** Page-level permission passed to `<ui-data-table [permission]>`. Anything other than 'full' is read-only
100
101
  * (apps use values like 'read-only' / 'hidden'). */
101
102
  export type TablePermission = 'full' | 'read' | 'read-only' | 'none' | 'hidden';
102
- export type CellType = 'text' | 'badge' | 'date' | 'phone' | 'number' | 'avatar-name' | 'days-ago' | 'progress' | 'tags' | 'image' | 'actions';
103
+ export type CellType = 'text' | 'badge' | 'date' | 'phone' | 'number' | 'avatar-name' | 'days-ago' | 'progress' | 'tags' | 'image' | 'link' | 'actions';
104
+ /**
105
+ * Where a `cellType: 'link'` cell points. Return a plain string from `linkFn` as shorthand for `{ href }`.
106
+ *
107
+ * - `routerLink` (+ `queryParams`) is an in-app route: the anchor gets the real URL as its `href` (so right-click,
108
+ * middle-click, ctrl/cmd-click, "open in new tab" and "copy link address" all work) and a plain left click
109
+ * navigates through the Angular `Router` without a page reload. `routerLink` wins when `href` is also set.
110
+ * - `href` accepts ONLY `mailto:`, `tel:`, `http:`, `https:` and root-relative paths (`/cases/12`). Anything else
111
+ * (`javascript:`, `data:`, `//host`, empty, control characters) is never rendered as an href: the cell shows plain
112
+ * text. A root-relative `href` is a normal link (full page load); use `routerLink` for in-app navigation.
113
+ * - `target: '_blank'` (default `'_self'`) adds `rel="noopener noreferrer"` and a "(opens in a new tab)" screen-reader
114
+ * hint. `http(s):` hrefs always get the same `rel`.
115
+ * - `label` is the link text when the cell has no text of its own (`render` and `item[key]` are empty).
116
+ * - `ariaLabel` replaces the link's accessible name (default: its text).
117
+ * - `download` adds the `download` attribute.
118
+ */
119
+ export interface LinkTarget {
120
+ href?: string;
121
+ routerLink?: string | readonly any[];
122
+ queryParams?: Record<string, any>;
123
+ target?: '_self' | '_blank';
124
+ label?: string;
125
+ ariaLabel?: string;
126
+ download?: boolean;
127
+ }
128
+ /** Payload of `(linkClick)`: a link cell's anchor was activated by a click (the row click is NOT also emitted). */
129
+ export interface LinkClickEvent<T = any> {
130
+ item: T;
131
+ column: ColumnDef;
132
+ /** The anchor's href (null only for a routerLink that could not be resolved, which renders no anchor anyway). */
133
+ href: string | null;
134
+ }
103
135
  export interface ColumnDef {
104
136
  key: string;
105
137
  label: string;
@@ -133,6 +165,13 @@ export interface ColumnDef {
133
165
  * Return null to render a plain '—' instead of a badge (e.g. for an empty value).
134
166
  */
135
167
  badgeFn?: (value: any, item?: any) => BadgeConfig | null;
168
+ /**
169
+ * Puts a gold `<ui-star>` to the LEFT of the cell's text when it returns a non-empty string; that string is the star's tooltip
170
+ * (hover / keyboard focus) and accessible name. Return null / undefined / '' for no star. The star is always gold.
171
+ * Applies to plain text cells (and the title of the phone card); the row's accessible name is unchanged (`nameFn` / `render`).
172
+ * Example: `{ key: 'name', label: 'Name', starFn: (doc) => doc.isAuthForm ? 'Authorization Form' : null }`
173
+ */
174
+ starFn?: (item: any) => string | null | undefined;
136
175
  /** Date format string. Default: 'MMM d, y' */
137
176
  dateFormat?: string;
138
177
  /** Returns the display name for the row. Default: item[col.key] */
@@ -158,6 +197,15 @@ export interface ColumnDef {
158
197
  /** Key in item holding the tags array. Defaults to col.key.
159
198
  * Expected shape: { name: string; variant?: BadgeVariant }[] */
160
199
  tagsKey?: string;
200
+ /**
201
+ * `cellType: 'link'` — returns where the cell points for a row (`value` is `item[key]`), or null / undefined /
202
+ * '' for no link (the cell then shows plain text). A string is shorthand for `{ href }`. See {@link LinkTarget}
203
+ * for the contract and the allowed schemes. Text shown: `render(item)` if set, else `item[key]`, else the
204
+ * target's `label`, else '—'. The text is always escaped. A throwing `linkFn` renders plain text.
205
+ * Sorting, search, filters and export are independent of the link (export uses the host's `exportRow`).
206
+ * Clicking or keying the anchor never emits `rowClick`; it emits `(linkClick)`.
207
+ */
208
+ linkFn?: (item: any, value: any) => LinkTarget | string | null | undefined;
161
209
  /** Returns the image URL for the row. A falsy value (null / undefined / '') shows the placeholder icon. */
162
210
  imageFn?: (item: any) => string | null | undefined;
163
211
  /** Focal point (percent, 50/50 = centered) of the cropped image, applied as CSS object-position. */
@@ -906,7 +954,7 @@ export declare class SelectComponent implements ControlValueAccessor, Validator,
906
954
  readonly error: import("@angular/core").InputSignal<string>;
907
955
  /** Reveal the built-in error messages now (e.g. after a failed Save click), without waiting for blur. */
908
956
  readonly showErrors: import("@angular/core").InputSignal<boolean>;
909
- readonly size: import("@angular/core").InputSignal<"sm" | "default">;
957
+ readonly size: import("@angular/core").InputSignal<"default" | "sm">;
910
958
  readonly _disabledInput: import("@angular/core").InputSignal<boolean>;
911
959
  private readonly _cvaDisabled;
912
960
  protected readonly disabled: import("@angular/core").Signal<boolean>;
@@ -1123,6 +1171,15 @@ export declare class ImportExportComponent {
1123
1171
  }
1124
1172
  /** Simple English plural: "property" → "properties", "box" → "boxes", "contact" → "contacts". */
1125
1173
  export declare function pluralize(word: string): string;
1174
+ /** Resolved anchor data of a link cell (internal). */
1175
+ interface ResolvedLink {
1176
+ href: string;
1177
+ tree: UrlTree | null;
1178
+ blank: boolean;
1179
+ rel: string | null;
1180
+ download: boolean;
1181
+ ariaLabel: string | null;
1182
+ }
1126
1183
  export declare class DataTableComponent<T extends Record<string, any>> implements OnInit {
1127
1184
  readonly endpoint: import("@angular/core").InputSignal<string>;
1128
1185
  readonly columns: import("@angular/core").InputSignal<ColumnDef[]>;
@@ -1202,6 +1259,12 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1202
1259
  */
1203
1260
  readonly bulkUpdateOptions: import("@angular/core").InputSignal<BulkUpdateOption<T>[]>;
1204
1261
  readonly rowClick: import("@angular/core").OutputEmitterRef<T>;
1262
+ /**
1263
+ * A `cellType: 'link'` anchor was clicked (plain or with ctrl/cmd/shift; a middle-button click is an `auxclick`, not emitted). Fires instead of
1264
+ * `rowClick`: the anchor stops the click from reaching the row. For an in-app `routerLink` the table has already
1265
+ * called `Router.navigateByUrl` on a plain left click (modifier / `_blank` clicks are left to the browser).
1266
+ */
1267
+ readonly linkClick: import("@angular/core").OutputEmitterRef<LinkClickEvent<T>>;
1205
1268
  /**
1206
1269
  * Optional per-row opt-out for (rowClick): rows where it returns false get no pointer cursor,
1207
1270
  * no tab stop and no click/keyboard activation (e.g. log rows with no linked record).
@@ -1535,6 +1598,16 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1535
1598
  protected imageClass(col: ColumnDef, small: boolean): string;
1536
1599
  protected onImageError(url: string): void;
1537
1600
  protected phoneCell(value: any): string;
1601
+ private readonly _injector;
1602
+ /** Safe hrefs only: mailto:, tel:, http(s):// and root-relative paths. Everything else is refused (null). */
1603
+ private _safeHref;
1604
+ /** Text and (when there is a safe target) the anchor data of a `cellType: 'link'` cell. Never throws. */
1605
+ protected linkView(col: ColumnDef, item: T): {
1606
+ text: string;
1607
+ link: ResolvedLink | null;
1608
+ };
1609
+ /** Anchor click: never reaches the row; a plain left click on an in-app route navigates via the Router (SPA, no reload). */
1610
+ protected onLinkClick(e: MouseEvent, link: ResolvedLink, col: ColumnDef, item: T): void;
1538
1611
  protected getInitials(name: string): string;
1539
1612
  protected isCardOverdue(col: ColumnDef, item: T): boolean;
1540
1613
  /** Renders any non-badge column value as a plain string for the mobile card view. */
@@ -1688,6 +1761,7 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1688
1761
  };
1689
1762
  }, {
1690
1763
  "rowClick": "rowClick";
1764
+ "linkClick": "linkClick";
1691
1765
  "tableButtonClick": "tableButtonClick";
1692
1766
  "panelActionClick": "panelActionClick";
1693
1767
  "saveViewClick": "saveViewClick";
@@ -2130,7 +2204,7 @@ export declare class AudioPlayerComponent {
2130
2204
  /** Accessible name of the player. */
2131
2205
  readonly ariaLabel: import("@angular/core").InputSignal<string>;
2132
2206
  /** How much to load before play: 'metadata' (default), 'auto' or 'none'. */
2133
- readonly preload: import("@angular/core").InputSignal<"none" | "metadata" | "auto">;
2207
+ readonly preload: import("@angular/core").InputSignal<"none" | "auto" | "metadata">;
2134
2208
  /** Fires when the browser can't load or decode the source. */
2135
2209
  readonly playbackError: import("@angular/core").OutputEmitterRef<void>;
2136
2210
  protected readonly failed: import("@angular/core").WritableSignal<boolean>;
@@ -2238,6 +2312,33 @@ export declare class TooltipDirective {
2238
2312
  };
2239
2313
  }, {}, never, never, true, never>;
2240
2314
  }
2315
+ /**
2316
+ * A small gold star with a hover / focus tooltip: `<ui-star tooltip="Authorization Form" />`.
2317
+ *
2318
+ * The star is always gold (there is no color input). `tooltip` is the text the user sees on hover or keyboard focus (library `uiTooltip`);
2319
+ * it is also the star's accessible name, so a screen reader reads it too. Without a tooltip the star is decorative (hidden from assistive tech).
2320
+ * In `<ui-data-table>` use `ColumnDef.starFn` to put one to the left of a text cell (e.g. the name column).
2321
+ */
2322
+ export declare class StarComponent {
2323
+ /** Tooltip text (also the accessible name). Empty = no tooltip and the star is decorative. */
2324
+ readonly tooltip: import("@angular/core").InputSignal<string>;
2325
+ /** Star size in px. Default 16. */
2326
+ readonly size: import("@angular/core").InputSignal<number>;
2327
+ protected readonly hasTooltip: import("@angular/core").Signal<boolean>;
2328
+ static ɵfac: i0.ɵɵFactoryDeclaration<StarComponent, never>;
2329
+ static ɵcmp: i0.ɵɵComponentDeclaration<StarComponent, "ui-star", never, {
2330
+ "tooltip": {
2331
+ "alias": "tooltip";
2332
+ "required": false;
2333
+ "isSignal": true;
2334
+ };
2335
+ "size": {
2336
+ "alias": "size";
2337
+ "required": false;
2338
+ "isSignal": true;
2339
+ };
2340
+ }, {}, never, never, true, never>;
2341
+ }
2241
2342
  export type ImageRepositionShape = 'circle' | 'rounded';
2242
2343
  /**
2243
2344
  * Drag-to-center photo frame. The image fills the frame (`object-fit: cover`) and the user drags it, or uses the arrow
@@ -3011,7 +3112,7 @@ export declare class PhoneNumberPickerComponent {
3011
3112
  protected readonly modeCtl: FormControl<string>;
3012
3113
  protected readonly zipCtl: FormControl<string>;
3013
3114
  protected readonly areaCtl: FormControl<string>;
3014
- protected readonly step: import("@angular/core").WritableSignal<2 | 1 | 3>;
3115
+ protected readonly step: import("@angular/core").WritableSignal<1 | 2 | 3>;
3015
3116
  protected readonly mode: import("@angular/core").WritableSignal<PhoneNumberSearchMode>;
3016
3117
  private readonly _zip;
3017
3118
  private readonly _area;
@@ -3134,7 +3235,7 @@ export declare class ButtonComponent {
3134
3235
  */
3135
3236
  readonly href: import("@angular/core").InputSignal<string | null>;
3136
3237
  /** Where to open the `href` (`_blank` = a new tab). */
3137
- readonly target: import("@angular/core").InputSignal<"_blank" | "_self" | "_parent" | "_top" | null>;
3238
+ readonly target: import("@angular/core").InputSignal<"_self" | "_blank" | "_parent" | "_top" | null>;
3138
3239
  /** `rel` of the `href` link. A `_blank` target always gets `noopener noreferrer` added. */
3139
3240
  readonly rel: import("@angular/core").InputSignal<string | null>;
3140
3241
  /** The href when it is allowed (http, https, mailto, tel, sms or a relative URL), else null. */
@@ -3142,8 +3243,8 @@ export declare class ButtonComponent {
3142
3243
  protected readonly _rel: import("@angular/core").Signal<string | null>;
3143
3244
  readonly color: import("@angular/core").InputSignal<string | undefined>;
3144
3245
  readonly active: import("@angular/core").InputSignal<boolean>;
3145
- readonly size: import("@angular/core").InputSignal<"sm" | "md" | "lg" | "xs">;
3146
- readonly type: import("@angular/core").InputSignal<"button" | "submit" | "reset">;
3246
+ readonly size: import("@angular/core").InputSignal<"sm" | "md" | "xs" | "lg">;
3247
+ readonly type: import("@angular/core").InputSignal<"reset" | "submit" | "button">;
3147
3248
  readonly disabled: import("@angular/core").InputSignal<boolean>;
3148
3249
  readonly loading: import("@angular/core").InputSignal<boolean>;
3149
3250
  readonly fullWidth: import("@angular/core").InputSignal<boolean>;
@@ -6074,7 +6175,7 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
6074
6175
  * `search`, `text`, `none`). Empty (default) = the browser's default for the field, except `type="tel"`, which keeps `tel`.
6075
6176
  * It only picks the keyboard; it does not restrict what can be typed or pasted.
6076
6177
  */
6077
- readonly inputmode: import("@angular/core").InputSignal<"" | "none" | "text" | "email" | "tel" | "search" | "url" | "decimal" | "numeric">;
6178
+ readonly inputmode: import("@angular/core").InputSignal<"" | "none" | "text" | "search" | "email" | "tel" | "url" | "decimal" | "numeric">;
6078
6179
  readonly readonly: import("@angular/core").InputSignal<boolean>;
6079
6180
  readonly hint: import("@angular/core").InputSignal<string>;
6080
6181
  readonly error: import("@angular/core").InputSignal<string>;
@@ -6086,6 +6187,12 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
6086
6187
  readonly max: import("@angular/core").InputSignal<number | undefined>;
6087
6188
  /** `type="number"` only: most digits allowed after the decimal point. */
6088
6189
  readonly maxDecimals: import("@angular/core").InputSignal<number | undefined>;
6190
+ /**
6191
+ * `type="number"` only: how much the browser's spinner arrows and the Up / Down keys change the value (e.g. `0.01` for money).
6192
+ * Leave it unset for the browser's default step of 1. A value that is not a number above 0 is ignored (also the default step).
6193
+ * It only picks the increment; it does not restrict what can be typed (use `maxDecimals` for that).
6194
+ */
6195
+ readonly step: import("@angular/core").InputSignal<number | undefined>;
6089
6196
  /**
6090
6197
  * What the control value is. `'string'` (default) = the typed text. `'number'` = `number | null`; requires `type="number"`
6091
6198
  * (otherwise a dev-mode warning is logged once and string behaviour is used).
@@ -6104,6 +6211,8 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
6104
6211
  private readonly _reveal;
6105
6212
  /** True when the control value is a number: `valueType="number"` on a `type="number"` field. */
6106
6213
  private readonly _numeric;
6214
+ /** The native `step` attribute: only for `type="number"` and only for a finite number above 0; otherwise none (the browser's step of 1). */
6215
+ protected readonly _stepAttr: import("@angular/core").Signal<string | null>;
6107
6216
  private _warned;
6108
6217
  private readonly _formatting;
6109
6218
  private readonly _issue;
@@ -6213,6 +6322,11 @@ export declare class TextInputComponent implements ControlValueAccessor, Validat
6213
6322
  "required": false;
6214
6323
  "isSignal": true;
6215
6324
  };
6325
+ "step": {
6326
+ "alias": "step";
6327
+ "required": false;
6328
+ "isSignal": true;
6329
+ };
6216
6330
  "valueType": {
6217
6331
  "alias": "valueType";
6218
6332
  "required": false;
@@ -6251,6 +6365,8 @@ export declare class TextareaInputComponent implements ControlValueAccessor, Val
6251
6365
  */
6252
6366
  readonly hintAsDescription: import("@angular/core").InputSignal<boolean>;
6253
6367
  readonly error: import("@angular/core").InputSignal<string>;
6368
+ /** Opt-in. `true` shows the text in a monospace font (HTML / code / merge-tag fields). Default `false` = unchanged. */
6369
+ readonly monospace: import("@angular/core").InputSignal<boolean>;
6254
6370
  /** Reveal the built-in error messages now (e.g. after a failed Save click), without waiting for blur. */
6255
6371
  readonly showErrors: import("@angular/core").InputSignal<boolean>;
6256
6372
  protected readonly _disabledInput: import("@angular/core").InputSignal<boolean>;
@@ -6341,6 +6457,11 @@ export declare class TextareaInputComponent implements ControlValueAccessor, Val
6341
6457
  "required": false;
6342
6458
  "isSignal": true;
6343
6459
  };
6460
+ "monospace": {
6461
+ "alias": "monospace";
6462
+ "required": false;
6463
+ "isSignal": true;
6464
+ };
6344
6465
  "showErrors": {
6345
6466
  "alias": "showErrors";
6346
6467
  "required": false;
@@ -7211,7 +7332,7 @@ export declare class FormSectionComponent {
7211
7332
  }, {}, never, ["*"], true, never>;
7212
7333
  }
7213
7334
  export declare class FormRowComponent {
7214
- readonly cols: import("@angular/core").InputSignal<2 | 1 | 3>;
7335
+ readonly cols: import("@angular/core").InputSignal<1 | 2 | 3>;
7215
7336
  static ɵfac: i0.ɵɵFactoryDeclaration<FormRowComponent, never>;
7216
7337
  static ɵcmp: i0.ɵɵComponentDeclaration<FormRowComponent, "ui-form-row", never, {
7217
7338
  "cols": {
@@ -8405,7 +8526,8 @@ export declare class ErrorComponent {
8405
8526
  }, {}, never, never, true, never>;
8406
8527
  }
8407
8528
  export type KpiCardColor = 'green' | 'blue' | 'amber' | 'orange' | 'red' | 'slate';
8408
- export type KpiCardValueColor = 'default' | 'green' | 'blue';
8529
+ /** Colour of the big value: `default` (dark slate), `green`, `blue`, or `red` (for figures that need attention: overdue, outstanding, failed). */
8530
+ export type KpiCardValueColor = 'default' | 'green' | 'blue' | 'red';
8409
8531
  /**
8410
8532
  * Compact KPI summary card: optional icon, uppercase label, large value, optional subtitle.
8411
8533
  * Set `clickable` to show a hover state + chevron (bind `(click)` on the host). A clickable card is a keyboard button:
@@ -9162,7 +9284,7 @@ export declare class DetailCardComponent {
9162
9284
  /** Bold uppercase title. Empty = no title text. */
9163
9285
  readonly title: import("@angular/core").InputSignal<string>;
9164
9286
  /** 1-6 makes the title a real heading for screen readers (role=heading, aria-level); null keeps it plain text. */
9165
- readonly headingLevel: import("@angular/core").InputSignal<2 | 1 | 5 | 3 | 6 | 4 | null>;
9287
+ readonly headingLevel: import("@angular/core").InputSignal<1 | 2 | 3 | 4 | 5 | 6 | null>;
9166
9288
  /** Optional id for the title text; the card is then labelled by it. Without it the title is the card's aria-label. */
9167
9289
  readonly titleId: import("@angular/core").InputSignal<string>;
9168
9290
  /** Accessible name of the card when the header gives none (no header, or no title). A visible title wins; blank = no name. */