@guildofgleks/ui 21.6.0 → 21.6.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.5.1`**. 21.5.0 removed a batch
8
+ Everything below reflects the library's actual source as of **`21.6.1`**. 21.5.0 removed a batch
9
9
  of deprecated API — see **Removed in 21.5.0** near the end of this file, which exists so code
10
10
  written against 21.4.x can be migrated — and `CHANGELOG.md` has the rest. `README.md` covers the
11
11
  same ground at a higher level — install, setup, theming, global configuration — and is accurate;
@@ -235,6 +235,7 @@ parent's config**, one level deep per key — it does not replace it.
235
235
  | `tooltip` | `position`, `showDelay`, `hideDelay` | the `gogTooltip` directive. |
236
236
  | `scroll` | `autoHide`, `hideDelay`, `size`, `overscrollBehavior`, `showTrack` | `gog-scroll` (and every component that uses one internally). |
237
237
  | `button` | `debounce` | `gog-button`. |
238
+ | `ripple` | `enabled` | the press ripple on `gog-button`, `[gogButton]`, `gog-button-toggle-group`, `gog-chip`, `gog-tabs`, `gog-accordion`, `gogCollapsibleTrigger`, `gogMenuItem` and the `gog-select`/`gog-multiselect`/`gog-autocomplete` options. **Off by default.** Each of those takes a `ripple` input that wins over it. Not the `gogRipple` directive — writing that attribute is already the per-element decision. |
238
239
  | `inputfield` | `showSpinButtons` | `gog-inputfield`. |
239
240
  | `textarea` | `resize` | `gog-textarea`. |
240
241
  | `paginator` | `showPageSizeSelect`, `pageSizeOptions` | `gog-paginator`, and through it `gog-table`'s built-in pagination. |
@@ -267,6 +268,7 @@ provideGogConfig({
267
268
  previousPage: 'Предыдущая страница',
268
269
  nextPage: 'Следующая страница',
269
270
  openCalendar: 'Открыть календарь',
271
+ togglePanel: 'Свернуть раздел', // gog-panel's toggle, only when it has no heading
270
272
  rowsPerPage: 'Строк на странице', // gog-paginator's size select
271
273
  total: 'Всего', // gog-table's row-count label
272
274
  tablePagination: 'Навигация по таблице',
@@ -415,6 +417,7 @@ Every component below is exported from `@guildofgleks/ui`'s root — `import { X
415
417
  | `loading` | `boolean` | `false` | shows an inline `gog-spinner`, blocks clicks |
416
418
  | `debounce` | `number \| undefined` | `300` | ms; via `GOG_CONFIG.button.debounce` — see note below |
417
419
  | `ariaLabel` | `string \| null` | `null` | **use this, not a raw `aria-label` attribute** |
420
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
418
421
 
419
422
  Outputs: `gogClick: MouseEvent`.
420
423
 
@@ -432,18 +435,19 @@ fires immediately (leading edge); further clicks within `debounce` ms are silent
432
435
 
433
436
  A row of buttons, single- or multi-select, built from your own option objects.
434
437
 
435
- | Input | Type | Default | Notes |
436
- | ------------------------------------ | ------------------------------------------ | ---------------------- | ------------------------------------- |
437
- | `options` | `TOption[]` | `[]` | |
438
- | `optionLabel` | accessor | `'name'` | |
439
- | `optionValue` | accessor \| `null` | `'id'` | `null` emits the option object |
440
- | `optionDisabled` | accessor | `'disabled'` | |
441
- | `optionIcon` | accessor → `GogIconName \| null` \| `null` | `null` | optional leading icon per option |
442
- | `multiple` | `boolean` | `false` | changes ARIA role entirely — see note |
443
- | `appearance` | `'joined' \| 'separated'` | `'joined'` | |
444
- | `orientation` | `GogOrientation` | `'horizontal'` | |
445
- | `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
446
- | `disabled`, `fullWidth`, `ariaLabel` | | `false`, `false`, `''` | |
438
+ | Input | Type | Default | Notes |
439
+ | ------------------------------------ | ------------------------------------------ | ---------------------- | --------------------------------------------- |
440
+ | `options` | `TOption[]` | `[]` | |
441
+ | `optionLabel` | accessor | `'name'` | |
442
+ | `optionValue` | accessor \| `null` | `'id'` | `null` emits the option object |
443
+ | `optionDisabled` | accessor | `'disabled'` | |
444
+ | `optionIcon` | accessor → `GogIconName \| null` \| `null` | `null` | optional leading icon per option |
445
+ | `multiple` | `boolean` | `false` | changes ARIA role entirely — see note |
446
+ | `appearance` | `'joined' \| 'separated'` | `'joined'` | |
447
+ | `orientation` | `GogOrientation` | `'horizontal'` | |
448
+ | `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
449
+ | `disabled`, `fullWidth`, `ariaLabel` | | `false`, `false`, `''` | |
450
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
447
451
 
448
452
  Model: `value: TValue | TValue[] | null` (single value, or array in `multiple` mode). CVA: yes.
449
453
  Slot: `<ng-template gogButtonToggleOption let-opt let-selected="selected">` for custom button
@@ -552,6 +556,7 @@ and multiselect unless noted otherwise):
552
556
  | `disabled`, `fullWidth` | | `false`, `true` | |
