@forgeroute/ui 0.4.54 → 0.4.56

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.54",
3
+ "version": "0.4.56",
4
4
  "peerDependencies": {
5
5
  "@angular/common": ">=22.0.0",
6
6
  "@angular/core": ">=22.0.0",
@@ -1836,6 +1836,14 @@ export declare class DataTableComponent<T extends Record<string, any>> implement
1836
1836
  * zero rows, so they can change or clear it. Headers are plain labels otherwise.
1837
1837
  */
1838
1838
  protected readonly _showColumnControls: Signal<boolean>;
1839
+ /**
1840
+ * Keys of sortable columns whose cell is blank ('' or '—') on every loaded row — nothing to sort by, so the sort control is
1841
+ * hidden. Judged from the loaded page (the same text the cells show); an active sort on the column keeps its control so it
1842
+ * can be changed or cleared.
1843
+ */
1844
+ protected readonly _emptyColumnKeys: Signal<Set<string>>;
1845
+ /** A header sort control shows for a sortable column while there are rows and the column has a value somewhere. */
1846
+ protected _canShowSort(col: ColumnDef): boolean;
1839
1847
  protected readonly _hasPhoneControls: Signal<boolean>;
1840
1848
  /** Phone sort select → sets the sort directly (same state, page reset and load as the header's onSort). */
1841
1849
  protected onPhoneSort(value: string): void;
@@ -6729,6 +6737,11 @@ export type CardListIcon = 'call' | 'email' | 'sms' | 'task' | 'doc' | 'money' |
6729
6737
  * Other projected content (default slot) shows after the chips at the row end; `[card-stats]` and `[card-detail]` add a footer row
6730
6738
  * under the row (use the `.cli-stat`, `.cli-stat-label`, `.cli-stat-value`, `.cli-stat-divider` classes inside `[card-stats]`).
6731
6739
  *
6740
+ * Delete and a generic action (additive; unused = the row looks exactly as before). Row-end order: Edit, View, action, delete, then projected content.
6741
+ * - `[deletable]="true"` shows a red icon-only trash button (28px, 44px hit area on touch) -> `(delete)`; name it with `deleteLabel` (default "Delete"), `deleteTestId` sets its `data-testid`.
6742
+ * The page owns the confirmation (the component never deletes anything itself).
6743
+ * - `actionLabel="Download"` shows a grey chip button with that text -> `(action)`; `actionTestId`, `actionAriaLabel` as for the other buttons.
6744
+ *
6732
6745
  * Accessibility: the Edit / View buttons are real buttons; give them a longer name with `editAriaLabel` / `viewAriaLabel`
6733
6746
  * ("Edit task Call John") when the visible word alone is ambiguous in a list. The priority dot (`high`/`medium`/`low`) has a tooltip and an
6734
6747
  * accessible name (`High priority`, ...), is focusable so the tooltip works by keyboard; `blue`/`green` dots are decorative. The icon circle is decorative.
@@ -6757,10 +6770,22 @@ export declare class CardListItemComponent {
6757
6770
  /** Accessible names for the buttons when the visible word is not enough ("Edit task Call John"). */
6758
6771
  readonly editAriaLabel: import("@angular/core").InputSignal<string>;
6759
6772
  readonly viewAriaLabel: import("@angular/core").InputSignal<string>;
6773
+ /** Built-in icon-only trash button -> `delete`. The accessible name comes from `deleteLabel` ("Delete task Call John"). */
6774
+ readonly deletable: import("@angular/core").InputSignal<boolean>;
6775
+ readonly deleteLabel: import("@angular/core").InputSignal<string>;
6776
+ /** `data-testid` of the delete button (none when empty). */
6777
+ readonly deleteTestId: import("@angular/core").InputSignal<string>;
6778
+ /** Built-in secondary chip button (text = `actionLabel`, e.g. "Download") -> `action`; shown only while `actionLabel` is not empty. */
6779
+ readonly actionLabel: import("@angular/core").InputSignal<string>;
6780
+ readonly actionTestId: import("@angular/core").InputSignal<string>;
6781
+ readonly actionAriaLabel: import("@angular/core").InputSignal<string>;
6760
6782
  readonly editClick: import("@angular/core").OutputEmitterRef<void>;
6761
6783
  readonly viewClick: import("@angular/core").OutputEmitterRef<void>;
6784
+ readonly delete: import("@angular/core").OutputEmitterRef<void>;
6785
+ readonly action: import("@angular/core").OutputEmitterRef<void>;
6762
6786
  protected readonly calendarPath = "M5 4h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z M16 2v4 M8 2v4 M3 10h18";
6763
6787
  protected readonly editPath = "M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7 M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z";
6788
+ protected readonly trashPath = "M3 6h18 M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2 M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6 M10 11v6 M14 11v6";
6764
6789
  protected readonly iconPath: import("@angular/core").Signal<string | null>;
6765
6790
  protected readonly indicatorLabel: import("@angular/core").Signal<string>;
6766
6791
  static ɵfac: i0.ɵɵFactoryDeclaration<CardListItemComponent, never>;
@@ -6830,9 +6855,41 @@ export declare class CardListItemComponent {
6830
6855
  "required": false;
6831
6856
  "isSignal": true;
6832
6857
  };
6858
+ "deletable": {
6859
+ "alias": "deletable";
6860
+ "required": false;
6861
+ "isSignal": true;
6862
+ };
6863
+ "deleteLabel": {
6864
+ "alias": "deleteLabel";
6865
+ "required": false;
6866
+ "isSignal": true;
6867
+ };
6868
+ "deleteTestId": {
6869
+ "alias": "deleteTestId";
6870
+ "required": false;
6871
+ "isSignal": true;
6872
+ };
6873
+ "actionLabel": {
6874
+ "alias": "actionLabel";
6875
+ "required": false;
6876
+ "isSignal": true;
6877
+ };
6878
+ "actionTestId": {
6879
+ "alias": "actionTestId";
6880
+ "required": false;
6881
+ "isSignal": true;
6882
+ };
6883
+ "actionAriaLabel": {
6884
+ "alias": "actionAriaLabel";
6885
+ "required": false;
6886
+ "isSignal": true;
6887
+ };
6833
6888
  }, {
6834
6889
  "editClick": "editClick";
6835
6890
  "viewClick": "viewClick";
6891
+ "delete": "delete";
6892
+ "action": "action";
6836
6893
  }, never, ["[card-list-inline]", "[card-list-action]", "*", "[card-stats]", "[card-detail]"], true, never>;
6837
6894
  }
