@guildofgleks/ui 21.6.1 → 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.6.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
 
@@ -253,40 +290,39 @@ in English sets these once rather than on every control:
253
290
  ```ts
254
291
  provideGogConfig({
255
292
  labels: {
256
- clear: 'Очистить', // inputfield / textarea clear button
257
- clearSelection: 'Очистить выбор', // select / multiselect / autocomplete
258
- clearDate: 'Очистить дату', // datepicker
259
- selectAll: 'Выбрать все', // multiselect panel
260
- clearAll: 'Очистить', // multiselect panel
261
- increment: 'Увеличить', // number spin buttons
262
- decrement: 'Уменьшить',
263
- showPassword: 'Показать пароль',
264
- hidePassword: 'Скрыть пароль',
265
- closeDialog: 'Закрыть',
266
- closeToast: 'Закрыть',
267
- pagination: 'Навигация по страницам',
268
- previousPage: 'Предыдущая страница',
269
- nextPage: 'Следующая страница',
270
- openCalendar: 'Открыть календарь',
271
- togglePanel: 'Свернуть раздел', // gog-panel's toggle, only when it has no heading
272
- rowsPerPage: 'Строк на странице', // gog-paginator's size select
273
- total: 'Всего', // gog-table's row-count label
274
- tablePagination: 'Навигация по таблице',
275
- selectRow: 'Выбрать строку',
276
- selectAllRows: 'Выбрать все строки на странице',
277
- today: 'Сегодня',
278
- thisMonth: 'Текущий месяц',
279
- previousMonth: 'Предыдущий месяц',
280
- nextMonth: 'Следующий месяц',
281
- previousYear: 'Предыдущий год',
282
- nextYear: 'Следующий год',
283
- hours: 'Часы',
284
- minutes: 'Минуты',
285
- 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',
286
323
  // The one non-string field: it interpolates the page number, and word order and
287
324
  // agreement around a number vary by language, so it takes a formatter.
288
- page: (page, isCurrent) =>
289
- isCurrent ? `Страница ${page}, текущая` : `Перейти на страницу ${page}`,
325
+ page: (page, isCurrent) => (isCurrent ? `Seite ${page}, aktuell` : `Zu Seite ${page} wechseln`),
290
326
  },
291
327
  });
292
328
  ```
@@ -322,7 +358,7 @@ provideGogConfig({
322
358
  followSystem: true, // open in the OS prefers-color-scheme, and keep following it
323
359
  // until the app calls setTheme/toggleTheme
324
360
  lightTheme: 'light', // the two names followSystem maps to and toggleTheme alternates
325
- darkTheme: 'cyberpunk', // between
361
+ darkTheme: 'one-dark', // between
326
362
  defaultTheme: 'light', // used when nothing else decides
327
363
  },
328
364
  });
@@ -370,10 +406,31 @@ async confirmDelete(): Promise<void> {
370
406
  }
371
407
  ```
372
408
 
373
- `DialogConfig`: `{ title?, component, data?, modal? (default true), closable?, draggable?, closeIconName?, closeIconTemplate?, width?, maxWidth?, role? ('dialog' default | 'alertdialog'), zIndex? }`.
374
- `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:
375
411
  `closeAll(result?)`, `updatePosition(id, offsetX, offsetY)` (for `draggable` dialogs).
376
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
+
377
434
  The library ships a ready-made `ConfirmationDialogComponent` for yes/no prompts — pass it as
378
435
  `component` with `data: { title, description, confirmText, cancelText }`; it resolves the
379
436
  dialog's result to `true`/`false`.
@@ -424,6 +481,13 @@ Outputs: `gogClick: MouseEvent`.
424
481
  **`debounce` is a spam guard, not a delay before the first click.** The first click in a window
425
482
  fires immediately (leading edge); further clicks within `debounce` ms are silently dropped.
426
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
+
427
491
  ```html
428
492
  <gog-button variant="primary" [loading]="saving()" (gogClick)="save()">Save</gog-button>
429
493
  <gog-button variant="ghost" ariaLabel="Close" (gogClick)="close()"
@@ -1626,17 +1690,33 @@ Everything the package currently deprecates, as data:
1626
1690
  ```ts
1627
1691
  import { GOG_DEPRECATIONS, type GogDeprecation } from '@guildofgleks/ui';
1628
1692
 