553
557
  | `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
554
558
  | `inputId` (select/autocomplete only) | `string` | `''` | |
559
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
555
560
 
556
561
  `gog-select`-specific: `value: model<TValue>(null)`.
557
562
  `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`).
@@ -595,6 +600,7 @@ panel-filter box; it filters/searches off what's typed in the field itself.
595
600
  | `loading` | `boolean` | `false` | shows a spinner in the trailing slot |
596
601
  | `emptyMessage` | `string` | `'No matches'` | |
597
602
  | `forceSelection` | `boolean` | `true` | see note below |
603
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
598
604
 
599
605
  Outputs: `gogSearch: string` (debounced query — wire your server lookup here),
600
606
  `gogLoadMore: void` (panel scrolled to the end — fetch the next page).
@@ -855,11 +861,12 @@ the element stays yours and the directive only gives it the look.
855
861
  <a gogButton fullWidth routerLink="/checkout">Checkout</a>
856
862
  ```
857
863
 
858
- | Input | Type | Default |
859
- | ----------- | ------------------------ | ------------------------------------- |
860
- | `variant` | `GogVariant` | `'primary'` |
861
- | `size` | `GogSize \| undefined` | `'md'`; via `GOG_CONFIG.control.size` |
862
- | `fullWidth` | `boolean` (bare attr ok) | `false` |
864
+ | Input | Type | Default |
865
+ | ----------- | ------------------------ | ---------------------------------------- |
866
+ | `variant` | `GogVariant` | `'primary'` |
867
+ | `size` | `GogSize \| undefined` | `'md'`; via `GOG_CONFIG.control.size` |
868
+ | `fullWidth` | `boolean` (bare attr ok) | `false` |
869
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
863
870
 
864
871
  Selector is `a[gogButton], button[gogButton]` — deliberately not a bare `[gogButton]`, because on
865
872
  a `<div>` the result looks like a button and is invisible to the keyboard and to assistive tech.
@@ -900,16 +907,17 @@ are impossible by design.
900
907
 
901
908
  #### `gog-chip`
902
909
 
903
- | Input | Type | Default |
904
- | ------------------------------ | ----------------------------------- | --------------------- |
905
- | `size` | `GogSize` | `'md'` |
906
- | `shape` | `GogTagShape` (`'rounded'\|'pill'`) | `'rounded'` |
907
- | `disabled`, `clickable` | `boolean` | `false`, `true` |
908
- | `removable` | `boolean` | `false` |
909
- | `fullWidth` | `boolean` | `false` |
910
- | `ariaLabel`, `removeAriaLabel` | `string` | `''`, `'Remove chip'` |
911
- | `avatarUrl`, `avatarAlt` | `string \| null` / `string` | `null`, `''` |
912
- | `iconName` | `GogIconName \| null` | `null` |
910
+ | Input | Type | Default |
911
+ | ------------------------------ | ----------------------------------- | ---------------------------------------- |
912
+ | `size` | `GogSize` | `'md'` |
913
+ | `shape` | `GogTagShape` (`'rounded'\|'pill'`) | `'rounded'` |
914
+ | `disabled`, `clickable` | `boolean` | `false`, `true` |
915
+ | `removable` | `boolean` | `false` |
916
+ | `fullWidth` | `boolean` | `false` |
917
+ | `ariaLabel`, `removeAriaLabel` | `string` | `''`, `'Remove chip'` |
918
+ | `avatarUrl`, `avatarAlt` | `string \| null` / `string` | `null`, `''` |
919
+ | `iconName` | `GogIconName \| null` | `null` |
920
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
913
921
 
914
922
  Outputs: `gogClick: MouseEvent | KeyboardEvent`, `gogRemove: void`.
915
923
 
@@ -1000,6 +1008,63 @@ Label is projected content, not an input — put an icon or a `gog-tag` inside i
1000
1008
  <gog-divider>OR</gog-divider>
