@guildofgleks/ui 21.3.1 → 21.4.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 ADDED
@@ -0,0 +1,1351 @@
1
+ # @guildofgleks/ui — AI agent guide
2
+
3
+ This file is for an AI coding agent (Claude, Copilot, Cursor, etc.) helping a developer build
4
+ an app that **consumes** the published `@guildofgleks/ui` npm package. It is not about
5
+ authoring the library — if you are working inside the `gleks_web_ui` monorepo itself, read
6
+ `.github/instructions/*.md` instead.
7
+
8
+ Everything below reflects the library's actual source as of **`21.4.0`**. `README.md` covers the
9
+ same ground at a higher level — install, setup, theming, global configuration — and is accurate;
10
+ this file goes further, into per-component input tables, and is the one to trust for exact names,
11
+ types and defaults.
12
+
13
+ > **Maintainers:** this file ships inside the npm package and is the API reference an agent reads
14
+ > while writing code against it, so a stale table here becomes wrong code in someone else's app —
15
+ > silently, because nothing fails a build. **Any change to an input, output, slot, type, service
16
+ > method or default updates this file in the same change**, and moves the version marker in the
17
+ > paragraph above. See `.github/instructions/gleks-ui-library.instructions.md`, definition of
18
+ > done, step 9.
19
+
20
+ ## Quick facts
21
+
22
+ - Angular **v21+** only (`peerDependencies` require `^21.2.0` for `@angular/core`,
23
+ `@angular/common`, `@angular/forms`, `@angular/platform-browser`). No support for older
24
+ Angular.
25
+ - No Angular CDK, no Material. Only runtime dependency is `tslib`.
26
+ - Every component is **standalone**, `ChangeDetectionStrategy.OnPush`, and built with signals —
27
+ `input()` / `output()` / `model()`, never `@Input()`/`@Output()` decorators, never `ngClass`/
28
+ `ngStyle`.
29
+ - **Reactive Forms only.** Every form control implements `ControlValueAccessor` and is built
30
+ and tested against `[formControl]` / `formControlName`. The library never imports
31
+ `FormsModule` and `[(ngModel)]` is untested — don't suggest it.
32
+ - Theming is 100% CSS custom properties (`--gog-*`) — no Sass config, no JS theme objects, no
33
+ build step to restyle anything.
34
+ - Tree-shakeable: `"sideEffects": false` and every component is a separate standalone import, so
35
+ importing `ButtonComponent` alone does not pull in the rest of the library.
36
+ - SSR-safe: anything touching `window`/`document` is guarded with `isPlatformBrowser`/
37
+ `afterNextRender`.
38
+
39
+ ## Install & setup
40
+
41
+ ```bash
42
+ npm install @guildofgleks/ui
43
+ ```
44
+
45
+ Add the baseline stylesheet once — it carries every token the components read plus their
46
+ utility classes, so without it components render unstyled:
47
+
48
+ ```jsonc
49
+ // angular.json → projects.<app>.architect.build.options
50
+ "styles": [
51
+ "node_modules/@guildofgleks/ui/styles/index.css",
52
+ "src/styles.scss", // your own styles, after the baseline so they win
53
+ ],
54
+ ```
55
+
56
+ Import components where you use them — every one is standalone:
57
+
58
+ ```ts
59
+ import { Component } from '@angular/core';
60
+ import { ButtonComponent, SelectComponent } from '@guildofgleks/ui';
61
+
62
+ @Component({
63
+ selector: 'app-example',
64
+ imports: [ButtonComponent, SelectComponent],
65
+ template: `
66
+ <gog-select label="Region" [options]="regions" [(value)]="region" />
67
+ <gog-button (gogClick)="save()">Save</gog-button>
68
+ `,
69
+ })
70
+ export class ExampleComponent {}
71
+ ```
72
+
73
+ ## Core conventions (read once, applies everywhere)
74
+
75
+ These hold for essentially every component in the library. Knowing them means you can guess a
76
+ new component's API correctly instead of guessing wrong and hallucinating an input that doesn't
77
+ exist.
78
+
79
+ - **Selector prefix `gog-`** for components (`gog-button`, `gog-select`, …), attribute selectors
80
+ for directives (`gogTooltip`, `[gogBadge]`).
81
+ - **Outputs are prefixed `gog`** so they never collide with native DOM events —
82
+ `gogClick`, `gogToggle`, `gogSearch`, `gogTabChange`, `gogRemove`, `gogScroll`, `gogLoadMore`,
83
+ `gogDateSelect`. **Inputs keep their natural name** (`variant`, `size`, `disabled`).
84
+ - **Two-way binding via `model()`.** Wherever a component holds a value the consumer drives, it's
85
+ a `model()` input — bind with `[(value)]="signal"` / `[(checked)]="signal"` /
86
+ `[(open)]="signal"` etc., or split into `[value]` + `(valueChange)`.
87
+ - **Every input has a zero-config default.** Nothing requires configuration to render something
88
+ reasonable.
89
+ - **`size` is `GogSize = 'xsm' | 'sm' | 'md' | 'lg' | 'slg'`**, shared by every sized component.
90
+ Default is `'md'` almost everywhere — exceptions: `gog-accordion` and `gog-table` default to
91
+ `'lg'` (their `size` means row/section density, not form-control size), `gog-paginator`
92
+ defaults to `'sm'`.
93
+ - **`variant` is `GogVariant = 'primary' | 'secondary' | 'outline' | 'ghost'`** on `gog-button`.
94
+ Status-colored components (`gog-tag`, `gog-badge`) use a different, four-value
95
+ `GogTagVariant = 'success' | 'danger' | 'warning' | 'info'` instead — don't confuse the two.
96
+ - **`errorDisplay: GogErrorDisplay = 'auto' | 'manual'`** (default `'manual'`) on every control
97
+ that shows a validation message (inputfield, textarea, select, multiselect, autocomplete,
98
+ radio-group, slider, datepicker). `'manual'`: the field shows `errorMessage` whenever it's
99
+ non-empty — you own the timing (`errorMessage="control.invalid && control.touched ? 'Required' : ''"`).
100
+ `'auto'`: shown once the attached `[formControl]`/`formControlName` is touched _and_ invalid —
101
+ you only supply the message text. `'auto'` silently behaves like `'manual'` if there's no real
102
+ form control attached.
103
+ - **`inputId` is optional everywhere.** Every form control renders a real `id` — its own if you
104
+ pass one, a generated one otherwise — so the `<label for>` and the error message's
105
+ `aria-describedby` are always wired up. Pass `inputId` only when something outside the
106
+ component needs to reference the field by a known id; never pass one just to get a label.
107
+ - **User-visible chrome strings come from `GOG_CONFIG.labels`**, not from an input per string —
108
+ "Clear", "Close dialog", "Go to page 4" and the rest. Per-instance label inputs exist where a
109
+ single control realistically differs and win over the config. See
110
+ [`labels`](#labels--translating-the-library).
111
+ - **`floatLabel: GogFloatLabelVariant = 'none' | 'in' | 'on' | 'over'`** (default `'none'`) on
112
+ the six field controls: inputfield, textarea, select, multiselect, autocomplete, datepicker.
113
+ `'in'` floats up but stays inside the border, `'on'` floats to sit centered on the top border
114
+ line, `'over'` floats fully above the field. Pair with `floatLabelShowPlaceholder` (default
115
+ `false`) to reveal the field's own `placeholder` once the label has floated clear.
116
+ - **`clearable`** (default varies) on inputfield, textarea, select, multiselect, autocomplete,
117
+ datepicker — shows a clear (×) button once the field has content. Off by default everywhere
118
+ except `gog-multiselect`, which had one before the input existed.
119
+ - **Generic option accessors, not a fixed DTO.** Any collection-driven control (`gog-select`,
120
+ `gog-multiselect`, `gog-autocomplete`, `gog-button-toggle-group`) takes **your own object
121
+ shape** through `optionLabel` / `optionValue` / `optionDisabled` — each is a property path
122
+ (`'name'`, dot-paths like `'profile.title'` work) **or** a function
123
+ `(option: T) => TResult`. Defaults are `'name'` / `'id'` / `'disabled'`. Set
124
+ `[optionValue]="null"` to emit **the option object itself** instead of a plucked id — the
125
+ control then round-trips your own object with no lookup table needed:
126
+ ```html
127
+ <gog-select [options]="members" [optionLabel]="nameOf" [optionValue]="null" [(value)]="member" />
128
+ ```
129
+ - **Global defaults via `GOG_CONFIG` / `provideGogConfig(...)`** — see its own section below.
130
+ Precedence is always: the instance's own input (if set) → `GOG_CONFIG` → the component's
131
+ built-in default.
132
+ - **Don't bind both a `model()` and a form directive on the same instance.** Every CVA control
133
+ (checkbox, toggle, radio-group, inputfield, textarea, select, multiselect, autocomplete,
134
+ slider, datepicker) exposes its value as both a two-way `model()` (`[(checked)]`, `[(value)]`)
135
+ and, separately, `ControlValueAccessor` for `[formControl]`/`formControlName`. Pick one per
136
+ instance — wiring both gives the value two competing sources of truth.
137
+ - **The custom-content slot pattern.** Wherever a component needs custom markup for a specific
138
+ part of itself, it's an attribute directive read with `contentChild()`, given a **typed**
139
+ context via `let-` variables — never a plain `TemplateRef` input, never a string-keyed lookup.
140
+ Recognize the shape:
141
+ ```html
142
+ <gog-accordion [items]="items">
143
+ <ng-template gogAccordionHeader let-item let-open="open">{{ item.title }}</ng-template>
144
+ </gog-accordion>
145
+ ```
146
+ See the per-component tables below for which slot directives exist on which component.
147
+ - **Legacy `TemplateRef` inputs and string-keyed lookups still exist on a few components and
148
+ still work, but are `@deprecated` — do not use them in new code.** See
149
+ [Deprecated patterns — do not use in new code](#deprecated-patterns--do-not-use-in-new-code).
150
+ - **Accessibility is built in**, not optional: keyboard navigation (roving tabindex, arrow keys,
151
+ Home/End), ARIA roles/states, `:focus-visible` styling, `prefers-reduced-motion` handling, and
152
+ WCAG AA contrast are already implemented — you don't need to add any of this yourself, just
153
+ supply `ariaLabel`/`label` inputs where a component has no visible text of its own (icon-only
154
+ buttons, `gog-progressbar`, `gog-scroll`).
155
+ - **`aria-label` on the host tag does nothing.** Several components (`gog-button` chief among
156
+ them) render their real interactive element (a `<button>`) _inside_ the component's own host
157
+ tag. An `aria-label` attribute placed directly on `<gog-button>` in a template lands on the
158
+ custom element wrapper, not on the inner `<button>`, so assistive tech never sees it — always
159
+ use the component's own `ariaLabel` input instead.
160
+
161
+ ## Theming
162
+
163
+ Full model is in `README.md`'s Theming section and `theming.md`; short version:
164
+
165
+ - Every visual value (color, spacing, radius, shadow, duration) is a `--gog-*` CSS custom
166
+ property, layered **foundation** (`--gog-accent-color`, `--gog-space-md`, …, restyles
167
+ everything) → **component** (`--gog-btn-primary-bg`, …, one block per component) →
168
+ **instance** (`--gog-btn-bg`, …, deliberately undeclared escape hatch for one element).
169
+ - Theme switch is a `data-theme` attribute, usually on `<html>`, toggled through the
170
+ `ThemeService` (`inject(ThemeService).setTheme('dark')` / `.toggleTheme()` / `.theme` signal).
171
+ Ships `light` and `dark`. Three more importable presets: `slate`, `one-dark`, `one-light`
172
+ (`@guildofgleks/ui/styles/presets/<name>.css`).
173
+ - Restyle one instance without touching a theme: `<gog-button style="--gog-btn-bg: #ff4edb">`.
174
+ - Build a custom theme by declaring a palette against a new `data-theme` value (see
175
+ `theming.md` for the full worked example) — component tokens re-derive automatically, you
176
+ don't restate them.
177
+
178
+ ## Global configuration — `GOG_CONFIG` / `provideGogConfig(...)`
179
+
180
+ For the handful of inputs an app typically wants to set once (a size for every form control, a
181
+ locale for every datepicker) rather than repeat on every instance:
182
+
183
+ ```ts
184
+ import { provideGogConfig } from '@guildofgleks/ui';
185
+
186
+ bootstrapApplication(App, {
187
+ providers: [
188
+ provideGogConfig({
189
+ control: { size: 'sm', errorDisplay: 'auto', clearable: true },
190
+ dropdown: { appendToBody: true, filter: true },
191
+ datepicker: { locale: 'de-DE', firstDayOfWeek: 1, format: 'dd.MM.yyyy' },
192
+ toast: { position: 'top-right', duration: 4000 },
193
+ }),
194
+ ],
195
+ });
196
+ ```
197
+
198
+ Precedence, always: **instance input → `GOG_CONFIG` → component's built-in default.** A nested
199
+ `provideGogConfig(...)` (in a route's or component's own `providers`) **layers onto the
200
+ parent's config**, one level deep per key — it does not replace it.
201
+
202
+ | Key | Fields | Applies to |
203
+ | -------------- | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
204
+ | `control` | `size`, `errorDisplay`, `clearable` | `size`: button, inputfield, textarea, select, multiselect, checkbox, radio-group, button-toggle-group, datepicker. `errorDisplay`: inputfield, textarea, select, multiselect, autocomplete, radio-group, slider, datepicker. `clearable`: inputfield, textarea, select, multiselect, autocomplete, datepicker. Not table/accordion/paginator (density, not form size), not spinner/skeleton/tag/chip/toggle. |
205
+ | `dropdown` | `appendToBody`, `direction`, `filter`, `filterPosition` | `gog-select`, `gog-multiselect`. `gog-datepicker`/`gog-autocomplete` honour `appendToBody`/`direction` too (autocomplete has no `filter` box — it filters via the trigger's own text). |
206
+ | `floatLabel` | `variant`, `showPlaceholder` | inputfield, textarea, select, multiselect, autocomplete, datepicker. |
207
+ | `datepicker` | `locale`, `firstDayOfWeek`, `format` | `gog-datepicker`, `gog-calendar`. |
208
+ | `autocomplete` | `searchDebounce`, `minLength`, `openOnFocus` | `gog-autocomplete`. |
209
+ | `tooltip` | `position`, `showDelay`, `hideDelay` | the `gogTooltip` directive. |
210
+ | `scroll` | `autoHide`, `hideDelay`, `size`, `overscrollBehavior`, `showTrack` | `gog-scroll` (and every component that uses one internally). |
211
+ | `button` | `debounce` | `gog-button`. |
212
+ | `inputfield` | `showSpinButtons` | `gog-inputfield`. |
213
+ | `textarea` | `resize` | `gog-textarea`. |
214
+ | `paginator` | `showPageSizeSelect`, `pageSizeOptions` | `gog-paginator`, and through it `gog-table`'s built-in pagination.
215
+ | `toast` | `position`, `duration` | `ToastService`. |
216
+ | `theme` | `storageKey`, `defaultTheme`, `followSystem`, `lightTheme`, `darkTheme` | `ThemeService`. All off/neutral by default — see below. |
217
+ | `labels` | every fixed string the library renders — see below | inputfield, textarea, select, multiselect, autocomplete, datepicker, calendar, paginator, table, `DialogService`, `ToastService`. |
218
+
219
+ Anything visual does **not** belong here — override the `--gog-*` token instead.
220
+
221
+ ### `labels` — translating the library
222
+
223
+ Every string a component renders that the consumer never writes markup for. An app that isn't
224
+ in English sets these once rather than on every control:
225
+
226
+ ```ts
227
+ provideGogConfig({
228
+ labels: {
229
+ clear: 'Очистить', // inputfield / textarea clear button
230
+ clearSelection: 'Очистить выбор', // select / multiselect / autocomplete
231
+ clearDate: 'Очистить дату', // datepicker
232
+ selectAll: 'Выбрать все', // multiselect panel
233
+ clearAll: 'Очистить', // multiselect panel
234
+ increment: 'Увеличить', // number spin buttons
235
+ decrement: 'Уменьшить',
236
+ showPassword: 'Показать пароль',
237
+ hidePassword: 'Скрыть пароль',
238
+ closeDialog: 'Закрыть',
239
+ closeToast: 'Закрыть',
240
+ pagination: 'Навигация по страницам',
241
+ previousPage: 'Предыдущая страница',
242
+ nextPage: 'Следующая страница',
243
+ openCalendar: 'Открыть календарь',
244
+ rowsPerPage: 'Строк на странице', // gog-paginator's size select
245
+ total: 'Всего', // gog-table's row-count label
246
+ tablePagination: 'Навигация по таблице',
247
+ selectRow: 'Выбрать строку',
248
+ selectAllRows: 'Выбрать все строки на странице',
249
+ today: 'Сегодня',
250
+ thisMonth: 'Текущий месяц',
251
+ previousMonth: 'Предыдущий месяц',
252
+ nextMonth: 'Следующий месяц',
253
+ previousYear: 'Предыдущий год',
254
+ nextYear: 'Следующий год',
255
+ hours: 'Часы',
256
+ minutes: 'Минуты',
257
+ seconds: 'Секунды',
258
+ // The one non-string field: it interpolates the page number, and word order and
259
+ // agreement around a number vary by language, so it takes a formatter.
260
+ page: (page, isCurrent) =>
261
+ isCurrent ? `Страница ${page}, текущая` : `Перейти на страницу ${page}`,
262
+ },
263
+ });
264
+ ```
265
+
266
+ Strings that describe **one** control rather than library chrome — `gog-checkbox`'s `ariaLabel`,
267
+ `gog-button`'s `ariaLabel`, any field's `label`/`placeholder` — are deliberately **not** here.
268
+ Those stay per instance. Where a per-instance label input exists (`clearAriaLabel`, `todayLabel`,
269
+ …) it still wins over the configured value.
270
+
271
+ ## Services
272
+
273
+ ### `ThemeService`
274
+
275
+ ```ts
276
+ private readonly theme = inject(ThemeService);
277
+ this.theme.theme(); // Signal<string>, READ-ONLY — current data-theme
278
+ this.theme.setTheme('dark'); // any theme name, including a custom one you declared in CSS
279
+ this.theme.toggleTheme(); // flips between the configured light and dark names
280
+ ```
281
+
282
+ `theme` is read-only on purpose: writing to it would move the signal without touching the
283
+ `data-theme` attribute the styles actually read. Never suggest `theme.set(...)` — it does not
284
+ exist.
285
+
286
+ Zero-config behaviour: adopt whatever `data-theme` is already on `<html>`, else `'light'`.
287
+ Persistence and following the OS setting are **opt-in**, so upgrading cannot change which theme
288
+ an existing app opens in:
289
+
290
+ ```ts
291
+ provideGogConfig({
292
+ theme: {
293
+ storageKey: 'app-theme', // persist the choice in localStorage; unset = no persistence
294
+ followSystem: true, // open in the OS prefers-color-scheme, and keep following it
295
+ // until the app calls setTheme/toggleTheme
296
+ lightTheme: 'light', // the two names followSystem maps to and toggleTheme alternates
297
+ darkTheme: 'cyberpunk', // between
298
+ defaultTheme: 'light', // used when nothing else decides
299
+ },
300
+ });
301
+ ```
302
+
303
+ Resolution order at startup: existing `data-theme` on the document → persisted value →
304
+ OS setting (if `followSystem`) → `defaultTheme` → `'light'`.
305
+
306
+ ### `ToastService`
307
+
308
+ Root-provided singleton. Requires a `<gog-toast-container />` placed once in your app (see
309
+ [gog-toast](#gog-toast--gog-toast-container) below — it is **not** wired up automatically).
310
+
311
+ ```ts
312
+ private readonly toast = inject(ToastService);
313
+
314
+ this.toast.success('Saved');
315
+ this.toast.error('Could not save', {
316
+ isSticky: true,
317
+ actions: [{ label: 'Retry', onClick: () => this.save() }],
318
+ });
319
+ // also: .warning(msg, config?), .info(msg, config?), .show(config), .dismiss(id), .dismissAll()
320
+ ```
321
+
322
+ `ToastConfig`: `{ message, type?, iconName?, iconTemplate?, actions?, dedupeKey?, isSticky?, duration?, position? }`.
323
+ Repeated calls with the same (explicit or inferred) `dedupeKey` replace the existing toast in
324
+ place instead of stacking a duplicate.
325
+
326
+ ### `DialogService`
327
+
328
+ Root-provided singleton, imperative dynamic-component dialogs. Requires a `<gog-dialog />`
329
+ placed once in your app (see [gog-dialog](#gog-dialog) below — also **not** automatic).
330
+
331
+ ```ts
332
+ private readonly dialogService = inject(DialogService);
333
+
334
+ async confirmDelete(): Promise<void> {
335
+ const handle = this.dialogService.open<boolean>({
336
+ component: ConfirmationDialogComponent, // or your own component
337
+ title: 'Delete this item?',
338
+ role: 'alertdialog',
339
+ data: { message: 'This cannot be undone.' },
340
+ });
341
+ const confirmed = await handle.afterClosed; // boolean | undefined
342
+ }
343
+ ```
344
+
345
+ `DialogConfig`: `{ title?, component, data?, modal? (default true), closable?, draggable?, closeIconName?, closeIconTemplate?, width?, maxWidth?, role? ('dialog' default | 'alertdialog'), zIndex? }`.
346
+ `open()` returns `{ close(result?), afterClosed: Promise<TResult | undefined> }`. Also:
347
+ `closeAll(result?)`, `updatePosition(id, offsetX, offsetY)` (for `draggable` dialogs).
348
+
349
+ The library ships a ready-made `ConfirmationDialogComponent` for yes/no prompts — pass it as
350
+ `component` with `data: { title, description, confirmText, cancelText }`; it resolves the
351
+ dialog's result to `true`/`false`.
352
+
353
+ **Wiring a custom component into a dialog** — it reads its data via `DIALOG_DATA` and closes
354
+ itself via `DIALOG_REF`:
355
+
356
+ ```ts
357
+ import { Component, inject } from '@angular/core';
358
+ import { DIALOG_DATA, DIALOG_REF } from '@guildofgleks/ui';
359
+
360
+ @Component({ selector: 'app-edit-dialog', template: `…` })
361
+ export class EditDialogComponent {
362
+ protected readonly data = inject<{ userId: string }>(DIALOG_DATA);
363
+ private readonly ref = inject(DIALOG_REF);
364
+
365
+ save(): void {
366
+ this.ref.close({ saved: true });
367
+ }
368
+ }
369
+ ```
370
+
371
+ ---
372
+
373
+ ## Component reference
374
+
375
+ Every component below is exported from `@guildofgleks/ui`'s root — `import { X } from '@guildofgleks/ui'`.
376
+ "CVA" = implements `ControlValueAccessor` (works with `[formControl]`/`formControlName`).
377
+
378
+ ### Buttons & choices
379
+
380
+ #### `gog-button`
381
+
382
+ | Input | Type | Default | Notes |
383
+ | ----------- | --------------------------------- | ----------- | ----------------------------------------------------- |
384
+ | `variant` | `GogVariant` | `'primary'` | |
385
+ | `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
386
+ | `disabled` | `boolean` | `false` | |
387
+ | `fullWidth` | `boolean` | `false` | |
388
+ | `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | |
389
+ | `loading` | `boolean` | `false` | shows an inline `gog-spinner`, blocks clicks |
390
+ | `debounce` | `number \| undefined` | `300` | ms; via `GOG_CONFIG.button.debounce` — see note below |
391
+ | `ariaLabel` | `string \| null` | `null` | **use this, not a raw `aria-label` attribute** |
392
+
393
+ Outputs: `gogClick: MouseEvent`.
394
+
395
+ **`debounce` is a spam guard, not a delay before the first click.** The first click in a window
396
+ fires immediately (leading edge); further clicks within `debounce` ms are silently dropped.
397
+
398
+ ```html
399
+ <gog-button variant="primary" [loading]="saving()" (gogClick)="save()">Save</gog-button>
400
+ <gog-button variant="ghost" ariaLabel="Close" (gogClick)="close()"
401
+ ><gog-icon name="close"
402
+ /></gog-button>
403
+ ```
404
+
405
+ #### `gog-button-toggle-group`
406
+
407
+ A row of buttons, single- or multi-select, built from your own option objects.
408
+
409
+ | Input | Type | Default | Notes |
410
+ | ------------------------------------ | ------------------------------------------ | ---------------------- | ------------------------------------- |
411
+ | `options` | `TOption[]` | `[]` | |
412
+ | `optionLabel` | accessor | `'name'` | |
413
+ | `optionValue` | accessor \| `null` | `'id'` | `null` emits the option object |
414
+ | `optionDisabled` | accessor | `'disabled'` | |
415
+ | `optionIcon` | accessor → `GogIconName \| null` \| `null` | `null` | optional leading icon per option |
416
+ | `multiple` | `boolean` | `false` | changes ARIA role entirely — see note |
417
+ | `appearance` | `'joined' \| 'separated'` | `'joined'` | |
418
+ | `orientation` | `GogOrientation` | `'horizontal'` | |
419
+ | `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
420
+ | `disabled`, `fullWidth`, `ariaLabel` | | `false`, `false`, `''` | |
421
+
422
+ Model: `value: TValue | TValue[] | null` (single value, or array in `multiple` mode). CVA: yes.
423
+ Slot: `<ng-template gogButtonToggleOption let-opt let-selected="selected">` for custom button
424
+ markup. **Single mode is a radio group** (`role="radiogroup"`, arrows move _and_ select);
425
+ **multiple mode is a toolbar of independent toggles** (`role="group"`, arrows only move, Space
426
+ toggles) — this is a real ARIA distinction, not cosmetic.
427
+
428
+ ```html
429
+ <gog-button-toggle-group [options]="alignments" [(value)]="align" />
430
+ <gog-button-toggle-group [options]="tools" [multiple]="true" [(value)]="activeTools" />
431
+ ```
432
+
433
+ ### Form fields
434
+
435
+ #### `gog-inputfield`
436
+
437
+ | Input | Type | Default | Notes |
438
+ | ----------------------------------------- | ------------------------------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------ |
439
+ | `label`, `placeholder` | `string` | `''` | |
440
+ | `type` | `GogInputType` | `'text'` | `text`/`password`/`email`/`number`/`search`/`tel`/`url`/`date`/`time`/`datetime-local` |
441
+ | `readonly` | `boolean` | `false` | value stays focusable and submitted, edits blocked; hides the clear button and stepper |
442
+ | `maxlength`, `minlength` | `number \| null` | `null` | native attributes |
443
+ | `pattern` | `string` | `''` | native attribute, regex source |
444
+ | `inputMode` | `GogInputMode \| null` | `null` | on-screen keyboard hint (`numeric`, `tel`, …) |
445
+ | `spellcheck` | `boolean \| null` | `null` | unset = browser default |
446
+ | `inputId` | `string` | `''` → generated | a real id is always rendered; pass one only to reference the field externally |
447
+ | `min`, `max`, `step` | `number \| null` | `null` | `type="number"` only |
448
+ | `showSpinButtons` | `boolean \| undefined` | `true` | own +/- glyphs on `type="number"`; via `GOG_CONFIG.inputfield.showSpinButtons` |
449
+ | `errorMessage`, `errorDisplay` | | `''`, `'manual'` | see conventions |
450
+ | `disabled`, `size`, `fullWidth` | | `false`, `'md'`, `true` | |
451
+ | `iconStart` / `iconEnd` | `GogIconName \| ''` | `''` | bare leading/trailing icon |
452
+ | `clearable`, `clearAriaLabel` | | `false`, `'Clear'` | on `type="number"` the clear button renders alongside the stepper |
453
+ | `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
454
+ | `showPasswordLabel` / `hidePasswordLabel` | `string \| undefined` | `'Show password'`/`'Hide password'` | `type="password"` reveal toggle aria-labels; via `GOG_CONFIG.labels` |
455
+ | `incrementLabel` / `decrementLabel` | `string \| undefined` | `'Increment'`/`'Decrement'` | spin button aria-labels; via `GOG_CONFIG.labels` |
456
+
457
+ Model: `value: string` (always a string, even for `type="number"` — the _form control_ value is
458
+ `number | null`, but the `[(value)]` model mirrors the raw text). CVA: yes.
459
+
460
+ Slots: project `<span gogInputAddonStart>`/`<span gogInputAddonEnd>` (or a `<button>`) for
461
+ custom leading/trailing markup — a normal DOM element with its own `aria-label`, click handler
462
+ and disabled state, not a component-managed slot. This is the **current, non-deprecated**
463
+ replacement for the old icon-template/icon-fn/icon-label input quartet — see
464
+ [Deprecated patterns](#deprecated-patterns--do-not-use-in-new-code).
465
+
466
+ ```html
467
+ <gog-inputfield
468
+ label="Email"
469
+ type="email"
470
+ formControlName="email"
471
+ errorDisplay="auto"
472
+ errorMessage="Enter a valid email"
473
+ [clearable]="true"
474
+ />
475
+
476
+ <gog-inputfield label="Amount" [fullWidth]="false">
477
+ <span gogInputAddonStart>€</span>
478
+ </gog-inputfield>
479
+ ```
480
+
481
+ #### `gog-textarea`
482
+
483
+ | Input | Type | Default |
484
+ | ------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------- |
485
+ | `label`, `placeholder` | `string` | `''` |
486
+ | `rows` | `number` | `4` |
487
+ | `readonly` | `boolean` | `false` |
488
+ | `maxlength`, `minlength` | `number \| null` | `null` |
489
+ | `spellcheck` | `boolean \| null` | `null` |
490
+ | `inputId` | `string` | `''` → generated, same as inputfield |
491
+ | `resize` | `GogTextareaResize \| undefined` (`'vertical'\|'horizontal'\|'both'\|'none'`) | `'vertical'`; via `GOG_CONFIG.textarea.resize` |
492
+ | `errorMessage`, `errorDisplay`, `disabled`, `size`, `fullWidth` | | same shape as inputfield |
493
+ | `clearable`, `clearAriaLabel`, `floatLabel`, `floatLabelShowPlaceholder` | | same shape as inputfield |
494
+
495
+ Model: `value: string`. CVA: yes.
496
+
497
+ ```html
498
+ <gog-textarea label="Notes" formControlName="notes" [rows]="6" resize="vertical" />
499
+ ```
500
+
501
+ #### `gog-select`
502
+
503
+ Extends the shared listbox behaviour (`GogDropdownBase`) that also backs `gog-multiselect` and
504
+ partly `gog-autocomplete` — placement, the append-to-body overlay, click-outside, keyboard nav,
505
+ and CVA all come from there. Full shared input surface (documented once, applies to both select
506
+ and multiselect unless noted otherwise):
507
+
508
+ | Input | Type | Default | Notes |
509
+ | ------------------------------------------------------ | --------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
510
+ | `label`, `ariaLabel`, `placeholder` | `string` | `''`, `''`, `'Select...'` | |
511
+ | `options` | `TOption[]` | `[]` | your own objects |
512
+ | `optionLabel` | accessor | `'name'` | path or fn |
513
+ | `optionValue` | accessor \| `null` | `'id'` | `null` = emit the option object |
514
+ | `optionDisabled` | accessor | `'disabled'` | |
515
+ | `clearable`, `clearAriaLabel` | | `false` (select) / `true` (multiselect), `'Clear selection'` | |
516
+ | `minWidth` | `string \| null` | `null` | only with `[fullWidth]="false"` |
517
+ | `filter` | `boolean \| undefined` | `false` | search box in the panel; via `GOG_CONFIG.dropdown.filter` |
518
+ | `filterPlaceholder`, `filterEmptyMessage` | `string` | `'Search...'`, `'No matches'` | |
519
+ | `filterPosition` | `'top' \| 'bottom' \| undefined` | `'top'` | via `GOG_CONFIG.dropdown.filterPosition` |
520
+ | `filterMatch` | `((option, query) => boolean) \| null` | `null` | custom matcher, else case-insensitive substring on the resolved label |
521
+ | `errorMessage`, `errorDisplay` | | `''`, `'manual'` | |
522
+ | `size` | `GogSize \| undefined` | `'md'` | |
523
+ | `dropdownDirection` | `'auto' \| 'up' \| 'down' \| undefined` | `'auto'` | |
524
+ | `dropdownZIndex`, `dropdownWidth`, `dropdownMaxHeight` | | `null` | only meaningful with `appendToBody` |
525
+ | `appendToBody` | `boolean \| undefined` | `false` | renders the panel into `<body>` — needed inside a scroll/overflow-clipped container |
526
+ | `disabled`, `fullWidth` | | `false`, `true` | |
527
+ | `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
528
+ | `inputId` (select/autocomplete only) | `string` | `''` | |
529
+
530
+ `gog-select`-specific: `value: model<TValue>(null)`.
531
+ `gog-multiselect`-specific additions: `value: model<TValue[]>([])`, `showControls: boolean` (default `false`, a select-all/clear row), `controlsPosition: 'top'|'bottom'` (default `'top'`), and `selectAllLabel`/`clearAllLabel` for that row's two buttons (`'Select all'`/`'Clear'`, also via `GOG_CONFIG.labels`).
532
+
533
+ CVA: yes, both. Slots (shared): `<ng-template gogDropdownChevron>` (custom chevron markup),
534
+ `<ng-template gogDropdownOption let-opt let-selected="selected" let-label="label">` (custom
535
+ option row). Multiselect adds `<ng-template gogMultiselectClearIcon>`.
536
+
537
+ ```html
538
+ <gog-select
539
+ label="Region"
540
+ [options]="regions"
541
+ optionLabel="title"
542
+ [(value)]="regionId"
543
+ [filter]="true"
544
+ />
545
+
546
+ <gog-multiselect
547
+ label="Tags"
548
+ [options]="tags"
549
+ [(value)]="selectedTagIds"
550
+ [showControls]="true"
551
+ formControlName="tags"
552
+ errorDisplay="auto"
553
+ />
554
+ ```
555
+
556
+ #### `gog-autocomplete`
557
+
558
+ Shares `GogDropdownBase` too, but the trigger is a real `<input>` (combobox pattern,
559
+ `aria-activedescendant`), not a listbox button — so it does **not** reuse the base's built-in
560
+ panel-filter box; it filters/searches off what's typed in the field itself.
561
+
562
+ | Input | Type | Default | Notes |
563
+ | ------------------------------------------------------------------------------------------------------ | ---------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
564
+ | _(all the shared `GogDropdownBase` inputs above except `filter`/`filterPlaceholder`/`filterPosition`)_ | | | |
565
+ | `filterLocal` | `boolean` | `true` | narrow `options` client-side as you type; turn **off** when `gogSearch` already returns a filtered server list (avoids double-filtering) |
566
+ | `minLength` | `number \| undefined` | `1` | via `GOG_CONFIG.autocomplete.minLength` |
567
+ | `openOnFocus` | `boolean \| undefined` | `true` | via `GOG_CONFIG.autocomplete.openOnFocus` |
568
+ | `searchDebounce` | `number \| undefined` | `300` | ms before `gogSearch` fires; via `GOG_CONFIG.autocomplete.searchDebounce` |
569
+ | `loading` | `boolean` | `false` | shows a spinner in the trailing slot |
570
+ | `emptyMessage` | `string` | `'No matches'` | |
571
+ | `forceSelection` | `boolean` | `true` | see note below |
572
+
573
+ Outputs: `gogSearch: string` (debounced query — wire your server lookup here),
574
+ `gogLoadMore: void` (panel scrolled to the end — fetch the next page).
575
+
576
+ Model: `value: TValue | null`. CVA: yes.
577
+
578
+ **`forceSelection` matters.** On (default): the field always ends up reflecting a real
579
+ selection — free-typed text that matches nothing snaps back on blur/Escape. Off: what the user
580
+ typed is itself meaningful (a create-as-you-type flow) — read the typed text from `gogSearch`,
581
+ not from `value`, since `value` clears the moment the text stops matching the selection.
582
+
583
+ ```html
584
+ <gog-autocomplete
585
+ [options]="users"
586
+ optionLabel="profile.fullName"
587
+ [optionValue]="null"
588
+ [(value)]="user"
589
+ [loading]="searching()"
590
+ (gogSearch)="search($event)"
591
+ />
592
+ ```
593
+
594
+ #### `gog-checkbox`
595
+
596
+ | Input | Type | Default |
597
+ | ---------------------------------------- | ---------------------- | ------- |
598
+ | `label`, `ariaLabel` | `string` | `''` |
599
+ | `size` | `GogSize \| undefined` | `'md'` |
600
+ | `indeterminate`, `disabled`, `fullWidth` | `boolean` | `false` |
601
+
602
+ Model: `checked: boolean`. CVA: yes. Slot: `<ng-template gogCheckboxIcon>` for a custom tick
603
+ icon (replaces the deprecated `checkIconTemplate` input).
604
+
605
+ ```html
606
+ <gog-checkbox label="I agree to the terms" formControlName="agree" />
607
+ ```
608
+
609
+ #### `gog-toggle`
610
+
611
+ An on/off switch (`role="switch"`) — semantically different from a checkbox ("is this setting
612
+ on", not "is this one of the things you selected").
613
+
614
+ | Input | Type | Default |
615
+ | ----------------------- | ---------------------- | ------- |
616
+ | `label`, `ariaLabel` | `string` | `''` |
617
+ | `size` | `GogSize \| undefined` | `'md'` |
618
+ | `disabled`, `fullWidth` | `boolean` | `false` |
619
+ | `labelPosition` | `'start' \| 'end'` | `'end'` |
620
+ | `onLabel`, `offLabel` | `string` | `''` | text rendered inside the track itself |
621
+
622
+ Model: `checked: boolean`. CVA: yes.
623
+
624
+ ```html
625
+ <gog-toggle label="Notifications" formControlName="notificationsOn" onLabel="ON" offLabel="OFF" />
626
+ ```
627
+
628
+ #### `gog-radio-group`
629
+
630
+ | Input | Type | Default |
631
+ | ------------------------------ | ----------------------------------------------- | ---------------- |
632
+ | `options` | `GogRadioOption[]` (`{ id, label, disabled? }`) | `[]` |
633
+ | `label`, `ariaLabel`, `name` | `string` | `''` |
634
+ | `size` | `GogSize \| undefined` | `'md'` |
635
+ | `disabled`, `fullWidth` | `boolean` | `false` |
636
+ | `orientation` | `GogOrientation` | `'vertical'` |
637
+ | `errorMessage`, `errorDisplay` | | `''`, `'manual'` |
638
+
639
+ Model: `value: string | number | null`. CVA: yes. Fixed `{ id, label, disabled? }` shape (not
640
+ a generic accessor, unlike select/multiselect/button-toggle).
641
+
642
+ ```html
643
+ <gog-radio-group
644
+ [options]="[{id:'m',label:'Male'},{id:'f',label:'Female'}]"
645
+ formControlName="gender"
646
+ />
647
+ ```
648
+
649
+ #### `gog-slider`
650
+
651
+ | Input | Type | Default |
652
+ | ------------------------------ | ---------------------- | ---------------------------------------------- |
653
+ | `label`, `ariaLabel` | `string` | `''` |
654
+ | `min`, `max`, `step` | `number` | `0`, `100`, `1` |
655
+ | `showValue`, `showThumb` | `boolean` | `true` |
656
+ | `errorMessage`, `errorDisplay` | | `''`, `'manual'` |
657
+ | `disabled` | `boolean` | `false` |
658
+ | `fullWidth` | `boolean` | `true` (ignored when `orientation="vertical"`) |
659
+ | `orientation` | `GogSliderOrientation` | `'horizontal'` |
660
+
661
+ Model: `value: number`. CVA: yes. Backed by a real `<input type="range">` (rotated via
662
+ `writing-mode` for vertical), so dragging/touch/keyboard all come from the platform.
663
+
664
+ ```html
665
+ <gog-slider label="Volume" [min]="0" [max]="100" formControlName="volume" />
666
+ ```
667
+
668
+ #### `gog-datepicker` / `gog-calendar`
669
+
670
+ `gog-datepicker` is a field + panel; `gog-calendar` is the month grid alone (what `inline` mode
671
+ renders). Native `Date` only — no date library, no adapter.
672
+
673
+ | Input | Type | Default | Notes |
674
+ | ----------------------------------------------------- | -------------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------- |
675
+ | `inputId`, `label`, `ariaLabel`, `placeholder` | `string` | `''` | |
676
+ | `selectionMode` | `GogDateSelectionMode` (`'single'\|'range'`) | `'single'` | |
677
+ | `min`, `max` | `Date \| null` | `null` | |
678
+ | `disabledDates` | `((date: Date) => boolean) \| null` | `null` | predicate, not a list |
679
+ | `defaultMonth` | `Date \| null` | `null` | which month opens when nothing is selected |
680
+ | `numberOfMonths` | `number` | `1` | `2` is what makes a range picker usable |
681
+ | `showTime`, `hourFormat`, `minuteStep`, `showSeconds` | | `false`, `'24'`, `1`, `false` | |
682
+ | `showTodayButton` | `boolean` | `true` | **selects** today |
683
+ | `showThisMonthButton` | `boolean` | `false` | only moves the _view_, leaves selection alone |
684
+ | `format` | `string \| null` | `null` | display/parse pattern (`'dd.MM.yyyy'`); derived from `showTime` when unset |
685
+ | `locale` | `string \| undefined` | `'en-US'` | via `GOG_CONFIG.datepicker.locale` |
686
+ | `firstDayOfWeek` | `number \| undefined` | locale's own | via `GOG_CONFIG.datepicker.firstDayOfWeek` |
687
+ | `allowTextInput` | `boolean` | `true` | typed text parsed against `format`; unparseable drafts don't clear the value |
688
+ | `inline` | `boolean` | `false` | renders the calendar with no field/panel |
689
+ | `disabled`, `fullWidth` | | `false`, `true` | |
690
+ | `clearable`, `clearAriaLabel` | | `false`, `'Clear date'` | |
691
+ | `errorMessage`, `errorDisplay`, `size` | | `''`, `'manual'`, `'md'` | |
692
+ | `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
693
+ | `appendToBody`, `dropdownDirection`, `dropdownZIndex` | | `false`, `'auto'`, `null` | |
694
+
695
+ Model: `value: Date | GogDateRange | null` (`GogDateRange = { start: Date | null; end: Date | null }`).
696
+ CVA: yes.
697
+
698
+ `gog-calendar` (usable standalone) takes most of the same date/range/time inputs directly, plus
699
+ `gogDateSelect: output<GogDatepickerValue>()` fired only on a _complete_ selection. It resolves
700
+ `locale` and `firstDayOfWeek` from `GOG_CONFIG.datepicker` itself, so a standalone calendar
701
+ honours an app-wide locale without being handed one; its navigation, shortcut and time labels
702
+ (`todayLabel`, `thisMonthLabel`, `previousMonthLabel`, `nextMonthLabel`, `previousYearLabel`,
703
+ `nextYearLabel`, `hoursLabel`, `minutesLabel`, `secondsLabel`) resolve through
704
+ `GOG_CONFIG.labels` the same way.
705
+
706
+ Also exported for direct reuse: `formatDate(date, pattern)`, `parseDate(text, pattern)`, and a
707
+ family of date-math helpers (`addDays`, `addMonths`, `isSameDay`, `isWithinBounds`, …) from
708
+ `date-utils`.
709
+
710
+ ```html
711
+ <gog-datepicker label="Birth date" [(value)]="birthDate" [max]="today" />
712
+ <gog-datepicker selectionMode="range" [(value)]="stayRange" [numberOfMonths]="2" />
713
+ ```
714
+
715
+ ### Display, feedback & status
716
+
717
+ #### `gog-icon`
718
+
719
+ | Input | Type | Default |
720
+ | ------------ | --------------------- | -------------------------------------------------- |
721
+ | `name` | `GogIconName` | `'close'` |
722
+ | `template` | `TemplateRef \| null` | `null` — custom markup instead of the built-in SVG |
723
+ | `title` | `string` | `''` |
724
+ | `ariaHidden` | `boolean` | `true` |
725
+
726
+ The package ships **41** glyphs (`GogBuiltinIconName`), all from [Lucide](https://lucide.dev)
727
+ and inlined so the package keeps zero runtime dependencies:
728
+
729
+ | Group | Names |
730
+ | --- | --- |
731
+ | Chevrons & arrows | `chevron-up`, `chevron-down`, `chevron-left`, `chevron-right`, `arrow-left`, `arrow-right` |
732
+ | Confirm & dismiss | `check`, `close`, `checkbox`, `checkbox-checked` |
733
+ | Status | `success`, `error`, `warning`, `info` |
734
+ | Sorting | `sort`, `sort-up`, `sort-down`, `filter` |
735
+ | Actions | `search`, `plus`, `minus`, `trash`, `pencil`, `copy`, `download`, `upload`, `refresh`, `external-link` |
736
+ | Chrome | `menu`, `more-horizontal`, `more-vertical`, `settings` |
737
+ | Objects & state | `user`, `lock`, `mail`, `calendar`, `clock`, `eye`, `eye-off`, `star`, `star-filled` |
738
+
739
+ `star` / `star-filled` is the one outline/filled pair, for a rating or favourite **toggle** —
740
+ the same reason `checkbox` / `checkbox-checked` exists. The set is otherwise outline-only on
741
+ purpose; a blanket solid duplicate of every glyph would double the payload for a distinction
742
+ almost nothing needs. If you want a filled variant of something else, register it with
743
+ `provideGogIcons`.
744
+
745
+ `Object.keys(ICON_DEFS)` is the runtime list, if you need to enumerate them (an icon picker, a
746
+ gallery). Do not hand-copy the names into an array — that is what goes stale.
747
+
748
+ ```html
749
+ <gog-icon name="calendar" />
750
+ ```
751
+
752
+ ##### Registering your own icons — `provideGogIcons(...)`
753
+
754
+ `name` is typed `GogIconName = GogBuiltinIconName | (string & {})`: the built-ins autocomplete,
755
+ and any name you register is accepted. **This is the supported way to use your own icon set** —
756
+ prefer it over the `template` input, which costs an `<ng-template>` at every use site and is for
757
+ one-offs.
758
+
759
+ ```ts
760
+ // app.config.ts
761
+ import { provideGogIcons } from '@guildofgleks/ui';
762
+
763
+ providers: [
764
+ provideGogIcons({
765
+ cart: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">…</svg>',
766
+ rocket: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">…</svg>',
767
+ }),
768
+ ];
769
+ ```
770
+
771
+ ```html
772
+ <gog-icon name="cart" />
773
+ <gog-tag iconName="cart">In basket</gog-tag>
774
+ <!-- works anywhere an icon *name* is taken -->
775
+ ```
776
+
777
+ - **Registered names win over built-ins of the same name** — that is how you replace the
778
+ library's checkmark or chevrons across every component at once, without touching any of them.
779
+ - **Nested `provideGogIcons(...)` layers onto the parent set** rather than replacing it, the same
780
+ as `provideGogConfig`: a lazy route can register only what it uses.
781
+ - **An unknown name renders nothing and warns in dev mode; it never throws.** An icon is
782
+ decoration — failing the render over a typo would be the worse outcome.
783
+ - **Write the SVG for inheritance:** a `viewBox`, `stroke="currentColor"` (or `fill`), and no
784
+ width/height — `gog-icon` drives size and stroke width from the `--gog-icon-*` tokens, so a
785
+ registered icon scales and colours like a built-in.
786
+ - **Security:** the markup is inserted with `bypassSecurityTrustHtml` (Angular's HTML sanitizer
787
+ strips SVG, so there is no alternative). That is fine for static icon markup you authored;
788
+ **never** build a registered icon string from user input or fetch it at runtime unsanitized.
789
+
790
+ #### `[gogButton]` — the link-flavoured button
791
+
792
+ `gog-button` renders its own `<button>`, so it can never *be* a link. `[gogButton]` inverts that:
793
+ the element stays yours and the directive only gives it the look.
794
+
795
+ ```html
796
+ <a gogButton routerLink="/pricing">See pricing</a>
797
+ <a gogButton variant="ghost" href="https://example.com" target="_blank" rel="noreferrer">Docs</a>
798
+ <button gogButton variant="outline" size="sm" type="submit">Save</button>
799
+ <a gogButton fullWidth routerLink="/checkout">Checkout</a>
800
+ ```
801
+
802
+ | Input | Type | Default |
803
+ | ----------- | ----------------------- | ------------------------------------ |
804
+ | `variant` | `GogVariant` | `'primary'` |
805
+ | `size` | `GogSize \| undefined` | `'md'`; via `GOG_CONFIG.control.size` |
806
+ | `fullWidth` | `boolean` (bare attr ok) | `false` |
807
+
808
+ Selector is `a[gogButton], button[gogButton]` — deliberately not a bare `[gogButton]`, because on
809
+ a `<div>` the result looks like a button and is invisible to the keyboard and to assistive tech.
810
+
811
+ **Which to reach for.** `gog-button` for a button that acts on the page: it owns `loading` (a
812
+ centred spinner it projects), `debounce` click throttling and the `gogClick` output, none of which
813
+ a bare element can provide. `[gogButton]` when the element must be a link, or when you need to
814
+ keep directives of your own on it — `routerLink`, `href`, `target`, `download`, `type="submit"`
815
+ and anything else keep working because they were never brokered through an input in the first
816
+ place. That is also why the library still has no `@angular/router` dependency.
817
+
818
+ Two things it deliberately does not do: no `disabled` on an `<a>` (there is no such thing — drop
819
+ the `href` or render a real `<button>`), and no loading state (the spinner is a projected child a
820
+ directive cannot add without taking over the element's content).
821
+
822
+ #### `[gogBadge]` — directive, not a component
823
+
824
+ Decorates an existing element (a button, an icon, an avatar) with a count/status dot — it never
825
+ wraps its host.
826
+
827
+ | Input | Type | Default |
828
+ | ---------------- | --------------------------------------------------------------------------- | -------------------------------- |
829
+ | `gogBadge` | `string \| number \| null` | `null` — the content |
830
+ | `badgePosition` | `GogBadgePosition` (`'top-end'\|'top-start'\|'bottom-end'\|'bottom-start'`) | `'top-end'` |
831
+ | `badgeVariant` | `GogTagVariant` | `'danger'` |
832
+ | `badgeDot` | `boolean` | `false` — bare dot, no text |
833
+ | `badgeMax` | `number` | `99` — beyond this, renders `N+` |
834
+ | `badgeHidden` | `boolean` | `false` |
835
+ | `badgeAriaLabel` | `string` | `''` |
836
+
837
+ Renders **nothing** when the value is `0`, `null` or empty and `badgeDot` is off — "0" badges
838
+ are impossible by design.
839
+
840
+ ```html
841
+ <gog-button gogBadge="12" badgeAriaLabel="12 unread">Inbox</gog-button>
842
+ <gog-icon name="info" gogBadge badgeDot />
843
+ ```
844
+
845
+ #### `gog-chip`
846
+
847
+ | Input | Type | Default |
848
+ | ------------------------------ | ----------------------------------- | --------------------- |
849
+ | `size` | `GogSize` | `'md'` |
850
+ | `shape` | `GogTagShape` (`'rounded'\|'pill'`) | `'rounded'` |
851
+ | `disabled`, `clickable` | `boolean` | `false`, `true` |
852
+ | `removable` | `boolean` | `false` |
853
+ | `fullWidth` | `boolean` | `false` |
854
+ | `ariaLabel`, `removeAriaLabel` | `string` | `''`, `'Remove chip'` |
855
+ | `avatarUrl`, `avatarAlt` | `string \| null` / `string` | `null`, `''` |
856
+ | `iconName` | `GogIconName \| null` | `null` |
857
+
858
+ Outputs: `gogClick: MouseEvent | KeyboardEvent`, `gogRemove: void`.
859
+
860
+ ```html
861
+ <gog-chip [avatarUrl]="user.photo" [removable]="true" (gogRemove)="removeUser(user)"
862
+ >{{ user.name }}</gog-chip
863
+ >
864
+ ```
865
+
866
+ #### `gog-tag`
867
+
868
+ | Input | Type | Default |
869
+ | ----------- | --------------------- | ----------- |
870
+ | `variant` | `GogTagVariant` | `'info'` |
871
+ | `size` | `GogSize` | `'md'` |
872
+ | `shape` | `GogTagShape` | `'rounded'` |
873
+ | `iconName` | `GogIconName \| null` | `null` |
874
+ | `fullWidth` | `boolean` | `false` |
875
+
876
+ Slot: `<ng-template gogTagIcon>` for custom icon markup (replaces the deprecated `iconTemplate`).
877
+
878
+ ```html
879
+ <gog-tag variant="success">Active</gog-tag>
880
+ ```
881
+
882
+ #### `gog-spinner` / `gog-spinner-overlay`
883
+
884
+ | Input | Type | Default |
885
+ | -------------------------------- | ------------------------------------------------- | ------------------------------------------- |
886
+ | `size` | `GogSize` | `'md'` |
887
+ | `variant` | `GogSpinnerVariant` (`'runic'\|'ring'\|'custom'`) | `'runic'` |
888
+ | `ariaLabel` | `string` | `'Loading'` |
889
+ | `overlay` (spinner only) | `boolean` | `false` |
890
+ | `loading` (spinner-overlay only) | `boolean` | `false` — toggles the overlay + `aria-busy` |
891
+
892
+ `variant="custom"` renders your own projected markup, still inheriting the size wrapper and
893
+ `--gog-spinner-color` theming.
894
+
895
+ ```html
896
+ <gog-spinner-overlay [loading]="isLoading()">
897
+ <app-content-that-loads />
898
+ </gog-spinner-overlay>
899
+ ```
900
+
901
+ #### `gog-skeleton`
902
+
903
+ | Input | Type | Default |
904
+ | ----------------- | -------------------------------------------------- | ---------------------------------------------------- |
905
+ | `shape` | `GogSkeletonShape` (`'text'\|'circle'\|'rect'`) | `'text'` |
906
+ | `size` | `GogSize` | `'md'` |
907
+ | `animation` | `GogSkeletonAnimation` (`'pulse'\|'wave'\|'none'`) | `'pulse'` |
908
+ | `width`, `height` | `string \| null` | `null` |
909
+ | `lines` | `number` | `1` — `shape="text"` only, last line renders shorter |
910
+ | `rounded` | `boolean` | `true` |
911
+ | `ariaLabel` | `string \| null` | `null` — decorative (no `role`) unless set |
912
+
913
+ ```html
914
+ <gog-skeleton shape="text" [lines]="3" /> <gog-skeleton shape="circle" width="48px" />
915
+ ```
916
+
917
+ #### `gog-progressbar`
918
+
919
+ | Input | Type | Default |
920
+ | ----------------- | ---------------------------------------------------------------------------- | --------------- |
921
+ | `value`, `buffer` | `number` (0–100, clamped) | `0` |
922
+ | `mode` | `GogProgressbarMode` (`'determinate'\|'indeterminate'\|'buffer'`) | `'determinate'` |
923
+ | `variant` | `GogProgressbarVariant` (`'accent'\|'success'\|'danger'\|'warning'\|'info'`) | `'accent'` |
924
+ | `size` | `GogSize` | `'md'` |
925
+ | `showValue` | `boolean` | `false` |
926
+ | `ariaLabel` | `string` | `''` |
927
+
928
+ ```html
929
+ <gog-progressbar mode="indeterminate" ariaLabel="Loading" />
930
+ <gog-progressbar mode="buffer" [value]="42" [buffer]="70" />
931
+ ```
932
+
933
+ #### `gog-divider`
934
+
935
+ | Input | Type | Default |
936
+ | ------------- | --------------------------------------------------- | -------------- |
937
+ | `orientation` | `GogOrientation` | `'horizontal'` |
938
+ | `variant` | `GogDividerVariant` (`'solid'\|'dashed'\|'dotted'`) | `'solid'` |
939
+ | `inset` | `boolean` | `false` |
940
+
941
+ Label is projected content, not an input — put an icon or a `gog-tag` inside it if needed.
942
+
943
+ ```html
944
+ <gog-divider>OR</gog-divider>
945
+ ```
946
+
947
+ #### `gogTooltip` — directive, not a component
948
+
949
+ Drop on any element — a `gog-*` component's host tag or a plain native one.
950
+
951
+ | Input | Type | Default |
952
+ | --------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------ |
953
+ | `gogTooltip` | `string \| TemplateRef \| null` | `null` — content |
954
+ | `gogTooltipPosition` | `GogTooltipPosition` (`'auto'\|'top'\|'bottom'\|'left'\|'right'`) | `'auto'`; via `GOG_CONFIG.tooltip.position` |
955
+ | `gogTooltipShowDelay` | `number \| undefined` | `300`; via `GOG_CONFIG.tooltip.showDelay` |
956
+ | `gogTooltipHideDelay` | `number \| undefined` | `100`; via `GOG_CONFIG.tooltip.hideDelay` |
957
+ | `gogTooltipDisabled` | `boolean` | `false` |
958
+ | `gogTooltipClass` | `string` | `''` — class on the bubble itself, since it's portaled to `<body>` |
959
+
960
+ ```html
961
+ <button gogTooltip="Save changes">💾</button> <gog-chip [gogTooltip]="hintTemplate">Beta</gog-chip>
962
+ ```
963
+
964
+ ### Layout & navigation
965
+
966
+ #### `gog-accordion`
967
+
968
+ | Input | Type | Default |
969
+ | --------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------- |
970
+ | `items` | `GogAccordionItem[]` (`{ id, title, disabled?, [key: string]: unknown }`) | `[]` |
971
+ | `size` | `GogSize` | `'lg'` (not `'md'` — see conventions) |
972
+ | `expandFirst`, `multi`, `loading` | `boolean` | `false` |
973
+ | `skeletonCount` | `number` | `3` — rows shown while `loading` and `items` is still empty |
974
+ | `showChevron` | `boolean` | `true` |
975
+ | `headingLevel` | `2\|3\|4\|5\|6 \| undefined` | `undefined` — wraps headers in `role="heading"` when set |
976
+
977
+ Model: `openIds: ReadonlySet<string | number>`. Output: `gogToggle: { item, open }`.
978
+
979
+ Slots: `<ng-template gogAccordionHeader let-item let-open="open">`,
980
+ `<ng-template gogAccordionContent let-item>`, `<ng-template gogAccordionChevron let-item let-open="open">`.
981
+ This is the library's canonical example of the slot pattern — copy its shape for anything similar.
982
+
983
+ ```html
984
+ <gog-accordion [items]="faqItems" [multi]="true">
985
+ <ng-template gogAccordionContent let-item>{{ item.answer }}</ng-template>
986
+ </gog-accordion>
987
+ ```
988
+
989
+ #### `gog-collapsible` + `gogCollapsibleTrigger` / `gogCollapsibleContent`
990
+
991
+ **Headless primitive** — owns no markup at all, just open/close state plus two attribute
992
+ directives you place on your own elements. Use this when `gog-accordion`'s opinionated markup
993
+ doesn't fit (e.g. a sidebar nav group).
994
+
995
+ | Input (on `gog-collapsible`) | Type | Default |
996
+ | ---------------------------- | --------- | ---------------------------------------------------------- |
997
+ | `disabled` | `boolean` | `false` |
998
+ | `collapseOnFocusOut` | `boolean` | `false` — close once focus leaves both trigger and content |
999
+
1000
+ Model: `open: boolean`.
1001
+
1002
+ ```html
1003
+ <gog-collapsible [(open)]="isOpen">
1004
+ <button gogCollapsibleTrigger>Advanced options</button>
1005
+ <div gogCollapsibleContent>
1006
+ <!-- any markup -->
1007
+ </div>
1008
+ </gog-collapsible>
1009
+ ```
1010
+
1011
+ #### `gog-tabs` + `gog-tab`
1012
+
1013
+ | Input (on `gog-tabs`) | Type | Default |
1014
+ | ------------------------ | ------------------------------------------------------ | ---------------------------------------------------- |
1015
+ | `align` | `GogTabsAlign` (`'start'\|'center'\|'end'\|'stretch'`) | `'start'` |
1016
+ | `orientation` | `GogOrientation` | `'horizontal'` |
1017
+ | `size` | `GogSize` | `'md'` |
1018
+ | `fullWidth`, `ariaLabel` | | `false`, `''` |
1019
+ | `scrollActiveIntoView` | `boolean` | `true` |
1020
+ | `showScrollTrack` | `boolean \| undefined` | follows `scrollActiveIntoView` (hidden when it's on) |
1021
+
1022
+ Model: `activeIndex: number`. Output: `gogTabChange: number`.
1023
+
1024
+ | Input (on `gog-tab`) | Type | Default |
1025
+ | -------------------- | --------------------- | ------- |
1026
+ | `label` | `string` | `''` |
1027
+ | `iconName` | `GogIconName \| null` | `null` |
1028
+ | `disabled` | `boolean` | `false` |
1029
+
1030
+ Slots: `<ng-template gogTabHeader let-tab let-active="active">` on `gog-tabs` for custom header
1031
+ markup; `<ng-template gogTabContent>` **inside** a `gog-tab` to make that tab's content **lazy**
1032
+ (built on first activation, then kept alive) instead of the default (rendered immediately,
1033
+ hidden via `[hidden]` while inactive — preserves scroll/input state).
1034
+
1035
+ ```html
1036
+ <gog-tabs [(activeIndex)]="tabIndex">
1037
+ <gog-tab label="Profile"><app-profile /></gog-tab>
1038
+ <gog-tab label="Report" iconName="info">
1039
+ <ng-template gogTabContent><app-expensive-report /></ng-template>
1040
+ </gog-tab>
1041
+ </gog-tabs>
1042
+ ```
1043
+
1044
+ #### `gog-paginator`
1045
+
1046
+ | Input | Type | Default |
1047
+ | ------------------------------- | ------------------------------------------------ | ------------------------------ |
1048
+ | `fullWidth`, `totalPages` | `boolean`, `number` | `true`, `1` |
1049
+ | `rangeMode` | `GogPaginatorRangeMode` (`'window'\|'ellipsis'`) | `'window'` — see note |
1050
+ | `visiblePages` | `number` | `5` — `'window'` mode only |
1051
+ | `showFirstPage`, `showLastPage` | `boolean` | `false` — `'window'` mode only |
1052
+ | `siblingCount` | `number` | `2` — `'ellipsis'` mode only |
1053
+ | `size` | `GogSize` | `'sm'` |
1054
+ | `disabled`, `ariaLabel` | | `false`, `'Pagination'` |
1055
+ | `totalRecords` | `number \| null` | `null` — see below |
1056
+ | `pageSize` | `model<number>` | `10` — two-way bindable |
1057
+ | `showPageSizeSelect` | `boolean \| undefined` | `false`; via `GOG_CONFIG.paginator` |
1058
+ | `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; via `GOG_CONFIG.paginator` |
1059
+
1060
+ The step buttons (`'Previous page'`/`'Next page'`) and the per-page names are configured, not
1061
+ input-driven: `GOG_CONFIG.labels.previousPage`/`nextPage`, and `labels.page`, a
1062
+ `(page: number, isCurrent: boolean) => string` formatter defaulting to
1063
+ `` `Page ${page}, current page` `` / `` `Go to page ${page}` ``.
1064
+
1065
+ Models: `page: number` (1-based, self-clamps) and `pageSize: number`.
1066
+
1067
+ **Give it `totalRecords` instead of `totalPages` when you know the row count** — it then derives
1068
+ the page count from `pageSize` itself, which is what removes the
1069
+ `computed(() => Math.ceil(total / size))` a consumer would otherwise have to write *and* keep in
1070
+ sync with the rows-per-page select:
1071
+
1072
+ ```html
1073
+ <gog-paginator
1074
+ [(page)]="page"
1075
+ [(pageSize)]="size"
1076
+ [totalRecords]="items().length"
1077
+ [showPageSizeSelect]="true"
1078
+ />
1079
+ ```
1080
+
1081
+ `totalPages` still works and is the right input when the server tells you a page count directly;
1082
+ `totalRecords` wins when both are set. Changing the page size always returns to page 1 — "page 5"
1083
+ of 10-row pages is not "page 5" of 50-row ones, so clamping alone would leave the user somewhere
1084
+ they never asked to be.
1085
+
1086
+ `'window'`: a fixed number of page buttons that slides to keep the current page centered.
1087
+ `'ellipsis'`: first/last pinned, `siblingCount` around the current page, "…" fills the gap
1088
+ (what `gog-table`'s built-in pagination uses).
1089
+
1090
+ ```html
1091
+ <gog-paginator [(page)]="page" [totalPages]="totalPages" />
1092
+ ```
1093
+
1094
+ #### `gog-table<T>`
1095
+
1096
+ | Input | Type | Default |
1097
+ | ----------------------------- | ----------------------------- | --------------------------------- |
1098
+ | `value` | `T[]` | `[]` |
1099
+ | `fullWidth` | `boolean` | `true` |
1100
+ | `pageSize` | `model<number>` | `0` (no pagination) — two-way |
1101
+ | `showPageSizeSelect` | `boolean \| undefined` | `false`; forwarded to the paginator |
1102
+ | `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; forwarded |
1103
+ | `showRowNumbers`, `showTotal` | `boolean` | `true`, `false` |
1104
+ | `emptyPlaceholder` | `string` | `'-'` |
1105
+ | `paginatorPosition` | `'left'\|'center'\|'right'` | `'center'` |
1106
+ | `totalPosition` | `'left'\|'right'\|'opposite'` | `'opposite'` |
1107
+ | `loading` | `boolean` | `false` |
1108
+ | `showColumnBorders` | `boolean` | `false` |
1109
+ | `stickyHeader` | `boolean` | `false` |
1110
+ | `size` | `GogSize` | `'lg'` (row density — not `'md'`) |
1111
+ | `lazy` | `boolean` | `false` — see below |
1112
+ | `totalRecords` | `number \| null` | `null` — `lazy` only |
1113
+ | `selectionMode` | `GogTableSelectionMode` | `'none'` |
1114
+ | `selection` | `model<T[]>` | `[]` — two-way bindable |
1115
+ | `dataKey` | `string` | `''` — row identity field |
1116
+ | `showSelectionColumn` | `boolean` | `true` (once selection is on) |
1117
+ | `interactiveRows` | `boolean` | `false` |
1118
+
1119
+ Outputs: `gogSortChange: GogTableSortEvent` (`{ field, direction }`, `{ field: '', direction:
1120
+ null }` when the third click clears it), `gogPageChange: number` (1-based; **does not fire** on
1121
+ first render, nor for the page reset a new sort causes — that reset belongs to the sort),
1122
+ `gogRowClick: GogTableRowClickEvent<T>` (`{ row, index, originalEvent }`).
1123
+
1124
+ Columns are declared as **projected `gog-column` children**, not an input array:
1125
+
1126
+ ```html
1127
+ <gog-table [value]="rows">
1128
+ <gog-column field="name" header="Name" sortable="true" />
1129
+ <gog-column field="email" header="Email" />
1130
+ <gog-column field="status" header="Status">
1131
+ <ng-template gogColumnBody let-row let-value="value">
1132
+ <gog-tag [variant]="row.active ? 'success' : 'danger'">{{ value }}</gog-tag>
1133
+ </ng-template>
1134
+ </gog-column>
1135
+ </gog-table>
1136
+ ```
1137
+
1138
+ ##### Server-driven tables — `lazy`
1139
+
1140
+ By default the table owns the whole data set: it sorts `value` and slices the page itself. With
1141
+ `[lazy]="true"` it does neither — `value` **is** the current page, already sorted, and the table
1142
+ renders it untouched. Supply `totalRecords` (without it the table cannot know how many pages
1143
+ exist, so pagination stays hidden and it warns in dev), then refetch from the two outputs:
1144
+
1145
+ ```html
1146
+ <gog-table
1147
+ [value]="page()"
1148
+ [lazy]="true"
1149
+ [totalRecords]="total()"
1150
+ [pageSize]="20"
1151
+ [loading]="loading()"
1152
+ dataKey="id"
1153
+ (gogSortChange)="sort.set($event); reload()"
1154
+ (gogPageChange)="pageNumber.set($event); reload()"
1155
+ >
1156
+ ```
1157
+
1158
+ Row numbers still count from the current page (`(page - 1) * pageSize + i + 1`), and `showTotal`
1159
+ reports `totalRecords` rather than `value.length`. **Do not** sort or slice `value` yourself in
1160
+ addition — that is what the flag turns off.
1161
+
1162
+ ##### Rows per page
1163
+
1164
+ `pageSize` is a **`model`**, not an input: `[pageSize]="20"` works exactly as before, and
1165
+ `[(pageSize)]="size"` becomes possible. That is what makes the rows-per-page select work with no
1166
+ wiring — the table binds its own model straight to the paginator's, the select writes back
1167
+ through it, and there is no intermediate signal to keep in sync in either direction.
1168
+
1169
+ ```html
1170
+ <!-- off by default; turn it on per table, or app-wide via GOG_CONFIG.paginator -->
1171
+ <gog-table [value]="rows" [(pageSize)]="size" [showPageSizeSelect]="true" [pageSizeOptions]="[5, 10, 20]">
1172
+ ```
1173
+
1174
+ Changing the size returns to page 1 and does **not** emit `gogPageChange` — the consumer already
1175
+ knows from `pageSizeChange`, and firing both would make a lazy table fetch twice. In `lazy` mode
1176
+ `pageSizeChange` is the refetch signal; bind `[pageSize]` + `(pageSizeChange)` rather than the
1177
+ banana-box if you need to act on it.
1178
+
1179
+ The footer stays visible at a single page whenever the select is on — hiding it would strand the
1180
+ user on whatever size produced that one page, with no control left to pick a smaller one.
1181
+
1182
+ ##### Selection
1183
+
1184
+ `selectionMode` turns it on; `[(selection)]` is always a `T[]`, including in `'single'` mode
1185
+ where it holds zero or one row — one shape rather than a union to narrow on every read.
1186
+
1187
+ ```html
1188
+ <gog-table [value]="rows" selectionMode="multiple" [(selection)]="selected" dataKey="id">
1189
+ ```
1190
+
1191
+ - **Set `dataKey`.** Without it rows are matched by object identity, so any refetch that produces
1192
+ new objects silently drops the selection. It is also the `@for` track key, which is what lets
1193
+ the DOM survive a refetch instead of being rebuilt.
1194
+ - The checkbox column renders automatically (`showSelectionColumn` to turn it off, e.g. for a
1195
+ table that selects by row click — pair that with `interactiveRows`).
1196
+ - The header select-all appears only in `'multiple'` mode and covers **the current page**, never
1197
+ the whole data set: in `lazy` mode the table has never seen the other pages, and a control that
1198
+ behaved differently between the two modes would be worse than either.
1199
+
1200
+ ##### Clickable rows
1201
+
1202
+ `gogRowClick` fires on a click regardless, but a `<tr>` is not focusable, so on its own that is a
1203
+ mouse-only affordance. `interactiveRows` makes rows focusable and styles them as clickable, and
1204
+ Enter/Space then activate the focused row. If the action is really "open this one thing", a link
1205
+ or button inside a cell is better than a whole-row target.
1206
+
1207
+ `gog-column` inputs: `field` (required, dot-paths ok), `header`, `sortable` (default `false`),
1208
+ `width`/`minWidth`/`maxWidth`, `comparator` (custom `(a, b) => number`, defaults to a
1209
+ locale-aware collator for strings). Slots inside a column: `<ng-template gogColumnBody let-row let-value="value" let-index="index">`,
1210
+ `<ng-template gogColumnHeader let-header let-field="field">`.
1211
+
1212
+ **Sorting, empty/loading states and pagination are all built in** — sortable columns toggle
1213
+ asc → desc → unsorted on click, `loading` shows a spinner in place of rows, an empty `value`
1214
+ shows `emptyPlaceholder`, and `pageSize > 0` turns on the internal paginator automatically. You
1215
+ don't need to hand-roll any of this.
1216
+
1217
+ There is **no typed row-selection API** in the current version — if you need it, track
1218
+ selection yourself (e.g. a `Set` keyed by row id) and render a `gogColumnBody` checkbox column.
1219
+
1220
+ #### `gog-scroll`
1221
+
1222
+ Drop-in replacement for `overflow: auto` — content still scrolls natively (wheel, touch,
1223
+ keyboard); only the browser's own scrollbar chrome is replaced with a themeable overlay thumb.
1224
+ Used internally by several other components (`gog-dialog`'s body, `gog-select`'s panel,
1225
+ `gog-tabs`' header row) and equally usable directly in your own markup for any scrollable
1226
+ region — the library's official recommendation over a raw `overflow-x`/`overflow-y`.
1227
+
1228
+ | Input | Type | Default |
1229
+ | -------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
1230
+ | `axis` | `GogScrollAxis` (`'vertical'\|'horizontal'\|'both'`) | `'vertical'` |
1231
+ | `size` | `GogScrollSize \| undefined` (`'normal'\|'thin'`) | `'normal'`; via `GOG_CONFIG.scroll.size` |
1232
+ | `autoHide` | `boolean \| undefined` | `true`; via `GOG_CONFIG.scroll.autoHide` |
1233
+ | `hideDelay` | `number \| undefined` | `800`; via `GOG_CONFIG.scroll.hideDelay` |
1234
+ | `reachThreshold` | `number` | `0` |
1235
+ | `focusable` | `boolean` | `true` — turn off when the parent already owns focus (a dialog with its own focus trap) |
1236
+ | `ariaLabel` | `string` | `''` |
1237
+ | `overscrollBehavior` | `GogScrollOverscrollBehavior \| undefined` (`'auto'\|'contain'\|'none'`) | `'auto'`; via `GOG_CONFIG.scroll.overscrollBehavior` |
1238
+ | `showTrack` | `boolean \| undefined` | `true`; via `GOG_CONFIG.scroll.showTrack` |
1239
+
1240
+ Outputs: `gogScroll: GogScrollMetrics`, `gogReachStart`/`gogReachEnd: 'vertical'|'horizontal'`.
1241
+ Methods (via template ref): `scrollTo(options)`, `scrollToTop()`, `scrollToBottom()`,
1242
+ `scrollToLeft()`, `scrollToRight()`.
1243
+
1244
+ ```html
1245
+ <gog-scroll size="thin" [focusable]="false" overscrollBehavior="contain" style="max-height: 320px">
1246
+ <!-- content that might overflow -->
1247
+ </gog-scroll>
1248
+ ```
1249
+
1250
+ ### Overlays
1251
+
1252
+ #### `gog-dialog`
1253
+
1254
+ A **single** `<gog-dialog />` renders **every** dialog `DialogService.open(...)` creates —
1255
+ place it once, typically in your root app component's template, not per-page and not per-dialog
1256
+ call:
1257
+
1258
+ ```html
1259
+ <!-- app.html -->
1260
+ <router-outlet />
1261
+ <gog-dialog />
1262
+ ```
1263
+
1264
+ It has no inputs of its own — everything is driven through `DialogService` (see
1265
+ [Services](#services) above). Supports nesting, dragging (when `draggable !== false` and the
1266
+ dialog has a title or close button), a focus trap for modal dialogs, `Escape` to close (when
1267
+ `closable !== false`), and click-outside-to-close on the backdrop.
1268
+
1269
+ #### `gog-toast` / `gog-toast-container`
1270
+
1271
+ Same pattern — place **one** `<gog-toast-container />`, typically in the root component:
1272
+
1273
+ ```html
1274
+ <gog-toast-container [maxVisiblePerPosition]="5" />
1275
+ ```
1276
+
1277
+ `maxVisiblePerPosition` (default `5`) caps how many toasts stack at once per corner; the rest
1278
+ queue. Individual `gog-toast` instances are rendered internally by the container from
1279
+ `ToastService.toasts()` — you don't place these yourself. Toasts auto-dismiss after their
1280
+ `duration` unless `isSticky`; hovering pauses the countdown (front-of-stack toast only).
1281
+
1282
+ Announcements come from two permanently-mounted, visually-hidden live regions the container
1283
+ owns — polite, and assertive for `error`/`warning`. The toasts themselves carry no
1284
+ `role`/`aria-live`: a live region created in the same tick as its text is routinely skipped by
1285
+ screen readers, and a second region would announce everything twice. Don't add either back.
1286
+
1287
+ ---
1288
+
1289
+ ## Deprecated patterns — do not use in new code
1290
+
1291
+ These still work (nothing breaks if you use them), but are marked `@deprecated` and **will be
1292
+ removed** on the stated schedule. Don't generate new code using any of them — use the listed
1293
+ replacement instead.
1294
+
1295
+ | Deprecated | Removed in | Replacement |
1296
+ | ---------------------------------------------------------------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------------------- |
1297
+ | `GogSelectOption`, `GogMultiselectOption` type aliases | `21.4.0` | `GogDropdownOption` |
1298
+ | `gog-select`/`gog-multiselect` `chevronTemplate` input | `21.5.0` | `<ng-template gogDropdownChevron>` |
1299
+ | `gog-checkbox` `checkIconTemplate` input | `21.5.0` | `<ng-template gogCheckboxIcon>` |
1300
+ | `gog-tag` `iconTemplate` input | `21.5.0` | `<ng-template gogTagIcon>` |
1301
+ | `gog-multiselect` `clearIconTemplate` input | `21.5.0` | `<ng-template gogMultiselectClearIcon>` |
1302
+ | `gog-inputfield` `iconStartTemplate`/`iconEndTemplate`/`iconStartFn`/`iconEndFn`/`iconStartLabel`/`iconEndLabel` | `21.5.0` | `<span gogInputAddonStart>`/`<span gogInputAddonEnd>` (or a `<button>` with its own handler) |
1303
+ | `gog-table`'s `[template]` attribute (`<ng-template template="field" type="body">`) | `21.5.0` | `<ng-template gogColumnBody>` / `<ng-template gogColumnHeader>` declared **inside** the matching `<gog-column>` |
1304
+ | `<column>` selector / `Column` export | `21.5.0` | `<gog-column>` / `GogColumn` |
1305
+
1306
+ The general rule they all follow: a `TemplateRef` **input** or a string-keyed lookup is the old
1307
+ shape; a **projected content directive with a typed context**, declared where it's used, is the
1308
+ current one. If you're about to write `fooTemplate` next to an existing `foo` input, or key
1309
+ something off a string that has to match another string elsewhere, that's this exact
1310
+ anti-pattern — reach for a slot directive instead.
1311
+
1312
+ ## Full type reference
1313
+
1314
+ Shared enum-like types (`import type { ... } from '@guildofgleks/ui'`):
1315
+
1316
+ | Type | Values |
1317
+ | ----------------------------- | --------------------------------------------------------------------------------------------- |
1318
+ | `GogSize` | `'xsm' \| 'sm' \| 'md' \| 'lg' \| 'slg'` |
1319
+ | `GogVariant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost'` |
1320
+ | `GogTagVariant` | `'success' \| 'danger' \| 'warning' \| 'info'` |
1321
+ | `GogOrientation` | `'horizontal' \| 'vertical'` |
1322
+ | `GogTagShape` | `'rounded' \| 'pill'` |
1323
+ | `GogSpinnerVariant` | `'runic' \| 'ring' \| 'custom'` |
1324
+ | `GogSkeletonShape` | `'text' \| 'circle' \| 'rect'` |
1325
+ | `GogSkeletonAnimation` | `'pulse' \| 'wave' \| 'none'` |
1326
+ | `GogPaginatorRangeMode` | `'window' \| 'ellipsis'` |
1327
+ | `GogScrollAxis` | `'vertical' \| 'horizontal' \| 'both'` |
1328
+ | `GogScrollSize` | `'normal' \| 'thin'` |
1329
+ | `GogScrollOverscrollBehavior` | `'auto' \| 'contain' \| 'none'` |
1330
+ | `GogTooltipPosition` | `'auto' \| 'top' \| 'bottom' \| 'left' \| 'right'` |
1331
+ | `GogFloatLabelVariant` | `'none' \| 'in' \| 'on' \| 'over'` |
1332
+ | `GogDropdownFilterPosition` | `'top' \| 'bottom'` |
1333
+ | `GogDividerVariant` | `'solid' \| 'dashed' \| 'dotted'` |
1334
+ | `GogBadgePosition` | `'top-end' \| 'top-start' \| 'bottom-end' \| 'bottom-start'` |
1335
+ | `GogProgressbarMode` | `'determinate' \| 'indeterminate' \| 'buffer'` |
1336
+ | `GogProgressbarVariant` | `'accent' \| 'success' \| 'danger' \| 'warning' \| 'info'` |
1337
+ | `GogButtonToggleAppearance` | `'joined' \| 'separated'` |
1338
+ | `GogTabsAlign` | `'start' \| 'center' \| 'end' \| 'stretch'` |
1339
+ | `GogDateSelectionMode` | `'single' \| 'range'` |
1340
+ | `GogHourFormat` | `'12' \| '24'` |
1341
+ | `GogTextareaResize` | `'vertical' \| 'horizontal' \| 'both' \| 'none'` |
1342
+ | `GogInputType` | `'text' \| 'password' \| 'email' \| 'number' \| 'search' \| 'tel' \| 'url' \| 'date' \| 'time' \| 'datetime-local'` |
1343
+ | `GogInputMode` | `'none' \| 'text' \| 'decimal' \| 'numeric' \| 'tel' \| 'search' \| 'email' \| 'url'` |
1344
+ | `GogTableSelectionMode` | `'none' \| 'single' \| 'multiple'` |
1345
+ | `GogTableSortEvent` | `{ field: string; direction: SortDirection }` |
1346
+ | `GogTableRowClickEvent<T>` | `{ row: T; index: number; originalEvent: MouseEvent \| KeyboardEvent }` |
1347
+ | `GogErrorDisplay` | `'auto' \| 'manual'` |
1348
+ | `GogDropdownDirection` | `'auto' \| 'up' \| 'down'` |
1349
+ | `GogTooltipSide` | `'top' \| 'bottom' \| 'left' \| 'right'` (resolved form of `GogTooltipPosition`, no `'auto'`) |
1350
+ | `GogBuiltinIconName` | the 20 glyphs the package ships — see [`gog-icon`](#gog-icon) |
1351
+ | `GogIconName` | `GogBuiltinIconName \| (string & {})` — built-ins plus anything registered via `provideGogIcons` |