1629
- GOG_DEPRECATIONS.filter((entry) => entry.removedIn === '21.7.0');
1630
- // { kind: 'token', name: '--gog-btn-bg', replacement: '--gog-button-bg',
1631
- // since: '21.5.0', sinceDate: '2026-08-19', removedIn: '21.7.0' }
1693
+ GOG_DEPRECATIONS; // []
1632
1694
  ```
1633
1695
 
1634
1696
  `kind` is `'symbol'` for an export or input and `'token'` for a `--gog-*` custom property. The
1635
1697
  list is generated from the library's source — tags for symbols, stylesheets for tokens — so it
1636
1698
  matches what actually still resolves in the version you installed.
1637
1699
 
1638
- **In 21.5.0 it holds 154 tokens and no symbols.** Nothing in the TypeScript API is deprecated
1639
- 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.
1640
1720
 
1641
1721
  ## Removed in 21.5.0
1642
1722
 
package/CHANGELOG.md CHANGED
@@ -4,6 +4,165 @@ 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.7.0] - 29.08.2026
8
+
9
+ ### Added
10
+
11
+ - **Every shipped theme now meets WCAG AA, and the check runs in CI.** Nine gated failures across
12
+ five palettes were fixed: muted text in `slate`, `one-dark` and `one-light`; the filled button's
13
+ label, at rest and on hover, in `light`, `primeng`, `one-light` and `slate`. `check:contrast` is
14
+ now a CI step — it was deliberately kept out while any finding was open, because a permanently
15
+ red step over a known condition teaches everyone to ignore CI.
16
+
17
+ **`one-dark` and `one-light` changed colour, and that is a trade you may notice.** They
18
+ reproduce a named editor palette; `#5c6370` is One Dark's own comment colour and reads at 2.32:1
19
+ on its own background — fine for code, well under AA for UI text. Muted text is now `#9099a8`
20
+ (dark) and `#6e6f77` (light). If you depended on the exact original hues, set
21
+ `--gog-muted-text-color` back in your own theme block.
22
+
23
+ - **Tokens read from TypeScript now resolve `calc()` instead of silently falling back.**
24
+ `--gog-scroll-thumb-min-size`, `--gog-tooltip-gap`, `--gog-tooltip-z` and `--gog-dropdown-z` were
25
+ read with `parseFloat`, which returns `NaN` for `calc(...)` — and every caller turned that into
26
+ its default without a word. **This is a consumer-facing fix:** writing
27
+ `--gog-scroll-thumb-min-size: calc(2rem + 4px)` in your own theme now works, where before it
28
+ quietly became 32px. `rem`, `em` and `%` work too.
29
+
30
+ - **Two WCAG AA contrast fixes, both on a filled button's own label.** The library's own `light`
31
+ theme: `--gog-accent-color` `#9e6f00` → `#926600` (white on the old value was 4.44:1, under
32
+ AA's 4.5), and its hover fill `#c88e00` → `#7a5500` — the hover is now _darker_ than the rest
33
+ state rather than brighter, because gold light enough to read as "a brighter gleam" cannot
34
+ carry white text at all (the old hover was 2.87:1, worse than the rest state everyone had
35
+ noticed). `primeng`: moved one step down Aura's own blue ramp, `#3b82f6` → `#2563eb`
36
+ (500 → 600), rather than inventing a colour outside the palette it reproduces; 3.68:1 → 5.17:1.
37
+
38
+ **If you were overriding either token, nothing breaks** — but a theme built on top of `light`'s
39
+ gold may want to re-check its own derived shades. `check:contrast` now also tests the hover
40
+ fill, which was never checked before; `one-dark`, `one-light` and `slate` keep their known
41
+ findings on purpose (see `docs/backlog.md`).
42
+
43
+ - **Three new presets complete the catalogue: `terminal`, `bevel` and `parchment`.** Retro and
44
+ Historical, the two families `docs/themes.md` had left unbuilt. `terminal` is green phosphor on
45
+ an unlit screen, monospaced throughout, square, no motion. `bevel` is the early-web desktop —
46
+ grey panels, raised buttons, sunken fields, navy — and is the first theme to use
47
+ `--gog-border-style: outset`/`inset`, which is what the character layer carries a border _style_
48
+ for. `parchment` is ink on laid paper: old-style serif, oxblood accent, roomy margins. Each is
49
+ `@import '@guildofgleks/ui/styles/presets/<name>.css'`, then `data-theme="<name>"`. All three
50
+ pass `check:contrast` on every gated pair.
51
+
52
+ - **Optional webfont files, and the rule they exist to keep: a preset never makes a network
53
+ request.** Each preset sets a font stack that resolves to a real system face. Where a webfont
54
+ is worth offering, it is a separate file the consumer imports _after_ the preset —
55
+ `presets/terminal.fonts.css` (IBM Plex Mono) and `presets/parchment.fonts.css` (EB Garamond).
56
+ Import the preset alone and nothing is downloaded.
57
+
58
+ - **`slate`, `one-dark` and `one-light` now set a character, not just a palette** — so every
59
+ shipped preset does. They were palette-only, which made them recoloured defaults: they wore another product's
60
+ colours on this library's shape. Each now sets the character layer and a density —
61
+ `slate` becomes the catalogue's _soft modern_ entry (12px corners, hairline borders, roomier at
62
+ `1.05`), and `one-dark`/`one-light` become editor chrome (4px corners, compact at `0.9`,
63
+ sentence-case labels), deliberately identical to each other so a toggle between them changes
64
+ tone and nothing else. **Palettes are unchanged**; `check:contrast` reports the same eight
65
+ findings as before. See `docs/themes.md` iteration 4.
66
+
67
+ - **`--gog-density`: one number that sets the spacing of the whole library.** A theme makes
68
+ every component tighter or roomier with `--gog-density: 0.9` — no component tokens named, no
69
+ list to keep up to date as components are added. It multiplies a new fourteen-step spacing
70
+ scale (`--gog-space-2` … `--gog-space-48`, named for their pixel value at density 1), and
71
+ every padding and gap in `theme.css` now derives from that scale. The three shipped presets
72
+ that carry a character layer use it: `ledger` at `0.9` (administrative software packed a
73
+ screen), `primeng` at `0.95`, `material` at `1.1` (M3's posture is generous by design).
74
+
75
+ The five existing `--gog-space-xs|sm|md|lg|2xl` names are kept as aliases with their exact
76
+ previous values, so no existing theme or consumer stylesheet changes. **Nothing moves at
77
+ density 1** except thirteen paddings that were never on a grid to begin with — five of them
78
+ fractional `rem` values computing to 3.6px, 4.4px, 4.8px, 7.2px and 9.6px — which now round
79
+ to the nearest 2px step. The largest single change is 4px, on `--gog-panel-slg-padding-x`.
80
+ See `docs/themes.md` iteration 6.
81
+
82
+ - **A new shipped preset: `ledger`** — the square-cornered, hard-shadowed, system-font
83
+ administrative-software identity. Beige and grey, a muted navy accent, uniform 1px borders,
84
+ zero corner radius, a hard offset shadow with no blur, and every transition duration set to
85
+ `0s`. `@import '@guildofgleks/ui/styles/presets/ledger.css'`, then `data-theme="ledger"`.
86
+ Palette-plus-character-layer only, like `material`/`primeng` below but with no per-component
87
+ overrides needed at all — see `docs/themes.md` iteration 4. Named `ledger`, not the plan's own
88
+ "Classic" (that names the catalogue _family_, not this preset) — `ui-showcase` already uses
89
+ "Classic" as the display label for `data-theme="light"`.
90
+
91
+ - **Two new shipped presets: `material` and `primeng`** — full visual identities (Material
92
+ Design 3 baseline and PrimeNG's Aura), not just palettes, ported from `gleks-ui-lab`'s
93
+ compare page. `@import '@guildofgleks/ui/styles/presets/material.css'` /
94
+ `.../presets/primeng.css'`, then `data-theme="material"` / `"primeng"`. Unlike `slate`/
95
+ `one-dark`/`one-light`, both set the character layer (corner rounding, border weight, casing,
96
+ tracking) plus a handful of settings the character layer has no vocabulary for yet (a pill
97
+ button, a table's header font). See each file's own header comment for what's per-component
98
+ and why.
99
+
100
+ - **Four new foundation tokens give a theme somewhere to set corner rounding, border weight and
101
+ emphasis casing/tracking once, instead of per component:** `--gog-text-transform` (default
102
+ `uppercase`), `--gog-letter-spacing` (default `1px`), `--gog-border-width` and `--gog-border-style`
103
+ (default `1px solid` — a third tier alongside the existing `--gog-control-border-*` for form
104
+ fields and `--gog-panel-border-*` for raised surfaces, covering everything smaller and inline:
105
+ chips, tags, badges, toggles, table rows, tabs, calendar cells). 40 component tokens across
106
+ `theme.css` now derive from these plus the pre-existing `--gog-radius` — no default changed;
107
+ every conversion was checked to render the identical pixel value before and after. Not
108
+ converted, on purpose: pill/circle radii and deliberately-flat corners (a rounding axis
109
+ shouldn't reshape a shape choice), and per-component values with no shared pattern to extract.
110
+ `docs/themes.md` has the full audit. Setting any of the four in a `[data-theme]` block now
111
+ restyles every component that reads it, with no per-component overrides to list.
112
+
113
+ - **`DialogService.open()` and `DialogConfig` gain an optional `TData` generic**, checked against
114
+ `data` at the call site: `dialogService.open<TResult, TData>({ data: /* checked against TData */ })`.
115
+ Supplying only `TResult` — the existing, common form — leaves `TData` as `unknown`, exactly as
116
+ before; nothing about an existing call changes. This closes only the sending half of the round
117
+ trip: the component you open still reads its data via `inject(DIALOG_DATA)`, one
118
+ `InjectionToken<unknown>` shared by every dialog, so it still needs its own cast
119
+ (`inject<TData>(DIALOG_DATA)`) — Angular's DI has no way to carry a per-call-site type through a
120
+ single shared token. See `AGENTS.md`'s `DialogService` section for the full example.
121
+
122
+ ### Removed
123
+
124
+ - **The three abbreviated token prefixes deprecated in 21.5.0 (21.3.0 for `--gog-ms-*`) are gone.**
125
+ Each had been declared nowhere, honoured only as a fallback the spelled-out token wrapped:
126
+ `--gog-button-x: var(--gog-btn-x, value)`. The fallback layer is deleted; the spelled-out name is
127
+ now the plain declaration, with the same final value as before. Rename any of the following you
128
+ still set, in your own stylesheet — a `var()` reference to a name nothing declares does not fail
129
+ a build, it just silently stops matching anything:
130
+
131
+ | Old | New |
132
+ | ----------------- | ----------------------------- |
133
+ | `--gog-btn-*` | `--gog-button-*` |
134
+ | `--gog-ms-*` | `--gog-multiselect-*` |
135
+ | `--gog-confirm-*` | `--gog-confirmation-dialog-*` |
136
+
137
+ `GogTokenName` (the exported type listing every `--gog-*` custom property) drops the 20 old
138
+ spellings it used to include — a compile error on a variable annotated with one of them is the
139
+ one part of this change your build will actually catch.
140
+
141
+ ### Fixed
142
+
143
+ - **Four tokens that resolved to nothing, found while surveying this release's removal below.**
144
+ Each was a `var(--gog-…)` with no fallback, naming a custom property nothing declares — which
145
+ makes the token holding it guaranteed-invalid, so the declaration reading it silently computes to
146
+ nothing. No build error, nothing to see except the missing style:
147
+
148
+ - **`gog-multiselect`'s focus glow never rendered.** Its default read the deprecated
149
+ `--gog-ms-focus-ring`, declared nowhere — now reads its own `--gog-multiselect-focus-ring`,
150
+ matching `gog-input` and `gog-select`.
151
+ - **The filter box inside `gog-select` and `gog-multiselect` had no border and the wrong text
152
+ colour.** Both read a `--gog-{select,multiselect}-control-*` pair that was never declared
153
+ anywhere in the library — now read `--gog-{select,multiselect}-field-*`, the tokens that were
154
+ actually meant. The border loss was total (`border` is a shorthand, so one invalid part drops
155
+ the whole declaration); the colour loss was invisible, because `color` just inherited instead.
156
+ - **`gog-multiselect`'s JS-computed panel height silently used hardcoded defaults instead of the
157
+ themed metrics.** Its `GogDropdownBase` token overrides named the deprecated `--gog-ms-*`
158
+ prefix, which `theme.css` has never declared as a real property — so
159
+ `getComputedStyle().getPropertyValue()` always read `''`. Now reads `--gog-multiselect-*`,
160
+ matching the pattern `gog-select` already used correctly.
161
+
162
+ A consumer who set `--gog-multiselect-focus-glow`, `--gog-select-filter-input-color/-border` or
163
+ their `gog-multiselect` counterparts directly, to work around any of the first two being missing,
164
+ keeps working exactly as before — an explicit override still wins over the (now real) default.
165
+
7
166
  ## [21.6.1] - 26.08.2026
8
167
 
9
168
  ### Added
package/README.md CHANGED
@@ -81,6 +81,13 @@ export class ExampleComponent {}
81
81
  Outputs are prefixed `gog` (`gogClick`, `gogToggle`) so they never collide with native DOM
82
82
  events. Inputs keep their natural names.
83
83
 
84
+ > **Don't write `(click)` on `gog-button`.** Its click handler is bound on the `<button>` inside
85
+ > its own template, not on the host — a native click still bubbles up through the host element, so
86
+ > a `(click)` listener there fires on every press, silently bypassing `debounce`'s throttling.
87
+ > `(gogClick)` is the one that only emits once the debounce window has passed; use it instead. This
88
+ > is specific to the `gog-button` component — `[gogButton]` on your own `<a>`/`<button>` has no
89
+ > debounce to bypass, so your own `(click)` on it works exactly as written.
90
+
84
91
  **3. If you use dialogs or toasts, place their hosts once.** `DialogService.open()` and
85
92
  `ToastService.show()` update state but render nothing without them:
86
93
 
@@ -116,8 +123,12 @@ want for a toast.
116
123
 
117
124
  Every value the components paint with lives in `styles/theme.css`, in three layers:
118
125
 
119
- **Foundation** — palette, type scale, spacing, motion. Override these to restyle everything at
120
- once; component tokens derive from them, so a palette swap carries through on its own.
126
+ **Foundation** — palette, type scale, spacing, motion, and a small _character_ layer: corner
127
+ rounding (`--gog-radius`), border weight (`--gog-control-border-*` for form fields,
128
+ `--gog-panel-border-*` for raised surfaces, `--gog-border-*` for everything smaller and inline),
129
+ and emphasis casing/tracking (`--gog-text-transform`, `--gog-letter-spacing`). Override these to
130
+ restyle everything at once; component tokens derive from them, so a palette or character change
131
+ carries through on its own, with nothing to re-list per component.
121
132
 
122
133
  **Component** — `--gog-<component>-*`, one block per component, named after the component you
123
134
  write in markup (`gog-button` → `--gog-button-*`), to restyle a single component app-wide:
@@ -139,11 +150,11 @@ variant and size classes:
139
150
  }
140
151
  ```
141
152
 
142
- > **Renamed in 21.5.0.** Three prefixes were abbreviated and are now spelled out:
143
- > `--gog-btn-*` → `--gog-button-*`, `--gog-confirm-*` → `--gog-confirmation-dialog-*`, and
144
- > `--gog-ms-*` → `--gog-multiselect-*` (that one since 21.3.0). **The old spellings still work**
145
- > every new name derives from its old twinand are **removed in 21.7.0**. A CSS override that
146
- > stops being read fails silently, which is why the window is two minors rather than one.
153
+ > **Renamed in 21.5.0, removed in 21.7.0.** Three prefixes were abbreviated and are now spelled
154
+ > out: `--gog-btn-*` → `--gog-button-*`, `--gog-confirm-*` → `--gog-confirmation-dialog-*`, and
155
+ > `--gog-ms-*` → `--gog-multiselect-*` (that one since 21.3.0). **The old spellings no longer
156
+ > resolve.** If you set one of them, rename ita `var()` reference to a name nothing declares
157
+ > doesn't fail your build, it just silently stops matching anything.
147
158
  >
148
159
  > One prefix that looks abbreviated and is not: **`--gog-input-*`**. It names the shared
149
160
  > text-field block that both `gog-inputfield` and `gog-textarea` render (`.gog-input__field`), not
@@ -167,7 +178,7 @@ The active theme is a `data-theme` attribute on `:root`, managed by `ThemeServic
167
178
  ```ts
168
179
  private readonly theme = inject(ThemeService);
169
180
  this.theme.toggleTheme(); // light ⇄ dark
170
- this.theme.setTheme('cyberpunk'); // any name you declared in CSS
181
+ this.theme.setTheme('one-dark'); // any preset you imported, or any name you declared in CSS
171
182
  ```
172
183
 
173
184
  Out of the box it adopts whatever `data-theme` is already on the document, or `light`.
@@ -175,19 +186,64 @@ Persisting the choice and following the OS setting are opt-in:
175
186
 
176
187
  ```ts
177
188
  provideGogConfig({
178
- theme: { storageKey: 'app-theme', followSystem: true, darkTheme: 'cyberpunk' },
189
+ theme: { storageKey: 'app-theme', followSystem: true, darkTheme: 'one-dark' },
179
190
  });
180
191
  ```
181
192
 
182
- A theme only declares what it changes — the derived layer re-resolves against whatever palette is
183
- in scope, so a new palette restyles every component without listing any of them. The `slate`
184
- preset is the worked example, palette-only:
193
+ A theme only declares what it changes — the derived layer re-resolves against whatever is in
194
+ scope, so a theme restyles every component without listing any of them. **Eleven declarations
195
+ are a whole visual identity**, not a recolour:
185
196
 
186
197
  ```css
187
- @import '@guildofgleks/ui/styles/index.css';
188
- @import '@guildofgleks/ui/styles/presets/slate.css';
198
+ [data-theme='compact'] {
199
+ /* Palette: every colour in the library re-derives from these. */
200
+ --gog-background-color: #f4f6f8;
201
+ --gog-surface-color: #ffffff;
202
+ --gog-text-color: #1e293b;
203
+ --gog-accent-color: #4f46e5;
204
+
205
+ /* Character: every corner, border, label and gap in the library re-derives from these. */
206
+ --gog-radius: 2px;
207
+ --gog-density: 0.85; /* one number = every padding and gap at once */
208
+ --gog-control-border-width: 1px;
209
+ --gog-text-transform: none;
210
+ --gog-letter-spacing: normal;
211
+ }
189
212
  ```
190
213
 
214
+ The palette half has always worked this way. The **character layer** (`--gog-radius`,
215
+ `--gog-density`, the border and casing tokens — all since 21.7.0) is the other half: it is what
216
+ lets a theme change the library's _shape_ — square or generous corners, thin borders,
217
+ sentence-case labels, tight or roomy spacing — still without naming a single component.
218
+
219
+ Nine presets ship alongside the built-in `light` and `dark`, each at `styles/presets/<name>.css`
220
+ and activated by `data-theme="<name>"`. All nine set palette **and** character:
221
+
222
+ | Preset | The identity |
223
+ | ----------------------- | ----------------------------------------------------------------------- |
224
+ | `slate` | soft modern — 12px corners, hairline borders, roomier than the default |
225
+ | `one-dark`, `one-light` | editor chrome — 4px corners, compact, sentence case; one UI, two tones |
226
+ | `material`, `primeng` | Material Design 3 and PrimeNG Aura, including their shape and density |
227
+ | `ledger` | administrative software — square corners, hard offset shadow, no motion |
228
+ | `terminal` | green phosphor — monospaced throughout, square, no motion |
229
+ | `bevel` | the early-web desktop — raised buttons, sunken fields, grey and navy |
230
+ | `parchment` | ink on laid paper — old-style serif, oxblood accent, roomy |
231
+
232
+ **No preset downloads a font.** Each sets a stack that resolves to a real system face — the
233
+ platform's own monospace for `terminal`, Tahoma/Verdana for `bevel`, Iowan Old Style/Palatino for
234
+ `parchment` — so importing a preset never adds a network request. Where a webfont makes a visible
235
+ difference, it lives in a separate opt-in file you import _after_ the preset:
236
+
237
+ ```css
238
+ @import '@guildofgleks/ui/styles/presets/parchment.css';
239
+ @import '@guildofgleks/ui/styles/presets/parchment.fonts.css'; /* optional: EB Garamond */
240
+ ```
241
+
242
+ `terminal.fonts.css` (IBM Plex Mono) is the other one. `material` and `primeng` additionally set
243
+ a few things the character layer has no vocabulary for (a pill button, a table's header font), and
244
+ `bevel` sets one (a button's bevel must disagree with a field's). `AGENTS.md` has the per-preset
245
+ detail and the full token list.
246
+
191
247
  Fonts are left alone on purpose (system stacks, no webfont download). Add
192
248
  `@guildofgleks/ui/styles/fonts.css` for the showcase's typography.
193
249
 
@@ -202,7 +258,7 @@ provideGogConfig({
202
258
  dropdown: { appendToBody: true },
203
259
  datepicker: { locale: 'de-DE', format: 'dd.MM.yyyy' },
204
260
  ripple: { enabled: true }, // press feedback on every interactive surface at once
205
- labels: { clear: 'Очистить', selectAll: 'Выбрать все' }, // translate the library once
261
+ labels: { clear: 'Löschen', selectAll: 'Alle auswählen' }, // translate the library once
206
262
  });
207
263
  ```
208
264