1001
1009
  ```
1002
1010
 
1011
+ #### `gogRipple` — directive, not a component
1012
+
1013
+ A pointer-position wash that grows from where you pressed and fades when you let go. Drop it on
1014
+ any element you already have — it adds no wrapper and changes no layout.
1015
+
1016
+ | Input | Type | Default |
1017
+ | ---------------- | --------- | ----------------------------------------------------------- |
1018
+ | `rippleDisabled` | `boolean` | `false` |
1019
+ | `rippleCentred` | `boolean` | `false` — start from the middle instead of from the pointer |
1020
+
1021
+ ```html
1022
+ <button gogRipple>Press me</button>
1023
+ <div gogRipple rippleCentred class="tile">A tile</div>
1024
+ ```
1025
+
1026
+ Four things suppress it, none of which you have to wire up: `rippleDisabled`, a host carrying
1027
+ `disabled`, a host carrying `aria-disabled="true"`, and `prefers-reduced-motion: reduce` — the
1028
+ last one **suppressed outright, not shortened**. Keyboard activation (`Enter`/`Space`) is always
1029
+ centred, because a key press carries no coordinates.
1030
+
1031
+ **Put it on the element that paints the surface.** The wash lives in its own layer that clips
1032
+ itself — the host is never given `overflow: hidden`, so a `gogBadge` on the same element is not
1033
+ clipped — and that layer takes its corner radius from its host with `border-radius: inherit`. On a
1034
+ wrapper whose _child_ paints the rounded background, the layer inherits the wrapper's radius (very
1035
+ often `0`) and the wash squares off at the corners.
1036
+
1037
+ Tokens: `--gog-ripple-color` (`currentColor`, so the wash reads as the surface's own foreground on
1038
+ a filled surface and a ghost one alike), `--gog-ripple-opacity`, `--gog-ripple-enter-duration`,
1039
+ `--gog-ripple-exit-duration`, `--gog-ripple-easing`. All five are ordinary inherited custom
1040
+ properties, so setting one anywhere above the host is the per-instance override.
1041
+
1042
+ #### Turning the ripple on for the library's own components
1043
+
1044
+ You do **not** add `gogRipple` to a `gog-*` component: each one already owns the element that
1045
+ paints its surface, so it wires its own. What you do is switch it on, once:
1046
+
1047
+ ```ts
1048
+ provideGogConfig({ ripple: { enabled: true } });
1049
+ ```
1050
+
1051
+ That covers `gog-button`, `[gogButton]`, `gog-button-toggle-group`, `gog-chip`, `gog-tabs`
1052
+ headers, `gog-accordion` headers, `gogCollapsibleTrigger`, `gogMenuItem`, and the options inside
1053
+ `gog-select` / `gog-multiselect` / `gog-autocomplete`. `gog-paginator` follows because its page
1054
+ buttons are `gog-button`s.
1055
+
1056
+ **Off by default**, so adding the ripple to the library changed the look of nothing. Every one of
1057
+ those takes a `ripple` input that beats the config in both directions: `[ripple]="false"` opts one
1058
+ control out of an app-wide on, `[ripple]="true"` opts one in without switching the app over.
1059
+
1060
+ Not covered, and deliberately: `gog-table` rows and `gogCardLink`. A row and a card are hundreds
1061
+ of pixels wide, so the wave has to travel the whole surface and reads as a flash rather than as
1062
+ feedback at the point you pressed — and a table renders one directive per row, with no
1063
+ virtualization in this library yet. Put `gogRipple` on them yourself if you disagree.
1064
+
1065
+ A chip that is not `clickable`, or is `disabled`, never ripples whatever the config says: a label
1066
+ answering a press is a promise it cannot keep.
1067
+
1003
1068
  #### `gogTooltip` — directive, not a component
1004
1069
 
1005
1070
  Drop on any element — a `gog-*` component's host tag or a plain native one.
@@ -1029,6 +1094,7 @@ Drop on any element — a `gog-*` component's host tag or a plain native one.
1029
1094
  | `skeletonCount` | `number` | `3` — rows shown while `loading` and `items` is still empty |
1030
1095
  | `showChevron` | `boolean` | `true` |
1031
1096
  | `headingLevel` | `2\|3\|4\|5\|6 \| undefined` | `undefined` — wraps headers in `role="heading"` when set |
1097
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
1032
1098
 
1033
1099
  Model: `openIds: ReadonlySet<string | number>`. Output: `gogToggle: { item, open }`.
1034
1100
 
@@ -1070,6 +1136,10 @@ ARIA wiring, because the browser already handles focus and keys. On anything els
1070
1136
  announces is one a keyboard can actually reach. If you set `role` or `tabindex` yourself, the
1071
1137
  directive leaves both alone: you have said what the element is.
1072
1138
 
1139
+ `gogCollapsibleTrigger` takes a **`ripple`** input of its own (`boolean | undefined`, `false`, via
1140
+ `GOG_CONFIG.ripple.enabled`) — the trigger is your element, but the directive owns the ripple so
1141
+ you do not have to add `gogRipple` beside it.
1142
+
1073
1143
  An open panel is as tall as its content — `--gog-collapsible-max-height` defaults to
1074
1144
  `max-content`. Set it to a length on an instance to cap one deliberately; the panel is
1075
1145
  `overflow: hidden`, so a cap **clips** rather than scrolls. (Before 21.4.4 that default was
@@ -1085,6 +1155,7 @@ An open panel is as tall as its content — `--gog-collapsible-max-height` defau
1085
1155
  | `fullWidth`, `ariaLabel` | | `false`, `''` |
1086
1156
  | `scrollActiveIntoView` | `boolean` | `true` |
1087
1157
  | `showScrollTrack` | `boolean \| undefined` | follows `scrollActiveIntoView` (hidden when it's on) |
1158
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
1088
1159
 
1089
1160
  Model: `activeIndex: number`. Output: `gogTabChange: number`.
1090
1161
 
@@ -1108,6 +1179,94 @@ hidden via `[hidden]` while inactive — preserves scroll/input state).
1108
1179
  </gog-tabs>
1109
1180
  ```
