@guildofgleks/ui 21.3.2 → 21.4.1

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
@@ -5,7 +5,7 @@ an app that **consumes** the published `@guildofgleks/ui` npm package. It is not
5
5
  authoring the library — if you are working inside the `gleks_web_ui` monorepo itself, read
6
6
  `.github/instructions/*.md` instead.
7
7
 
8
- Everything below reflects the library's actual source as of **`21.3.2`**. `README.md` covers the
8
+ Everything below reflects the library's actual source as of **`21.4.0`**. `README.md` covers the
9
9
  same ground at a higher level — install, setup, theming, global configuration — and is accurate;
10
10
  this file goes further, into per-component input tables, and is the one to trust for exact names,
11
11
  types and defaults.
@@ -211,9 +211,10 @@ parent's config**, one level deep per key — it does not replace it.
211
211
  | `button` | `debounce` | `gog-button`. |
212
212
  | `inputfield` | `showSpinButtons` | `gog-inputfield`. |
213
213
  | `textarea` | `resize` | `gog-textarea`. |
214
+ | `paginator` | `showPageSizeSelect`, `pageSizeOptions` | `gog-paginator`, and through it `gog-table`'s built-in pagination.
214
215
  | `toast` | `position`, `duration` | `ToastService`. |
215
216
  | `theme` | `storageKey`, `defaultTheme`, `followSystem`, `lightTheme`, `darkTheme` | `ThemeService`. All off/neutral by default — see below. |
216
- | `labels` | every fixed string the library renders — see below | inputfield, textarea, select, multiselect, autocomplete, datepicker, calendar, paginator, `DialogService`, `ToastService`. |
217
+ | `labels` | every fixed string the library renders — see below | inputfield, textarea, select, multiselect, autocomplete, datepicker, calendar, paginator, table, `DialogService`, `ToastService`. |
217
218
 
218
219
  Anything visual does **not** belong here — override the `--gog-*` token instead.
219
220
 
