@guildofgleks/ui 21.6.0 → 21.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -5,12 +5,13 @@ 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
9
- of deprecated API — see **Removed in 21.5.0** near the end of this file, which exists so code
10
- written against 21.4.x can be migrated and `CHANGELOG.md` has the rest. `README.md` covers the
11
- same ground at a higher level install, setup, theming, global configuration and is accurate;
12
- this file goes further, into per-component input tables, and is the one to trust for exact names,
13
- types and defaults.
8
+ Everything below reflects the library's actual source as of **`21.7.0`**. 21.7.0 removed the three
9
+ abbreviated token prefixes and 21.5.0 removed a batch of deprecated API — see **Removed in 21.7.0**
10
+ and **Removed in 21.5.0** near the end of this file, which exist so code written against an older
11
+ version can be migrated and `CHANGELOG.md` has the rest. `README.md` covers the same ground at a
12
+ higher level install, setup, theming, global configuration and is accurate; this file goes
13
+ further, into per-component input tables, and is the one to trust for exact names, types and
14
+ defaults.
14
15
 
15
16
  > **Maintainers:** this file ships inside the npm package and is the API reference an agent reads
16
17
  > while writing code against it, so a stale table here becomes wrong code in someone else's app —
@@ -166,30 +167,66 @@ exist.
166
167
 
167
168
  ## Theming
168
169
 
169
- Full model is in `README.md`'s Theming section and `theming.md`; short version:
170
+ Full model is in `README.md`'s Theming section; short version:
170
171
 
171
172
  - Every visual value (color, spacing, radius, shadow, duration) is a `--gog-*` CSS custom
172
173
  property, layered **foundation** (`--gog-accent-color`, `--gog-space-md`, …, restyles
173
174
  everything) → **component** (`--gog-button-primary-bg`, …, one block per component, named after
174
175
  the component's own element) → **instance** (`--gog-button-bg`, …, deliberately undeclared
175
176
  escape hatch for one element).
177
+ - **Foundation includes a small character layer** (since 21.7.0, `docs/themes.md` iteration 1):
178
+ `--gog-radius` (corner rounding), `--gog-control-border-*`/`--gog-panel-border-*`/`--gog-border-*`
179
+ (border weight — form fields, raised surfaces, everything smaller and inline, respectively),
180
+ `--gog-text-transform`/`--gog-letter-spacing` (emphasis casing/tracking). Component tokens in
181
+ the categories these cover derive from them by default; setting one in a `[data-theme]` block
182
+ restyles every component that reads it, with nothing to re-list per component.
183
+ - **`--gog-density` is the character layer for spacing** (since 21.7.0, `docs/themes.md`
184
+ iteration 6). It multiplies the fourteen-step scale `--gog-space-2` … `--gog-space-48`, named
185
+ for their pixel value at density 1, and every padding and gap in the library derives from a
186
+ step. `--gog-density: 0.9` in a `[data-theme]` block makes the whole library tighter; nothing
187
+ else needs to be named. `--gog-space-xs|sm|md|lg|2xl` are aliases for steps 4/8/16/24/48 and
188
+ still work. Icon offsets follow density; the glyph box, focus-ring offset and float-label
189
+ reserve deliberately do not — they are legibility, not spacing.
176
190
  - **Component prefixes are spelled out** since 21.5.0: `--gog-button-*`, `--gog-multiselect-*`,
177
191
  `--gog-confirmation-dialog-*`. The abbreviated `--gog-btn-*`, `--gog-ms-*` and `--gog-confirm-*`
178
- still resolve and are removed in 21.7.0 — don't write new code with them. The exception is
179
- `--gog-input-*`, which is not an abbreviation: it is the shared text-field block that
180
- `gog-inputfield` and `gog-textarea` both render, and it keeps that name.
192
+ were removed in 21.7.0 — if you're reading a codebase or an example that still uses one, rename
193
+ it; it no longer resolves. The exception is `--gog-input-*`, which is not an abbreviation: it is
194
+ the shared text-field block that `gog-inputfield` and `gog-textarea` both render, and it keeps
195
+ that name.
181
196
  - **The package does not need the app's `box-sizing` reset** (since 21.6.0): `utilities.css`
182
197
  sets `border-box` on every element carrying a `gog-*` class, including the ones the library
183
198
  puts on a consumer's own element. Do not add a reset "so the components line up" — they
184
199
  already do, and a `* { box-sizing: content-box }` in an app is the only thing that undoes it.
185
200
  - Theme switch is a `data-theme` attribute, usually on `<html>`, toggled through the
186
201
  `ThemeService` (`inject(ThemeService).setTheme('dark')` / `.toggleTheme()` / `.theme` signal).