1110
1181
 
1182
+ #### `gog-card` + `gogCardHeader` / `gogCardMedia` / `gogCardFooter` / `gogCardLink`
1183
+
1184
+ A surface for one self-contained thing — a product tile, a summary, a search result.
1185
+
1186
+ | Input | Type | Default |
1187
+ | --------------- | -------------------------------------------------------- | --------------------------------------- |
1188
+ | `variant` | `GogSurfaceVariant` (`'outlined'\|'elevated'\|'filled'`) | `'outlined'` |
1189
+ | `size` | `GogSize` | `'md'` — drives padding and the row gap |
1190
+ | `disabled` | `boolean` (bare attribute works) | `false` |
1191
+ | `loading` | `boolean` (bare attribute works) | `false` |
1192
+ | `skeletonLines` | `number` | `2` — body lines shown while `loading` |
1193
+
1194
+ No outputs. Slots, all **attribute** directives on your own elements (not `ng-template`):
1195
+ `gogCardHeader`, `gogCardMedia`, `gogCardFooter`, `gogCardLink`. Layout order is fixed by the
1196
+ component — media, heading, body (the default slot), footer — not by the order you write them.
1197
+
1198
+ ```html
1199
+ <gog-card>
1200
+ <img gogCardMedia [src]="person.photo" alt="" />
1201
+ <h3 gogCardHeader><a gogCardLink [routerLink]="['/people', person.id]">{{ person.name }}</a></h3>
1202
+ <p>{{ person.role }}</p>
1203
+ <div gogCardFooter>
1204
+ <gog-button size="xsm" (gogClick)="shortlist(person)">Shortlist</gog-button>
1205
+ </div>
1206
+ </gog-card>
1207
+ ```
1208
+
1209
+ - **`gogCardHeader` names the card.** The card reads that element's `id` (minting one if it has
1210
+ none) and points its own `aria-labelledby` at it, with `role="group"`. A card with no header
1211
+ gets neither — an unnamed group is noise, not structure. The heading level is yours; the visual
1212
+ size comes from `--gog-card-heading-font-size` regardless of it.
1213
+ - **There is no `interactive` input, and no `gogClick` output.** A card becomes interactive by
1214
+ _containing_ a `gogCardLink`, which stretches that link's hit area over the whole surface. The
1215
+ link stays yours: `routerLink`, `href`, `target`, middle-click, "open in new tab" and Enter all
1216
+ behave normally, and the focus ring is drawn around the card. `gogCardLink` only applies to
1217
+ `<a>` and `<button>` — on a `<div>` it does nothing, deliberately.
1218
+ - **Other controls inside an interactive card still get their own clicks.** A footer button, a
1219
+ checkbox, a second link: each sits above the stretched hit area automatically.
1220
+ - Two costs of the pattern, inherent to it: text in the card cannot be selected by dragging, and
1221
+ a second link is reachable by keyboard but not by clicking the surface around it.
1222
+ - **`loading`** replaces the content with a title bar plus `skeletonLines` text lines and sets
1223
+ `aria-busy`; **`disabled`** dims the card, sets `aria-disabled`, and takes the card link out of
1224
+ the tab order. Both make the link non-clickable. For a _refresh_ of a card that already has
1225
+ content, project a `gog-spinner-overlay` instead — `loading` is the first-paint treatment.
1226
+ - `gogCardMedia` runs full-bleed to the card's edges, and rounds into its top corners when it is
1227
+ the first element in the card.
1228
+
1229
+ #### `gog-panel` + `gogPanelHeader` / `gogPanelFooter`
1230
+
1231
+ A titled region of a page — a settings section, a dashboard area, a form group.
1232
+
1233
+ | Input | Type | Default |
1234
+ | --------------- | -------------------------------- | ------------ |
1235
+ | `variant` | `GogSurfaceVariant` | `'elevated'` |
1236
+ | `size` | `GogSize` | `'lg'` |
1237
+ | `collapsible` | `boolean` (bare attribute works) | `false` |
1238
+ | `disabled` | `boolean` (bare attribute works) | `false` |
1239
+ | `loading` | `boolean` (bare attribute works) | `false` |
1240
+ | `skeletonLines` | `number` | `3` |
1241
+
1242
+ Model: `open: boolean` (default `true`, ignored while `collapsible` is off). No outputs beyond
1243
+ `openChange`. Slots: `gogPanelHeader`, `gogPanelFooter` — attribute directives on your elements.
1244
+
1245
+ ```html
1246
+ <gog-panel [collapsible]="true" [(open)]="notificationsOpen">
1247
+ <h2 gogPanelHeader>Notifications</h2>
1248
+ <gog-checkbox label="Email digest" [(checked)]="emailDigest" />
1249
+ <div gogPanelFooter><gog-button size="xsm">Save</gog-button></div>
1250
+ </gog-panel>
1251
+ ```
1252
+
1253
+ - **It is a landmark.** With a `gogPanelHeader` it renders `role="region"` named by that heading —
1254
+ which is why the panel gets one and `gog-card` gets `role="group"`: a handful of named regions
1255
+ is how a page is navigated, a landmark per card would bury that list.
1256
+ - **Collapsing composes `gog-collapsible`**, so the state, the id wiring and the animation are the
1257
+ library's existing ones. The heading stays a heading: the toggle is a separate `<button>` named
1258
+ by it through `aria-labelledby`, with its hit area stretched across the header row so clicking
1259
+ the title works for the pointer. Without a header the toggle falls back to
1260
+ `GOG_CONFIG.labels.togglePanel` (default `'Toggle section'`).
1261
+ - **A non-collapsible panel does not clip.** It undoes the collapse geometry it inherits,
1262
+ `overflow` included, so a dropdown or menu opened inside it escapes the panel's box. A
1263
+ _collapsible_ one does clip while animating, exactly like `gog-collapsible` — prefer
1264
+ `[appendToBody]` for an overlay inside one.
1265
+ - **`loading` keeps the heading and the footer** and replaces only the body: a page section is
1266
+ titled before its content arrives, and blanking the title would move the layout twice.
1267
+ - **The surface is never itself a link** — there is no `gogPanelLink`. Controls live inside a
1268
+ panel, and a region that is a link cannot hold them. Use `gog-card` for that.
1269
+
1111
1270
  #### `gog-paginator`