@@ -240,6 +241,11 @@ provideGogConfig({
240
241
  previousPage: 'Предыдущая страница',
241
242
  nextPage: 'Следующая страница',
242
243
  openCalendar: 'Открыть календарь',
244
+ rowsPerPage: 'Строк на странице', // gog-paginator's size select
245
+ total: 'Всего', // gog-table's row-count label
246
+ tablePagination: 'Навигация по таблице',
247
+ selectRow: 'Выбрать строку',
248
+ selectAllRows: 'Выбрать все строки на странице',
243
249
  today: 'Сегодня',
244
250
  thisMonth: 'Текущий месяц',
245
251
  previousMonth: 'Предыдущий месяц',
@@ -717,16 +723,102 @@ family of date-math helpers (`addDays`, `addMonths`, `isSameDay`, `isWithinBound
717
723
  | `title` | `string` | `''` |
718
724
  | `ariaHidden` | `boolean` | `true` |
719
725
 
720
- `GogIconName` is a **closed set of exactly 20 names**: `check`, `close`, `chevron-up`,
721
- `chevron-down`, `chevron-left`, `chevron-right`, `calendar`, `clock`, `sort`, `sort-up`,
722
- `sort-down`, `success`, `error`, `warning`, `info`, `checkbox`, `checkbox-checked`, `eye`,
723
- `eye-off`, `copy`. There is no way to pass an arbitrary SVG/URL as `name` — use the `template`
724
- input (or project your own `<svg>`/icon component elsewhere) for anything outside this set.
726
+ The package ships **41** glyphs (`GogBuiltinIconName`), all from [Lucide](https://lucide.dev)
727
+ and inlined so the package keeps zero runtime dependencies:
728
+
729
+ | Group | Names |
730
+ | --- | --- |
731
+ | Chevrons & arrows | `chevron-up`, `chevron-down`, `chevron-left`, `chevron-right`, `arrow-left`, `arrow-right` |
732
+ | Confirm & dismiss | `check`, `close`, `checkbox`, `checkbox-checked` |
733
+ | Status | `success`, `error`, `warning`, `info` |
734
+ | Sorting | `sort`, `sort-up`, `sort-down`, `filter` |
735
+ | Actions | `search`, `plus`, `minus`, `trash`, `pencil`, `copy`, `download`, `upload`, `refresh`, `external-link` |
736
+ | Chrome | `menu`, `more-horizontal`, `more-vertical`, `settings` |
737
+ | Objects & state | `user`, `lock`, `mail`, `calendar`, `clock`, `eye`, `eye-off`, `star`, `star-filled` |
738
+
739
+ `star` / `star-filled` is the one outline/filled pair, for a rating or favourite **toggle** —
740
+ the same reason `checkbox` / `checkbox-checked` exists. The set is otherwise outline-only on
741
+ purpose; a blanket solid duplicate of every glyph would double the payload for a distinction
742
+ almost nothing needs. If you want a filled variant of something else, register it with
743
+ `provideGogIcons`.
744
+
745
+ `Object.keys(ICON_DEFS)` is the runtime list, if you need to enumerate them (an icon picker, a
746
+ gallery). Do not hand-copy the names into an array — that is what goes stale.
725
747
 
726
748
  ```html
727
749
  <gog-icon name="calendar" />
728
750
  ```
729
751
 
752
+ ##### Registering your own icons — `provideGogIcons(...)`
753
+
754
+ `name` is typed `GogIconName = GogBuiltinIconName | (string & {})`: the built-ins autocomplete,
755
+ and any name you register is accepted. **This is the supported way to use your own icon set** —
756
+ prefer it over the `template` input, which costs an `<ng-template>` at every use site and is for
757
+ one-offs.
758
+
759
+ ```ts
760
+ // app.config.ts
761
+ import { provideGogIcons } from '@guildofgleks/ui';
762
+
763
+ providers: [
764
+ provideGogIcons({
765
+ cart: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">…</svg>',
766
+ rocket: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">…</svg>',
767
+ }),
768
+ ];
769
+ ```
770
+
771
+ ```html
772
+ <gog-icon name="cart" />
773
+ <gog-tag iconName="cart">In basket</gog-tag>
774
+ <!-- works anywhere an icon *name* is taken -->
775
+ ```
776
+
777
+ - **Registered names win over built-ins of the same name** — that is how you replace the
778
+ library's checkmark or chevrons across every component at once, without touching any of them.
779
+ - **Nested `provideGogIcons(...)` layers onto the parent set** rather than replacing it, the same
780
+ as `provideGogConfig`: a lazy route can register only what it uses.
781
+ - **An unknown name renders nothing and warns in dev mode; it never throws.** An icon is
782
+ decoration — failing the render over a typo would be the worse outcome.
783
+ - **Write the SVG for inheritance:** a `viewBox`, `stroke="currentColor"` (or `fill`), and no
784
+ width/height — `gog-icon` drives size and stroke width from the `--gog-icon-*` tokens, so a
785
+ registered icon scales and colours like a built-in.
786
+ - **Security:** the markup is inserted with `bypassSecurityTrustHtml` (Angular's HTML sanitizer
787
+ strips SVG, so there is no alternative). That is fine for static icon markup you authored;
788
+ **never** build a registered icon string from user input or fetch it at runtime unsanitized.
789
+
790
+ #### `[gogButton]` — the link-flavoured button
791
+
792
+ `gog-button` renders its own `<button>`, so it can never *be* a link. `[gogButton]` inverts that:
793
+ the element stays yours and the directive only gives it the look.
794
+
795
+ ```html
796
+ <a gogButton routerLink="/pricing">See pricing</a>
797
+ <a gogButton variant="ghost" href="https://example.com" target="_blank" rel="noreferrer">Docs</a>
798
+ <button gogButton variant="outline" size="sm" type="submit">Save</button>
799
+ <a gogButton fullWidth routerLink="/checkout">Checkout</a>
800
+ ```
801
+
802
+ | Input | Type | Default |
803
+ | ----------- | ----------------------- | ------------------------------------ |
804
+ | `variant` | `GogVariant` | `'primary'` |
805
+ | `size` | `GogSize \| undefined` | `'md'`; via `GOG_CONFIG.control.size` |
806
+ | `fullWidth` | `boolean` (bare attr ok) | `false` |
807
+
808
+ Selector is `a[gogButton], button[gogButton]` — deliberately not a bare `[gogButton]`, because on
809
+ a `<div>` the result looks like a button and is invisible to the keyboard and to assistive tech.
810
+
811
+ **Which to reach for.** `gog-button` for a button that acts on the page: it owns `loading` (a
812
+ centred spinner it projects), `debounce` click throttling and the `gogClick` output, none of which
813
+ a bare element can provide. `[gogButton]` when the element must be a link, or when you need to
814
+ keep directives of your own on it — `routerLink`, `href`, `target`, `download`, `type="submit"`
815
+ and anything else keep working because they were never brokered through an input in the first
816
+ place. That is also why the library still has no `@angular/router` dependency.
817
+
818
+ Two things it deliberately does not do: no `disabled` on an `<a>` (there is no such thing — drop
819
+ the `href` or render a real `<button>`), and no loading state (the spinner is a projected child a
820
+ directive cannot add without taking over the element's content).
821
+
730
822
  #### `[gogBadge]` — directive, not a component
731
823
 
732
824
  Decorates an existing element (a button, an icon, an avatar) with a count/status dot — it never
@@ -960,13 +1052,36 @@ hidden via `[hidden]` while inactive — preserves scroll/input state).
960
1052
  | `siblingCount` | `number` | `2` — `'ellipsis'` mode only |
961
1053
  | `size` | `GogSize` | `'sm'` |
962
1054
  | `disabled`, `ariaLabel` | | `false`, `'Pagination'` |
1055
+ | `totalRecords` | `number \| null` | `null` — see below |
1056
+ | `pageSize` | `model<number>` | `10` — two-way bindable |
1057
+ | `showPageSizeSelect` | `boolean \| undefined` | `false`; via `GOG_CONFIG.paginator` |
1058
+ | `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; via `GOG_CONFIG.paginator` |
963
1059
 
964
1060
  The step buttons (`'Previous page'`/`'Next page'`) and the per-page names are configured, not
965
1061
  input-driven: `GOG_CONFIG.labels.previousPage`/`nextPage`, and `labels.page`, a
966
1062
  `(page: number, isCurrent: boolean) => string` formatter defaulting to
967
1063
  `` `Page ${page}, current page` `` / `` `Go to page ${page}` ``.
968
1064
 
969
- Model: `page: number` (1-based, self-clamps to `totalPages`).
1065
+ Models: `page: number` (1-based, self-clamps) and `pageSize: number`.
1066
+
1067
+ **Give it `totalRecords` instead of `totalPages` when you know the row count** — it then derives
1068
+ the page count from `pageSize` itself, which is what removes the
1069
+ `computed(() => Math.ceil(total / size))` a consumer would otherwise have to write *and* keep in
1070
+ sync with the rows-per-page select:
1071
+
1072
+ ```html
1073
+ <gog-paginator
1074
+ [(page)]="page"
1075
+ [(pageSize)]="size"
1076
+ [totalRecords]="items().length"
1077
+ [showPageSizeSelect]="true"
1078
+ />
1079
+ ```
1080
+
1081
+ `totalPages` still works and is the right input when the server tells you a page count directly;
1082
+ `totalRecords` wins when both are set. Changing the page size always returns to page 1 — "page 5"
1083
+ of 10-row pages is not "page 5" of 50-row ones, so clamping alone would leave the user somewhere
1084
+ they never asked to be.
970
1085
 
971
1086
  `'window'`: a fixed number of page buttons that slides to keep the current page centered.
972
1087
  `'ellipsis'`: first/last pinned, `siblingCount` around the current page, "…" fills the gap
@@ -982,7 +1097,9 @@ Model: `page: number` (1-based, self-clamps to `totalPages`).
982
1097
  | ----------------------------- | ----------------------------- | --------------------------------- |
983
1098
  | `value` | `T[]` | `[]` |
984
1099
  | `fullWidth` | `boolean` | `true` |
985
- | `pageSize` | `number` | `0` (no pagination) |
1100
+ | `pageSize` | `model<number>` | `0` (no pagination) — two-way |
1101
+ | `showPageSizeSelect` | `boolean \| undefined` | `false`; forwarded to the paginator |
1102
+ | `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; forwarded |
986
1103
  | `showRowNumbers`, `showTotal` | `boolean` | `true`, `false` |
987
1104
  | `emptyPlaceholder` | `string` | `'-'` |
988
1105
  | `paginatorPosition` | `'left'\|'center'\|'right'` | `'center'` |
@@ -991,6 +1108,18 @@ Model: `page: number` (1-based, self-clamps to `totalPages`).
991
1108
  | `showColumnBorders` | `boolean` | `false` |
992
1109
  | `stickyHeader` | `boolean` | `false` |
993
1110
  | `size` | `GogSize` | `'lg'` (row density — not `'md'`) |
1111
+ | `lazy` | `boolean` | `false` — see below |
1112
+ | `totalRecords` | `number \| null` | `null` — `lazy` only |
1113
+ | `selectionMode` | `GogTableSelectionMode` | `'none'` |
1114
+ | `selection` | `model<T[]>` | `[]` — two-way bindable |
1115
+ | `dataKey` | `string` | `''` — row identity field |
1116
+ | `showSelectionColumn` | `boolean` | `true` (once selection is on) |
1117
+ | `interactiveRows` | `boolean` | `false` |
1118
+
1119
+ Outputs: `gogSortChange: GogTableSortEvent` (`{ field, direction }`, `{ field: '', direction:
1120
+ null }` when the third click clears it), `gogPageChange: number` (1-based; **does not fire** on
1121
+ first render, nor for the page reset a new sort causes — that reset belongs to the sort),
1122
+ `gogRowClick: GogTableRowClickEvent<T>` (`{ row, index, originalEvent }`).
994
1123
 
995
1124
  Columns are declared as **projected `gog-column` children**, not an input array:
996
1125
 
@@ -1006,6 +1135,75 @@ Columns are declared as **projected `gog-column` children**, not an input array:
1006
1135
  </gog-table>
1007
1136
  ```
1008
1137
 
1138
+ ##### Server-driven tables — `lazy`
1139
+
1140
+ By default the table owns the whole data set: it sorts `value` and slices the page itself. With
1141
+ `[lazy]="true"` it does neither — `value` **is** the current page, already sorted, and the table
1142
+ renders it untouched. Supply `totalRecords` (without it the table cannot know how many pages
1143
+ exist, so pagination stays hidden and it warns in dev), then refetch from the two outputs:
1144
+
1145
+ ```html
1146
+ <gog-table
1147
+ [value]="page()"
1148
+ [lazy]="true"
1149
+ [totalRecords]="total()"
1150
+ [pageSize]="20"
1151
+ [loading]="loading()"
1152
+ dataKey="id"
1153
+ (gogSortChange)="sort.set($event); reload()"
1154
+ (gogPageChange)="pageNumber.set($event); reload()"
1155
+ >
1156
+ ```
1157
+
1158
+ Row numbers still count from the current page (`(page - 1) * pageSize + i + 1`), and `showTotal`
1159
+ reports `totalRecords` rather than `value.length`. **Do not** sort or slice `value` yourself in
1160
+ addition — that is what the flag turns off.
1161
+
1162
+ ##### Rows per page
1163
+
1164
+ `pageSize` is a **`model`**, not an input: `[pageSize]="20"` works exactly as before, and
1165
+ `[(pageSize)]="size"` becomes possible. That is what makes the rows-per-page select work with no
1166
+ wiring — the table binds its own model straight to the paginator's, the select writes back
1167
+ through it, and there is no intermediate signal to keep in sync in either direction.
1168
+
1169
+ ```html
1170
+ <!-- off by default; turn it on per table, or app-wide via GOG_CONFIG.paginator -->
1171
+ <gog-table [value]="rows" [(pageSize)]="size" [showPageSizeSelect]="true" [pageSizeOptions]="[5, 10, 20]">
1172
+ ```
1173
+
1174
+ Changing the size returns to page 1 and does **not** emit `gogPageChange` — the consumer already
1175
+ knows from `pageSizeChange`, and firing both would make a lazy table fetch twice. In `lazy` mode
1176
+ `pageSizeChange` is the refetch signal; bind `[pageSize]` + `(pageSizeChange)` rather than the
1177
+ banana-box if you need to act on it.
1178
+
1179
+ The footer stays visible at a single page whenever the select is on — hiding it would strand the
1180
+ user on whatever size produced that one page, with no control left to pick a smaller one.
1181
+
1182
+ ##### Selection
1183
+
1184
+ `selectionMode` turns it on; `[(selection)]` is always a `T[]`, including in `'single'` mode
1185
+ where it holds zero or one row — one shape rather than a union to narrow on every read.
1186
+
1187
+ ```html
1188
+ <gog-table [value]="rows" selectionMode="multiple" [(selection)]="selected" dataKey="id">
1189
+ ```
1190
+
1191
+ - **Set `dataKey`.** Without it rows are matched by object identity, so any refetch that produces
1192
+ new objects silently drops the selection. It is also the `@for` track key, which is what lets
1193
+ the DOM survive a refetch instead of being rebuilt.
1194
+ - The checkbox column renders automatically (`showSelectionColumn` to turn it off, e.g. for a
1195
+ table that selects by row click — pair that with `interactiveRows`).
1196
+ - The header select-all appears only in `'multiple'` mode and covers **the current page**, never
1197
+ the whole data set: in `lazy` mode the table has never seen the other pages, and a control that
1198
+ behaved differently between the two modes would be worse than either.
1199
+
1200
+ ##### Clickable rows
1201
+
1202
+ `gogRowClick` fires on a click regardless, but a `<tr>` is not focusable, so on its own that is a
1203
+ mouse-only affordance. `interactiveRows` makes rows focusable and styles them as clickable, and
1204
+ Enter/Space then activate the focused row. If the action is really "open this one thing", a link
1205
+ or button inside a cell is better than a whole-row target.
1206
+
1009
1207
  `gog-column` inputs: `field` (required, dot-paths ok), `header`, `sortable` (default `false`),
1010
1208
  `width`/`minWidth`/`maxWidth`, `comparator` (custom `(a, b) => number`, defaults to a
1011
1209
  locale-aware collator for strings). Slots inside a column: `<ng-template gogColumnBody let-row let-value="value" let-index="index">`,
@@ -1143,7 +1341,11 @@ Shared enum-like types (`import type { ... } from '@guildofgleks/ui'`):
1143
1341
  | `GogTextareaResize` | `'vertical' \| 'horizontal' \| 'both' \| 'none'` |
1144
1342
  | `GogInputType` | `'text' \| 'password' \| 'email' \| 'number' \| 'search' \| 'tel' \| 'url' \| 'date' \| 'time' \| 'datetime-local'` |
1145
1343
  | `GogInputMode` | `'none' \| 'text' \| 'decimal' \| 'numeric' \| 'tel' \| 'search' \| 'email' \| 'url'` |
1344
+ | `GogTableSelectionMode` | `'none' \| 'single' \| 'multiple'` |
1345
+ | `GogTableSortEvent` | `{ field: string; direction: SortDirection }` |
1346
+ | `GogTableRowClickEvent<T>` | `{ row: T; index: number; originalEvent: MouseEvent \| KeyboardEvent }` |
1146
1347
  | `GogErrorDisplay` | `'auto' \| 'manual'` |
1147
1348
  | `GogDropdownDirection` | `'auto' \| 'up' \| 'down'` |
1148
1349
  | `GogTooltipSide` | `'top' \| 'bottom' \| 'left' \| 'right'` (resolved form of `GogTooltipPosition`, no `'auto'`) |
1149
- | `GogIconName` | closed set of 20 — see [`gog-icon`](#gog-icon) |
1350
+ | `GogBuiltinIconName` | the 20 glyphs the package ships — see [`gog-icon`](#gog-icon) |
1351
+ | `GogIconName` | `GogBuiltinIconName \| (string & {})` — built-ins plus anything registered via `provideGogIcons` |