@guildofgleks/ui 21.5.2 → 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 +221 -29
- package/CHANGELOG.md +232 -1
- package/README.md +31 -5
- package/TOKENS.md +3 -0
- package/fesm2022/guildofgleks-ui.mjs +963 -76
- package/fesm2022/guildofgleks-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/button.css +19 -2
- package/styles/index.css +5 -0
- package/styles/menu.css +13 -3
- package/styles/ripple.css +94 -0
- package/styles/surfaces.css +151 -0
- package/styles/theme.css +132 -0
- package/styles/utilities.css +37 -1
- package/types/guildofgleks-ui.d.ts +377 -16
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.
|
|
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;
|
|
@@ -178,6 +178,10 @@ Full model is in `README.md`'s Theming section and `theming.md`; short version:
|
|
|
178
178
|
still resolve and are removed in 21.7.0 — don't write new code with them. The exception is
|
|
179
179
|
`--gog-input-*`, which is not an abbreviation: it is the shared text-field block that
|
|
180
180
|
`gog-inputfield` and `gog-textarea` both render, and it keeps that name.
|
|
181
|
+
- **The package does not need the app's `box-sizing` reset** (since 21.6.0): `utilities.css`
|
|
182
|
+
sets `border-box` on every element carrying a `gog-*` class, including the ones the library
|
|
183
|
+
puts on a consumer's own element. Do not add a reset "so the components line up" — they
|
|
184
|
+
already do, and a `* { box-sizing: content-box }` in an app is the only thing that undoes it.
|
|
181
185
|
- Theme switch is a `data-theme` attribute, usually on `<html>`, toggled through the
|
|
182
186
|
`ThemeService` (`inject(ThemeService).setTheme('dark')` / `.toggleTheme()` / `.theme` signal).
|
|
183
187
|
Ships `light` and `dark`. Three more importable presets: `slate`, `one-dark`, `one-light`
|
|
@@ -231,6 +235,7 @@ parent's config**, one level deep per key — it does not replace it.
|
|
|
231
235
|
| `tooltip` | `position`, `showDelay`, `hideDelay` | the `gogTooltip` directive. |
|
|
232
236
|
| `scroll` | `autoHide`, `hideDelay`, `size`, `overscrollBehavior`, `showTrack` | `gog-scroll` (and every component that uses one internally). |
|
|
233
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. |
|
|
234
239
|
| `inputfield` | `showSpinButtons` | `gog-inputfield`. |
|
|
235
240
|
| `textarea` | `resize` | `gog-textarea`. |
|
|
236
241
|
| `paginator` | `showPageSizeSelect`, `pageSizeOptions` | `gog-paginator`, and through it `gog-table`'s built-in pagination. |
|
|
@@ -263,6 +268,7 @@ provideGogConfig({
|
|
|
263
268
|
previousPage: 'Предыдущая страница',
|
|
264
269
|
nextPage: 'Следующая страница',
|
|
265
270
|
openCalendar: 'Открыть календарь',
|
|
271
|
+
togglePanel: 'Свернуть раздел', // gog-panel's toggle, only when it has no heading
|
|
266
272
|
rowsPerPage: 'Строк на странице', // gog-paginator's size select
|
|
267
273
|
total: 'Всего', // gog-table's row-count label
|
|
268
274
|
tablePagination: 'Навигация по таблице',
|
|
@@ -411,6 +417,7 @@ Every component below is exported from `@guildofgleks/ui`'s root — `import { X
|
|
|
411
417
|
| `loading` | `boolean` | `false` | shows an inline `gog-spinner`, blocks clicks |
|
|
412
418
|
| `debounce` | `number \| undefined` | `300` | ms; via `GOG_CONFIG.button.debounce` — see note below |
|
|
413
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` |
|
|
414
421
|
|
|
415
422
|
Outputs: `gogClick: MouseEvent`.
|
|
416
423
|
|
|
@@ -428,18 +435,19 @@ fires immediately (leading edge); further clicks within `debounce` ms are silent
|
|
|
428
435
|
|
|
429
436
|
A row of buttons, single- or multi-select, built from your own option objects.
|
|
430
437
|
|
|
431
|
-
| Input | Type | Default | Notes
|
|
432
|
-
| ------------------------------------ | ------------------------------------------ | ---------------------- |
|
|
433
|
-
| `options` | `TOption[]` | `[]` |
|
|
434
|
-
| `optionLabel` | accessor | `'name'` |
|
|
435
|
-
| `optionValue` | accessor \| `null` | `'id'` | `null` emits the option object
|
|
436
|
-
| `optionDisabled` | accessor | `'disabled'` |
|
|
437
|
-
| `optionIcon` | accessor → `GogIconName \| null` \| `null` | `null` | optional leading icon per option
|
|
438
|
-
| `multiple` | `boolean` | `false` | changes ARIA role entirely — see note
|
|
439
|
-
| `appearance` | `'joined' \| 'separated'` | `'joined'` |
|
|
440
|
-
| `orientation` | `GogOrientation` | `'horizontal'` |
|
|
441
|
-
| `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size`
|
|
442
|
-
| `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` |
|
|
443
451
|
|
|
444
452
|
Model: `value: TValue | TValue[] | null` (single value, or array in `multiple` mode). CVA: yes.
|
|
445
453
|
Slot: `<ng-template gogButtonToggleOption let-opt let-selected="selected">` for custom button
|
|
@@ -548,6 +556,7 @@ and multiselect unless noted otherwise):
|
|
|
548
556
|
| `disabled`, `fullWidth` | | `false`, `true` | |
|
|
549
557
|
| `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
|
|
550
558
|
| `inputId` (select/autocomplete only) | `string` | `''` | |
|
|
559
|
+
| `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
|
|
551
560
|
|
|
552
561
|
`gog-select`-specific: `value: model<TValue>(null)`.
|
|
553
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`).
|
|
@@ -591,6 +600,7 @@ panel-filter box; it filters/searches off what's typed in the field itself.
|
|
|
591
600
|
| `loading` | `boolean` | `false` | shows a spinner in the trailing slot |
|
|
592
601
|
| `emptyMessage` | `string` | `'No matches'` | |
|
|
593
602
|
| `forceSelection` | `boolean` | `true` | see note below |
|
|
603
|
+
| `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
|
|
594
604
|
|
|
595
605
|
Outputs: `gogSearch: string` (debounced query — wire your server lookup here),
|
|
596
606
|
`gogLoadMore: void` (panel scrolled to the end — fetch the next page).
|
|
@@ -851,11 +861,12 @@ the element stays yours and the directive only gives it the look.
|
|
|
851
861
|
<a gogButton fullWidth routerLink="/checkout">Checkout</a>
|
|
852
862
|
```
|
|
853
863
|
|
|
854
|
-
| Input | Type | Default
|
|
855
|
-
| ----------- | ------------------------ |
|
|
856
|
-
| `variant` | `GogVariant` | `'primary'`
|
|
857
|
-
| `size` | `GogSize \| undefined` | `'md'`; via `GOG_CONFIG.control.size`
|
|
858
|
-
| `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` |
|
|
859
870
|
|
|
860
871
|
Selector is `a[gogButton], button[gogButton]` — deliberately not a bare `[gogButton]`, because on
|
|
861
872
|
a `<div>` the result looks like a button and is invisible to the keyboard and to assistive tech.
|
|
@@ -896,16 +907,17 @@ are impossible by design.
|
|
|
896
907
|
|
|
897
908
|
#### `gog-chip`
|
|
898
909
|
|
|
899
|
-
| Input | Type | Default
|
|
900
|
-
| ------------------------------ | ----------------------------------- |
|
|
901
|
-
| `size` | `GogSize` | `'md'`
|
|
902
|
-
| `shape` | `GogTagShape` (`'rounded'\|'pill'`) | `'rounded'`
|
|
903
|
-
| `disabled`, `clickable` | `boolean` | `false`, `true`
|
|
904
|
-
| `removable` | `boolean` | `false`
|
|
905
|
-
| `fullWidth` | `boolean` | `false`
|
|
906
|
-
| `ariaLabel`, `removeAriaLabel` | `string` | `''`, `'Remove chip'`
|
|
907
|
-
| `avatarUrl`, `avatarAlt` | `string \| null` / `string` | `null`, `''`
|
|
908
|
-
| `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` |
|
|
909
921
|
|
|
910
922
|
Outputs: `gogClick: MouseEvent | KeyboardEvent`, `gogRemove: void`.
|
|
911
923
|
|
|
@@ -996,6 +1008,63 @@ Label is projected content, not an input — put an icon or a `gog-tag` inside i
|
|
|
996
1008
|
<gog-divider>OR</gog-divider>
|
|
997
1009
|
```
|
|
998
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
|
+
|
|
999
1068
|
#### `gogTooltip` — directive, not a component
|
|
1000
1069
|
|
|
1001
1070
|
Drop on any element — a `gog-*` component's host tag or a plain native one.
|
|
@@ -1025,6 +1094,7 @@ Drop on any element — a `gog-*` component's host tag or a plain native one.
|
|
|
1025
1094
|
| `skeletonCount` | `number` | `3` — rows shown while `loading` and `items` is still empty |
|
|
1026
1095
|
| `showChevron` | `boolean` | `true` |
|
|
1027
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` |
|
|
1028
1098
|
|
|
1029
1099
|
Model: `openIds: ReadonlySet<string | number>`. Output: `gogToggle: { item, open }`.
|
|
1030
1100
|
|
|
@@ -1066,6 +1136,10 @@ ARIA wiring, because the browser already handles focus and keys. On anything els
|
|
|
1066
1136
|
announces is one a keyboard can actually reach. If you set `role` or `tabindex` yourself, the
|
|
1067
1137
|
directive leaves both alone: you have said what the element is.
|
|
1068
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
|
+
|
|
1069
1143
|
An open panel is as tall as its content — `--gog-collapsible-max-height` defaults to
|
|
1070
1144
|
`max-content`. Set it to a length on an instance to cap one deliberately; the panel is
|
|
1071
1145
|
`overflow: hidden`, so a cap **clips** rather than scrolls. (Before 21.4.4 that default was
|
|
@@ -1081,6 +1155,7 @@ An open panel is as tall as its content — `--gog-collapsible-max-height` defau
|
|
|
1081
1155
|
| `fullWidth`, `ariaLabel` | | `false`, `''` |
|
|
1082
1156
|
| `scrollActiveIntoView` | `boolean` | `true` |
|
|
1083
1157
|
| `showScrollTrack` | `boolean \| undefined` | follows `scrollActiveIntoView` (hidden when it's on) |
|
|
1158
|
+
| `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
|
|
1084
1159
|
|
|
1085
1160
|
Model: `activeIndex: number`. Output: `gogTabChange: number`.
|
|
1086
1161
|
|
|
@@ -1104,6 +1179,94 @@ hidden via `[hidden]` while inactive — preserves scroll/input state).
|
|
|
1104
1179
|
</gog-tabs>
|
|
1105
1180
|
```
|
|
1106
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
|
+
|
|
1107
1270
|
#### `gog-paginator`
|
|
1108
1271
|
|
|
1109
1272
|
| Input | Type | Default |
|
|
@@ -1169,7 +1332,8 @@ they never asked to be.
|
|
|
1169
1332
|
| `totalPosition` | `'left'\|'right'\|'opposite'` | `'opposite'` |
|
|
1170
1333
|
| `loading` | `boolean` | `false` |
|
|
1171
1334
|
| `showColumnBorders` | `boolean` | `false` |
|
|
1172
|
-
| `stickyHeader` | `boolean` | `false`
|
|
1335
|
+
| `stickyHeader` | `boolean` | `false` — pair with `maxHeight` |
|
|
1336
|
+
| `maxHeight` | `string \| null` | `null` — any CSS length |
|
|
1173
1337
|
| `size` | `GogSize` | `'lg'` (row density — not `'md'`) |
|
|
1174
1338
|
| `lazy` | `boolean` | `false` — see below |
|
|
1175
1339
|
| `totalRecords` | `number \| null` | `null` — `lazy` only |
|
|
@@ -1184,6 +1348,29 @@ null }` when the third click clears it), `gogPageChange: number` (1-based; **doe
|
|
|
1184
1348
|
first render, nor for the page reset a new sort causes — that reset belongs to the sort),
|
|
1185
1349
|
`gogRowClick: GogTableRowClickEvent<T>` (`{ row, index, originalEvent }`).
|
|
1186
1350
|
|
|
1351
|
+
**`fullWidth` also picks the layout algorithm.** Left at its default the table is `100%` wide with
|
|
1352
|
+
`table-layout: fixed`; since 21.6.0 `[fullWidth]="false"` makes it `fit-content` with
|
|
1353
|
+
`table-layout: auto`, so the columns are measured against their content instead of splitting the
|
|
1354
|
+
total evenly. Before 21.6.0 that split clipped the widest header, and a `width` on the column was
|
|
1355
|
+
the workaround — under auto layout a stated `width` is a suggestion weighed against content
|
|
1356
|
+
rather than a hard split, so those can usually go.
|
|
1357
|
+
|
|
1358
|
+
**`stickyHeader` needs `maxHeight`** (both since 21.6.0 for the pairing). A sticky element
|
|
1359
|
+
resolves against its nearest scroll container, and the table wraps itself in a `gog-scroll`;
|
|
1360
|
+
once that scroller moves on either axis it is a scroll container on _both_, because CSS coerces
|
|
1361
|
+
`overflow-y: visible` to `auto` beside a scrolling `overflow-x` (and `clip` to `hidden`). So the
|
|
1362
|
+
header can only ever stick to something inside the table — and without `maxHeight` that viewport
|
|
1363
|
+
is exactly as tall as its content and never scrolls, so there is nothing to stick to.
|
|
1364
|
+
|
|
1365
|
+
```html
|
|
1366
|
+
<gog-table [value]="rows" maxHeight="260px" [stickyHeader]="true">…</gog-table>
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
`maxHeight` takes any CSS length and is what makes the table own its vertical scrolling. Left
|
|
1370
|
+
`null`, the table grows to its content and an ancestor scrolls it — the header then follows that
|
|
1371
|
+
ancestor's scroll like everything else, which is the pre-21.6.0 behaviour and is fine as long as
|
|
1372
|
+
you are not asking for a sticky header.
|
|
1373
|
+
|
|
1187
1374
|
Columns are declared as **projected `gog-column` children**, not an input array:
|
|
1188
1375
|
|
|
1189
1376
|
```html
|
|
@@ -1358,6 +1545,10 @@ trigger's measured rect, so a menu inside `gog-scroll`, `gog-table` or any `over
|
|
|
1358
1545
|
ancestor is not clipped and needs no configuration. It also takes the `--gog-dropdown-z` its
|
|
1359
1546
|
trigger inherits, so a menu opened inside a `gog-dialog` stacks above the dialog.
|
|
1360
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
|
+
|
|
1361
1552
|
Output: `gogClosed` — fires after every close, whatever caused it.
|
|
1362
1553
|
|
|
1363
1554
|
Public methods, for driving it yourself: `open(trigger, 'first' | 'last')`, `close(restoreFocus?)`,
|
|
@@ -1480,6 +1671,7 @@ Shared enum-like types (`import type { ... } from '@guildofgleks/ui'`):
|
|
|
1480
1671
|
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
1481
1672
|
| `GogSize` | `'xsm' \| 'sm' \| 'md' \| 'lg' \| 'slg'` |
|
|
1482
1673
|
| `GogVariant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost'` |
|
|
1674
|
+
| `GogSurfaceVariant` | `'outlined' \| 'elevated' \| 'filled'` — `gog-card` and `gog-panel` |
|
|
1483
1675
|
| `GogTagVariant` | `'success' \| 'danger' \| 'warning' \| 'info'` |
|
|
1484
1676
|
| `GogOrientation` | `'horizontal' \| 'vertical'` |
|
|
1485
1677
|
| `GogTagShape` | `'rounded' \| 'pill'` |
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,237 @@ 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
|
+
|
|
115
|
+
## [21.6.0] - 23.08.2026
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- **A loading `gog-accordion` announces itself.** It sets `aria-busy="true"` while `loading` is
|
|
120
|
+
on. Its skeleton bars are `aria-hidden` and the real headers are not rendered, so without it the
|
|
121
|
+
component was not "loading" to a screen reader — it was **empty**, which reads as "there is
|
|
122
|
+
nothing here" rather than "this is coming".
|
|
123
|
+
|
|
124
|
+
The placeholder rows also gained the shape they were missing: **a chevron placeholder**, so the
|
|
125
|
+
silhouette is the one that arrives instead of the chevron appearing out of nowhere, and
|
|
126
|
+
**varying title widths** instead of every bar at a flat 55%, which read as a repeating progress
|
|
127
|
+
artifact rather than as titles of differing length. Widths cycle by row index rather than
|
|
128
|
+
randomising, so a server-rendered pass and its hydration agree.
|
|
129
|
+
|
|
130
|
+
The geometry was already correct and is unchanged — measured at the same `size`, the skeleton
|
|
131
|
+
header is 46px against the real 47px.
|
|
132
|
+
|
|
133
|
+
**`gog-table` and `gog-autocomplete` still do not set `aria-busy`.** The rule is now written
|
|
134
|
+
down in the repo's API guide along with which loading treatment belongs to which kind of
|
|
135
|
+
component; those two are its known violations and are tracked.
|
|
136
|
+
|
|
137
|
+
- **The library no longer needs your `box-sizing` reset.** Everything it renders now sizes itself
|
|
138
|
+
`border-box`, set once in `styles/utilities.css` on any element carrying a `gog-*` class.
|
|
139
|
+
|
|
140
|
+
Without a reset the components quietly stopped honouring their own size tokens:
|
|
141
|
+
`--gog-select-min-width` is `120px`, and a `gog-select` measured **148px**; its inner control
|
|
142
|
+
went from 120px to 176px, and `gog-toggle__track` and `gog-checkbox__box` inflated the same way.
|
|
143
|
+
Measured across four showcase pages, 12–16 elements per page came out a different size. Nothing
|
|
144
|
+
looked broken, which is why it went unreported — a consumer without a reset simply got a
|
|
145
|
+
differently-proportioned library.
|
|
146
|
+
|
|
147
|
+
**If your app has `* { box-sizing: border-box }`, nothing changes**; the library was already
|
|
148
|
+
rendering under it. The rule is a single class in specificity, so your own styles still win.
|
|
149
|
+
|
|
150
|
+
- **Disabled and focus styling can no longer be lost to an ordinary app stylesheet.** `[gogButton]`
|
|
151
|
+
and `gogMenuItem` are applied to _your_ element, which is the element you style — and a plain
|
|
152
|
+
`.my-button { cursor: pointer }` in an Angular component stylesheet is the same specificity as
|
|
153
|
+
the library's `.gog-btn:disabled` once `[_ngcontent-…]` is stamped on it, so it won on source
|
|
154
|
+
order. Measured: a disabled button reading `cursor: pointer` at full opacity — enabled-looking
|
|
155
|
+
and enabled-feeling while disabled. For a menu item it is worse, because the arrow keys step
|
|
156
|
+
over a disabled item: the pointer and the keyboard disagreed about what was there.
|
|
157
|
+
|
|
158
|
+
Five rules across `styles/button.css` and `styles/menu.css` — the `:disabled` and
|
|
159
|
+
`:focus-visible` pairs — now carry one more point of specificity. `:hover` and `:active` needed
|
|
160
|
+
nothing; their `:not(:disabled)` already had it.
|
|
161
|
+
|
|
162
|
+
**Restyling these is still yours**, it just has to be deliberate now: any selector of your own
|
|
163
|
+
with two classes wins, as before. The base `.gog-btn` look is untouched and as overridable as
|
|
164
|
+
it ever was — what is defended is state and focus visibility, where losing silently is a
|
|
165
|
+
correctness and accessibility bug rather than a difference of taste.
|
|
166
|
+
|
|
167
|
+
- **A disabled `gogCollapsibleTrigger` shows a disabled cursor.** It carried whatever cursor the
|
|
168
|
+
consumer had put on the element — usually `pointer`, since the trigger is their own button.
|
|
169
|
+
The library's `cursor: not-allowed` was there, and losing: an ordinary
|
|
170
|
+
`.my-trigger { cursor: pointer }` in a consumer's component stylesheet is the same specificity
|
|
171
|
+
once Angular stamps `[_ngcontent-…]` onto it, and it comes later in the cascade. The rule is
|
|
172
|
+
now scoped through `gog-collapsible`, which settles it without `!important`.
|
|
173
|
+
|
|
174
|
+
If you added a `cursor` to your own trigger, you can drop it — the directive has set
|
|
175
|
+
`cursor: pointer` since it started applying `.gog-collapsible__trigger`.
|
|
176
|
+
|
|
177
|
+
- **`gog-autocomplete`: the text can be erased again.** With a selection held, backspacing could
|
|
178
|
+
not clear the field — nine backspaces on "Amsterdam" left "Amsterdam". Deleting the last
|
|
179
|
+
character takes the text under `minLength`, which closes the panel; the effect that keeps the
|
|
180
|
+
field showing its selection used `!isOpen()` to mean "the user is not mid-edit", so closing the
|
|
181
|
+
panel made it write the selected label straight back into the input.
|
|
182
|
+
|
|
183
|
+
The panel closes for reasons that are not "the user finished" — text under `minLength`, Escape,
|
|
184
|
+
Tab — so editing is now tracked directly instead of inferred from it. The effect keeps its real
|
|
185
|
+
job, which is syncing a value the component did not set: a form writing one in, or the options
|
|
186
|
+
arriving after the value did.
|
|
187
|
+
|
|
188
|
+
**`forceSelection` is unaffected.** It still snaps the field back to the selection on blur and
|
|
189
|
+
on Escape, which is where that was always meant to happen; what stopped is the snap-back firing
|
|
190
|
+
mid-keystroke.
|
|
191
|
+
|
|
192
|
+
### Added
|
|
193
|
+
|
|
194
|
+
- **`gog-table`: `maxHeight`** — any CSS length (`'420px'`, `'60vh'`), capping the table's own
|
|
195
|
+
scroll viewport so the table owns its vertical scrolling. **This is what makes `stickyHeader`
|
|
196
|
+
work**, and the two are meant to be used together:
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<gog-table [value]="rows" maxHeight="260px" [stickyHeader]="true">…</gog-table>
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
`stickyHeader` has never held while the table scrolled sideways. A sticky element resolves
|
|
203
|
+
against its nearest scroll container; the table wraps itself in a `gog-scroll`, and the moment
|
|
204
|
+
that scroller moves on one axis it is a scroll container on **both**, because CSS coerces
|
|
205
|
+
`overflow-y: visible` to `auto` next to a scrolling `overflow-x` — and `clip` to `hidden`, which
|
|
206
|
+
is also a scroll container. Measured in a browser: a header 147px out of view, with the
|
|
207
|
+
component's own `overflow-y: visible` showing as a computed `auto`.
|
|
208
|
+
|
|
209
|
+
So the header cannot be made to stick to anything outside the table, and the fix is to give the
|
|
210
|
+
inside something to stick to. With `maxHeight` set the viewport is the vertical scrollport and
|
|
211
|
+
the header pins to it — verified with both axes scrolling at once.
|
|
212
|
+
|
|
213
|
+
An input rather than a `--gog-table-*` token, even though the value only lands in CSS, because
|
|
214
|
+
it also decides whether the internal scroller handles the vertical axis. It has to for a capped
|
|
215
|
+
table; it must **not** for an uncapped one, or every table becomes a scroll container and takes
|
|
216
|
+
the consumer's own scrolling region out of its descendants' sticky chain — measured at 147px
|
|
217
|
+
when tried that way. That is behaviour, not appearance.
|
|
218
|
+
|
|
219
|
+
**Nothing changes for a table without it.** `maxHeight` defaults to `null`, the viewport stays
|
|
220
|
+
at content height and its vertical axis stays inert.
|
|
221
|
+
|
|
222
|
+
### Fixed
|
|
223
|
+
|
|
224
|
+
- **`[fullWidth]="false"` no longer clips the widest column's header.** The table is
|
|
225
|
+
`table-layout: fixed` and switches to `width: fit-content` in this mode, so the browser split
|
|
226
|
+
that width evenly across the columns instead of measuring them against their content — and
|
|
227
|
+
`overflow: hidden` on the cell cut whatever did not fit. Measured on a five-column table:
|
|
228
|
+
"Component" was given 100px of 448px while needing 119px.
|
|
229
|
+
|
|
230
|
+
It now lays out with `table-layout: auto` whenever `fullWidth` is false. Fixed layout only buys
|
|
231
|
+
anything when the width comes from outside the table; at `fit-content` it comes from the content
|
|
232
|
+
anyway, so there was nothing to trade away. The same table now gives "Component" 133px of 454px.
|
|
233
|
+
|
|
234
|
+
**`fullWidth` left at its default is untouched** and still uses fixed layout. If you set `width`
|
|
235
|
+
on a `gog-column` purely to work around the clipping, you can drop it — under auto layout a
|
|
236
|
+
stated width becomes a suggestion the browser weighs against content, rather than a hard split.
|
|
237
|
+
|
|
7
238
|
## [21.5.2] - 22.08.2026
|
|
8
239
|
|
|
9
240
|
Documentation only, and specifically the copy of this file that ships inside the package: 21.5.0
|
|
@@ -66,7 +297,7 @@ a version bump with nothing to migrate.
|
|
|
66
297
|
**`npm run check:logical-properties` now fails the build** on a `padding`, `margin`,
|
|
67
298
|
`border-width` or `border-radius` shorthand that sets the two horizontal sides differently.
|
|
68
299
|
21.5.0 converted 16 stylesheets to logical properties and still missed these four, because the
|
|
69
|
-
sidedness lives in a value's
|
|
300
|
+
sidedness lives in a value's _position inside a shorthand_ rather than in a `left`/`right`
|
|
70
301
|
keyword — invisible to a grep, and invisible to a unit test with no style engine.
|
|
71
302
|
|
|
72
303
|
- **Documented the `position: fixed` containing-block caveat**, in one place plus a line on each
|