1112
1271
 
1113
1272
  | Input | Type | Default |
@@ -1198,7 +1357,7 @@ rather than a hard split, so those can usually go.
1198
1357
 
1199
1358
  **`stickyHeader` needs `maxHeight`** (both since 21.6.0 for the pairing). A sticky element
1200
1359
  resolves against its nearest scroll container, and the table wraps itself in a `gog-scroll`;
1201
- once that scroller moves on either axis it is a scroll container on *both*, because CSS coerces
1360
+ once that scroller moves on either axis it is a scroll container on _both_, because CSS coerces
1202
1361
  `overflow-y: visible` to `auto` beside a scrolling `overflow-x` (and `clip` to `hidden`). So the
1203
1362
  header can only ever stick to something inside the table — and without `maxHeight` that viewport
1204
1363
  is exactly as tall as its content and never scrolls, so there is nothing to stick to.
@@ -1386,6 +1545,10 @@ trigger's measured rect, so a menu inside `gog-scroll`, `gog-table` or any `over
1386
1545
  ancestor is not clipped and needs no configuration. It also takes the `--gog-dropdown-z` its
1387
1546
  trigger inherits, so a menu opened inside a `gog-dialog` stacks above the dialog.
1388
1547
 
1548
+ `gogMenuItem` takes a **`ripple`** input (`boolean | undefined`, `false`, via
1549
+ `GOG_CONFIG.ripple.enabled`). The item is your own `<button>`, but the directive owns the ripple,
1550
+ so there is no `gogRipple` to add.
1551
+
1389
1552
  Output: `gogClosed` — fires after every close, whatever caused it.
1390
1553
 
1391
1554
  Public methods, for driving it yourself: `open(trigger, 'first' | 'last')`, `close(restoreFocus?)`,
@@ -1508,6 +1671,7 @@ Shared enum-like types (`import type { ... } from '@guildofgleks/ui'`):
1508
1671
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
1509
1672
  | `GogSize` | `'xsm' \| 'sm' \| 'md' \| 'lg' \| 'slg'` |
1510
1673
  | `GogVariant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost'` |
1674
+ | `GogSurfaceVariant` | `'outlined' \| 'elevated' \| 'filled'` — `gog-card` and `gog-panel` |
1511
1675
  | `GogTagVariant` | `'success' \| 'danger' \| 'warning' \| 'info'` |
1512
1676
  | `GogOrientation` | `'horizontal' \| 'vertical'` |
1513
1677
  | `GogTagShape` | `'rounded' \| 'pill'` |
package/CHANGELOG.md CHANGED
@@ -4,6 +4,114 @@ All notable changes to `@guildofgleks/ui` are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.0.0/); this project has not yet
5
5
  reached 1.0, so breaking changes may land in minor versions.
6
6
 
