@guildofgleks/ui 21.12.0 → 21.13.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.
package/AGENTS.md CHANGED
@@ -36,7 +36,14 @@ defaults.
36
36
  - Theming is 100% CSS custom properties (`--gog-*`) — no Sass config, no JS theme objects, no
37
37
  build step to restyle anything.
38
38
  - Tree-shakeable: `"sideEffects": false` and every component is a separate standalone import, so
39
- importing `ButtonComponent` alone does not pull in the rest of the library.
39
+ importing `ButtonComponent` alone does not pull in the rest of the library — measured on the real
40
+ CLI, a button costs about 10 kB gzip over an empty app and all 31 components about 75 kB.
41
+ **It does not yet code-split**: a component used only behind a lazy route still ships in the
42
+ initial bundle, because the root is one module. `docs/entry-points.md` in the repository is the
43
+ plan that fixes it.
44
+ - **Import from `@guildofgleks/ui`.** `@guildofgleks/ui/shared` also resolves — it is the
45
+ package's internal entry point, which the root and its other entry points share so that
46
+ `GOG_CONFIG` exists once. It is not an API to build an app on.
40
47
  - SSR-safe: anything touching `window`/`document` is guarded with `isPlatformBrowser`/
41
48
  `afterNextRender`.
42
49
 
@@ -301,7 +308,7 @@ parent's config**, one level deep per key — it does not replace it.
301
308
  | Key | Fields | Applies to |
302
309
  | -------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
303
310
  | `control` | `size`, `errorDisplay`, `clearable` | `size`: button, `[gogButton]`, button-toggle-group, checkbox, toggle, radio-group, inputfield, textarea, select, multiselect, autocomplete, datepicker. `errorDisplay`: inputfield, textarea, select, multiselect, autocomplete, datepicker, radio-group, slider. `clearable`: inputfield, textarea, select, multiselect, autocomplete, datepicker. Not table/accordion/paginator (density, not form size), not spinner/skeleton/tag/chip. |
