@guildofgleks/ui 21.4.1 → 21.4.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,1006 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@guildofgleks/ui` are documented here. Format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.0.0/); this project has not yet
5
+ reached 1.0, so breaking changes may land in minor versions.
6
+
7
+ ## [21.5.0] - planned
8
+
9
+ Nothing yet. What this release already owes is in the source: every
10
+ `@deprecated … Removed in 21.5.0` tag — the six legacy icon inputs on `gog-inputfield`, the
11
+ `checkIconTemplate` / `clearIconTemplate` / `iconTemplate` / `chevronTemplate` inputs, the
12
+ `<column>` element with its `Column` const and type, the string-keyed `[template]` column slot,
13
+ and the `GogSelectOption` / `GogMultiselectOption` aliases (those two overran their announced
14
+ 21.4.0 removal). Each names its replacement in the tag itself.
15
+
16
+ ## [21.4.3] - 16.08.2026
17
+
18
+ Two components stopped fighting their container. Nothing was added or removed from any public
19
+ API, but **`gog-calendar` and `gog-datepicker [inline]` are narrower than they were** — see the
20
+ first entry if you were relying on a calendar filling its container.
21
+
22
+ ### Changed
23
+
24
+ - **`gog-calendar` caps itself at its own month grid.** Its host was a block with no width, so
25
+ in any container wider than the grid the header — a flex row carrying the title and its six
26
+ nav chevrons — stretched the full width while the days huddled at the start. Every consumer
27
+ placing a calendar in a plain container had to write a width by hand to get a calendar that
28
+ looked like one. The new `--gog-calendar-max-width` (default `max-content`) does it once:
29
+ `max-content` needs no numbers and already tracks the size variants, `numberOfMonths`,
30
+ `showTime` and locales whose weekday abbreviations run wider than English's. It is a cap, so
31
+ a narrower container still constrains the host.
32
+
33
+ This sizes `inline` mode too — `[inline]="true"` renders `gog-calendar` with a border and
34
+ nothing else. **Set `--gog-calendar-max-width: 100%` to keep the old full-width behaviour.**
35
+ - **`--gog-datepicker-panel-width`** (default `max-content`) exposes the dropdown panel's width,
36
+ which was hardcoded. Same value as before; nothing changes unless you set it.
37
+
38
+ ### Fixed
39
+
40
+ - **`gog-tabs` no longer scrolls the page to reach its active header.** It centred the active
41
+ header with `Element.scrollIntoView()`, which scrolls _every_ scrollable ancestor that needs to
42
+ move — so a tabs component below the fold dragged the whole page down to itself the moment it
43
+ initialised, and a page with several of them landed the reader at the last one. It now scrolls
44
+ its own header viewport directly: the active header still centres, nothing outside the
45
+ component moves. No API change; `scrollActiveIntoView` behaves as documented, only without the
46
+ side effect.
47
+
48
+ ## [21.4.2] - 15.08.2026
49
+
50
+ Documentation only — no component, input, output or token changed, so upgrading from 21.4.1 is
51
+ a version bump with nothing to migrate.
52
+
53
+ ### Added
54
+
55
+ - **`CHANGELOG.md` now ships inside the npm package**, alongside `README.md`, `AGENTS.md` and
56
+ `TOKENS.md`. It was repo-only, which meant nothing could show release notes for the exact
57
+ version a reader has installed — and this file is the only source that cannot drift from the
58
+ package, because it travels inside it. Read it at
59
+ `node_modules/@guildofgleks/ui/CHANGELOG.md`; it is also what the documentation site's releases
60
+ page will render. Costs ~68 KB in the tarball and nothing at runtime.
61
+
62
+ ### Fixed
63
+
64
+ - **`AGENTS.md` was missing `gog-slider`'s range mode.** `range`, `rangeValue`
65
+ (`GogSliderRange`), `startDisabled`/`endDisabled` and `startAriaLabel`/`endAriaLabel` shipped
66
+ in 21.3.1 but never reached the agent reference. An agent reading it concluded the slider
67
+ cannot express a range and built a two-slider workaround — the API was there all along.
68
+ - **`AGENTS.md` did not mention the `GOG_ICONS` token.** `provideGogIcons(...)` was documented,
69
+ but not the token it provides — which is what an app injects to read the registered set back
70
+ (an icon picker enumerating it). Public since 21.4.0, undocumented until now.
71
+
72
+ `AGENTS.md` is the file a coding agent reads to build against this package, so a gap in it
73
+ produces code written against API that does not exist, or workarounds for API that does. Both
74
+ entries above are that kind of gap, which is why a documentation-only patch was worth cutting.
75
+
76
+ ## [21.4.1] - 14.08.2026
77
+
78
+ ### Fixed
79
+
80
+ - **Overlays ignored custom properties set on `:root`.** A select panel, tooltip or any other
81
+ overlay rendered into `<body>` copied the `data-theme` of its trigger's nearest themed
82
+ ancestor. When that ancestor is `<html>` — the usual case — the copy made the overlay match
83
+ `theme.css`'s derived layer (`:root, [data-theme]`) *locally*, re-declaring every component
84
+ token against the plain preset palette and discarding anything set on the root that the preset
85
+ does not itself declare.
86
+
87
+ Inline custom properties are what this hit: a page that overrides `--gog-*` on
88
+ `document.documentElement` — a live theme editor, or any runtime accent switch — saw the
89
+ document follow while every overlay kept rendering the un-edited theme.
90
+
91
+ The attribute is now copied only for a genuinely *scoped* theme, where the overlay would
92
+ otherwise pick up the document's; when the theme sits on the document element, inheritance
93
+ already does the work. Several themes rendered side by side in scoped subtrees keep working
94
+ exactly as before.
95
+
96
+ ## [21.4.0] - 14.08.2026
97
+
98
+ A minor rather than a patch: this adds public API. Iterations 5 and 6 of the consumer-DX plan
99
+ (`docs/consumer-dx-plan.md`).
100
+
101
+ ### Added
102
+
103
+ - **`gog-table`: outputs.** The component had none at all, which is what made it a display-only
104
+ grid. `gogSortChange` (`{ field, direction }`, including the third click that clears the sort),
105
+ `gogPageChange` (the new 1-based page), and `gogRowClick`
106
+ (`{ row, index, originalEvent }`).
107
+
108
+ `gogPageChange` deliberately stays quiet in two cases: the initial render, and the reset to
109
+ page 1 that a new sort causes — that reset is part of the sort, and a consumer refetching from
110
+ both events would issue two requests for one user action.
111
+ - **`gog-table`: `lazy` — server-driven sorting and paging.** With `[lazy]="true"` the table
112
+ stops sorting and slicing `value` and renders it exactly as handed over, treating it as the
113
+ current page; `totalRecords` tells the paginator how many pages exist, and the two outputs are
114
+ the refetch signals. Row numbering still counts from the current page, and `showTotal` reports
115
+ `totalRecords` rather than `value.length`. Without `totalRecords` pagination stays hidden and
116
+ the table warns in dev mode. Until now the table sorted and paged purely in memory, so anything
117
+ backed by a real endpoint had to be built on something else.
118
+ - **`gog-table`: row selection.** `selectionMode` (`'none' | 'single' | 'multiple'`) plus a
119
+ two-way `[(selection)]`, always a `T[]` — in `'single'` mode it simply holds zero or one row,
120
+ which is one shape to read rather than a `T | T[] | null` union to narrow. A checkbox column
121
+ renders automatically (`showSelectionColumn` turns it off), and the header select-all appears
122
+ only in `'multiple'` mode.
123
+
124
+ **The select-all covers the current page, not the whole data set** — in `lazy` mode the table
125
+ has never seen the other pages, and a control that meant different things in the two modes
126
+ would be worse than either behaviour on its own.
127
+ - **`gog-table`: `dataKey`.** The field (or dot-path) identifying a row. Selection matches on it
128
+ instead of object identity — without it a refetch producing new objects silently drops the
129
+ selection — and it becomes the `@for` track key, so the rendered DOM survives a refetch of the
130
+ same page instead of being torn down and rebuilt.
131
+ - **`gog-table`: `interactiveRows`.** Makes rows focusable and styles them as clickable, with
132
+ Enter and Space activating the focused row. `gogRowClick` fires on a click either way; this is
133
+ what stops a whole-row target from being mouse-only.
134
+ - **`[gogButton]` — a link that looks like a button.** `gog-button` renders its own `<button>`,
135
+ so it could never *be* a link, and a large share of buttons on a real site are navigation. The
136
+ directive inverts the relationship: the element stays the consumer's, and only the look is
137
+ applied.
138
+
139
+ ```html
140
+ <a gogButton routerLink="/pricing">See pricing</a>
141
+ <a gogButton variant="ghost" href="https://example.com" target="_blank" rel="noreferrer">Docs</a>
142
+ <button gogButton variant="outline" size="sm" type="submit">Save</button>
143
+ ```
144
+
145
+ Chosen over an `as="a"` / `routerLink` input trio on `gog-button` because that would mean
146
+ brokering the router's whole input surface through the component **and taking a dependency on
147
+ `@angular/router`** — a fifth peer, and one that would break every app without a router. With
148
+ the directive, `routerLink`, `href`, `target`, `download`, `type="submit"` and anything else
149
+ keep working because they were never taken away.
150
+
151
+ `variant`, `size` and `fullWidth` behave exactly as on the component, `size` included in its
152
+ `GOG_CONFIG.control.size` fallback. It deliberately has no `disabled` (there is no such thing
153
+ on an `<a>`) and no `loading` (the spinner is a projected child a directive cannot add). The
154
+ selector is `a[gogButton], button[gogButton]`, not a bare attribute, so it cannot be put on a
155
+ `<div>` and produce something that looks clickable and is invisible to the keyboard.
156
+ - **`gog-paginator`: a rows-per-page select.** `showPageSizeSelect` turns it on (**off by
157
+ default** — a paginator that silently grew a control would change every existing layout) and
158
+ `pageSizeOptions` sets the choices, defaulting to `[10, 20, 30, 40, 50]`. Both are also
159
+ settable app-wide through the new `GOG_CONFIG.paginator`, so one page can offer `5, 10, 20`
160
+ while the rest of the app uses the house default.
161
+ - **`gog-paginator`: `pageSize` (a `model`) and `totalRecords`.** Given `totalRecords`, the
162
+ paginator derives the page count from `pageSize` itself — which removes the
163
+ `computed(() => Math.ceil(total / size))` a consumer would otherwise have to write *and* keep
164
+ in sync with the select. `totalPages` still works and is right when a server hands you a page
165
+ count directly; `totalRecords` wins if both are set. Changing the size returns to page 1:
166
+ "page 5" of 10-row pages is not "page 5" of 50-row ones, so clamping alone would leave the user
167
+ somewhere they never asked to be.
168
+ - **`gog-table`: `showPageSizeSelect` / `pageSizeOptions`**, forwarded to its paginator, and
169
+ **`GOG_CONFIG.labels.rowsPerPage`** for the select's accessible name.
170
+ - **`GOG_CONFIG.labels`: `total`, `tablePagination`, `selectRow`, `selectAllRows`.** The table's
171
+ own chrome — the row-count label read `Total:` from a hardcoded string, and its paginator was
172
+ labelled `Table pagination` with no way to change either.
173
+
174
+ - **`provideGogIcons(...)` — register your own icons by name.** `gog-icon` shipped a closed set
175
+ of 20 glyphs, and the only way to render anything else was a `TemplateRef` per instance,
176
+ which costs an `<ng-template>` at every use site and does not work at all for the components
177
+ that take an icon *name* (`gog-tag`, `gog-chip`, `gog-tabs`, `gog-button-toggle-group`,
178
+ `ToastService`, `DialogService`). In practice that meant installing a second icon library —
179
+ precisely the dependency the "no CDK, no Material" footprint exists to avoid.
180
+
181
+ ```ts
182
+ // app.config.ts
183
+ providers: [provideGogIcons({ cart: '<svg viewBox="0 0 24 24">…</svg>' })];
184
+ ```
185
+
186
+ ```html
187
+ <gog-icon name="cart" />
188
+ <gog-tag iconName="cart">In basket</gog-tag>
189
+ ```
190
+
191
+ - A registered name **overrides a built-in of the same name**, so an app can replace the
192
+ library's checkmark or chevrons everywhere without touching a single component.
193
+ - Providing it again lower in the injector tree **layers onto** the parent set rather than
194
+ replacing it, matching `provideGogConfig`.
195
+ - The registry is also exposed as the `GOG_ICONS` injection token.
196
+ - **`GogBuiltinIconName`** — the closed union of the shipped glyphs, for code that wants
197
+ exhaustiveness (an icon gallery, a `Record` keyed by icon).
198
+ - **21 more built-in icons, taking the set from 20 to 41.** The old set covered what the
199
+ library's own components needed and almost nothing an app needs: there was no `search` for a
200
+ field, no `trash` for a destructive action, no `more-vertical` for a table row menu. Added, all
201
+ Lucide, all on the same 24×24 / stroke-2 grid as the existing ones:
202
+ - actions — `search`, `plus`, `minus`, `trash`, `pencil`, `download`, `upload`, `refresh`,
203
+ `filter`, `external-link`;
204
+ - chrome — `menu`, `more-horizontal`, `more-vertical`, `settings`;
205
+ - navigation — `arrow-left`, `arrow-right` (distinct from the chevrons, which read as
206
+ disclosure rather than movement);
207
+ - objects and state — `user`, `lock`, `mail`, `star`, `star-filled`.
208
+
209
+ `star`/`star-filled` is the only outline/filled pair, for a rating or favourite **toggle** —
210
+ the same case `checkbox`/`checkbox-checked` already covers. The set stays outline-only
211
+ otherwise: a solid duplicate of every glyph would double the payload for a distinction almost
212
+ nothing needs, and `provideGogIcons` covers the exceptions.
213
+
214
+ Cost: `ICON_DEFS` is one object, so every consumer pays for all of it — it grew from 8.0 KB to
215
+ 16.5 KB raw, **1.6 KB to 2.7 KB gzipped**.
216
+ - **Attribution for the icons.** The glyphs were always Lucide but the package said so nowhere;
217
+ Lucide's ISC licence asks for the notice to travel with them. It is now at the top of
218
+ `icons.ts` and summarised in the README's licence section.
219
+
220
+ ### Changed
221
+
222
+ - **The button's `.gog-btn*` block moved from the component stylesheet into
223
+ `styles/button.css`**, which `styles/index.css` imports. Angular's emulated encapsulation
224
+ would never let a component stylesheet reach an `<a>` declared in a consumer's template, so
225
+ `[gogButton]` needs the rules to be global — the same reason `gogBadge` and `gog-collapsible`
226
+ already keep theirs there. One source for both, no duplication. Costs about 1 KB gzipped in the
227
+ always-loaded stylesheet; nothing changes for anyone already importing `index.css`, which the
228
+ Setup section has always required.
229
+ - **`npm run check:tokens` now covers the global stylesheets too.** It scanned `lib/**/*.scss`
230
+ plus a hardcoded `utilities.css`; it now walks `styles/*.css` as a directory, so a new global
231
+ stylesheet is under the token contract the moment it exists rather than whenever someone
232
+ remembers to add it. 34 stylesheets checked before, 38 now.
233
+ - **`gog-table`'s `pageSize` is a `model`, not an `input`.** `[pageSize]="20"` is unchanged;
234
+ `[(pageSize)]="size"` is now possible, and that is what lets the rows-per-page select work with
235
+ no wiring — the table binds its own model straight to the paginator's, so nothing is ferried
236
+ between the two by hand. `pageSizeChange` comes free from the model and is the refetch signal
237
+ in `lazy` mode.
238
+ - **The table footer no longer hides at a single page while the size select is on.** Hiding it
239
+ would strand the user on whatever size produced that one page, with no control left to choose a
240
+ smaller one. With the select off, the old behaviour is unchanged.
241
+ - **`GogIconName` is now open: `GogBuiltinIconName | (string & {})`.** The built-ins still
242
+ autocomplete; a registered name is now accepted wherever an icon name is taken, with no change
243
+ at any of the ten call sites that use the type. The trade is deliberate and comes with the
244
+ registry: a typo is no longer a compile error, so **an unknown name renders nothing and warns
245
+ in dev mode** (once per name) instead of throwing — an icon is decoration, and failing a render
246
+ over a glyph name would be the worse failure. Code that relied on `GogIconName` being closed
247
+ — an exhaustive `switch`, `Record<GogIconName, …>` — should move to `GogBuiltinIconName`.
248
+
249
+ ### Fixed
250
+
251
+ - **Buttons no longer inherit the anchor underline.** `.gog-btn` never reset `text-decoration`,
252
+ because a `<button>` has none to reset — the moment the same block landed on an `<a>` via
253
+ `[gogButton]`, every link-button came out underlined.
254
+ - **`--gog-icon-stroke-width` now applies to every shape in an icon.** The rule listed only
255
+ `path`, `circle` and `rect` — which happened to be all the original 20 glyphs used, so the gap
256
+ was invisible. Any icon drawn with `line`, `polyline` or `polygon` (half the new ones, and
257
+ whatever a consumer registers) silently ignored the token and fell back to the `stroke-width`
258
+ attribute baked into its own markup. `ellipse` is covered too.
259
+
260
+ ## [21.3.2] - 13.08.2026
261
+
262
+ First batch of the consumer-DX plan (`docs/consumer-dx-plan.md`, iterations 1–4): the seam
263
+ between the package and the developer installing it — setup that failed on the documented
264
+ path, accessibility that depended on optional inputs, and native attributes a wrapper component
265
+ made unreachable.
266
+
267
+ ### Added
268
+
269
+ - **The baseline stylesheet now also ships at `@guildofgleks/ui/styles/`.** This is the path the
270
+ README has always documented, and until now it did not exist in the package — the files were
271
+ only under `src/styles/`, so a setup copied from the README failed on a missing file and every
272
+ component rendered unstyled. Both paths ship for one deprecation window, and `package.json`'s
273
+ `exports` map now lists them, so `@import '@guildofgleks/ui/styles/index.css'` resolves from
274
+ SCSS as well as from `angular.json`.
275
+ - **`gog-inputfield` / `gog-textarea`: the native attribute space.** `readonly`, `maxlength`,
276
+ `minlength`, `spellcheck`, plus `pattern` and `inputMode` on the input. `readonly` differs
277
+ from `disabled` in the usual way (still focusable, still submitted) and suppresses the clear
278
+ button and the number field's spin buttons, since both offer an edit the field would refuse.
279
+ `autofocus` is deliberately **not** forwarded — moving focus unasked is a documented a11y
280
+ problem and the repo's own lint rule rejects it.
281
+ - **`gog-inputfield`: `tel`, `url`, `search`, `time` and `datetime-local` types**, via the new
282
+ exported `GogInputType`. The new `GogInputMode` types the `inputMode` input.
283
+ - **`GOG_CONFIG.labels`.** App-wide defaults for every fixed string the library renders —
284
+ `clear`, `clearSelection`, `clearDate`, `selectAll`, `clearAll`, `increment`, `decrement`,
285
+ `showPassword`, `hidePassword`, `closeDialog`, `closeToast`, `pagination`, `previousPage`,
286
+ `nextPage`, `openCalendar`, `today`, `thisMonth`, `previousMonth`, `nextMonth`,
287
+ `previousYear`, `nextYear`, `hours`, `minutes`, `seconds`, plus `page` (a formatter — see
288
+ Fixed). A non-English app relabels the library once instead of on every instance.
289
+ Per-instance inputs still win where they exist.
290
+ - **`gog-multiselect`: `selectAllLabel` / `clearAllLabel`.** The panel's two buttons rendered
291
+ literal `Select all` / `Clear` with no way to change them at all.
292
+ - **`gog-calendar`: `hoursLabel` / `minutesLabel` / `secondsLabel`.** The time section's three
293
+ fields had hardcoded English `aria-label`s.
294
+ - **`GOG_CONFIG.theme`.** `storageKey` persists the chosen theme in `localStorage`;
295
+ `followSystem` opens in the OS `prefers-color-scheme` setting and keeps following it until the
296
+ app calls `setTheme`; `defaultTheme`, `lightTheme` and `darkTheme` name the themes involved.
297
+ All off by default, so an app that configures nothing keeps today's behaviour exactly.
298
+ - **`@angular/platform-browser` is now declared as a peer dependency.** `gog-icon` has always
299
+ imported `DomSanitizer` from it; the omission only worked because npm's flat tree hides it,
300
+ and broke under strict pnpm.
301
+
302
+ ### Fixed
303
+
304
+ - **Form controls are labelled without an `inputId`.** `gog-inputfield` and `gog-textarea` now
305
+ generate an id when none is given, so the `<label for>` actually points at the field (clicking
306
+ the label focuses it, assistive tech gets a name) and the error message is reachable through
307
+ `aria-describedby`. Previously both were silently dropped unless the consumer happened to pass
308
+ `inputId` — the default configuration was inaccessible. `gog-select` already worked this way;
309
+ the id generator is now shared (`gog-radio-group` and `gog-slider` use it too, with unchanged
310
+ output).
311
+ - **`aria-describedby` no longer points at an element that isn't rendered.** It was keyed off
312
+ `hasError()`, while the message element renders on `visibleError()` — with `errorDisplay="auto"`
313
+ and an empty `errorMessage` the two disagree.
314
+ - **Toasts are announced reliably.** `aria-live` moved off the individual toast, which enters the
315
+ DOM together with its own text (a live region created at the same moment as its content is
316
+ routinely skipped by screen readers), onto two permanently-mounted regions in
317
+ `gog-toast-container` — polite, and assertive for `error`/`warning`. Individual toasts no longer
318
+ carry `role`/`aria-live`, so nothing is announced twice.
319
+ - **`gog-inputfield`: the clear button on a number field wrote `''` instead of `null`.** A
320
+ `FormControl<number | null>` ended up holding a string, which then failed numeric validators
321
+ and round-tripped the wrong type. It now writes exactly what emptying the field by hand writes.
322
+ - **`ThemeService.theme` is read-only.** It was a writable signal, so `theme.set(...)` moved the
323
+ signal without touching the `data-theme` attribute the styles read, leaving the two out of
324
+ sync. Use `setTheme`/`toggleTheme`.
325
+ - **`gog-inputfield`: a `clearable` number field had no clear button.** The stepper and the
326
+ clear button share the field's end slot, and the stepper won outright — so `clearable` was
327
+ silently a no-op on `type="number"` unless `showSpinButtons` was also off. Both now render:
328
+ the clear button sits one stepper-width further in, and the field's text gutter widens to fit
329
+ the pair (`--gog-input-spin-width`, new). It still disappears when there is nothing to clear,
330
+ taking the extra gutter with it.
331
+ - **`gog-calendar` now reads `GOG_CONFIG.datepicker`.** `locale` and `firstDayOfWeek` were
332
+ documented as applying to `gog-calendar` as well as `gog-datepicker`, but the calendar only
333
+ ever honoured its own inputs — so a standalone `<gog-calendar>` in an app with an app-wide
334
+ locale silently rendered in `en-US`. Rendered through `gog-datepicker` nothing changes: that
335
+ component passes its own already-resolved values, which still win.
336
+ - **`gog-paginator`: the per-page button names are translatable.** "Go to page 4" / "Page 4,
337
+ current page" were built by string concatenation in the template. They now come from
338
+ `GOG_CONFIG.labels.page`, a `(page, isCurrent) => string` formatter — a function rather than a
339
+ placeholder string, since the number's position and the grammar around it are language
340
+ dependent.
341
+ - **The textarea resize grip's offsets are real tokens.** `--gog-textarea-resize-grip-offset`
342
+ and `--gog-textarea-resize-inset-right`/`-bottom` are declared in `theme.css` instead of
343
+ living as literal `var()` fallbacks in the component stylesheet, which the token-contract
344
+ check (`npm run check:tokens`) had been failing on. Geometry is unchanged.
345
+
346
+ ### Changed
347
+
348
+ - **The generated token catalogue moved from `README.md` to `TOKENS.md`.** It was ~200 KB of
349
+ reference table in the middle of the README, burying the Setup section that a new consumer has
350
+ to find within seconds on npm. The README keeps the three-layer explanation and links across;
351
+ the README itself is now ~14 KB. `GOG_TOKEN_GROUPS` is unaffected.
352
+ - **README: `<gog-dialog />` and `<gog-toast-container />` are documented.** `DialogService.open()`
353
+ and `ToastService.show()` render nothing until those host elements are in a template, which
354
+ the README never said.
355
+ - **README: a `## Global configuration` section.** `provideGogConfig` was never documented in the
356
+ README at all — only individual keys mentioned in passing — so the app-wide settings, and now
357
+ `labels` and `theme` with them, were undiscoverable to anyone reading the package page. Adds
358
+ the key list, the precedence rule, the injector-tree merge, and a translation example.
359
+ - **README / `AGENTS.md`: the new API is documented.** Both ship inside the package. `AGENTS.md`
360
+ (the consumer-facing agent reference) has the native attributes, `GOG_CONFIG.labels` and
361
+ `.theme`, the read-only `ThemeService.theme`, generated field ids, the toast live regions, and
362
+ `GogInputType`/`GogInputMode` in its type table.
363
+ - **README: the component list is complete again.** It advertised 18 components and listed 21,
364
+ while omitting `gog-autocomplete`, `gogBadge`, `gog-button-toggle-group`, `gog-datepicker`,
365
+ `gog-divider`, `gog-progressbar`, `gog-tabs` and `gog-toggle` entirely.
366
+ - Label inputs that now resolve through `GOG_CONFIG.labels` changed their default from a literal
367
+ string to `undefined` (`clearAriaLabel`, `incrementLabel`, `decrementLabel`, `showPasswordLabel`,
368
+ `hidePasswordLabel`, `todayLabel`, `thisMonthLabel`, `previousMonthLabel`, `nextMonthLabel`,
369
+ `previousYearLabel`, `nextYearLabel`, `openCalendarLabel`, `gog-paginator`'s `ariaLabel`).
370
+ Rendered output is identical unless the app configures `labels`; only reading the input back
371
+ in TypeScript now yields `undefined` rather than the English default. `gog-calendar`'s
372
+ `locale` and `firstDayOfWeek` changed the same way, for the same reason.
373
+
374
+ ### Deprecated
375
+
376
+ - `@guildofgleks/ui/src/styles/…` — use `@guildofgleks/ui/styles/…`. Both ship until **21.5.0**,
377
+ when the `src/styles/` copy is removed.
378
+
379
+ ## [21.3.1] - 11.08.2026
380
+
381
+ ### Added
382
+
383
+ - **`gog-slider`: `range`.** Switches the slider to two independently focusable native
384
+ thumbs for picking a span instead of a single value — bind `[(rangeValue)]` (a
385
+ `GogSliderRange` `{ start, end }` pair) instead of `[(value)]`; the two are mutually
386
+ exclusive, and `writeValue`/the `ControlValueAccessor` follow whichever one `range` selects.
387
+ Neither thumb can be dragged, keyboard-nudged, or written past the other — crossing is
388
+ clamped in JS rather than through the native `min`/`max` attribute, since narrowing that
389
+ per thumb would desync the browser's own (invisible) thumb position from the custom
390
+ `--range-start-pos`/`--range-end-pos`-driven visuals. Works in both orientations and with
391
+ `showThumb`/`fullWidth`/`disabled`/error display exactly as the single-value mode does.
392
+ `startAriaLabel`/`endAriaLabel` (defaulting to `'Minimum'`/`'Maximum'`, prefixed with
393
+ `label()` when set) name the two thumbs for assistive tech, since a single `<label for>`
394
+ can't target both. The value readout (`showValue`) reserves stable width up front, sized
395
+ from `min()`/`max()`/`step()` rather than the live value, so it — and, in a `fit-content`
396
+ vertical slider, the whole control along with it — doesn't visibly resize on every drag.
397
+ - **`gog-slider`: `startDisabled`/`endDisabled`.** Disable just one thumb in `range` mode —
398
+ e.g. pin a range's floor while leaving its ceiling adjustable, or vice versa — instead of
399
+ `disabled`, which still takes out both together. ORed with `disabled` rather than
400
+ overriding it, and ignored outside `range` mode (nothing to disable "one side" of there). A
401
+ one-sided disable only dims and disables that one thumb (its native input's own `disabled`
402
+ attribute takes it out of the tab order); the whole-control `.gog-slider--disabled` styling
403
+ (dimming + `pointer-events: none` over the whole track) only kicks in once *both* sides are
404
+ disabled, since applying it for just one would also block pointer input to the other,
405
+ still-enabled thumb. Reactive forms are unaffected by this addition: a `[formControl]`'s own
406
+ `.disable()`/`.enable()` still speaks for both thumbs at once, same as before — one
407
+ `FormControl` backs one `rangeValue` and has no way to target just one side of it.
408
+
409
+ - **`gog-autocomplete`: `openOnFocus`.** Focusing the field now opens the panel immediately with
410
+ the full option list, ignoring `minLength` — the common "browse everything, then narrow it
411
+ down" pattern a plain type-ahead can't offer. On by default; turn it off (or set
412
+ `GOG_CONFIG.autocomplete.openOnFocus = false`) to keep the previous behaviour of nothing
413
+ showing until enough has been typed. The list stays unfiltered even when the field already
414
+ displays a previously-selected label, and normal filtering resumes on the first keystroke.
415
+ - **`gog-autocomplete`: `gogLoadMore`.** Fires once the panel is scrolled to the end of the
416
+ option list — the signal to fetch and append another page, instead of handing a huge or
417
+ server-backed source over up front (500,000 rows loaded 20 at a time, not all at once).
418
+ Forwarded from the panel's own `gog-scroll`.
419
+ - **`gog-tabs`: `scrollActiveIntoView`.** With an overflowing header row, selecting a tab —
420
+ by click, the arrow keys, or a consumer setting `activeIndex` directly — now scrolls the
421
+ header so the active tab stays in view, centered where there's room so its neighbours on
422
+ both sides stay visible too. The same "show what's around the current position" idea
423
+ `gog-paginator` already uses for pages. On by default; instant on first render, smooth (or
424
+ instant under `prefers-reduced-motion`) after. Turn it off to own the scroll position
425
+ yourself.
426
+ - **`gog-tabs`: `showScrollTrack`; `gog-scroll`: `showTrack`.** With `scrollActiveIntoView`
427
+ driving the header's scroll position, its own draggable thumb/track next to the active-tab
428
+ underline read as two conflicting position indicators for the same thing — confusing rather
429
+ than helpful, per feedback on the first cut of `scrollActiveIntoView`. `gog-tabs` now hides
430
+ the track by default while `scrollActiveIntoView` is on, and shows it by default once that's
431
+ off (the only way left to reach an off-screen tab by mouse); either can be pinned explicitly
432
+ with `showScrollTrack`, regardless of the other. Native scrolling — wheel, touch, keyboard,
433
+ and any programmatic `scrollTo`/`scrollIntoView` — is unaffected either way; only the visual
434
+ affordance is gone. The underlying toggle lives on `gog-scroll` itself as `showTrack`
435
+ (instance input, or app-wide via `GOG_CONFIG.scroll.showTrack`), so any other panel built on
436
+ it gets the same option.
437
+ - **`gog-textarea`: `resize`.** Which direction(s) the field's own drag handle resizes it in —
438
+ `'vertical'` (the default, matching a plain `<textarea>`), `'horizontal'`, `'both'`, or
439
+ `'none'` to remove it entirely. Settable app-wide via `GOG_CONFIG.textarea.resize`. The
440
+ handle itself is also restyled: the browser's native glyph is barely visible at a glance, so
441
+ it's blanked out (`::-webkit-resizer`, where that's even stylable — Firefox never exposed a
442
+ hook for its own) and replaced with two short diagonal strokes in the field's own border
443
+ colour, sized and positioned to sit inside the border rather than past it. A `ResizeObserver`
444
+ on the field keeps the grip glued to its actual corner as it's dragged narrower/shorter than
445
+ its container (`'horizontal'`/`'both'`) — it's anchored to the container, not the field
446
+ itself, since a `<textarea>` can't reliably host `::after`. The drag stays entirely native;
447
+ only the glyph and its tracking are new.
448
+ - **`gog-inputfield`: number spin buttons.** A `type="number"` field now gets the library's own
449
+ increment/decrement buttons instead of the browser's native ones, which render inconsistently
450
+ across Chromium/Firefox/Safari and were never themed. Flush against the field's own border as
451
+ one grouped stepper (a divider on each side), not floating loose in the icon gutter. Steps by
452
+ `step` (default `1`), clamps to `min`/`max`, and disables the button at whichever boundary is
453
+ reached. Arrow-key stepping on the focused field is untouched — that's native
454
+ `<input type="number">` behaviour, unrelated to which glyphs are visible. `showSpinButtons`
455
+ turns them off entirely (native glyphs never come back — off means no stepper UI at all);
456
+ settable app-wide via `GOG_CONFIG.inputfield.showSpinButtons`.
457
+ - **`gog-icon`: `copy`.** A new glyph for the common "copy this field's value" trailing-action
458
+ pattern (see the inputfield showcase page for a full example built on `gogInputAddonEnd`).
459
+ - **`AGENTS.md`.** A consumer-facing reference for AI coding agents building apps against the
460
+ published package — conventions, theming/`GOG_CONFIG` summary, a full per-component API table
461
+ (inputs, outputs, slots, CVA status), and the deprecated-pattern list, all derived from the
462
+ library's actual source rather than the (currently lagging) `README.md`. Shipped alongside
463
+ `README.md`/`LICENSE` in the npm package via `ng-package.json`'s `assets`.
464
+
465
+ ### Fixed
466
+
467
+ - **`gog-scroll`: thumb too small to reliably click, especially at `size="thin"`.** The
468
+ thumb's own visible box is exactly as wide as `size` says — that part is unchanged — but its
469
+ clickable/draggable _region_ now extends a few pixels past every edge
470
+ (`--gog-scroll-thumb-hit-padding`, bigger on `thin`, where the visible thumb was hardest to
471
+ land a cursor on), so a near-miss click still grabs the thumb instead of falling through to
472
+ the track, which pages the view rather than dragging. Purely an invisible hit-area change —
473
+ no new input, no behaviour change for the mouse wheel, which already worked fine.
474
+ - **`gog-autocomplete`: option rows spilling out of the panel.** The panel's `.gog-scroll` was
475
+ never actually constrained to `--gog-autocomplete-panel-max-height` — a classic flexbox trap
476
+ where a `max-height`-only container doesn't give its flex-grow children a definite size to
477
+ shrink into, so the option list rendered at full content height and visibly overflowed past
478
+ the panel's own border into whatever sat below it. Most visible with `appendToBody` and a
479
+ longer list (typing narrowed it back under the cap, masking the issue until the panel was
480
+ reopened with more matches, which also made it look like "the panel closes on its own" — it
481
+ hadn't; the list had just spilled out from under it). Fixed by giving the panel the same
482
+ `display: flex` + `flex: 1; min-height: 0` chain `gog-select` and `gog-multiselect` already
483
+ use, plus a defensive `overflow: hidden`.
484
+
485
+ ## [21.3.0] - 08.08.2026
486
+
487
+ ### Added
488
+
489
+ - **Eight new components**, the Angular Material set this library was missing:
490
+
491
+ - **`gog-datepicker`** — a date field with a calendar panel: single date, `selectionMode="range"`
492
+ (with `numberOfMonths` for a two-month view), and an optional clock via `showTime` /
493
+ `hourFormat` / `minuteStep` / `showSeconds`. `min`, `max` and a `disabledDates` **predicate**
494
+ (an array cannot express "weekends"), `inline` for an always-visible calendar, `allowTextInput`
495
+ with parsing, plus the usual `clearable` / `floatLabel` / `errorMessage` / `appendToBody`.
496
+
497
+ The panel's footer carries **two separate actions**, never one: `showTodayButton` (on by
498
+ default) _selects_ today, and `showThisMonthButton` (off by default) only moves the view back
499
+ to the current month. A single button doing both is ambiguous — after paging away, the same
500
+ label reads as "take me back" to one person and "set it to today" to another. "Today" is
501
+ disabled when `min`/`max` or `disabledDates` rule today out, rather than silently doing
502
+ nothing. Wording via `todayLabel` / `thisMonthLabel`.
503
+
504
+ Native `Date`, **no date library and no adapter abstraction** — the package keeps its zero
505
+ runtime dependencies. `Intl` supplies month and weekday names; the display format is a token
506
+ pattern (`dd.MM.yyyy`, `yyyy-MM-dd`, …) used for _both_ rendering and parsing, so what is
507
+ written can always be read back. `31.02.2026` is rejected rather than silently becoming
508
+ 3 March. `locale`, `firstDayOfWeek` and `format` are also settable app-wide through
509
+ `GOG_CONFIG.datepicker`.
510
+
511
+ - **`gog-calendar`** — the month grid behind it, exported and usable on its own. Follows the
512
+ ARIA grid pattern: arrows by day, `PageUp`/`PageDown` by month, `Shift` + those by year,
513
+ `Home`/`End` to the week's ends, and one tab stop across all 42 cells. Always six weeks, so
514
+ the calendar's height never changes as you page through months.
515
+ - **`gog-autocomplete`** — a text field that suggests options as you type, on the same
516
+ `GogDropdownBase` as `gog-select` and taking the same `optionLabel` / `optionValue` /
517
+ `optionDisabled` accessors. The trigger is a real `<input>`, which is what makes it a separate
518
+ control rather than a mode of `gog-select`: focus never leaves the field and the highlighted
519
+ row is pointed at with `aria-activedescendant`. `gogSearch` is debounced (`searchDebounce`,
520
+ 300 ms) for a server-backed source, and `[filterLocal]="false"` stops that server's answer
521
+ being filtered a second time. Plus `minLength`, `loading`, `emptyMessage` and
522
+ `forceSelection`.
523
+ - **`gog-tabs` / `gog-tab`** — a tablist over projected children, each tab declaring its own
524
+ `label`, `iconName` and `disabled`. Content written inside a tab renders eagerly and is
525
+ merely hidden while inactive, so scroll position and half-typed input survive a switch; an
526
+ `<ng-template gogTabContent>` is instead built on first activation and kept alive after.
527
+ Which you get is decided by whether that template is present. `gogTabHeader` replaces the
528
+ header button entirely. Overflowing headers scroll inside a `<gog-scroll>`, not a native
529
+ `overflow-x`.
530
+ - **`gog-button-toggle-group`** — a row of buttons where one, or with `multiple` several, can
531
+ be picked. Options-driven with the same accessors as the dropdowns, plus `optionIcon` and a
532
+ `gogButtonToggleOption` slot. Single and multiple are genuinely different widgets to
533
+ assistive tech and are exposed as such: `role="radiogroup"`/`aria-checked` with arrows that
534
+ move _and_ select, versus `role="group"`/`aria-pressed` with arrows that only move.
535
+ `appearance` picks between one segmented control and discrete buttons.
536
+ - **`gog-toggle`** — an on/off switch. A native `<input type="checkbox">` carrying
537
+ `role="switch"`, so it announces as "switch, on" rather than "checkbox, checked" while the
538
+ platform keeps owning the keyboard and forms. `onLabel` / `offLabel` render _inside_ the
539
+ track — the one thing a checkbox cannot do — and both stay in the DOM so the track's width
540
+ cannot jump as it flips. Shares `gog-checkbox`'s size scale.
541
+ - **`gog-progressbar`** — determinate, indeterminate and buffer modes, five sizes and the
542
+ semantic colour set. `value` and `buffer` are clamped to 0–100 rather than trusted.
543
+ Indeterminate reports **no** `aria-valuenow` at all, which is what marks it indeterminate,
544
+ and its animation is replaced by a static stripe under `prefers-reduced-motion`.
545
+ - **`gogBadge`** — a count or dot pinned to another element's corner. A directive, so it
546
+ decorates a button, icon or avatar without wrapping it. `badgePosition`, `badgeVariant`,
547
+ `badgeDot`, `badgeMax` (`99+` beyond it), `badgeHidden` and `badgeAriaLabel`. It renders
548
+ **nothing at all** for `0`, `null` or `''` — a badge reading "0" is the defining bug of this
549
+ component class, so it is not reachable.
550
+ - **`gog-divider`** — a rule between two regions, horizontal or vertical, solid/dashed/dotted,
551
+ with an optional projected label running through it and an `inset` variant for lists. No
552
+ `hasLabel` input: the two forms are told apart by whether anything was actually projected.
553
+
554
+ - `GogOrientation` — one shared `'horizontal' | 'vertical'` type. `GogSliderOrientation` is now
555
+ an alias of it, so nothing changes for existing code.
556
+ - `roving-focus.ts` gained an `orientation` (so a horizontal tablist leaves `ArrowDown` to the
557
+ page) and an optional predicate for skipping disabled items. Both default to the previous
558
+ behaviour, so `gog-select`, `gog-multiselect` and `gog-accordion` are unaffected.
559
+ - Four icons: `calendar`, `clock`, `chevron-left`, `chevron-right`.
560
+ - `.gog-visually-hidden` in `styles/utilities.css`.
561
+
562
+ - `GogFloatLabelState` (exported) — the shared float-label state behind `gog-inputfield`,
563
+ `gog-textarea`, `gog-select` and `gog-multiselect`, previously three near-identical copies of
564
+ the same five `computed()`s. A plain composition class in the mould of `GogErrorState`, so it
565
+ serves the two components that share no base class as well as `GogDropdownBase`, which is one.
566
+ Each control still supplies its own "has content" signal, since that genuinely differs
567
+ (non-empty string / non-null selection / non-empty selection array).
568
+ - `resolveConfigured(instanceValue, configuredValue, fallback)` (exported) — the library's
569
+ input → `GOG_CONFIG` → built-in default precedence rule in one place, instead of the `??`
570
+ chain hand-written at each configurable input.
571
+ - `GOG_CONFIG` now covers the settings an app otherwise repeats on every instance:
572
+ `control.size` and `control.errorDisplay` (the latter is what makes `errorDisplay="auto"` an
573
+ app-wide decision for a Reactive Forms app rather than per-field boilerplate),
574
+ `dropdown.appendToBody`, `dropdown.direction`, and `toast.position` / `toast.duration`.
575
+ `control.size` deliberately covers only the interactive form controls — `gog-table`,
576
+ `gog-accordion` and `gog-paginator` keep their own density defaults, as do `gog-spinner`,
577
+ `gog-skeleton`, `gog-tag` and `gog-chip`. All stay per-instance overridable.
578
+
579
+ - **A built-in clear button** on `gog-inputfield`, `gog-textarea`, `gog-select` and
580
+ `gog-multiselect`, via a `clearable` input (plus `clearAriaLabel`). It appears only once the
581
+ control has something to clear and disappears again when empty, so it adds no permanent
582
+ chrome — and it removes the need for a fake `"— not selected —"` option just to let someone
583
+ undo a choice. Also settable app-wide through `GOG_CONFIG.control.clearable`. Defaults to
584
+ `false`, except `gog-multiselect`, which already had a clear button and keeps it. On a
585
+ password field the built-in reveal toggle keeps the trailing slot.
586
+ - `filterPosition` on `gog-select` / `gog-multiselect` (`'top'` | `'bottom'`, plus
587
+ `GOG_CONFIG.dropdown.filterPosition`) sticks the search box to either end of the panel, and it
588
+ now carries a divider on the side facing the list so it reads as chrome rather than a row. The
589
+ name matches `gog-multiselect`'s existing `controlsPosition` rather than inventing a second
590
+ vocabulary for the same idea.
591
+ - **Filtering in `gog-select` and `gog-multiselect`** — `filter` puts a search box at the top of
592
+ the panel, matching case-insensitively on the resolved `optionLabel`. `filterMatch` swaps that
593
+ for your own predicate, `filterPlaceholder` and `filterEmptyMessage` cover the wording, and
594
+ `GOG_CONFIG.dropdown.filter` turns it on app-wide. The query resets when the panel closes, and
595
+ `gog-multiselect`'s "select all" deliberately takes only the _visible_ options so it means what
596
+ it says while a filter is active.
597
+ - `styles/presets/one-dark.css` and `styles/presets/one-light.css` — the Atom/JetBrains One
598
+ palettes, with the syntax hues mapped onto the library's semantic roles (blue is the accent,
599
+ green/red/yellow/cyan become success/danger/warning/info).
600
+
601
+ - **The token catalogue is generated, not hand-copied.** `npm run generate:tokens` derives
602
+ `GogTokenName` (a union of every `--gog-*` the library declares or documents), the
603
+ `GOG_TOKEN_GROUPS` runtime metadata, and the README's theming table straight from
604
+ `theme.css`. `npm run check:tokens` fails when they are out of date, so a stylesheet edit
605
+ cannot silently leave the docs behind. `GOG_TOKEN_GROUPS` is exported so a theme editor can
606
+ enumerate real tokens instead of keeping its own copy.
607
+ - `styles/presets/slate.css` — a second, importable preset (`data-theme="slate"`, cool/indigo).
608
+ It declares palette tokens only and still restyles everything, which is the theming contract
609
+ demonstrated rather than described.
610
+
611
+ - **`gog-select` and `gog-multiselect` take your own objects.** `optionLabel`, `optionValue` and
612
+ `optionDisabled` accept a property path (dot-paths included, `'profile.fullName'`) or a
613
+ function, so a real DTO goes straight in — no mapping into `{ id, name }` first, and no losing
614
+ the original object on the way back out. Set `[optionValue]="null"` and the control emits the
615
+ **option object itself** instead of an id. Both controls are now generic over their option and
616
+ value types, inferred from the bindings.
617
+
618
+ Defaults are `'name'` / `'id'` / `'disabled'`, so **existing code is unaffected** — the whole
619
+ 21.2.x select/multiselect spec suite passes unchanged. `GogDropdownOption` is no longer a
620
+ requirement, just the shape those default accessors expect.
621
+
622
+ - `gogDropdownOption` — a projected template for one option row, with
623
+ `{ $implicit: option, selected, disabled, label }` as its context.
624
+ - `getByPath`, `readOption`, `isSameOptionValue` and the `GogOptionAccessor<TOption, TResult>`
625
+ type are exported; `gog-table` now shares the same `getByPath` rather than keeping its own copy.
626
+
627
+ - **One slot mechanism across the library.** Custom markup is now projected as content and
628
+ picked up with `contentChild`, instead of a `TemplateRef` input per slot. New directives:
629
+ `gogColumnBody` / `gogColumnHeader` (per column, replacing the string-keyed
630
+ `<ng-template template="…" type="…">`), `gogCheckboxIcon`, `gogTagIcon`,
631
+ `gogMultiselectClearIcon`, `gogDropdownChevron`, and `gogInputAddonStart` /
632
+ `gogInputAddonEnd`. A projected slot always wins over the deprecated input it replaces, so a
633
+ codebase can migrate one call site at a time.
634
+ - `GogColumn` with the `gog-column` selector — the library's last unprefixed element name.
635
+ - `gog-inputfield` addon slots take arbitrary markup, including a real `<button>` with its own
636
+ `aria-label` and `(click)`. This replaces six inputs (`icon{Start,End}{Template,Fn,Label}`)
637
+ with two slots. On `type="password"` the built-in reveal toggle keeps the trailing slot, so a
638
+ projected addon can never displace the only control that shows the value.
639
+
640
+ ### Fixed
641
+
642
+ - **An auto-width dropdown clipped its own options.** With `[fullWidth]="false"` the trigger
643
+ sizes to the _current_ selection, and the panel copied that width — so picking a short option
644
+ cut the longer ones off the list. The relationship is now inverted: the panel sizes to its own
645
+ content with the trigger's width as a **floor**, capped by
646
+ `--gog-{select,multiselect}-panel-max-width`. New `minWidth` input (any CSS length) plus
647
+ `--gog-{select,multiselect}-min-width` (120px) so an auto-width trigger cannot collapse to its
648
+ own chrome either.
649
+ - **`gog-multiselect` now collapses a long selection into `+N`.** The trigger shows what fits on
650
+ one line and a count for the rest, with the full list in a tooltip. Measured with
651
+ `canvas.measureText` rather than by rendering candidates, and re-measured from a
652
+ `ResizeObserver` on the value element, since the space available changes when the _container_
653
+ resizes — something Angular never renders for.
654
+ - **`gog-select`'s chevron sat 42px from the trigger's right edge.**
655
+ `--gog-select-chevron-inset` was applied as the trigger's `padding-right` while the chevron
656
+ itself was a flex child _inside_ that padding, so the inset was counted twice. It now lands on
657
+ `--gog-control-icon-offset` (10px), the same line as `gog-inputfield`'s icons and
658
+ `gog-multiselect`'s arrow, which were at 10px and 16px — the three controls did not line up in
659
+ a form. The token keeps its name and now means what it says.
660
+ - **`gog-textarea`'s clear button sat inside the scrollbar.** It was inset 8px from the border
661
+ box while a scrolling textarea's scrollbar is ~19px wide, so once the content overflowed the
662
+ button was half-covered and competed with the thumb for clicks. It is now offset by the
663
+ measured scrollbar width (`--gog-textarea-scrollbar-width`, written from
664
+ `offsetWidth - clientWidth`; `scrollbar-gutter: stable` was rejected because it reserves the
665
+ gutter even when the field isn't scrolling).
666
+ - **`gog-textarea`'s clear glyph was 30% too small** — 13.4px against the library's 19.2px,
667
+ because it reused the dropdowns' 0.7 ratio, which suits their dense trigger and not a large
668
+ multi-line box. New `--gog-textarea-clear-icon-ratio` defaults to a full-size glyph.
669
+ - Nine specs in `scroll.component.spec.ts` awaited a single animation frame after dispatching a
670
+ scroll, while `ScrollComponent` coalesces measurement into its own frame — if that frame fired
671
+ during `whenStable()`, the effect scheduled a second one _after_ the test's, and the assertion
672
+ ran before the measurement. Intermittent by construction; replaced with a `settleMeasure()`
673
+ helper that covers both orderings.
674
+
675
+ ### Changed
676
+
677
+ - The clear button now takes the **outermost** trailing position on `gog-select` and
678
+ `gog-multiselect`, with the chevron/arrow shifting inward when it appears. Previously
679
+ `gog-multiselect` had them the other way round. Keeps the trigger width stable and keeps the
680
+ destructive control off the very edge.
681
+ - Float-label fields are less tall: `--gog-field-float-label-reserve` 18px → 14px and
682
+ `--gog-field-float-label-in-top` 8px → 6px, taking an `md` field from 63px to 59px (a plain one
683
+ is 45px). Both are tokens, so the old numbers are one declaration away.
684
+
685
+ ### Deprecated
686
+
687
+ Each of these keeps working unchanged and is **removed in 21.5.0**; the `@deprecated` tag on
688
+ every symbol carries the same date and removal version, so `grep -rn "@deprecated since"` lists
689
+ the full set at any time.
690
+
691
+ - `<column>` → `<gog-column>`, and the `Column` export → `GogColumn`.
692
+ - All `--gog-ms-*` tokens → `--gog-multiselect-*`. Both spellings work for the whole window:
693
+ the `--gog-ms-*` name stays the _declared_ one and the new name derives from it, so an
694
+ existing override of either still reaches the component. Verified in a browser both ways.
695
+ - `<ng-template template="field" type="body|header">` inside `gog-table` → a `gogColumnBody` /
696
+ `gogColumnHeader` template declared inside the column itself. The old form matched columns by
697
+ a string the compiler cannot check, so a typo silently fell back to the default cell.
698
+ - `gog-checkbox`'s `checkIconTemplate` → `gogCheckboxIcon`.
699
+ - `gog-tag`'s `iconTemplate` → `gogTagIcon`.
700
+ - `gog-multiselect`'s `clearIconTemplate` → `gogMultiselectClearIcon`.
701
+ - `gog-select` / `gog-multiselect` `chevronTemplate` → `gogDropdownChevron`.
702
+ - `gog-inputfield`'s `iconStartTemplate`, `iconEndTemplate`, `iconStartFn`, `iconEndFn`,
703
+ `iconStartLabel`, `iconEndLabel` → `gogInputAddonStart` / `gogInputAddonEnd`. `iconStart` and
704
+ `iconEnd` (a bare icon name) stay — that is the genuinely common case.
705
+
706
+ ### Changed
707
+
708
+ - **`provideGogConfig(...)` now merges with the config from the parent injector instead of
709
+ replacing it.** Previously a nested call — in a route's or a component's `providers` —
710
+ silently dropped every key it did not restate, so a route setting only `{ tooltip: … }` lost
711
+ the app-wide `button.debounce` with no error anywhere. Merging is one level deep, per
712
+ component key, nearest provider winning field by field. If you were working around the old
713
+ behaviour by repeating the whole config at each level, those repeats are now redundant but
714
+ harmless.
715
+
716
+ - Float label geometry is now themeable through `theme.css` instead of being hardcoded in the
717
+ component stylesheets. `--gog-{input,select,ms}-float-label-{reserve,in-top,over-gap,over-reserve}`
718
+ previously existed only as literal fallbacks (`18px`, `8px`, `1.4em`) inside four component
719
+ `.scss` files, so they were overridable but not discoverable, and `theme.css` did not describe
720
+ the components' full surface. They are now declared component tokens deriving from a new
721
+ shared `--gog-field-float-label-{reserve,in-top,over-gap,over-reserve}` scale, so one
722
+ declaration retunes every field at once while a single control can still be overridden.
723
+ No visual change — the defaults are identical. `--gog-{input,select,ms}-float-label-on-bg`
724
+ stays an instance-layer (undeclared) token as before.
725
+
726
+ ### Added
727
+
728
+ - Float label support for `gog-inputfield`, `gog-select`, `gog-multiselect` and
729
+ `gog-textarea`: a `floatLabel` input (`GogFloatLabelVariant`: `'none'` default, or
730
+ `'in'`/`'on'`/`'over'`, modeled on PrimeNG's own variant names) that rests the label inside
731
+ the field like a placeholder and floats it up on focus or once the field has content —
732
+ `'in'` stays fully inside the border, `'on'` ends up centered on the top border line (with
733
+ a background patch masking it), `'over'` floats fully above the field, outside the border.
734
+ A `floatLabelShowPlaceholder` input (`boolean`, default `false`) reveals the field's own
735
+ `placeholder` once the label has floated out of the way; left off, the placeholder stays
736
+ hidden the whole time a float label is active since the resting label already occupies that
737
+ space. Both are also settable app-wide via the new `GOG_CONFIG.floatLabel` (`variant` /
738
+ `showPlaceholder`), with the usual per-instance input taking priority. Implemented as a
739
+ style variant on each component (not a directive, unlike `gogTooltip`) since each control
740
+ already owns its label and has a different notion of "has content" (`value`,
741
+ `selectedOption`, selection length) that a directive sitting outside the component couldn't
742
+ see. New `--gog-{input,select,ms}-float-label-{in-top,on-bg,over-gap,over-reserve}` tokens.
743
+ - `gog-slider`'s new `orientation` input (`'horizontal'` default / `'vertical'`) — the
744
+ developer picks per instance, no global default, since it's a layout decision rather than a
745
+ house style. The vertical variant is the same native `<input type="range">` rotated via
746
+ `writing-mode: vertical-lr` + `direction: rtl` (not a custom drag implementation), so
747
+ dragging, touch and keyboard (Up/Down as well as Left/Right) all keep working exactly as they
748
+ do horizontally; value increases upward, matching a volume-fader convention. New
749
+ `--gog-slider-vertical-length` token (default `160px`) sizes its length, the vertical
750
+ counterpart to `--gog-slider-auto-width`. `fullWidth` is ignored when vertical, since a
751
+ vertical slider's width is its thickness, not its length.
752
+ - `gog-radio-group`: a new options-driven radio control (`GogRadioOption[]`), the radio
753
+ counterpart to `gog-checkbox`. Renders native `<input type="radio">`s sharing one
754
+ auto-generated (or explicit `name`) group name, so mutual exclusivity and arrow-key/Home/End
755
+ navigation between options come from the browser for free — no roving-focus code needed.
756
+ `ControlValueAccessor`-based, works with `formControl`/`formControlName`. `label`,
757
+ `ariaLabel`, `name`, `size`, `disabled` (group-level, plus per-option `disabled`),
758
+ `orientation` (`'vertical'` default / `'horizontal'`), `errorMessage`, `errorDisplay` and
759
+ `fullWidth` inputs; `[(value)]` two-way bindable. Reuses the `--gog-control-checkbox-*`
760
+ size scale via the shared checkable-control config, plus new `--gog-radio-*` tokens in
761
+ `theme.css`.
762
+ - `gog-collapsible`'s `collapseOnFocusOut` input (`boolean`, default `false`): closes the
763
+ panel once focus leaves both the trigger and the content — e.g. Tabbing past the last
764
+ focusable element inside, or a click landing elsewhere on the page. Off by default, since
765
+ plenty of consumers (an FAQ list, a settings section read top to bottom) want the panel to
766
+ stay open regardless of where focus goes next.
767
+ - `gogTooltip`: a new directive, not a component — drop it on any element, a `gog-*`
768
+ component's own host tag or a plain native one (`<button gogTooltip="Save changes">`,
769
+ `<gog-chip [gogTooltip]="hint">`), to add a hover/focus tooltip without that element
770
+ needing to know anything about it. Content is a plain string or a `TemplateRef` for richer
771
+ markup. `gogTooltipPosition` (`GogTooltipPosition`: `'auto'` default, or an explicit
772
+ `'top'`/`'bottom'`/`'left'`/`'right'` that flips to its opposite if it has no room),
773
+ `gogTooltipShowDelay` (default `300`ms), `gogTooltipHideDelay` (default `100`ms) and
774
+ `gogTooltipDisabled` inputs; the first three also read `GOG_CONFIG.tooltip` for an
775
+ app-wide default the same way `gog-scroll`/`gog-button` already do, with an instance's own
776
+ input always winning. Shown on both mouse hover and keyboard focus (`focusin`/`focusout`,
777
+ not `focus`/`blur`, so it stays replay-safe under SSR event replay), dismissible with
778
+ Escape, and hoverable — moving the pointer from the trigger onto the bubble itself (e.g. to
779
+ read more of a long one, or scroll one taller than `--gog-tooltip-max-height`) cancels the
780
+ pending hide instead of racing it — per WCAG 2.1 SC 1.4.13. The bubble is appended to
781
+ `document.body` (so it's never clipped by an ancestor's `overflow: hidden`) via a new
782
+ internal `GogTooltipOverlay`, built on `ViewContainerRef.createComponent` + relocating the
783
+ node rather than `GogDropdownOverlay`'s `TemplateRef` approach, since a directive has no
784
+ template of its own to attach from. Visually it's the same "floating panel" recipe as
785
+ `gog-dialog`'s panel and `gog-select`'s dropdown (`--gog-surface-color` background, plain
786
+ `--gog-border-color` border, `--gog-panel-shadow`), not a bespoke inverted bubble, so it
787
+ reads as part of a themed app rather than a generic dark tooltip dropped on top of it.
788
+ Content wraps to `--gog-tooltip-max-width` (`280px`) and is capped at
789
+ `--gog-tooltip-max-height` (`220px`) through an internal `gog-scroll` — content under the
790
+ cap renders at exactly its own height, content over it scrolls, using the same themeable
791
+ scrollbar every other overflowing panel in this library uses instead of a native one (see
792
+ `styling.instructions.md`'s new "Scrollable content" section for that convention).
793
+ `gogTooltipClass` applies a class straight to the bubble, for restyling (or resizing) one
794
+ instance — needed because the bubble sits outside any scoped ancestor's stylesheet once
795
+ appended to `document.body`, the same "Panels rendered outside the component subtree"
796
+ limitation `gog-select`'s `[appendToBody]` panel already has, so the class has to come from
797
+ an unscoped (global) stylesheet. New `--gog-tooltip-*` tokens in `theme.css`; `gog-dialog`'s
798
+ panel now also raises `--gog-tooltip-z` (mirroring the existing `--gog-dropdown-z` bump) so
799
+ a tooltip triggered inside a dialog stacks above it.
800
+
801
+ ### Changed
802
+
803
+ - `gog-slider`'s track now paints a border (new `--gog-slider-track-border-width`/`-style`/
804
+ `-color` tokens, transparent by default — same opt-in convention as `--gog-btn-primary-border`)
805
+ and its fill is bound via `background` instead of `background-color`, so
806
+ `--gog-slider-fill-bg` also accepts a gradient (e.g. `linear-gradient(...)`), not just a
807
+ solid color. The thumb ("handle") was already fully customizable via its existing
808
+ `--gog-slider-thumb-*` tokens (size, background, border, radius, glow) — no change there.
809
+
810
+ ### Fixed
811
+
812
+ - `gog-slider`'s track background (`--gog-slider-track-bg`) no longer reuses
813
+ `--gog-accent-dim` — it sat on the same hue ramp as the fill (`--gog-accent-color`), so at
814
+ the track's 4px height the two read as one blob instead of a recessed groove with an
815
+ accent fill on top. Now `color-mix(in srgb, var(--gog-text-color) 30%, var(--gog-border-color))`:
816
+ a desaturated, theme-adaptive gray that darkens toward black in the light theme and
817
+ lightens toward parchment in the dark theme (`--gog-text-color` sits at whichever end of
818
+ that range per theme), so it's always distinct from the accent-colored fill and legible
819
+ against its own theme's surface.
820
+
821
+ ## [21.2.4] - 05.08.2026
822
+
823
+ ### Added
824
+
825
+ - `gog-collapsible`: a headless expand/collapse primitive — inline, not a portal (unlike
826
+ `gog-select`/`gog-multiselect`'s panel). Owns no markup: project any element as the
827
+ trigger via `gogCollapsibleTrigger` and any element as the panel via
828
+ `gogCollapsibleContent`; `[(open)]` is two-way bindable, `disabled` blocks toggling.
829
+ New `--gog-collapsible-*` tokens in `theme.css`; the trigger/content CSS classes live in
830
+ `utilities.css` since the projected content sits outside the component's own view.
831
+ - `gog-textarea`: a multi-line counterpart to `gog-inputfield`, sharing its
832
+ `--gog-input-*` tokens. `ControlValueAccessor`-based, works with
833
+ `formControl`/`formControlName`. `label`, `placeholder`, `errorMessage`,
834
+ `errorDisplay`, `disabled`, `size`, `fullWidth` and `rows` inputs.
835
+ - `gog-inputfield`'s `type` input now also accepts `'number'` and `'date'`,
836
+ plus new `min`/`max`/`step` inputs (applied only for `type="number"`). For a
837
+ `number` field the value written to/read from an attached
838
+ `formControl`/`formControlName` is a `number` (`null` when the field is
839
+ empty) rather than a string — `[(value)]` stays a string either way, since
840
+ it mirrors the native input's raw text.
841
+ - `gog-scroll`: a drop-in replacement for a native `overflow: auto` region.
842
+ Content keeps scrolling natively (wheel, touch, keyboard, focus-into-view);
843
+ only the browser's own scrollbar chrome is hidden and replaced with a
844
+ themeable, draggable overlay thumb. `axis` (`vertical`/`horizontal`/`both`),
845
+ `size` (`normal`/`thin`), `autoHide`/`hideDelay`, `reachThreshold` with
846
+ `gogReachStart`/`gogReachEnd` outputs, a `gogScroll` metrics output, and
847
+ `scrollTo`/`scrollToTop`/`scrollToBottom`/`scrollToLeft`/`scrollToRight`
848
+ public methods. New `--gog-scroll-*` tokens in `theme.css`.
849
+ - `gog-scroll`'s `overscrollBehavior` input (`'auto'` | `'contain'` | `'none'`,
850
+ mirrors the CSS property of the same name): what happens when a scroll
851
+ gesture reaches this instance's edge. Defaults to `'auto'` — chains to the
852
+ next scrollable ancestor, same as an un-customized `overflow: auto` div, so
853
+ scrolling to the end of a `gog-scroll`'d section and continuing the same
854
+ gesture now keeps scrolling the page instead of stopping dead. `gog-select`/
855
+ `gog-multiselect`'s option panel and `gog-dialog`'s body now set
856
+ `overscrollBehavior="contain"` explicitly, preserving their existing
857
+ (correct, overlay-appropriate) behavior now that the component-wide default
858
+ has changed to chain-through.
859
+ - `GOG_CONFIG`/`GogGlobalConfig`/`provideGogConfig(...)`: one injection token
860
+ for app-wide defaults across the library's component inputs, instead of a
861
+ separate token per component per setting. Call `provideGogConfig({ scroll:
862
+ {...}, button: {...} })` once in your app's providers (or a route's/
863
+ component's own `providers` for a subtree-scoped override); any instance
864
+ that doesn't set the input itself falls back to the configured value, then
865
+ to the component's own hardcoded default. `gog-scroll`'s `size`, `autoHide`,
866
+ `hideDelay` and `overscrollBehavior` and `gog-button`'s `debounce` are the
867
+ first inputs wired up to it — see the "Global configuration" section in
868
+ `gleks-ui-library.instructions.md` for how to add more. This only covers
869
+ inputs read in TypeScript that can't already be a CSS token; visual
870
+ defaults remain the `--gog-*` custom properties in `theme.css`.
871
+
872
+ ### Changed
873
+
874
+ - `gog-checkbox` now registers its `ControlValueAccessor` by self-injecting
875
+ `NgControl` in the constructor, matching every other form control in the
876
+ library, instead of the `NG_VALUE_ACCESSOR`/`forwardRef` provider pattern.
877
+ No behavior change — `formControl`/`formControlName` usage is unaffected.
878
+ - `gog-scroll`'s `size`, `autoHide`, `hideDelay` and `overscrollBehavior` inputs and
879
+ `gog-button`'s `debounce` input now default to `undefined` instead of a hardcoded
880
+ value, so they can fall through to `GOG_CONFIG` — read the resolved value (e.g. via
881
+ the rendered DOM) rather than the raw input signal if you need the effective default.
882
+ - `gog-select` and `gog-multiselect`: the option panel now scrolls via
883
+ `gog-scroll` instead of native `overflow-y`.
884
+ - `gog-dialog`: the body now scrolls via `gog-scroll` instead of native
885
+ `overflow-y`.
886
+ - `gog-table`: horizontal scrolling now goes through `gog-scroll` instead of
887
+ native `overflow-x`.
888
+
889
+ ### Fixed
890
+
891
+ - `gog-scroll` internals used `height: 100%` chains from `:host` down to the
892
+ viewport. A host whose own height comes from being flex-grown inside a
893
+ `max-height`-only ancestor (exactly the select/multiselect dropdown panel
894
+ and dialog body cases above) still failed to resolve a percentage height
895
+ read off it, collapsing back to content size — the panel stopped clipping
896
+ and scrolling. Switched every level to flex-basis chains
897
+ (`flex: 1 1 auto` + `min-height: 0`), which don't have that failure mode.
898
+ - `gog-scroll`'s horizontal content wrapper used `width: max-content`, which
899
+ created a circular sizing reference against a `width: 100%` child (e.g.
900
+ `gog-table`'s own `<table>`) and made some browsers fall back to a huge
901
+ sentinel width (~1,000,000px), pushing the table off-screen. Removed —
902
+ children already overflow a normal block parent without it.
903
+ - `gog-scroll` set `overscroll-behavior: contain` (both axes) on the
904
+ viewport unconditionally, which also blocked wheel scroll on an axis the
905
+ instance never actually scrolls (e.g. vertical wheel over a horizontal-only
906
+ instance), preventing it from bubbling up to scroll the page. Now set only
907
+ on the axis that's actually acting as a scroll container.
908
+ - `gog-scroll` kept a disabled or currently-non-overflowing axis at
909
+ `overflow: hidden`/`auto`, which makes an element a "scroll container" per
910
+ spec regardless of whether it has anything to scroll — becoming the
911
+ containing block for `position: sticky` descendants and a scroll-chaining
912
+ boundary, whether needed or not. This broke `gog-table`'s `stickyHeader`
913
+ and swallowed wheel scroll whenever a `gog-table` (which always wraps its
914
+ own horizontal scroll in a `gog-scroll`) was itself nested inside another
915
+ scrolling container, e.g. a `gog-scroll` capping its height. Both axes are
916
+ now `visible` unless that specific axis is genuinely scrolling.
917
+
918
+ ## [21.2.3] - 03.08.2026
919
+
920
+ ### Added
921
+
922
+ - `fullWidth` input on `gog-checkbox`, `gog-chip` and `gog-tag`, matching the
923
+ existing `gog-button` behavior: `false` by default (sized to content), `true`
924
+ stretches the component to fill its container.
925
+ - `fullWidth` input on `gog-inputfield`, `gog-select`, `gog-multiselect`,
926
+ `gog-table`, `gog-paginator` and `gog-slider`. Inverted from the input above:
927
+ these are already full width of their container by default, so `fullWidth`
928
+ defaults to `true` and set it to `false` to shrink the control to fit its
929
+ content instead (a fixed `--gog-slider-auto-width`, 240px by default, for
930
+ `gog-slider` specifically — its track has no content of its own to size to).
931
+ - `gog-accordion`'s `skeletonCount` input: how many skeleton rows to render
932
+ while `loading` is true and `items` is still empty. Defaults to `3`.
933
+
934
+ ### Changed
935
+
936
+ - `gog-accordion`'s `loading` skeleton now renders with `gog-skeleton` instead
937
+ of a bespoke shimmer implementation. **Breaking:** the
938
+ `--gog-accordion-skeleton-start/-mid/-end/-radius/-height/-width/-duration`
939
+ tokens are gone — restyle the loading state via the shared `--gog-skeleton-*`
940
+ tokens instead.
941
+
942
+ ### Fixed
943
+
944
+ - `gog-accordion`'s `loading` skeleton now actually renders while `items` is
945
+ empty. It previously rendered one skeleton row per existing item, so the
946
+ most common real-world case — showing loading state before the item list
947
+ has arrived at all — silently rendered nothing. It now falls back to
948
+ `skeletonCount` rows whenever `items` is empty, and still mirrors `items`
949
+ once they exist.
950
+ - `gog-accordion`'s chevron no longer force-rotates 180° when a custom
951
+ `gogAccordionChevron` template is supplied. Previously the wrapper always
952
+ rotated on open regardless of what the template rendered, so a template that
953
+ swapped between a `chevron-up`/`chevron-down` icon per `open` state ended up
954
+ double-transformed (both states visually pointing the same way). The rotation
955
+ now only applies to the built-in default chevron; a custom template owns its
956
+ open/closed presentation entirely, including bringing its own animation or
957
+ swapping in a completely different icon.
958
+
959
+ ## [21.2.2] - 30.07.2026
960
+
961
+ ### Added
962
+
963
+ - `column`'s `comparator` input for custom per-column sort ordering; the default
964
+ comparator now uses `Intl.Collator` for numeric-aware string sorting
965
+ (`"item2" < "item10"`) instead of raw `<`/`>`.
966
+ - `gog-table` cell/sort values now resolve dot-path nested fields (e.g.
967
+ `field="address.city"`).
968
+ - ESLint (`@angular-eslint`, flat config) across `@gleks/ui` and `ui-showcase`, wired
969
+ into CI alongside `format:check` and a token-consistency check (every
970
+ `var(--gog-*)` read with no fallback must resolve to a declared default).
971
+ - `LICENSE` (MIT) and this changelog.
972
+
973
+ ### Changed
974
+
975
+ - **Breaking:** every previously unprefixed global design token in `theme.css`
976
+ (`--accent-color`, `--text-color`, `--radius`, `--control-*`, `--field-*`,
977
+ `--dropdown-z`, etc.) is now `--gog-*` prefixed, matching the component-token
978
+ convention. Update any consumer theme overrides to the new names.
979
+ - **Breaking:** `column`'s `field` input is now a plain `string` (was
980
+ `keyof T & string`) to support nested dot-paths.
981
+ - `gog-select`/`gog-multiselect` panel sizing constants (max height, estimated row
982
+ height) are now read from CSS custom properties
983
+ (`--gog-select-panel-max-height`, `--gog-select-option-height`, and the
984
+ multiselect equivalents) instead of hardcoded in TypeScript, so they're themeable.
985
+ - `gog-table`'s pagination state now uses `linkedSignal` instead of a manual
986
+ `effect`, resetting to page 1 on sort changes and clamping to `totalPages` on
987
+ data/page-size changes, while still deferring to `gog-paginator`'s own
988
+ self-clamping `page` model.
989
+
990
+ ### Fixed
991
+
992
+ - An append-to-body dropdown panel now copies the trigger's scoped `data-theme`
993
+ (not just `:root`'s) onto its overlay host, so panels stay themed when opened
994
+ inside a themed subtree.
995
+ - `gog-select` now correctly reads its own `--gog-select-option-gap` token for
996
+ panel-height estimation instead of the unused base default, fixing a latent
997
+ under-estimate in the panel's up/down placement math.
998
+
999
+ ## [0.0.1] through 0.2.2
1000
+
1001
+ Initial development, published as `0.0.1`: accordion, button, checkbox, chip, dialog,
1002
+ icon, inputfield, multiselect, paginator, select, skeleton, slider, spinner, table, tag
1003
+ and toast components, plus the shared theme (`styles/theme.css`) and `ThemeService`.
1004
+ Versions up to `0.2.2` were developed without per-release changelog entries. `0.2.2` was
1005
+ published with the wrong version scheme and immediately re-published, with no code
1006
+ changes, as `21.2.2` — this file tracks changes from `21.2.2` onward.