7
+ ## [21.6.1] - 26.08.2026
8
+
9
+ ### Added
10
+
11
+ - **`gog-card` — a surface for one self-contained thing.** A product tile, a summary, a search
12
+ result. It paints a background, a border and a radius, which a CSS class of your own could also
13
+ do; what it adds is what a class cannot:
14
+
15
+ - **It names itself.** The heading you project as `gogCardHeader` becomes the card's accessible
16
+ name — the card takes that element's id (minting one if it has none), points its own
17
+ `aria-labelledby` at it and announces itself as a group. `role="group"` rather than `region`
18
+ on purpose: a grid of twenty cards would put twenty landmarks in a screen reader's landmark
19
+ list, which is worse than none.
20
+ - **The whole surface can activate a link, without the card inventing a control.** There is no
21
+ `interactive` input and no `gogClick` output. Put `gogCardLink` on the `<a>` the card is about
22
+ — usually the one in its heading — and its hit area stretches over the card, with the focus
23
+ ring drawn around the surface. The link stays yours, so `routerLink`, `href`, `target`,
24
+ middle-click, "open in new tab" and Enter all keep working, and anything else focusable in the
25
+ card still receives its own clicks. A card rendering its own `<button>` instead could hold no
26
+ other control (a button may not contain one), could not navigate the way an app navigates, and
27
+ would announce the card's entire text as its name.
28
+ - **`loading` and `disabled` are folded in**, including `aria-busy`, the placeholder shaped like
29
+ a title and body copy, and taking the card's link out of the tab order.
30
+
31
+ Slots: `gogCardHeader`, `gogCardMedia` (full-bleed, rounds into the top corners), `gogCardFooter`.
32
+ Tokens: `--gog-card-*`, with `--gog-card-bg`, `--gog-card-border-color`, `--gog-card-shadow`,
33
+ `--gog-card-padding-y`, `--gog-card-padding-x` and `--gog-card-gap` left undeclared as the
34
+ per-instance escape hatch.
35
+
36
+ - **`gog-panel` — a titled region of a page.** A settings section, a dashboard area, a form group.
37
+ It differs from `gog-card` in behaviour, not only in size: it is a real landmark
38
+ (`role="region"` named by its `gogPanelHeader` heading), it can **collapse**, and its surface is
39
+ never itself a link, because controls live inside a panel.
40
+
41
+ Collapsing **composes `gog-collapsible`** rather than repeating it, so the open/close state, the
42
+ id wiring and the animation are the ones the rest of the library already uses. The heading stays
43
+ a heading: the toggle is a separate `<button>` named by it through `aria-labelledby`, with its
44
+ hit area stretched across the header row — so the pointer still gets "click the title to
45
+ collapse", while a screen reader gets a heading _and_ a named expandable button instead of a
46
+ heading swallowed by `role="button"`. A panel that cannot collapse undoes the collapse geometry
47
+ it inherits, `overflow` included, so a dropdown opened inside one is not clipped.
48
+
49
+ `loading` keeps the heading and the footer and replaces only the body — a page section is titled
50
+ before its content arrives. Slots: `gogPanelHeader`, `gogPanelFooter`. New label:
51
+ `GOG_CONFIG.labels.togglePanel`, used only by a collapsible panel with no heading to be named by.
52
+
53
+ **`--gog-panel-*` now means two things, deliberately.** `--gog-panel-radius`,
54
+ `--gog-panel-shadow`, `--gog-panel-border-width` and `--gog-panel-border-style` were already the
55
+ foundation surface tier that dialogs, dropdown panels and tooltips read; the component adopts
56
+ them instead of owning a fourth definition of "raised surface", so a theme's house radius and
57
+ shadow reach it for free. The rest of `--gog-panel-*` is the component's own. Nothing was
58
+ renamed and no existing override changes meaning.
59
+
60
+ - **`gogRipple` — a press wash for any element.** `<button gogRipple>`, `<div gogRipple>`, a
61
+ `gog-*` host tag: a directive, so it adds no wrapper and changes no layout. Built from scratch —
62
+ there is no `@angular/cdk` in this package and there will not be.
63
+
64
+ The interesting part is not the animation. A ripple normally clips its host, and `gogBadge`
65
+ pins its badge **outside** the host's box on purpose — so the first ordinary pairing anyone
66
+ tries, a badged button, loses its badge the moment the ripple is installed. This one never
67
+ clips the host: the wash lives in its own layer that clips itself, and that layer takes the
68
+ host's corner radius through `border-radius: inherit`, per instance and per theme, with no
69
+ token to set. The one thing to know follows from it — **put the directive on the element that
70
+ paints the surface**, since a wrapper whose child paints the rounded background passes on the
71
+ wrapper's radius (usually `0`) and the wash squares off.
72
+
73
+ Inputs: `rippleDisabled`, `rippleCentred`. Suppressed with no wiring on a host that is
74
+ `disabled` or `aria-disabled="true"`, and under `prefers-reduced-motion: reduce` — suppressed
75
+ outright there, not merely shortened. Keyboard activation is centred, since `Enter` and `Space`
76
+ carry no coordinates. Tokens: `--gog-ripple-color` (defaults to `currentColor`, so one value
77
+ works on a filled surface and a ghost one), `--gog-ripple-opacity`,
78
+ `--gog-ripple-enter-duration`, `--gog-ripple-exit-duration`, `--gog-ripple-easing`.
79
+
80
+ - **Every interactive surface in the library can ripple, from one switch.**
81
+ `provideGogConfig({ ripple: { enabled: true } })` turns it on for `gog-button`, `[gogButton]`,
82
+ `gog-button-toggle-group`, `gog-chip`, `gog-tabs` headers, `gog-accordion` headers,
83
+ `gogCollapsibleTrigger`, `gogMenuItem` and the options inside `gog-select`, `gog-multiselect`
84
+ and `gog-autocomplete` — `gog-paginator` follows, because its page buttons are `gog-button`s.
85
+
86
+ **Off by default**, so this release changes the appearance of nothing. Each of those components
87
+ also takes a **`ripple`** input that beats the app-wide setting in both directions:
88
+ `[ripple]="false"` opts one control out of an app that turned it on, `[ripple]="true"` opts one
89
+ in without switching the app over.
90
+
91
+ `ripple` is the first visual default in `GOG_CONFIG` rather than in `theme.css`, and the
92
+ exception is deliberate: `--gog-ripple-opacity: 0` hides the wash but still pays for the DOM
93
+ node, the pointer listeners and the animation frames. A real off has to reach the TypeScript —
94
+ and it does: a disabled ripple attaches no listeners and adds no class to its host.
95
+
96
+ Two surfaces are deliberately left out, with `gogRipple` available if you disagree: **`gog-table`
97
+ rows** and **`gogCardLink`**. Both are hundreds of pixels wide, so the wave has to cross the
98
+ whole surface and reads as a flash rather than as feedback where you pressed — and a table
99
+ installs one directive per row, with no virtualization in the library yet. A `gog-chip` that is
100
+ not `clickable`, or is `disabled`, never ripples whatever the config says.
101
+
102
+ - **`GogSurfaceVariant`** (`'outlined' | 'elevated' | 'filled'`), shared by both, so the two agree
103
+ on what each word looks like.
104
+
105
+ ### Fixed
106
+
107
+ - **A loading `gog-table` and `gog-autocomplete` announce themselves.** Both now set
108
+ `aria-busy="true"` while `loading` is on, which every component with a `loading` input is
109
+ supposed to do. A loading table replaces its rows with a single spinner cell and dims its
110
+ header; the autocomplete's spinner sits in its actions slot and is `aria-hidden`. Neither had
111
+ anything to tell a screen reader that a wait was in progress — the table read as a table with
112
+ no rows, and the field as idle. The autocomplete is the worse of the two, because unlike a
113
+ button the user pressed, that wait is not something they started.
114
+
7
115
  ## [21.6.0] - 23.08.2026