304
- | `dropdown` | `appendToBody`, `direction`, `filter`, `filterPosition` | `gog-select`, `gog-multiselect`. `gog-datepicker`/`gog-autocomplete` honour `appendToBody`/`direction` too (autocomplete has no `filter` box — it filters via the trigger's own text). |
311
+ | `dropdown` | `appendToBody`, `direction`, `filter`, `filterPosition`, `virtualize` | `gog-select`, `gog-multiselect`. `gog-datepicker`/`gog-autocomplete` honour `appendToBody`/`direction` too (autocomplete has no `filter` box — it filters via the trigger's own text). `virtualize` reaches all three dropdowns; not `gog-table`. |
305
312
  | `floatLabel` | `variant`, `showPlaceholder` | inputfield, textarea, select, multiselect, autocomplete, datepicker. |
306
313
  | `datepicker` | `locale`, `firstDayOfWeek`, `format` | `gog-datepicker`, `gog-calendar`. |
307
314
  | `autocomplete` | `searchDebounce`, `minLength`, `openOnFocus` | `gog-autocomplete`. |
@@ -338,6 +345,7 @@ provideGogConfig({
338
345
  hidePassword: 'Passwort verbergen',
339
346
  closeDialog: 'Schließen',
340
347
  closeToast: 'Schließen',
348
+ closeAlert: 'Meldung schließen', // gog-alert's dismiss button
341
349
  pagination: 'Seitennavigation',
342
350
  previousPage: 'Vorherige Seite',
343
351
  nextPage: 'Nächste Seite',
@@ -426,6 +434,12 @@ place instead of stacking a duplicate.
426
434
 
427
435
  ### `DialogService`
428
436
 
437
+ **Import from `@guildofgleks/ui/dialog`** — `DialogService`, `DIALOG_DATA`, `DIALOG_REF`, `DialogRef`, `DialogConfig` and the dialog components. The root still exports them in 21.13.0 and
438
+ stops in **21.14.0**, which is when the code moves into that entry point and a lazy route that
439
+ uses them stops carrying them in the initial bundle. **Your editor will not strike the root
440
+ import through**: ng-packagr's bundled types drop the deprecation tags on re-exports, measured. The
441
+ notice is this line, `CHANGELOG.md` and `GOG_DEPRECATIONS`.
442
+
429
443
  Root-provided singleton, imperative dynamic-component dialogs. Requires a `<gog-dialog />`
430
444
  placed once in your app (see [gog-dialog](#gog-dialog) below — also **not** automatic).
431
445
 
@@ -477,7 +491,7 @@ itself via `DIALOG_REF`:
477
491
 
478
492
  ```ts
479
493
  import { Component, inject } from '@angular/core';
480
- import { DIALOG_DATA, DIALOG_REF } from '@guildofgleks/ui';
494
+ import { DIALOG_DATA, DIALOG_REF } from '@guildofgleks/ui/dialog';
481
495
 
482
496
  @Component({ selector: 'app-edit-dialog', template: `…` })
483
497
  export class EditDialogComponent {
@@ -494,7 +508,11 @@ export class EditDialogComponent {
494
508
 
495
509
  ## Component reference
496
510
 
497
- Every component below is exported from `@guildofgleks/ui`'s root — `import { X } from '@guildofgleks/ui'`.
511
+ Every component below is exported from `@guildofgleks/ui`'s root — `import { X } from '@guildofgleks/ui'` —
512
+ **except `gog-table`, `gog-datepicker`/`gog-calendar` and `gog-dialog` with `DialogService`**, which
513
+ have their own entry points: `@guildofgleks/ui/table`, `/datepicker` and `/dialog`. They are the
514
+ three components heavy enough to be worth keeping out of an app's initial bundle, and splitting
515
+ them is how that becomes possible (21.14.0; see each section).
498
516
  "CVA" = implements `ControlValueAccessor` (works with `[formControl]`/`formControlName`).
499
517
 
500
518
  ### Buttons & choices
@@ -741,13 +759,47 @@ and multiselect unless noted otherwise):
741
759
  | `inputId` (select/autocomplete only) | `string` | `''` | |
742
760
  | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
743
761
 
744
- `gog-select`-specific: `value: model<TValue>(null)`.
762
+ `gog-select`-specific: `value: model<TValue>(null)`, and `virtualize: boolean | undefined` (default `false`, via `GOG_CONFIG.dropdown.virtualize`) — see below.
763
+ `virtualize: boolean | undefined` (default `false`, via `GOG_CONFIG.dropdown.virtualize`) is on
764
+ `gog-select`, `gog-multiselect` and `gog-autocomplete` alike.
765
+
745
766
  `gog-multiselect`-specific additions: `value: model<TValue[]>([])`, `showControls: boolean` (default `false`, a select-all/clear row), `controlsPosition: 'top'|'bottom'` (default `'top'`), and `selectAllLabel`/`clearAllLabel` for that row's two buttons (`'Select all'`/`'Clear'`, also via `GOG_CONFIG.labels`).
746
767
 
747
768
  CVA: yes, both. Slots (shared): `<ng-template gogDropdownChevron>` (custom chevron markup),
748
769
  `<ng-template gogDropdownOption let-opt let-selected="selected" let-label="label">` (custom
749
770
  option row). Multiselect adds `<ng-template gogMultiselectClearIcon>`.
750
771
 
772
+ **`virtualize` for a list in the thousands, and only when you mean it.** Unwindowed, 10 000
773
+ options build 10 000 DOM rows to show about six: measured in Chrome that is 512ms before the panel
774
+ appears, against 21ms windowed, on the same data. Windowed, the DOM holds roughly twenty rows
775
+ whatever the count is, and the scrollbar is identical because spacers stand in for the rows that
776
+ are not there.
777
+
778
+ It is **off by default and never switched on at a row-count threshold**, which is the same call
779
+ `GOG_CONFIG.ripple.enabled` makes: a windowed list behaves differently in ways nothing about the
780
+ data predicts, so flipping it when enough rows happen to arrive works in development and surprises
781
+ in production. Set it per field, or app-wide with `GOG_CONFIG.dropdown.virtualize`.
782
+
783
+ What changes while it is on:
784
+
785
+ - **`Ctrl+F` finds only the rendered rows**, and CSS targeting `:last-child` matches the last
786
+ _rendered_ row. Those are the two that catch people out.
787
+ - **The announced count stays honest.** `aria-setsize` and `aria-posinset` carry the real list and
788
+ the real position, so a screen reader is told "10 000 items", not "20".
789
+ - **Scrolling a keyboard-focused row out of view hands focus back to the trigger**, because the
790
+ row it was on no longer exists. Escape and ArrowDown both work from there. An unwindowed list
791
+ never has to do this.
792
+ - Arrow keys move through the whole list rather than the rendered part, so ArrowUp from the
793
+ trigger still reaches option 10 000.
794
+
795
+ All three dropdowns take it — `gog-select`, `gog-multiselect` and `gog-autocomplete`. **`gog-table`
796
+ does not**, and `[lazy]` is not a substitute: that keeps the _fetch_ small and still stamps every
797
+ row it is handed. The same distinction applies to `gog-autocomplete`'s `gogLoadMore`, and the two
798
+ compose — pair them on a long list, because neither implies the other.
799
+
800
+ On `gog-autocomplete` the focus caveat above does not apply: it is a combobox, so focus never
801
+ leaves the text field and the highlight is carried by `aria-activedescendant` either way.
802
+
751
803
  **Turn `filter` on past about seven options — or order them instead.** Choice time grows with the
752
804
  log of the count (`T = b · log₂(n + 1)`), so beyond roughly seven a panel stops being scanned and
753
805
  starts being read. The escape is not always the filter box: the law governs _unordered_ choices,
@@ -908,6 +960,13 @@ control or cut pointer events over the track, which would take the still-enabled
908
960
 
909
961
  #### `gog-datepicker` / `gog-calendar`
910
962
 
963
+ **Import from `@guildofgleks/ui/datepicker`** — `DatepickerComponent`, `CalendarComponent`, `GogCalendarDay` and `GogDatepickerValue`; the date
964
+ helpers (`formatDate`, `parseDate`, …) and `GogDateRange` stay in the root. The root still exports them in 21.13.0 and
965
+ stops in **21.14.0**, which is when the code moves into that entry point and a lazy route that
966
+ uses them stops carrying them in the initial bundle. **Your editor will not strike the root
967
+ import through**: ng-packagr's bundled types drop the deprecation tags on re-exports, measured. The
968
+ notice is this line, `CHANGELOG.md` and `GOG_DEPRECATIONS`.
969
+
911
970
  `gog-datepicker` is a field + panel; `gog-calendar` is the month grid alone (what `inline` mode
912
971
  renders). Native `Date` only — no date library, no adapter.
913
972
 
@@ -1143,6 +1202,55 @@ cancel out. A `disabled` chip keeps the ring but drops `aria-pressed`, which nee
1143
1202
  `role="button"` a disabled chip does not carry — "selected, and currently unavailable" is a real
1144
1203
  state and hiding it would leave it announced and invisible.
1145
1204
 
1205
+ #### `gog-alert`
1206
+
1207
+ A **persistent, in-flow message** — the one `gog-toast` cannot be. No timer, no queue, no overlay,
1208
+ no service: it renders where you write it and stays until your app removes it.
1209
+
1210
+ | Input | Type | Default |
1211
+ | ------------- | ---------------------------------- | --------------- |
1212
+ | `severity` | `GogSeverity` | `'accent'` |
1213
+ | `heading` | `string \| undefined` | `undefined` |
1214
+ | `dismissible` | `boolean` | `false` |
1215
+ | `iconName` | `GogIconName \| null \| undefined` | `undefined` |
1216
+ | `live` | `GogAlertLive \| undefined` | _from severity_ |
1217
+
1218
+ | Output | Type | When |
1219
+ | ----------- | ------ | -------------------------------- |
1220
+ | `dismissed` | `void` | the close button was **pressed** |
1221
+
1222
+ Slot: `<ng-template gogAlertIcon>` for custom icon markup. Body is projected content.
1223
+
1224
+ ```html
1225
+ <gog-alert severity="danger" heading="Payment failed" [dismissible]="true" (dismissed)="hide()">
1226
+ The card issuer declined the charge. No money has left your account.
1227
+ </gog-alert>
1228
+ ```
1229
+
1230
+ **`dismissed` means pressed, not removed.** The alert stays in the DOM and your app decides what
1231
+ happens — hide it, retry, navigate. A component that deleted itself would take the focused element
1232
+ with it and drop a keyboard reader back onto `<body>`.
1233
+
1234
+ The severity picks both the edge colour and the glyph (`success`/`error`/`warning`/`info`;
1235
+ `'accent'` borrows `info`'s, because it claims nothing). `iconName` overrides the glyph and
1236
+ **`[iconName]="null"` removes it** for a message whose words already carry the meaning.
1237
+
1238
+ **How it announces.** `live` is `'assertive' | 'polite' | 'off'`, defaulting from the severity —
1239
+ `danger` and `warning` interrupt, the rest wait. **Set `'off'` for a message that is already on the
1240
+ page when it loads**: that is the commonest case and the one the default gets wrong, because a
1241
+ reader arriving at a page does not need it interrupted about something that was already there.
1242
+
1243
+ The component cannot tell those apart for you, and that was measured rather than assumed:
1244
+ `@angular/core` exposes no stability member a component can read synchronously at construction,
1245
+ and `afterNextRender` reports its _own_ first render, which every alert has whenever it mounts.
1246
+
1247
+ The announcement lives in a **separate visually-hidden region**, empty until one render after the
1248
+ alert mounts, which is the only way it works: a live region filled in the same pass as its own
1249
+ creation announces nothing — the trap `gog-toast-container`'s permanently-mounted regions exist to
1250
+ avoid. Do not "simplify" this by putting `aria-live` on the alert itself.
1251
+
1252
+ `GOG_CONFIG.labels.closeAlert` names the dismiss button.
1253
+
1146
1254
  #### `gog-tag`
1147
1255
 
1148
1256
  | Input | Type | Default |
@@ -1563,29 +1671,37 @@ they never asked to be.
1563
1671
 
1564
1672
  #### `gog-table<T>`
1565
1673
 
1566
- | Input | Type | Default |
1567
- | ----------------------------- | ----------------------------- | ----------------------------------- |
1568
- | `value` | `T[]` | `[]` |
1569
- | `fullWidth` | `boolean` | `true` |
1570
- | `pageSize` | `model<number>` | `0` (no pagination) two-way |
1571
- | `showPageSizeSelect` | `boolean \| undefined` | `false`; forwarded to the paginator |
1572
- | `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; forwarded |
1573
- | `showRowNumbers`, `showTotal` | `boolean` | `true`, `false` |
1574
- | `emptyPlaceholder` | `string` | `'-'` |
1575
- | `paginatorPosition` | `'left'\|'center'\|'right'` | `'center'` |
1576
- | `totalPosition` | `'left'\|'right'\|'opposite'` | `'opposite'` |
1577
- | `loading` | `boolean` | `false` |
1578
- | `showColumnBorders` | `boolean` | `false` |
1579
- | `stickyHeader` | `boolean` | `false` pair with `maxHeight` |
1580
- | `maxHeight` | `string \| null` | `null` — any CSS length |
1581
- | `size` | `GogSize` | `'lg'` (row density — not `'md'`) |
1582
- | `lazy` | `boolean` | `false` — see below |
1583
- | `totalRecords` | `number \| null` | `null` — `lazy` only |
1584
- | `selectionMode` | `GogTableSelectionMode` | `'none'` |
1585
- | `selection` | `model<T[]>` | `[]` — two-way bindable |
1586
- | `dataKey` | `string` | `''` — row identity field |
1587
- | `showSelectionColumn` | `boolean` | `true` (once selection is on) |
1588
- | `interactiveRows` | `boolean` | `false` |
1674
+ **Import from `@guildofgleks/ui/table`** — `TableComponent`, `GogColumn` and its template directives, `defaultCompare`, and the table's
1675
+ event and context types. The root still exports them in 21.13.0 and
1676
+ stops in **21.14.0**, which is when the code moves into that entry point and a lazy route that
1677
+ uses them stops carrying them in the initial bundle. **Your editor will not strike the root
1678
+ import through**: ng-packagr's bundled types drop the deprecation tags on re-exports, measured. The
1679
+ notice is this line, `CHANGELOG.md` and `GOG_DEPRECATIONS`.
1680
+
1681
+ | Input | Type | Default |
1682
+ | ----------------------------- | ----------------------------- | ----------------------------------------- |
1683
+ | `value` | `T[]` | `[]` |
1684
+ | `fullWidth` | `boolean` | `true` |
1685
+ | `pageSize` | `model<number>` | `0` (no pagination) — two-way |
1686
+ | `showPageSizeSelect` | `boolean \| undefined` | `false`; forwarded to the paginator |
1687
+ | `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; forwarded |
1688
+ | `showRowNumbers`, `showTotal` | `boolean` | `true`, `false` |
1689
+ | `emptyPlaceholder` | `string` | `'-'` |
1690
+ | `paginatorPosition` | `'left'\|'center'\|'right'` | `'center'` |
1691
+ | `totalPosition` | `'left'\|'right'\|'opposite'` | `'opposite'` |
1692
+ | `loading` | `boolean` | `false` |
1693
+ | `showColumnBorders` | `boolean` | `false` |
1694
+ | `stickyHeader` | `boolean` | `false` — pair with `maxHeight` |
1695
+ | `maxHeight` | `string \| null` | `null` any CSS length |
1696
+ | `size` | `GogSize` | `'lg'` (row density — not `'md'`) |
1697
+ | `lazy` | `boolean` | `false` — see below |
1698
+ | `totalRecords` | `number \| null` | `null` — `lazy` only |
1699
+ | `selectionMode` | `GogTableSelectionMode` | `'none'` |
1700
+ | `selection` | `model<T[]>` | `[]` — two-way bindable |
1701
+ | `dataKey` | `string` | `''` — row identity field |
1702
+ | `showSelectionColumn` | `boolean` | `true` (once selection is on) |
1703
+ | `interactiveRows` | `boolean` | `false` |
1704
+ | `virtualize` | `boolean` | `false` — needs `maxHeight` + `fullWidth` |
1589
1705
 
1590
1706
  Outputs: `gogSortChange: GogTableSortEvent` (`{ field, direction }`, `{ field: '', direction:
1591
1707
  null }` when the third click clears it), `gogPageChange: number` (1-based; **does not fire** on
@@ -1599,6 +1715,41 @@ total evenly. Before 21.6.0 that split clipped the widest header, and a `width`
1599
1715
  the workaround — under auto layout a stated `width` is a suggestion weighed against content
1600
1716
  rather than a hard split, so those can usually go.
1601
1717
 
1718
+ **`virtualize` renders only the rows in view**, for a table long enough that stamping every row is
1719
+ the cost. It **requires `maxHeight` and `fullWidth`**, does nothing without either, and says which
1720
+ is missing in a dev-mode warning rather than half-working:
1721
+
1722
+ - Without `maxHeight` the table never scrolls vertically on its own, so there is no viewport to
1723
+ window against — the same constraint `stickyHeader` has, below.
1724
+ - `fullWidth="false"` means `table-layout: auto`, and the browser then sizes columns from the rows
1725
+ that are **rendered**. Measured: rendering 2 of 24 rows moved columns by up to 7.8px, so a
1726
+ windowed table would shift its own columns as you scroll.
1727
+
1728
+ Unlike the three dropdowns' `virtualize`, this one deals in rows whose heights genuinely differ —
1729
+ **a table row's height cannot be pinned**, because `height` on a `<tr>` or `<td>` is a _minimum_ in
1730
+ table layout, so a cell whose content wraps makes its row taller and no CSS stops it. The window
1731
+ measures each row as it renders and corrects itself, which means the scroll height is an estimate
1732
+ that sharpens as you scroll rather than an exact figure from the start.
1733
+
1734
+ What else changes while it is on: `Ctrl+F` finds only the rendered rows, CSS targeting
1735
+ `:last-child` matches the last rendered one, and with `interactiveRows` scrolling a focused row out
1736
+ of view moves focus to the scroll region, because the row it was on no longer exists.
1737
+ `aria-rowcount` and `aria-rowindex` keep the announced size and position honest, and every index
1738
+ the table hands out — `gogRowClick`'s `index`, the `showRowNumbers` column, and
1739
+ `GogColumnBodyContext.index` — still counts from the top of the page rather than the top of the
1740
+ window. There is a ceiling: Chrome clamps an element at 33 554 426px, about **745 000 rows** at 45px, and a dev-mode warning says so once when a table passes it — past that the rows stay correct while the scrollbar stops reaching the end of the data.
1741
+
1742
+ **Where `gog-table` stops.** No **column resizing or reordering** by the reader — a column's
1743
+ `width`/`minWidth`/`maxWidth` are set by whoever writes the template, not dragged by whoever reads
1744
+ it. No **frozen columns**, no **expandable rows**, no **row grouping**. `[lazy]="true"` keeps the
1745
+ _fetch_ small, which is usually the half that hurts; `virtualize` above is the DOM half, and
1746
+ neither substitutes for the other. If a request needs one of the rest, it needs a data grid, and
1747
+ this is not one — say so rather than reaching for `::ng-deep`.
1748
+
1749
+ `stickyHeader` is not any of them in disguise: it pins the header while rows scroll under it,
1750
+ which is the vertical axis. Freezing a first column against horizontal scroll is the thing that
1751
+ does not exist.
1752
+
1602
1753
  **`stickyHeader` needs `maxHeight`** (both since 21.6.0 for the pairing). A sticky element
1603
1754
  resolves against its nearest scroll container, and the table wraps itself in a `gog-scroll`;
1604
1755
  once that scroller moves on either axis it is a scroll container on _both_, because CSS coerces