6838
6895
  /** A note as the notes API returns it. */
@@ -6870,8 +6927,22 @@ export declare class NotesComponent {
6870
6927
  /** What the notes belong to, as the API names it: `contact`, `transaction`, `property`, `task`, ... */
6871
6928
  readonly resourceType: import("@angular/core").InputSignal<string>;
6872
6929
  readonly resourceId: import("@angular/core").InputSignal<string>;
6930
+ /**
6931
+ * Resource-specific notes API (optional). When set (e.g. `/permits/42/notes`, relative to `FORGEROUTE_CONFIG.baseUrl`), the component uses it instead of
6932
+ * the generic `/notes/{resourceType}/{resourceId}` API and `resourceType` / `resourceId` are not needed:
6933
+ * - `GET {basePath}?page=<0-based>&size=5` -> `{ content, totalElements, ... }` (`page` or Spring's `number` is accepted for the page index)
6934
+ * - `POST {basePath}` `{ content }` -> the new note
6935
+ * - `PUT {basePath}/{noteId}` `{ content }` (only when `editable` is true)
6936
+ * - `DELETE {basePath}/{noteId}`
6937
+ * A note's text may come as `body` or `content`; a missing `updatedAt` counts as `createdAt` (no "edited" tag). Empty (default) = the generic API.
6938
+ */
6939
+ readonly basePath: import("@angular/core").InputSignal<string>;
6873
6940
  /** True: list only, no add box, edit or delete. */
6874
6941
  readonly readOnly: import("@angular/core").InputSignal<boolean>;
6942
+ /** False hides the edit icon (for APIs without an update endpoint). Default true. */
6943
+ readonly editable: import("@angular/core").InputSignal<boolean>;
6944
+ /** Longest note, in characters after trimming (default `NOTES_MAX_LENGTH`, 10000). Set it to the API's column size, e.g. 255 for customer notes. */
6945
+ readonly maxLength: import("@angular/core").InputSignal<number>;
6875
6946
  /** Total number of notes after every load / add / delete. */
6876
6947
  readonly totalChange: import("@angular/core").OutputEmitterRef<number>;
6877
6948
  private readonly http;
@@ -6879,8 +6950,6 @@ export declare class NotesComponent {
6879
6950
  private readonly destroyRef;
6880
6951
  private readonly apiBase;
6881
6952
  readonly pageSize = 5;
6882
- /** The character limit of a note (see `NOTES_MAX_LENGTH`). */
6883
- readonly maxLength = 10000;
6884
6953
  readonly notes: import("@angular/core").WritableSignal<NoteItem[]>;
6885
6954
  readonly loading: import("@angular/core").WritableSignal<boolean>;
6886
6955
  readonly saving: import("@angular/core").WritableSignal<boolean>;
@@ -6898,6 +6967,12 @@ export declare class NotesComponent {
6898
6967
  /** True when the trimmed text is not empty and within the limit. */
6899
6968
  protected validBody(text: string): boolean;
6900
6969
  private get listUrl();
6970
+ private noteUrl;
6971
+ private get ready();
6972
+ /** Request body for create / update: `{ content }` for a `basePath` API, `{ body }` for the generic one. */
6973
+ private payload;
6974
+ /** Accepts `body` or `content` as the text and a missing `updatedAt`. */
6975
+ private normalize;
6901
6976
  constructor();
6902
6977
  loadPage(p: number): void;
6903
6978
  addNote(): void;
@@ -6918,11 +6993,26 @@ export declare class NotesComponent {
6918
6993
  "required": false;
6919
6994
  "isSignal": true;
6920
6995
  };
6996
+ "basePath": {
6997
+ "alias": "basePath";
6998
+ "required": false;
6999
+ "isSignal": true;
7000
+ };
6921
7001
  "readOnly": {
6922
7002
  "alias": "readOnly";
6923
7003
  "required": false;
6924
7004
  "isSignal": true;
6925
7005
  };
7006
+ "editable": {
7007
+ "alias": "editable";
7008
+ "required": false;
7009
+ "isSignal": true;
7010
+ };
7011
+ "maxLength": {
7012
+ "alias": "maxLength";
7013
+ "required": false;
7014
+ "isSignal": true;
7015
+ };
6926
7016
  }, {
6927
7017
  "totalChange": "totalChange";
6928
7018
  }, never, never, true, never>;
@@ -10980,13 +11070,32 @@ export interface CalendarSlotClick {
10980
11070
  * In the week view each all-day cell and each day column has a visually hidden "+ Add" button that appears when focused; Enter/Space on it
10981
11071
  * creates (all-day, or the first visible hour). The initial `view` must be one of `views`.
10982
11072
  *
11073
+ * Loading: bind `[loading]`. On the FIRST load (nothing shown yet) the whole page is drawn as one skeleton outline (`data-testid="calendar-skeleton"`,
11074
+ * one `role="status"` region "Loading calendar", only decorative `ui-skeleton`s): period label, Previous / Today / Next, view tabs, `loadingActions`
11075
+ * (default 2) action placeholders, the filter label + one chip per filter plus All, and the weekday row + day grid of the current view. The real
11076
+ * toolbar / filters / grid are not in the DOM until loading ends, but `rangeChange` still fires on the first render. After the first load,
11077
+ * `[loading]` replaces only the grid with a skeleton so the toolbar and period navigation stay usable. No markup or config is needed in the host;
11078
+ * start with `loading` true to avoid a one-frame flash of the real toolbar before the outline.
11079
+ *
10983
11080
  * Content slots: `[calendar-actions]` (right of the view switcher), `[calendar-summary]` (centre of the toolbar on
10984
11081
  * desktop) and `[calendar-summary-mobile]` (shown instead of it on phones).
10985
11082
  */
10986
11083
  export declare class CalendarComponent {
10987
11084
  /** Everything to show in the visible range. Ids must be unique across the whole list. */
10988
11085
  readonly items: import("@angular/core").InputSignal<CalendarItem[]>;
11086
+ /**
11087
+ * The host is loading the visible range. On the FIRST load (nothing shown yet) the component draws an outline of the whole page (period label,
11088
+ * nav buttons, view tabs, action placeholders, filter chips, weekday row and day grid) built only from decorative `ui-skeleton`s in ONE status
11089
+ * region (`data-testid="calendar-skeleton"`, "Loading calendar"); the real toolbar / filters / grid are not in the DOM until loading ends.
11090
+ * `rangeChange` still fires meanwhile. Later reloads (after a first load finished, or while items are shown) replace only the grid with a
11091
+ * skeleton so the toolbar and period navigation stay usable. Hosts need no markup or config: they just bind `[loading]`.
11092
+ */
10989
11093
  readonly loading: import("@angular/core").InputSignal<boolean>;
11094
+ /**
11095
+ * Optional. How many skeleton button placeholders the first-load outline draws in the actions position (where `[calendar-actions]` content
11096
+ * goes). The calendar cannot count projected content, so this is a number: default 2; 0 draws none. Not used after the first load.
11097
+ */
11098
+ readonly loadingActions: import("@angular/core").InputSignalWithTransform<number, unknown>;
10990
11099
  /** Active view; two-way bindable. A value that is not in `views` is replaced by the first allowed view. */
10991
11100
  readonly view: import("@angular/core").ModelSignal<CalendarViewMode>;
10992
11101
  /** Views the switcher offers. One entry hides the switcher. */
@@ -11033,6 +11142,13 @@ export declare class CalendarComponent {
11033
11142
  protected readonly weekDayLabels: string[];
11034
11143
  protected readonly weekDayNames: string[];
11035
11144
  protected readonly skeletonCells: number[];
11145
+ protected readonly skeletonDays: number[];
11146
+ protected readonly skeletonRows: number[];
11147
+ /** `idle` until the first loading cycle starts, `first` while it runs, `loaded` once it has ended: from then on `loading` only skeletons the grid. */
11148
+ private readonly loadPhase;
11149
+ /** True while the first load runs and nothing is shown yet: the whole page is drawn as a skeleton outline. */
11150
+ protected readonly showOutline: import("@angular/core").Signal<boolean>;
11151
+ protected readonly loadingActionSlots: import("@angular/core").Signal<number[]>;
11036
11152
  protected readonly formatHour: typeof formatHour;
11037
11153
  /** The browser clock's today as `YYYY-MM-DD`; refreshed at local midnight, when the tab becomes visible again and when the user presses Today. */
11038
11154
  private readonly clockToday;
@@ -11133,6 +11249,11 @@ export declare class CalendarComponent {
11133
11249
  "required": false;
11134
11250
  "isSignal": true;
11135
11251
  };
11252
+ "loadingActions": {
11253
+ "alias": "loadingActions";
11254
+ "required": false;
11255
+ "isSignal": true;
11256
+ };
11136
11257
  "view": {
11137
11258
  "alias": "view";
11138
11259
  "required": false;