8
116
 
9
117
  ### Fixed
@@ -40,7 +148,7 @@ reached 1.0, so breaking changes may land in minor versions.
40
148
  rendering under it. The rule is a single class in specificity, so your own styles still win.
41
149
 
42
150
  - **Disabled and focus styling can no longer be lost to an ordinary app stylesheet.** `[gogButton]`
43
- and `gogMenuItem` are applied to *your* element, which is the element you style — and a plain
151
+ and `gogMenuItem` are applied to _your_ element, which is the element you style — and a plain
44
152
  `.my-button { cursor: pointer }` in an Angular component stylesheet is the same specificity as
45
153
  the library's `.gog-btn:disabled` once `[_ngcontent-…]` is stamped on it, so it won on source
46
154
  order. Measured: a disabled button reading `cursor: pointer` at full opacity — enabled-looking
@@ -189,7 +297,7 @@ a version bump with nothing to migrate.
189
297
  **`npm run check:logical-properties` now fails the build** on a `padding`, `margin`,
190
298
  `border-width` or `border-radius` shorthand that sets the two horizontal sides differently.
191
299
  21.5.0 converted 16 stylesheets to logical properties and still missed these four, because the
192
- sidedness lives in a value's *position inside a shorthand* rather than in a `left`/`right`
300
+ sidedness lives in a value's _position inside a shorthand_ rather than in a `left`/`right`
193
301
  keyword — invisible to a grep, and invisible to a unit test with no style engine.
194
302
 
195
303
  - **Documented the `position: fixed` containing-block caveat**, in one place plus a line on each
package/README.md CHANGED
@@ -149,6 +149,13 @@ variant and size classes:
149
149
  > text-field block that both `gog-inputfield` and `gog-textarea` render (`.gog-input__field`), not
150
150
  > the `gog-inputfield` component — the two are meant to restyle together from one token set, so
151
151
  > there is no `--gog-inputfield-*` and there will not be one.
152
+ >
153
+ > And one prefix that means two things on purpose: **`--gog-panel-*`**. Four of them —
154
+ > `--gog-panel-radius`, `--gog-panel-shadow`, `--gog-panel-border-width`,
155
+ > `--gog-panel-border-style` — are the _foundation_ surface tier that dialogs, dropdown panels and
156
+ > tooltips read, and the `gog-panel` component reads them too rather than owning a fourth copy of
157
+ > "what a raised surface looks like here". Change one and every raised surface follows, which is
158
+ > the intent; the rest of `--gog-panel-*` belongs to the component alone.
152
159
 