187
- Ships `light` and `dark`. Three more importable presets: `slate`, `one-dark`, `one-light`
188
- (`@guildofgleks/ui/styles/presets/<name>.css`).
202
+ Ships `light` and `dark`, plus nine importable presets at
203
+ `@guildofgleks/ui/styles/presets/<name>.css`. **All nine set palette and character** (since
204
+ 21.7.0 — before it, three were palette-only, which made them recoloured defaults):
205
+
206
+ | Preset | Radius | Density | Identity |
207
+ | ---------------------- | ------ | ------- | ------------------------------------------------ |
208
+ | `slate` | 12px | 1.05 | soft modern — hairline borders, roomy |
209
+ | `one-dark`/`one-light` | 4px | 0.9 | editor chrome; identical character, two tones |
210
+ | `material` | 4px | 1.1 | Material Design 3, pill buttons |
211
+ | `primeng` | 6px | 0.95 | PrimeNG Aura |
212
+ | `ledger` | 0 | 0.9 | administrative — hard offset shadow, no motion |
213
+ | `terminal` | 0 | 0.85 | green phosphor, monospaced throughout, no motion |
214
+ | `bevel` | 0 | 0.9 | early-web desktop — `outset`/`inset` borders |
215
+ | `parchment` | 0 | 1.1 | ink on paper — old-style serif, oxblood |
216
+
217
+ `material`, `primeng` and `bevel` also set a few genuinely per-component things the character
218
+ layer has no vocabulary for (a pill button, a table's header font, a button bevel that has to
219
+ disagree with a field's); see their own file headers.
220
+
221
+ - **A preset never makes a network request.** Each sets a font _stack_ resolving to a real system
222
+ face. Where a webfont is worth offering, it is a separate opt-in file — `terminal.fonts.css`
223
+ (IBM Plex Mono), `parchment.fonts.css` (EB Garamond) — imported **after** the preset, since it
224
+ re-points the same tokens and later wins. Do not add an `@import url(…)` to a preset itself; put
225
+ it in a companion file, or the import becomes a download nobody asked for.
189
226
  - Restyle one instance without touching a theme: `<gog-button style="--gog-button-bg: #ff4edb">`.
190
- - Build a custom theme by declaring a palette against a new `data-theme` value (see
191
- `theming.md` for the full worked example) — component tokens re-derive automatically, you
192
- don't restate them.
227
+ - Build a custom theme by declaring a palette **and a character** against a new `data-theme`
228
+ value (see `README.md`'s Theming section for the full worked example) — component tokens
229
+ re-derive automatically, you don't restate them.
193
230
 
194
231
  ## Right-to-left
195
232
 
@@ -235,6 +272,7 @@ parent's config**, one level deep per key — it does not replace it.
235
272
  | `tooltip` | `position`, `showDelay`, `hideDelay` | the `gogTooltip` directive. |
236
273
  | `scroll` | `autoHide`, `hideDelay`, `size`, `overscrollBehavior`, `showTrack` | `gog-scroll` (and every component that uses one internally). |
237
274
  | `button` | `debounce` | `gog-button`. |
275
+ | `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
276
  | `inputfield` | `showSpinButtons` | `gog-inputfield`. |
239
277
  | `textarea` | `resize` | `gog-textarea`. |
240
278
  | `paginator` | `showPageSizeSelect`, `pageSizeOptions` | `gog-paginator`, and through it `gog-table`'s built-in pagination. |
@@ -252,39 +290,39 @@ in English sets these once rather than on every control:
252
290
  ```ts
253
291
  provideGogConfig({
254
292
  labels: {
255
- clear: 'Очистить', // inputfield / textarea clear button
256
- clearSelection: 'Очистить выбор', // select / multiselect / autocomplete
257
- clearDate: 'Очистить дату', // datepicker
258
- selectAll: 'Выбрать все', // multiselect panel
259
- clearAll: 'Очистить', // multiselect panel
260
- increment: 'Увеличить', // number spin buttons
261
- decrement: 'Уменьшить',
262
- showPassword: 'Показать пароль',
263
- hidePassword: 'Скрыть пароль',
264
- closeDialog: 'Закрыть',
265
- closeToast: 'Закрыть',
266
- pagination: 'Навигация по страницам',
267
- previousPage: 'Предыдущая страница',
268
- nextPage: 'Следующая страница',
269
- openCalendar: 'Открыть календарь',
270
- rowsPerPage: 'Строк на странице', // gog-paginator's size select
271
- total: 'Всего', // gog-table's row-count label
272
- tablePagination: 'Навигация по таблице',
273
- selectRow: 'Выбрать строку',
274
- selectAllRows: 'Выбрать все строки на странице',
275
- today: 'Сегодня',
276
- thisMonth: 'Текущий месяц',
277
- previousMonth: 'Предыдущий месяц',
278
- nextMonth: 'Следующий месяц',
279
- previousYear: 'Предыдущий год',
280
- nextYear: 'Следующий год',
281
- hours: 'Часы',
282
- minutes: 'Минуты',
283
- seconds: 'Секунды',
293
+ clear: 'Löschen', // inputfield / textarea clear button
294
+ clearSelection: 'Auswahl löschen', // select / multiselect / autocomplete
295
+ clearDate: 'Datum löschen', // datepicker
296
+ selectAll: 'Alle auswählen', // multiselect panel
297
+ clearAll: 'Alle löschen', // multiselect panel
298
+ increment: 'Erhöhen', // number spin buttons
299
+ decrement: 'Verringern',
300
+ showPassword: 'Passwort anzeigen',
301
+ hidePassword: 'Passwort verbergen',
302
+ closeDialog: 'Schließen',
303
+ closeToast: 'Schließen',
304
+ pagination: 'Seitennavigation',
305
+ previousPage: 'Vorherige Seite',
306
+ nextPage: 'Nächste Seite',
307
+ openCalendar: 'Kalender öffnen',
308
+ togglePanel: 'Bereich umschalten', // gog-panel's toggle, only when it has no heading
309
+ rowsPerPage: 'Zeilen pro Seite', // gog-paginator's size select
310
+ total: 'Gesamt', // gog-table's row-count label
311
+ tablePagination: 'Tabellennavigation',
312
+ selectRow: 'Zeile auswählen',
313
+ selectAllRows: 'Alle Zeilen auswählen',
314
+ today: 'Heute',
315
+ thisMonth: 'Aktueller Monat',
316
+ previousMonth: 'Vorheriger Monat',
317
+ nextMonth: 'Nächster Monat',
318
+ previousYear: 'Vorheriges Jahr',
319
+ nextYear: 'Nächstes Jahr',
320
+ hours: 'Stunden',
321
+ minutes: 'Minuten',
322
+ seconds: 'Sekunden',
284
323
  // The one non-string field: it interpolates the page number, and word order and
285
324
  // agreement around a number vary by language, so it takes a formatter.
286
- page: (page, isCurrent) =>
287
- isCurrent ? `Страница ${page}, текущая` : `Перейти на страницу ${page}`,
325
+ page: (page, isCurrent) => (isCurrent ? `Seite ${page}, aktuell` : `Zu Seite ${page} wechseln`),
288
326
  },
289
327
  });
290
328
  ```
@@ -320,7 +358,7 @@ provideGogConfig({
320
358
  followSystem: true, // open in the OS prefers-color-scheme, and keep following it
321
359
  // until the app calls setTheme/toggleTheme
322
360
  lightTheme: 'light', // the two names followSystem maps to and toggleTheme alternates
323
- darkTheme: 'cyberpunk', // between
361
+ darkTheme: 'one-dark', // between
324
362
  defaultTheme: 'light', // used when nothing else decides
325
363
  },
326
364
  });
@@ -368,10 +406,31 @@ async confirmDelete(): Promise<void> {
368
406
  }
369
407
  ```
370
408
 
371
- `DialogConfig`: `{ title?, component, data?, modal? (default true), closable?, draggable?, closeIconName?, closeIconTemplate?, width?, maxWidth?, role? ('dialog' default | 'alertdialog'), zIndex? }`.
372
- `open()` returns `{ close(result?), afterClosed: Promise<TResult | undefined> }`. Also:
409
+ `DialogConfig<TData>`: `{ title?, component, data?: TData, modal? (default true), closable?, draggable?, closeIconName?, closeIconTemplate?, width?, maxWidth?, role? ('dialog' default | 'alertdialog'), zIndex? }`.
410
+ `open<TResult, TData>()` returns `{ close(result?), afterClosed: Promise<TResult | undefined> }`. Also:
373
411
  `closeAll(result?)`, `updatePosition(id, offsetX, offsetY)` (for `draggable` dialogs).
374
412
 
413
+ **`open<TResult, TData>()` type-checks `data` against `TData` when you supply both type
414
+ arguments** — supplying only `TResult` (the common case above) leaves `TData` as `unknown`,
415
+ exactly as before:
416
+
417
+ ```ts
418
+ interface EditUserData {
419
+ userId: string;
420
+ }
421
+
422
+ const handle = this.dialogService.open<{ saved: boolean }, EditUserData>({
423
+ component: EditDialogComponent,
424
+ data: { userId: user.id }, // checked against EditUserData here
425
+ });
426
+ ```
427
+
428
+ This checks only the call site. `EditDialogComponent` still reads its data via `inject(DIALOG_DATA)`
429
+ — an `InjectionToken<unknown>` shared by every dialog, so it still needs its own cast
430
+ (`inject<EditUserData>(DIALOG_DATA)`, shown below). Angular's DI has no way to carry a
431
+ per-call-site type through one shared token, so the receiving half of the round trip is still on
432
+ trust — this closes only the half that can be closed.
433
+
375
434
  The library ships a ready-made `ConfirmationDialogComponent` for yes/no prompts — pass it as
376
435
  `component` with `data: { title, description, confirmText, cancelText }`; it resolves the
377
436
  dialog's result to `true`/`false`.
@@ -415,12 +474,20 @@ Every component below is exported from `@guildofgleks/ui`'s root — `import { X
415
474
  | `loading` | `boolean` | `false` | shows an inline `gog-spinner`, blocks clicks |
416
475
  | `debounce` | `number \| undefined` | `300` | ms; via `GOG_CONFIG.button.debounce` — see note below |
417
476
  | `ariaLabel` | `string \| null` | `null` | **use this, not a raw `aria-label` attribute** |
477
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
418
478
 
419
479
  Outputs: `gogClick: MouseEvent`.
420
480
 
421
481
  **`debounce` is a spam guard, not a delay before the first click.** The first click in a window
422
482
  fires immediately (leading edge); further clicks within `debounce` ms are silently dropped.
423
483
 
484
+ **Use `(gogClick)`, never `(click)`, on `gog-button`.** The click handler that drives `debounce`
485
+ and emits `gogClick` is bound on the `<button>` inside the component's own template, not on the
486
+ host — a native click still bubbles up through `<gog-button>`, so a `(click)` listener written
487
+ there fires on every press, silently bypassing the debounce entirely. This is specific to the
488
+ component: `[gogButton]` on your own `<a>`/`<button>` has no debounce to bypass, so `(click)` on
489
+ it works exactly as written.
490
+
424
491
  ```html
425
492
  <gog-button variant="primary" [loading]="saving()" (gogClick)="save()">Save</gog-button>
426
493
  <gog-button variant="ghost" ariaLabel="Close" (gogClick)="close()"
@@ -432,18 +499,19 @@ fires immediately (leading edge); further clicks within `debounce` ms are silent
432
499
 
433
500
  A row of buttons, single- or multi-select, built from your own option objects.
434
501
 
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`, `''` | |
502
+ | Input | Type | Default | Notes |
503
+ | ------------------------------------ | ------------------------------------------ | ---------------------- | --------------------------------------------- |
504
+ | `options` | `TOption[]` | `[]` | |
505
+ | `optionLabel` | accessor | `'name'` | |
506
+ | `optionValue` | accessor \| `null` | `'id'` | `null` emits the option object |
507
+ | `optionDisabled` | accessor | `'disabled'` | |
508
+ | `optionIcon` | accessor → `GogIconName \| null` \| `null` | `null` | optional leading icon per option |
509
+ | `multiple` | `boolean` | `false` | changes ARIA role entirely — see note |
510
+ | `appearance` | `'joined' \| 'separated'` | `'joined'` | |
511
+ | `orientation` | `GogOrientation` | `'horizontal'` | |
512
+ | `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
513
+ | `disabled`, `fullWidth`, `ariaLabel` | | `false`, `false`, `''` | |
514
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
447
515
 
448
516
  Model: `value: TValue | TValue[] | null` (single value, or array in `multiple` mode). CVA: yes.
449
517
  Slot: `<ng-template gogButtonToggleOption let-opt let-selected="selected">` for custom button
@@ -552,6 +620,7 @@ and multiselect unless noted otherwise):
552
620
  | `disabled`, `fullWidth` | | `false`, `true` | |
553
621
  | `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
554
622
  | `inputId` (select/autocomplete only) | `string` | `''` | |
623
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
555
624
 
556
625
  `gog-select`-specific: `value: model<TValue>(null)`.
557
626
  `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 +664,7 @@ panel-filter box; it filters/searches off what's typed in the field itself.
595
664
  | `loading` | `boolean` | `false` | shows a spinner in the trailing slot |
596
665
  | `emptyMessage` | `string` | `'No matches'` | |
597
666
  | `forceSelection` | `boolean` | `true` | see note below |
667
+ | `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
598
668
 
599
669
  Outputs: `gogSearch: string` (debounced query — wire your server lookup here),
600
670
  `gogLoadMore: void` (panel scrolled to the end — fetch the next page).
@@ -855,11 +925,12 @@ the element stays yours and the directive only gives it the look.
855
925
  <a gogButton fullWidth routerLink="/checkout">Checkout</a>
856
926
  ```
857
927
 
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` |
928
+ | Input | Type | Default |
929
+ | ----------- | ------------------------ | ---------------------------------------- |
930
+ | `variant` | `GogVariant` | `'primary'` |
931
+ | `size` | `GogSize \| undefined` | `'md'`; via `GOG_CONFIG.control.size` |
932
+ | `fullWidth` | `boolean` (bare attr ok) | `false` |
933
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
863
934
 
864
935
  Selector is `a[gogButton], button[gogButton]` — deliberately not a bare `[gogButton]`, because on
865
936
  a `<div>` the result looks like a button and is invisible to the keyboard and to assistive tech.
@@ -900,16 +971,17 @@ are impossible by design.
900
971
 
901
972
  #### `gog-chip`
902
973
 
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` |
974
+ | Input | Type | Default |
975
+ | ------------------------------ | ----------------------------------- | ---------------------------------------- |
976
+ | `size` | `GogSize` | `'md'` |
977
+ | `shape` | `GogTagShape` (`'rounded'\|'pill'`) | `'rounded'` |
978
+ | `disabled`, `clickable` | `boolean` | `false`, `true` |
979
+ | `removable` | `boolean` | `false` |
980
+ | `fullWidth` | `boolean` | `false` |
981
+ | `ariaLabel`, `removeAriaLabel` | `string` | `''`, `'Remove chip'` |
982
+ | `avatarUrl`, `avatarAlt` | `string \| null` / `string` | `null`, `''` |
983
+ | `iconName` | `GogIconName \| null` | `null` |
984
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
913
985
 
914
986
  Outputs: `gogClick: MouseEvent | KeyboardEvent`, `gogRemove: void`.
915
987
 
@@ -1000,6 +1072,63 @@ Label is projected content, not an input — put an icon or a `gog-tag` inside i
1000
1072
  <gog-divider>OR</gog-divider>
1001
1073
  ```
1002
1074
 
1075
+ #### `gogRipple` — directive, not a component
1076
+
1077
+ A pointer-position wash that grows from where you pressed and fades when you let go. Drop it on
1078
+ any element you already have — it adds no wrapper and changes no layout.
1079
+
1080
+ | Input | Type | Default |
1081
+ | ---------------- | --------- | ----------------------------------------------------------- |
1082
+ | `rippleDisabled` | `boolean` | `false` |
1083
+ | `rippleCentred` | `boolean` | `false` — start from the middle instead of from the pointer |
1084
+
1085
+ ```html
1086
+ <button gogRipple>Press me</button>
1087
+ <div gogRipple rippleCentred class="tile">A tile</div>
1088
+ ```
1089
+
1090
+ Four things suppress it, none of which you have to wire up: `rippleDisabled`, a host carrying
1091
+ `disabled`, a host carrying `aria-disabled="true"`, and `prefers-reduced-motion: reduce` — the
1092
+ last one **suppressed outright, not shortened**. Keyboard activation (`Enter`/`Space`) is always
1093
+ centred, because a key press carries no coordinates.
1094
+
1095
+ **Put it on the element that paints the surface.** The wash lives in its own layer that clips
1096
+ itself — the host is never given `overflow: hidden`, so a `gogBadge` on the same element is not
1097
+ clipped — and that layer takes its corner radius from its host with `border-radius: inherit`. On a
1098
+ wrapper whose _child_ paints the rounded background, the layer inherits the wrapper's radius (very
1099
+ often `0`) and the wash squares off at the corners.
1100
+
1101
+ Tokens: `--gog-ripple-color` (`currentColor`, so the wash reads as the surface's own foreground on
1102
+ a filled surface and a ghost one alike), `--gog-ripple-opacity`, `--gog-ripple-enter-duration`,
1103
+ `--gog-ripple-exit-duration`, `--gog-ripple-easing`. All five are ordinary inherited custom
1104
+ properties, so setting one anywhere above the host is the per-instance override.
1105
+
1106
+ #### Turning the ripple on for the library's own components
1107
+
1108
+ You do **not** add `gogRipple` to a `gog-*` component: each one already owns the element that
1109
+ paints its surface, so it wires its own. What you do is switch it on, once:
1110
+
1111
+ ```ts
1112
+ provideGogConfig({ ripple: { enabled: true } });
1113
+ ```
1114
+
1115
+ That covers `gog-button`, `[gogButton]`, `gog-button-toggle-group`, `gog-chip`, `gog-tabs`
1116
+ headers, `gog-accordion` headers, `gogCollapsibleTrigger`, `gogMenuItem`, and the options inside
1117
+ `gog-select` / `gog-multiselect` / `gog-autocomplete`. `gog-paginator` follows because its page
1118
+ buttons are `gog-button`s.
1119
+
1120
+ **Off by default**, so adding the ripple to the library changed the look of nothing. Every one of
1121
+ those takes a `ripple` input that beats the config in both directions: `[ripple]="false"` opts one
1122
+ control out of an app-wide on, `[ripple]="true"` opts one in without switching the app over.
1123
+
1124
+ Not covered, and deliberately: `gog-table` rows and `gogCardLink`. A row and a card are hundreds
1125
+ of pixels wide, so the wave has to travel the whole surface and reads as a flash rather than as
1126
+ feedback at the point you pressed — and a table renders one directive per row, with no
1127
+ virtualization in this library yet. Put `gogRipple` on them yourself if you disagree.
1128
+
1129
+ A chip that is not `clickable`, or is `disabled`, never ripples whatever the config says: a label
1130
+ answering a press is a promise it cannot keep.
1131
+
1003
1132
  #### `gogTooltip` — directive, not a component
1004
1133
 
1005
1134
  Drop on any element — a `gog-*` component's host tag or a plain native one.
@@ -1029,6 +1158,7 @@ Drop on any element — a `gog-*` component's host tag or a plain native one.
1029
1158
  | `skeletonCount` | `number` | `3` — rows shown while `loading` and `items` is still empty |
1030
1159
  | `showChevron` | `boolean` | `true` |
1031
1160
  | `headingLevel` | `2\|3\|4\|5\|6 \| undefined` | `undefined` — wraps headers in `role="heading"` when set |
1161
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
1032
1162
 
1033
1163
  Model: `openIds: ReadonlySet<string | number>`. Output: `gogToggle: { item, open }`.
1034
1164
 
@@ -1070,6 +1200,10 @@ ARIA wiring, because the browser already handles focus and keys. On anything els
1070
1200
  announces is one a keyboard can actually reach. If you set `role` or `tabindex` yourself, the
1071
1201
  directive leaves both alone: you have said what the element is.
1072
1202
 
1203
+ `gogCollapsibleTrigger` takes a **`ripple`** input of its own (`boolean | undefined`, `false`, via
1204
+ `GOG_CONFIG.ripple.enabled`) — the trigger is your element, but the directive owns the ripple so
1205
+ you do not have to add `gogRipple` beside it.
1206
+
1073
1207
  An open panel is as tall as its content — `--gog-collapsible-max-height` defaults to
1074
1208
  `max-content`. Set it to a length on an instance to cap one deliberately; the panel is
1075
1209
  `overflow: hidden`, so a cap **clips** rather than scrolls. (Before 21.4.4 that default was
@@ -1085,6 +1219,7 @@ An open panel is as tall as its content — `--gog-collapsible-max-height` defau
1085
1219
  | `fullWidth`, `ariaLabel` | | `false`, `''` |
1086
1220
  | `scrollActiveIntoView` | `boolean` | `true` |
1087
1221
  | `showScrollTrack` | `boolean \| undefined` | follows `scrollActiveIntoView` (hidden when it's on) |
1222
+ | `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
1088
1223
 
1089
1224
  Model: `activeIndex: number`. Output: `gogTabChange: number`.
1090
1225
 
@@ -1108,6 +1243,94 @@ hidden via `[hidden]` while inactive — preserves scroll/input state).
1108
1243
  </gog-tabs>
1109
1244
  ```
1110
1245
 
1246
+ #### `gog-card` + `gogCardHeader` / `gogCardMedia` / `gogCardFooter` / `gogCardLink`
1247
+
1248
+ A surface for one self-contained thing — a product tile, a summary, a search result.
1249
+
1250
+ | Input | Type | Default |
1251
+ | --------------- | -------------------------------------------------------- | --------------------------------------- |
1252
+ | `variant` | `GogSurfaceVariant` (`'outlined'\|'elevated'\|'filled'`) | `'outlined'` |
1253
+ | `size` | `GogSize` | `'md'` — drives padding and the row gap |
1254
+ | `disabled` | `boolean` (bare attribute works) | `false` |
1255
+ | `loading` | `boolean` (bare attribute works) | `false` |
1256
+ | `skeletonLines` | `number` | `2` — body lines shown while `loading` |
1257
+
1258
+ No outputs. Slots, all **attribute** directives on your own elements (not `ng-template`):
1259
+ `gogCardHeader`, `gogCardMedia`, `gogCardFooter`, `gogCardLink`. Layout order is fixed by the
1260
+ component — media, heading, body (the default slot), footer — not by the order you write them.
1261
+
1262
+ ```html
1263
+ <gog-card>
1264
+ <img gogCardMedia [src]="person.photo" alt="" />
1265
+ <h3 gogCardHeader><a gogCardLink [routerLink]="['/people', person.id]">{{ person.name }}</a></h3>
1266
+ <p>{{ person.role }}</p>
1267
+ <div gogCardFooter>
1268
+ <gog-button size="xsm" (gogClick)="shortlist(person)">Shortlist</gog-button>
1269
+ </div>
1270
+ </gog-card>
1271
+ ```
1272
+
1273
+ - **`gogCardHeader` names the card.** The card reads that element's `id` (minting one if it has
1274
+ none) and points its own `aria-labelledby` at it, with `role="group"`. A card with no header
1275
+ gets neither — an unnamed group is noise, not structure. The heading level is yours; the visual
1276
+ size comes from `--gog-card-heading-font-size` regardless of it.
1277
+ - **There is no `interactive` input, and no `gogClick` output.** A card becomes interactive by
1278
+ _containing_ a `gogCardLink`, which stretches that link's hit area over the whole surface. The
1279
+ link stays yours: `routerLink`, `href`, `target`, middle-click, "open in new tab" and Enter all
1280
+ behave normally, and the focus ring is drawn around the card. `gogCardLink` only applies to
1281
+ `<a>` and `<button>` — on a `<div>` it does nothing, deliberately.
1282
+ - **Other controls inside an interactive card still get their own clicks.** A footer button, a
1283
+ checkbox, a second link: each sits above the stretched hit area automatically.
1284
+ - Two costs of the pattern, inherent to it: text in the card cannot be selected by dragging, and
1285
+ a second link is reachable by keyboard but not by clicking the surface around it.
1286
+ - **`loading`** replaces the content with a title bar plus `skeletonLines` text lines and sets
1287
+ `aria-busy`; **`disabled`** dims the card, sets `aria-disabled`, and takes the card link out of
1288
+ the tab order. Both make the link non-clickable. For a _refresh_ of a card that already has
1289
+ content, project a `gog-spinner-overlay` instead — `loading` is the first-paint treatment.
1290
+ - `gogCardMedia` runs full-bleed to the card's edges, and rounds into its top corners when it is
1291
+ the first element in the card.
1292
+
1293
+ #### `gog-panel` + `gogPanelHeader` / `gogPanelFooter`
1294
+
1295
+ A titled region of a page — a settings section, a dashboard area, a form group.
1296
+
1297
+ | Input | Type | Default |
1298
+ | --------------- | -------------------------------- | ------------ |
1299
+ | `variant` | `GogSurfaceVariant` | `'elevated'` |
1300
+ | `size` | `GogSize` | `'lg'` |
1301
+ | `collapsible` | `boolean` (bare attribute works) | `false` |
1302
+ | `disabled` | `boolean` (bare attribute works) | `false` |
1303
+ | `loading` | `boolean` (bare attribute works) | `false` |
1304
+ | `skeletonLines` | `number` | `3` |
1305
+
1306
+ Model: `open: boolean` (default `true`, ignored while `collapsible` is off). No outputs beyond
1307
+ `openChange`. Slots: `gogPanelHeader`, `gogPanelFooter` — attribute directives on your elements.
1308
+
1309
+ ```html
1310
+ <gog-panel [collapsible]="true" [(open)]="notificationsOpen">
1311
+ <h2 gogPanelHeader>Notifications</h2>
1312
+ <gog-checkbox label="Email digest" [(checked)]="emailDigest" />
1313
+ <div gogPanelFooter><gog-button size="xsm">Save</gog-button></div>
1314
+ </gog-panel>
1315
+ ```
1316
+
1317
+ - **It is a landmark.** With a `gogPanelHeader` it renders `role="region"` named by that heading —
1318
+ which is why the panel gets one and `gog-card` gets `role="group"`: a handful of named regions
1319
+ is how a page is navigated, a landmark per card would bury that list.
1320
+ - **Collapsing composes `gog-collapsible`**, so the state, the id wiring and the animation are the
1321
+ library's existing ones. The heading stays a heading: the toggle is a separate `<button>` named
1322
+ by it through `aria-labelledby`, with its hit area stretched across the header row so clicking
1323
+ the title works for the pointer. Without a header the toggle falls back to
1324
+ `GOG_CONFIG.labels.togglePanel` (default `'Toggle section'`).
1325
+ - **A non-collapsible panel does not clip.** It undoes the collapse geometry it inherits,
1326
+ `overflow` included, so a dropdown or menu opened inside it escapes the panel's box. A
1327
+ _collapsible_ one does clip while animating, exactly like `gog-collapsible` — prefer
1328
+ `[appendToBody]` for an overlay inside one.
1329
+ - **`loading` keeps the heading and the footer** and replaces only the body: a page section is
1330
+ titled before its content arrives, and blanking the title would move the layout twice.
1331
+ - **The surface is never itself a link** — there is no `gogPanelLink`. Controls live inside a
1332
+ panel, and a region that is a link cannot hold them. Use `gog-card` for that.
1333
+
1111
1334
  #### `gog-paginator`
1112
1335
 
1113
1336
  | Input | Type | Default |
@@ -1198,7 +1421,7 @@ rather than a hard split, so those can usually go.
1198
1421
 
1199
1422
  **`stickyHeader` needs `maxHeight`** (both since 21.6.0 for the pairing). A sticky element
1200
1423
  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
1424
+ once that scroller moves on either axis it is a scroll container on _both_, because CSS coerces
1202
1425
  `overflow-y: visible` to `auto` beside a scrolling `overflow-x` (and `clip` to `hidden`). So the
1203
1426
  header can only ever stick to something inside the table — and without `maxHeight` that viewport
1204
1427
  is exactly as tall as its content and never scrolls, so there is nothing to stick to.
@@ -1386,6 +1609,10 @@ trigger's measured rect, so a menu inside `gog-scroll`, `gog-table` or any `over
1386
1609
  ancestor is not clipped and needs no configuration. It also takes the `--gog-dropdown-z` its
1387
1610
  trigger inherits, so a menu opened inside a `gog-dialog` stacks above the dialog.
1388
1611
 
1612
+ `gogMenuItem` takes a **`ripple`** input (`boolean | undefined`, `false`, via
1613
+ `GOG_CONFIG.ripple.enabled`). The item is your own `<button>`, but the directive owns the ripple,
1614
+ so there is no `gogRipple` to add.
1615
+
1389
1616
  Output: `gogClosed` — fires after every close, whatever caused it.
1390
1617
 
1391
1618
  Public methods, for driving it yourself: `open(trigger, 'first' | 'last')`, `close(restoreFocus?)`,
@@ -1463,17 +1690,33 @@ Everything the package currently deprecates, as data:
1463
1690
  ```ts
1464
1691
  import { GOG_DEPRECATIONS, type GogDeprecation } from '@guildofgleks/ui';
1465
1692
 
1466
- GOG_DEPRECATIONS.filter((entry) => entry.removedIn === '21.7.0');
1467
- // { kind: 'token', name: '--gog-btn-bg', replacement: '--gog-button-bg',
1468
- // since: '21.5.0', sinceDate: '2026-08-19', removedIn: '21.7.0' }
1693
+ GOG_DEPRECATIONS; // []
1469
1694
  ```
1470
1695
 
1471
1696
  `kind` is `'symbol'` for an export or input and `'token'` for a `--gog-*` custom property. The
1472
1697
  list is generated from the library's source — tags for symbols, stylesheets for tokens — so it
1473
1698
  matches what actually still resolves in the version you installed.
1474
1699
 
1475
- **In 21.5.0 it holds 154 tokens and no symbols.** Nothing in the TypeScript API is deprecated
1476
- right now; the three abbreviated token prefixes are, until 21.7.0.
1700
+ **As of 21.7.0 the list is empty on both halves.** Nothing in the TypeScript API is deprecated, and
1701
+ the three abbreviated token prefixes that used to fill the token half are gone rather than
1702
+ deprecated — see the removal table below. An empty list here means exactly that: nothing to
1703
+ migrate away from right now.
1704
+
1705
+ ## Removed in 21.7.0
1706
+
1707
+ **Nothing in this table exists any more.** Three CSS custom-property prefixes, abbreviations of a
1708
+ component's own name, are gone — each was honoured only as a fallback the spelled-out token wrapped
1709
+ (`--gog-button-x: var(--gog-btn-x, value)`), never declared on its own.
1710
+
1711
+ | Removed | Replacement |
1712
+ | ----------------- | ----------------------------- |
1713
+ | `--gog-btn-*` | `--gog-button-*` |
1714
+ | `--gog-ms-*` | `--gog-multiselect-*` |
1715
+ | `--gog-confirm-*` | `--gog-confirmation-dialog-*` |
1716
+
1717
+ A consumer's CSS that still sets one of the left-hand names doesn't fail their build — an
1718
+ unresolved `var()` just stops matching anything, silently. If a themed surface stopped picking up
1719
+ an override after upgrading to 21.7.0, this table is the first thing to check.
1477
1720
 
1478
1721
  ## Removed in 21.5.0
1479
1722
 
@@ -1508,6 +1751,7 @@ Shared enum-like types (`import type { ... } from '@guildofgleks/ui'`):
1508
1751
  | ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
1509
1752
  | `GogSize` | `'xsm' \| 'sm' \| 'md' \| 'lg' \| 'slg'` |
1510
1753
  | `GogVariant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost'` |
1754
+ | `GogSurfaceVariant` | `'outlined' \| 'elevated' \| 'filled'` — `gog-card` and `gog-panel` |
1511
1755
  | `GogTagVariant` | `'success' \| 'danger' \| 'warning' \| 'info'` |
1512
1756
  | `GogOrientation` | `'horizontal' \| 'vertical'` |
1513
1757
  | `GogTagShape` | `'rounded' \| 'pill'` |