153
160
  Every group and token name is in **[`TOKENS.md`](./TOKENS.md)**, generated from `theme.css` so it
154
161
  cannot drift, and available at runtime as `GOG_TOKEN_GROUPS`.
@@ -194,14 +201,20 @@ provideGogConfig({
194
201
  control: { size: 'sm', errorDisplay: 'auto', clearable: true },
195
202
  dropdown: { appendToBody: true },
196
203
  datepicker: { locale: 'de-DE', format: 'dd.MM.yyyy' },
204
+ ripple: { enabled: true }, // press feedback on every interactive surface at once
197
205
  labels: { clear: 'Очистить', selectAll: 'Выбрать все' }, // translate the library once
198
206
  });
199
207
  ```
200
208
 
201
209
  Keys: `control`, `dropdown`, `floatLabel`, `datepicker`, `autocomplete`, `inputfield`, `textarea`,
202
- `tooltip`, `scroll`, `button`, `paginator`, `toast`, `theme`, `labels`. An instance's own input
203
- always wins, and providing the config again lower in the injector tree layers onto the parent
204
- rather than replacing it.
210
+ `tooltip`, `scroll`, `button`, `ripple`, `paginator`, `toast`, `theme`, `labels`. An instance's own
211
+ input always wins, and providing the config again lower in the injector tree layers onto the
212
+ parent rather than replacing it.
213
+
214
+ `ripple` is the one visual default that is not a token, and the exception is deliberate:
215
+ `--gog-ripple-opacity: 0` would hide the wash but still pay for the DOM node, the listeners and
216
+ the animation frames, so a real off has to reach the TypeScript. It is **off by default**, and
217
+ every rippling component takes a `ripple` input that beats it in both directions.
205
218
 
206
219
  Icons work the same way — 41 Lucide glyphs ship with the package, and your own register by name:
207
220
 
@@ -243,13 +256,14 @@ dropdowns, always for the menu.
243
256
  | Form controls | `gog-inputfield`, `gog-textarea`, `gog-select`, `gog-multiselect`, `gog-autocomplete`, `gog-checkbox`, `gog-radio-group`, `gog-toggle`, `gog-slider`, `gog-datepicker`, `gog-calendar`, `gog-button-toggle-group` |
244
257
  | Actions | `gog-button`, `gog-chip` |
245
258
  | Data | `gog-table` (+ `gog-column`), `gog-paginator`, `gog-tag` |
246
- | Layout & disclosure | `gog-accordion`, `gog-tabs` (+ `gog-tab`), `gog-collapsible`, `gog-divider`, `gog-scroll` |
259
+ | Layout & disclosure | `gog-accordion`, `gog-tabs` (+ `gog-tab`), `gog-collapsible`, `gog-card`, `gog-panel`, `gog-divider`, `gog-scroll` |
247
260
  | Overlays | `gog-dialog`, `gog-confirmation-dialog`, `gog-toast` (+ `gog-toast-container`), `gog-menu` (+ `gogMenuTrigger` / `gogMenuItem`) |
248
261
  | Feedback | `gog-spinner`, `gog-spinner-overlay`, `gog-progressbar`, `gog-skeleton` |
249
262
  | Content | `gog-icon` |
250
263
 
251
264
  **Directives:** `gogButton` (a link that looks like a button), `gogTooltip`, `gogBadge`,
252
- `gogCollapsibleTrigger`, `gogCollapsibleContent`.
265
+ `gogRipple` (a press wash on any element), `gogCollapsibleTrigger`, `gogCollapsibleContent`,
266
+ `gogCardLink` (a link the whole card activates).
253
267
  **Services:** `DialogService`, `ToastService`, `ThemeService`.
254
268
 
255
269
  Seventeen more directives go on markup you own rather than configuring a component through an
@@ -270,6 +284,14 @@ A few things worth knowing before you reach for a workaround:
270
284
  They also generate their own `id`, so labels and error messages are wired up without `inputId`.
271
285
  - **`gog-collapsible` is headless** — no markup of its own. Project any element as the trigger and
272
286
  any element as the panel.
287
+ - **A clickable `gog-card` has no `interactive` input.** Put `gogCardLink` on the `<a>` the card is
288
+ about — usually the one in its heading — and the whole surface activates that link, keyboard,
289
+ middle-click and `routerLink` included. Same reasoning as `gog-button` above: the element stays
290
+ yours. Anything else focusable in the card keeps receiving its own clicks.
291
+ - **`gog-panel` shares the `--gog-panel-*` prefix with the foundation surface tier.**
292
+ `--gog-panel-radius`, `--gog-panel-shadow` and the border pair are the tokens dialogs and
293
+ dropdown panels already read, so a theme's idea of a raised surface reaches the component for
294
+ free. Its own family (background, padding, heading, toggle, footer) sits alongside them.
273
295
  - **`[(ngModel)]` is untested.** The library never imports `FormsModule`; use Reactive Forms.
274
296
 
275
297
  ## Documentation