@guildofgleks/ui 21.9.0 → 21.10.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 +1951 -1930
- package/CHANGELOG.md +2340 -2219
- package/TOKENS.md +1 -1
- package/fesm2022/guildofgleks-ui.mjs +17 -3
- package/fesm2022/guildofgleks-ui.mjs.map +1 -1
- package/package.json +1 -1
- package/styles/button.css +401 -387
- package/styles/presets/slate.css +92 -86
- package/styles/theme.css +2396 -2357
- package/types/guildofgleks-ui.d.ts +24 -8
package/AGENTS.md
CHANGED
|
@@ -1,1930 +1,1951 @@
|
|
|
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.9.0`** (in progress — the
|
|
9
|
-
released version is 21.8.0; see `CHANGELOG.md` for what 21.9.0 adds). 21.7.0 removed the three
|
|
10
|
-
abbreviated token prefixes and 21.5.0 removed a batch of deprecated API — see **Removed in 21.7.0**
|
|
11
|
-
and **Removed in 21.5.0** near the end of this file, which exist so code written against an older
|
|
12
|
-
version can be migrated — and `CHANGELOG.md` has the rest. `README.md` covers the same ground at a
|
|
13
|
-
higher level — install, setup, theming, global configuration — and is accurate; this file goes
|
|
14
|
-
further, into per-component input tables, and is the one to trust for exact names, types and
|
|
15
|
-
defaults.
|
|
16
|
-
|
|
17
|
-
> **Maintainers:** this file ships inside the npm package and is the API reference an agent reads
|
|
18
|
-
> while writing code against it, so a stale table here becomes wrong code in someone else's app —
|
|
19
|
-
> silently, because nothing fails a build. **Any change to an input, output, slot, type, service
|
|
20
|
-
> method or default updates this file in the same change**, and moves the version marker in the
|
|
21
|
-
> paragraph above. See `.github/instructions/gleks-ui-library.instructions.md`, definition of
|
|
22
|
-
> done, step 9.
|
|
23
|
-
|
|
24
|
-
## Quick facts
|
|
25
|
-
|
|
26
|
-
- Angular **v21+** only (`peerDependencies` require `^21.2.0` for `@angular/core`,
|
|
27
|
-
`@angular/common`, `@angular/forms`, `@angular/platform-browser`). No support for older
|
|
28
|
-
Angular.
|
|
29
|
-
- No Angular CDK, no Material. Only runtime dependency is `tslib`.
|
|
30
|
-
- Every component is **standalone**, `ChangeDetectionStrategy.OnPush`, and built with signals —
|
|
31
|
-
`input()` / `output()` / `model()`, never `@Input()`/`@Output()` decorators, never `ngClass`/
|
|
32
|
-
`ngStyle`.
|
|
33
|
-
- **Reactive Forms only.** Every form control implements `ControlValueAccessor` and is built
|
|
34
|
-
and tested against `[formControl]` / `formControlName`. The library never imports
|
|
35
|
-
`FormsModule` and `[(ngModel)]` is untested — don't suggest it.
|
|
36
|
-
- Theming is 100% CSS custom properties (`--gog-*`) — no Sass config, no JS theme objects, no
|
|
37
|
-
build step to restyle anything.
|
|
38
|
-
- Tree-shakeable: `"sideEffects": false` and every component is a separate standalone import, so
|
|
39
|
-
importing `ButtonComponent` alone does not pull in the rest of the library.
|
|
40
|
-
- SSR-safe: anything touching `window`/`document` is guarded with `isPlatformBrowser`/
|
|
41
|
-
`afterNextRender`.
|
|
42
|
-
|
|
43
|
-
## Install & setup
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
npm install @guildofgleks/ui
|
|
47
|
-
# or
|
|
48
|
-
yarn add @guildofgleks/ui
|
|
49
|
-
# or — installs it and adds the stylesheet below to angular.json automatically
|
|
50
|
-
ng add @guildofgleks/ui
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
Add the baseline stylesheet once — it carries every token the components read plus their
|
|
54
|
-
utility classes, so without it components render unstyled:
|
|
55
|
-
|
|
56
|
-
```jsonc
|
|
57
|
-
// angular.json → projects.<app>.architect.build.options
|
|
58
|
-
"styles": [
|
|
59
|
-
"node_modules/@guildofgleks/ui/styles/index.css",
|
|
60
|
-
"src/styles.scss", // your own styles, after the baseline so they win
|
|
61
|
-
],
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
Import components where you use them — every one is standalone:
|
|
65
|
-
|
|
66
|
-
```ts
|
|
67
|
-
import { Component } from '@angular/core';
|
|
68
|
-
import { ButtonComponent, SelectComponent } from '@guildofgleks/ui';
|
|
69
|
-
|
|
70
|
-
@Component({
|
|
71
|
-
selector: 'app-example',
|
|
72
|
-
imports: [ButtonComponent, SelectComponent],
|
|
73
|
-
template: `
|
|
74
|
-
<gog-select label="Region" [options]="regions" [(value)]="region" />
|
|
75
|
-
<gog-button (gogClick)="save()">Save</gog-button>
|
|
76
|
-
`,
|
|
77
|
-
})
|
|
78
|
-
export class ExampleComponent {}
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## Core conventions (read once, applies everywhere)
|
|
82
|
-
|
|
83
|
-
These hold for essentially every component in the library. Knowing them means you can guess a
|
|
84
|
-
new component's API correctly instead of guessing wrong and hallucinating an input that doesn't
|
|
85
|
-
exist.
|
|
86
|
-
|
|
87
|
-
- **Selector prefix `gog-`** for components (`gog-button`, `gog-select`, …), attribute selectors
|
|
88
|
-
for directives (`gogTooltip`, `[gogBadge]`).
|
|
89
|
-
- **Outputs are prefixed `gog`** so they never collide with native DOM events —
|
|
90
|
-
`gogClick`, `gogToggle`, `gogSearch`, `gogTabChange`, `gogRemove`, `gogScroll`, `gogLoadMore`,
|
|
91
|
-
`gogDateSelect`. **Inputs keep their natural name** (`variant`, `size`, `disabled`).
|
|
92
|
-
- **Two-way binding via `model()`.** Wherever a component holds a value the consumer drives, it's
|
|
93
|
-
a `model()` input — bind with `[(value)]="signal"` / `[(checked)]="signal"` /
|
|
94
|
-
`[(open)]="signal"` etc., or split into `[value]` + `(valueChange)`.
|
|
95
|
-
- **Every input has a zero-config default.** Nothing requires configuration to render something
|
|
96
|
-
reasonable.
|
|
97
|
-
- **`size` is `GogSize = 'xsm' | 'sm' | 'md' | 'lg' | 'slg'`**, shared by every sized component.
|
|
98
|
-
Default is `'md'` almost everywhere — exceptions: `gog-accordion` and `gog-table` default to
|
|
99
|
-
`'lg'` (their `size` means row/section density, not form-control size), `gog-paginator`
|
|
100
|
-
defaults to `'sm'`.
|
|
101
|
-
- **`variant` is `GogVariant = 'primary' | 'secondary' | 'outline' | 'ghost'`** on `gog-button`.
|
|
102
|
-
Status-colored components (`gog-tag`, `gog-badge`) use a different, four-value
|
|
103
|
-
`GogTagVariant = 'success' | 'danger' | 'warning' | 'info'` instead — don't confuse the two.
|
|
104
|
-
- **`errorDisplay: GogErrorDisplay = 'auto' | 'manual'`** (default `'manual'`) on every control
|
|
105
|
-
that shows a validation message (inputfield, textarea, select, multiselect, autocomplete,
|
|
106
|
-
radio-group, slider, datepicker). `'manual'`: the field shows `errorMessage` whenever it's
|
|
107
|
-
non-empty — you own the timing (`errorMessage="control.invalid && control.touched ? 'Required' : ''"`).
|
|
108
|
-
`'auto'`: shown once the attached `[formControl]`/`formControlName` is touched _and_ invalid —
|
|
109
|
-
you only supply the message text. `'auto'` silently behaves like `'manual'` if there's no real
|
|
110
|
-
form control attached.
|
|
111
|
-
- **`inputId` is optional everywhere.** Every form control renders a real `id` — its own if you
|
|
112
|
-
pass one, a generated one otherwise — so the `<label for>` and the error message's
|
|
113
|
-
`aria-describedby` are always wired up. Pass `inputId` only when something outside the
|
|
114
|
-
component needs to reference the field by a known id; never pass one just to get a label.
|
|
115
|
-
- **User-visible chrome strings come from `GOG_CONFIG.labels`**, not from an input per string —
|
|
116
|
-
"Clear", "Close dialog", "Go to page 4" and the rest. Per-instance label inputs exist where a
|
|
117
|
-
single control realistically differs and win over the config. See
|
|
118
|
-
[`labels`](#labels--translating-the-library).
|
|
119
|
-
- **`floatLabel: GogFloatLabelVariant = 'none' | 'in' | 'on' | 'over'`** (default `'none'`) on
|
|
120
|
-
the six field controls: inputfield, textarea, select, multiselect, autocomplete, datepicker.
|
|
121
|
-
`'in'` floats up but stays inside the border, `'on'` floats to sit centered on the top border
|
|
122
|
-
line, `'over'` floats fully above the field. Pair with `floatLabelShowPlaceholder` (default
|
|
123
|
-
`false`) to reveal the field's own `placeholder` once the label has floated clear.
|
|
124
|
-
- **`clearable`** (default varies) on inputfield, textarea, select, multiselect, autocomplete,
|
|
125
|
-
datepicker — shows a clear (×) button once the field has content. Off by default everywhere
|
|
126
|
-
except `gog-multiselect`, which had one before the input existed.
|
|
127
|
-
- **Generic option accessors, not a fixed DTO.** Any collection-driven control (`gog-select`,
|
|
128
|
-
`gog-multiselect`, `gog-autocomplete`, `gog-button-toggle-group`) takes **your own object
|
|
129
|
-
shape** through `optionLabel` / `optionValue` / `optionDisabled` — each is a property path
|
|
130
|
-
(`'name'`, dot-paths like `'profile.title'` work) **or** a function
|
|
131
|
-
`(option: T) => TResult`. Defaults are `'name'` / `'id'` / `'disabled'`. Set
|
|
132
|
-
`[optionValue]="null"` to emit **the option object itself** instead of a plucked id — the
|
|
133
|
-
control then round-trips your own object with no lookup table needed:
|
|
134
|
-
```html
|
|
135
|
-
<gog-select [options]="members" [optionLabel]="nameOf" [optionValue]="null" [(value)]="member" />
|
|
136
|
-
```
|
|
137
|
-
- **Global defaults via `GOG_CONFIG` / `provideGogConfig(...)`** — see its own section below.
|
|
138
|
-
Precedence is always: the instance's own input (if set) → `GOG_CONFIG` → the component's
|
|
139
|
-
built-in default.
|
|
140
|
-
- **Don't bind both a `model()` and a form directive on the same instance.** Every CVA control
|
|
141
|
-
(checkbox, toggle, radio-group, inputfield, textarea, select, multiselect, autocomplete,
|
|
142
|
-
slider, datepicker) exposes its value as both a two-way `model()` (`[(checked)]`, `[(value)]`)
|
|
143
|
-
and, separately, `ControlValueAccessor` for `[formControl]`/`formControlName`. Pick one per
|
|
144
|
-
instance — wiring both gives the value two competing sources of truth.
|
|
145
|
-
- **The custom-content slot pattern.** Wherever a component needs custom markup for a specific
|
|
146
|
-
part of itself, it's an attribute directive read with `contentChild()`, given a **typed**
|
|
147
|
-
context via `let-` variables — never a plain `TemplateRef` input, never a string-keyed lookup.
|
|
148
|
-
Recognize the shape:
|
|
149
|
-
```html
|
|
150
|
-
<gog-accordion [items]="items">
|
|
151
|
-
<ng-template gogAccordionHeader let-item let-open="open">{{ item.title }}</ng-template>
|
|
152
|
-
</gog-accordion>
|
|
153
|
-
```
|
|
154
|
-
See the per-component tables below for which slot directives exist on which component.
|
|
155
|
-
- **Legacy `TemplateRef` inputs and string-keyed lookups still exist on a few components and
|
|
156
|
-
still work, but are `@deprecated` — do not use them in new code.** See
|
|
157
|
-
[Deprecated patterns — do not use in new code](#deprecated-patterns--do-not-use-in-new-code).
|
|
158
|
-
- **Accessibility is built in**, not optional: keyboard navigation (roving tabindex, arrow keys,
|
|
159
|
-
Home/End), ARIA roles/states, `:focus-visible` styling, `prefers-reduced-motion` handling, and
|
|
160
|
-
WCAG AA contrast are already implemented — you don't need to add any of this yourself, just
|
|
161
|
-
supply `ariaLabel`/`label` inputs where a component has no visible text of its own (icon-only
|
|
162
|
-
buttons, `gog-progressbar`, `gog-scroll`).
|
|
163
|
-
- **`aria-label` on the host tag does nothing.** Several components (`gog-button` chief among
|
|
164
|
-
them) render their real interactive element (a `<button>`) _inside_ the component's own host
|
|
165
|
-
tag. An `aria-label` attribute placed directly on `<gog-button>` in a template lands on the
|
|
166
|
-
custom element wrapper, not on the inner `<button>`, so assistive tech never sees it — always
|
|
167
|
-
use the component's own `ariaLabel` input instead.
|
|
168
|
-
|
|
169
|
-
## Theming
|
|
170
|
-
|
|
171
|
-
Full model is in `README.md`'s Theming section; short version:
|
|
172
|
-
|
|
173
|
-
- Every visual value (color, spacing, radius, shadow, duration) is a `--gog-*` CSS custom
|
|
174
|
-
property, layered **foundation** (`--gog-accent-color`, `--gog-space-md`, …, restyles
|
|
175
|
-
everything) → **component** (`--gog-button-primary-bg`, …, one block per component, named after
|
|
176
|
-
the component's own element) → **instance** (`--gog-button-bg`, …, deliberately undeclared
|
|
177
|
-
escape hatch for one element).
|
|
178
|
-
- **Foundation includes a small character layer** (since 21.7.0, `docs/themes.md` iteration 1):
|
|
179
|
-
`--gog-radius` (corner rounding), `--gog-control-border-*`/`--gog-panel-border-*`/`--gog-border-*`
|
|
180
|
-
(border weight — form fields, raised surfaces, everything smaller and inline, respectively),
|
|
181
|
-
`--gog-text-transform`/`--gog-letter-spacing` (emphasis casing/tracking). Component tokens in
|
|
182
|
-
the categories these cover derive from them by default; setting one in a `[data-theme]` block
|
|
183
|
-
restyles every component that reads it, with nothing to re-list per component.
|
|
184
|
-
- **The type scale is `--gog-text-xs | sm | md | lg | slg | xl | 2xl | 3xl`.** `slg` (1.25rem)
|
|
185
|
-
fills the gap between `lg` and `xl` and is named for the control size that needed it. Every
|
|
186
|
-
component font size that is one of these reads the token, so retuning the scale retunes the
|
|
187
|
-
library; the handful that do not are off-scale on purpose (an 11px chip, the accordion
|
|
188
|
-
chevron's px ramp, the toggle's own micro-ramp).
|
|
189
|
-
|
|
190
|
-
- **Weight is `--gog-font-weight-medium | semibold | bold | heavy`** (500/600/700/900). Every
|
|
191
|
-
component weight reads one of them, so a lighter or heavier house style is four declarations.
|
|
192
|
-
|
|
193
|
-
- **`--gog-z-base` moves the whole stacking order.** Badge `+1`, toast `+100`, dropdowns, dialogs
|
|
194
|
-
and menus `+300`, tooltip `+400`, the blocking spinner overlay `+8000`. Set the base to lift
|
|
195
|
-
the library above your own chrome without disturbing its internal order.
|
|
196
|
-
|
|
197
|
-
- **`--gog-density` is the character layer for spacing** (since 21.7.0, `docs/themes.md`
|
|
198
|
-
iteration 6). It multiplies the fourteen-step scale `--gog-space-2` … `--gog-space-48`, named
|
|
199
|
-
for their pixel value at density 1, and every padding and gap in the library derives from a
|
|
200
|
-
step. `--gog-density: 0.9` in a `[data-theme]` block makes the whole library tighter; nothing
|
|
201
|
-
else needs to be named. `--gog-space-xs|sm|md|lg|2xl` are aliases for steps 4/8/16/24/48 and
|
|
202
|
-
still work. Icon offsets, dropdown panel gaps, error-line offsets and the badge's overhang
|
|
203
|
-
follow density; the glyph box, the focus-ring offset, the float-label reserve and the
|
|
204
|
-
scrollbar/toggle thumb insets deliberately do not — those are legibility or geometry fitted to
|
|
205
|
-
a fixed-width track, not spacing. Since 21.9.0 the split is enforced rather than trusted:
|
|
206
|
-
`check-tokens` rule H fails the build on a length token that restates a scale step's value as
|
|
207
|
-
a bare literal, with the three exceptions named in the script.
|
|
208
|
-
- **Component prefixes are spelled out** since 21.5.0: `--gog-button-*`, `--gog-multiselect-*`,
|
|
209
|
-
`--gog-confirmation-dialog-*`. The abbreviated `--gog-btn-*`, `--gog-ms-*` and `--gog-confirm-*`
|
|
210
|
-
were removed in 21.7.0 — if you're reading a codebase or an example that still uses one, rename
|
|
211
|
-
it; it no longer resolves. The exception is `--gog-input-*`, which is not an abbreviation: it is
|
|
212
|
-
the shared text-field block that `gog-inputfield` and `gog-textarea` both render, and it keeps
|
|
213
|
-
that name.
|
|
214
|
-
- **The package does not need the app's `box-sizing` reset** (since 21.6.0): `utilities.css`
|
|
215
|
-
sets `border-box` on every element carrying a `gog-*` class, including the ones the library
|
|
216
|
-
puts on a consumer's own element. Do not add a reset "so the components line up" — they
|
|
217
|
-
already do, and a `* { box-sizing: content-box }` in an app is the only thing that undoes it.
|
|
218
|
-
- Theme switch is a `data-theme` attribute, usually on `<html>`, toggled through the
|
|
219
|
-
`ThemeService` (`inject(ThemeService).setTheme('dark')` / `.toggleTheme()` / `.theme` signal).
|
|
220
|
-
Ships `light` and `dark`, plus nine importable presets at
|
|
221
|
-
`@guildofgleks/ui/styles/presets/<name>.css`. **All nine set palette and character** (since
|
|
222
|
-
21.7.0 — before it, three were palette-only, which made them recoloured defaults):
|
|
223
|
-
|
|
224
|
-
| Preset | Radius | Density | Identity |
|
|
225
|
-
| ---------------------- | ------ | ------- | ------------------------------------------------ |
|
|
226
|
-
| `slate` | 12px | 1.05 | soft modern — hairline borders, roomy |
|
|
227
|
-
| `one-dark`/`one-light` | 4px | 0.9 | editor chrome; identical character, two tones |
|
|
228
|
-
| `material` | 4px | 1.1 | Material Design 3, pill buttons |
|
|
229
|
-
| `primeng` | 6px | 0.95 | PrimeNG Aura |
|
|
230
|
-
| `ledger` | 0 | 0.9 | administrative — hard offset shadow, no motion |
|
|
231
|
-
| `terminal` | 0 | 0.85 | green phosphor, monospaced throughout, no motion |
|
|
232
|
-
| `bevel` | 0 | 0.9 | early-web desktop — `outset`/`inset` borders |
|
|
233
|
-
| `parchment` | 0 | 1.1 | ink on paper — old-style serif, oxblood |
|
|
234
|
-
|
|
235
|
-
`material`, `primeng` and `bevel` also set a few genuinely per-component things the character
|
|
236
|
-
layer has no vocabulary for (a pill button, a table's header font, a button bevel that has to
|
|
237
|
-
disagree with a field's); see their own file headers.
|
|
238
|
-
|
|
239
|
-
- **A preset never makes a network request.** Each sets a font _stack_ resolving to a real system
|
|
240
|
-
face. Where a webfont is worth offering, it is a separate opt-in file — `terminal.fonts.css`
|
|
241
|
-
(IBM Plex Mono), `parchment.fonts.css` (EB Garamond) — imported **after** the preset, since it
|
|
242
|
-
re-points the same tokens and later wins. Do not add an `@import url(…)` to a preset itself; put
|
|
243
|
-
it in a companion file, or the import becomes a download nobody asked for.
|
|
244
|
-
- Restyle one instance without touching a theme: `<gog-button style="--gog-button-bg: #ff4edb">`.
|
|
245
|
-
- Build a custom theme by declaring a palette **and a character** against a new `data-theme`
|
|
246
|
-
value (see `README.md`'s Theming section for the full worked example) — component tokens
|
|
247
|
-
re-derive automatically, you don't restate them.
|
|
248
|
-
|
|
249
|
-
## Right-to-left
|
|
250
|
-
|
|
251
|
-
Supported since 21.5.0. `dir="rtl"` on `<html>` or on any wrapper mirrors every component —
|
|
252
|
-
you write nothing per component. Portaled overlays (select/multiselect panels, tooltip bubbles)
|
|
253
|
-
copy a _scoped_ `dir` onto themselves, so an RTL region inside an LTR page works too.
|
|
254
|
-
|
|
255
|
-
Physical by design, in both directions: `gogTooltip [position]="'left' | 'right'"` and
|
|
256
|
-
`ToastConfig.position` (`'top-right'`, …). Use the tooltip's `'auto'` for direction-aware
|
|
257
|
-
placement; a toast corner is a deliberate choice, so it is not mirrored.
|
|
258
|
-
|
|
259
|
-
## Global configuration — `GOG_CONFIG` / `provideGogConfig(...)`
|
|
260
|
-
|
|
261
|
-
For the handful of inputs an app typically wants to set once (a size for every form control, a
|
|
262
|
-
locale for every datepicker) rather than repeat on every instance:
|
|
263
|
-
|
|
264
|
-
```ts
|
|
265
|
-
import { provideGogConfig } from '@guildofgleks/ui';
|
|
266
|
-
|
|
267
|
-
bootstrapApplication(App, {
|
|
268
|
-
providers: [
|
|
269
|
-
provideGogConfig({
|
|
270
|
-
control: { size: 'sm', errorDisplay: 'auto', clearable: true },
|
|
271
|
-
dropdown: { appendToBody: true, filter: true },
|
|
272
|
-
datepicker: { locale: 'de-DE', firstDayOfWeek: 1, format: 'dd.MM.yyyy' },
|
|
273
|
-
toast: { position: 'top-right', duration: 4000 },
|
|
274
|
-
}),
|
|
275
|
-
],
|
|
276
|
-
});
|
|
277
|
-
```
|
|
278
|
-
|
|
279
|
-
Precedence, always: **instance input → `GOG_CONFIG` → component's built-in default.** A nested
|
|
280
|
-
`provideGogConfig(...)` (in a route's or component's own `providers`) **layers onto the
|
|
281
|
-
parent's config**, one level deep per key — it does not replace it.
|
|
282
|
-
|
|
283
|
-
| Key | Fields | Applies to |
|
|
284
|
-
| -------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
285
|
-
| `control` | `size`, `errorDisplay`, `clearable` | `size`: button, `[gogButton]`, button-toggle-group, checkbox, toggle, radio-group, inputfield, textarea, select, multiselect, autocomplete, datepicker. `errorDisplay`: inputfield, textarea, select, multiselect, autocomplete, datepicker, radio-group, slider. `clearable`: inputfield, textarea, select, multiselect, autocomplete, datepicker. Not table/accordion/paginator (density, not form size), not spinner/skeleton/tag/chip. |
|
|
286
|
-
| `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). |
|
|
287
|
-
| `floatLabel` | `variant`, `showPlaceholder` | inputfield, textarea, select, multiselect, autocomplete, datepicker. |
|
|
288
|
-
| `datepicker` | `locale`, `firstDayOfWeek`, `format` | `gog-datepicker`, `gog-calendar`. |
|
|
289
|
-
| `autocomplete` | `searchDebounce`, `minLength`, `openOnFocus` | `gog-autocomplete`. |
|
|
290
|
-
| `tooltip` | `position`, `showDelay`, `hideDelay` | the `gogTooltip` directive. |
|
|
291
|
-
| `spinner` | `component`, `variant` | every spinner the library draws — `gog-spinner`, `gog-spinner-overlay`, and the ones inside `gog-button` and `gog-
|
|
292
|
-
| `scroll` | `autoHide`, `hideDelay`, `size`, `overscrollBehavior`, `showTrack`, `horizontalWheel` | `gog-scroll` (and every component that uses one internally). |
|
|
293
|
-
| `button` | `debounce` | `gog-button`. |
|
|
294
|
-
| `ripple` | `enabled` | the press ripple on `gog-button`, `[gogButton]`, `gog-button-toggle-group`, `gog-chip`, `gog-tabs`, `gog-accordion`, `gogCollapsibleTrigger`, `gogMenuItem` and the `gog-select`/`gog-multiselect`/`gog-autocomplete` options. **Off by default.** Each of those takes a `ripple` input that wins over it. Not the `gogRipple` directive — writing that attribute is already the per-element decision. |
|
|
295
|
-
| `inputfield` | `showSpinButtons` | `gog-inputfield`. |
|
|
296
|
-
| `textarea` | `resize` | `gog-textarea`. |
|
|
297
|
-
| `paginator` | `showPageSizeSelect`, `pageSizeOptions` | `gog-paginator`, and through it `gog-table`'s built-in pagination. |
|
|
298
|
-
| `toast` | `position`, `duration` | `ToastService`. |
|
|
299
|
-
| `theme` | `storageKey`, `defaultTheme`, `followSystem`, `lightTheme`, `darkTheme` | `ThemeService`. All off/neutral by default — see below. |
|
|
300
|
-
| `labels` | every fixed string the library renders — see below | inputfield, textarea, select, multiselect, autocomplete, datepicker, calendar, paginator, table, `DialogService`, `ToastService`. |
|
|
301
|
-
|
|
302
|
-
Anything visual does **not** belong here — override the `--gog-*` token instead.
|
|
303
|
-
|
|
304
|
-
### `labels` — translating the library
|
|
305
|
-
|
|
306
|
-
Every string a component renders that the consumer never writes markup for. An app that isn't
|
|
307
|
-
in English sets these once rather than on every control:
|
|
308
|
-
|
|
309
|
-
```ts
|
|
310
|
-
provideGogConfig({
|
|
311
|
-
labels: {
|
|
312
|
-
clear: 'Löschen', // inputfield / textarea clear button
|
|
313
|
-
clearSelection: 'Auswahl löschen', // select / multiselect / autocomplete
|
|
314
|
-
clearDate: 'Datum löschen', // datepicker
|
|
315
|
-
selectAll: 'Alle auswählen', // multiselect panel
|
|
316
|
-
clearAll: 'Alle löschen', // multiselect panel
|
|
317
|
-
increment: 'Erhöhen', // number spin buttons
|
|
318
|
-
decrement: 'Verringern',
|
|
319
|
-
showPassword: 'Passwort anzeigen',
|
|
320
|
-
hidePassword: 'Passwort verbergen',
|
|
321
|
-
closeDialog: 'Schließen',
|
|
322
|
-
closeToast: 'Schließen',
|
|
323
|
-
pagination: 'Seitennavigation',
|
|
324
|
-
previousPage: 'Vorherige Seite',
|
|
325
|
-
nextPage: 'Nächste Seite',
|
|
326
|
-
openCalendar: 'Kalender öffnen',
|
|
327
|
-
togglePanel: 'Bereich umschalten', // gog-panel's toggle, only when it has no heading
|
|
328
|
-
rowsPerPage: 'Zeilen pro Seite', // gog-paginator's size select
|
|
329
|
-
total: 'Gesamt', // gog-table's row-count label
|
|
330
|
-
tablePagination: 'Tabellennavigation',
|
|
331
|
-
selectRow: 'Zeile auswählen',
|
|
332
|
-
selectAllRows: 'Alle Zeilen auswählen',
|
|
333
|
-
today: 'Heute',
|
|
334
|
-
thisMonth: 'Aktueller Monat',
|
|
335
|
-
previousMonth: 'Vorheriger Monat',
|
|
336
|
-
nextMonth: 'Nächster Monat',
|
|
337
|
-
previousYear: 'Vorheriges Jahr',
|
|
338
|
-
nextYear: 'Nächstes Jahr',
|
|
339
|
-
hours: 'Stunden',
|
|
340
|
-
minutes: 'Minuten',
|
|
341
|
-
seconds: 'Sekunden',
|
|
342
|
-
// The one non-string field: it interpolates the page number, and word order and
|
|
343
|
-
// agreement around a number vary by language, so it takes a formatter.
|
|
344
|
-
page: (page, isCurrent) => (isCurrent ? `Seite ${page}, aktuell` : `Zu Seite ${page} wechseln`),
|
|
345
|
-
},
|
|
346
|
-
});
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
Strings that describe **one** control rather than library chrome — `gog-checkbox`'s `ariaLabel`,
|
|
350
|
-
`gog-button`'s `ariaLabel`, any field's `label`/`placeholder` — are deliberately **not** here.
|
|
351
|
-
Those stay per instance. Where a per-instance label input exists (`clearAriaLabel`, `todayLabel`,
|
|
352
|
-
…) it still wins over the configured value.
|
|
353
|
-
|
|
354
|
-
## Services
|
|
355
|
-
|
|
356
|
-
### `ThemeService`
|
|
357
|
-
|
|
358
|
-
```ts
|
|
359
|
-
private readonly theme = inject(ThemeService);
|
|
360
|
-
this.theme.theme(); // Signal<string>, READ-ONLY — current data-theme
|
|
361
|
-
this.theme.setTheme('dark'); // any theme name, including a custom one you declared in CSS
|
|
362
|
-
this.theme.toggleTheme(); // flips between the configured light and dark names
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
`theme` is read-only on purpose: writing to it would move the signal without touching the
|
|
366
|
-
`data-theme` attribute the styles actually read. Never suggest `theme.set(...)` — it does not
|
|
367
|
-
exist.
|
|
368
|
-
|
|
369
|
-
Zero-config behaviour: adopt whatever `data-theme` is already on `<html>`, else `'light'`.
|
|
370
|
-
Persistence and following the OS setting are **opt-in**, so upgrading cannot change which theme
|
|
371
|
-
an existing app opens in:
|
|
372
|
-
|
|
373
|
-
```ts
|
|
374
|
-
provideGogConfig({
|
|
375
|
-
theme: {
|
|
376
|
-
storageKey: 'app-theme', // persist the choice in localStorage; unset = no persistence
|
|
377
|
-
followSystem: true, // open in the OS prefers-color-scheme, and keep following it
|
|
378
|
-
// until the app calls setTheme/toggleTheme
|
|
379
|
-
lightTheme: 'light', // the two names followSystem maps to and toggleTheme alternates
|
|
380
|
-
darkTheme: 'one-dark', // between
|
|
381
|
-
defaultTheme: 'light', // used when nothing else decides
|
|
382
|
-
},
|
|
383
|
-
});
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
Resolution order at startup: existing `data-theme` on the document → persisted value →
|
|
387
|
-
OS setting (if `followSystem`) → `defaultTheme` → `'light'`.
|
|
388
|
-
|
|
389
|
-
### `ToastService`
|
|
390
|
-
|
|
391
|
-
Root-provided singleton. Requires a `<gog-toast-container />` placed once in your app (see
|
|
392
|
-
[gog-toast](#gog-toast--gog-toast-container) below — it is **not** wired up automatically).
|
|
393
|
-
|
|
394
|
-
```ts
|
|
395
|
-
private readonly toast = inject(ToastService);
|
|
396
|
-
|
|
397
|
-
this.toast.success('Saved');
|
|
398
|
-
this.toast.error('Could not save', {
|
|
399
|
-
isSticky: true,
|
|
400
|
-
actions: [{ label: 'Retry', onClick: () => this.save() }],
|
|
401
|
-
});
|
|
402
|
-
// also: .warning(msg, config?), .info(msg, config?), .show(config), .dismiss(id), .dismissAll()
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
`ToastConfig`: `{ message, type?, iconName?, iconTemplate?, actions?, dedupeKey?, isSticky?, duration?, position? }`.
|
|
406
|
-
Repeated calls with the same (explicit or inferred) `dedupeKey` replace the existing toast in
|
|
407
|
-
place instead of stacking a duplicate.
|
|
408
|
-
|
|
409
|
-
### `DialogService`
|
|
410
|
-
|
|
411
|
-
Root-provided singleton, imperative dynamic-component dialogs. Requires a `<gog-dialog />`
|
|
412
|
-
placed once in your app (see [gog-dialog](#gog-dialog) below — also **not** automatic).
|
|
413
|
-
|
|
414
|
-
```ts
|
|
415
|
-
private readonly dialogService = inject(DialogService);
|
|
416
|
-
|
|
417
|
-
async confirmDelete(): Promise<void> {
|
|
418
|
-
const handle = this.dialogService.open<boolean>({
|
|
419
|
-
component: ConfirmationDialogComponent, // or your own component
|
|
420
|
-
title: 'Delete this item?',
|
|
421
|
-
role: 'alertdialog',
|
|
422
|
-
data: { message: 'This cannot be undone.' },
|
|
423
|
-
});
|
|
424
|
-
const confirmed = await handle.afterClosed; // boolean | undefined
|
|
425
|
-
}
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
`DialogConfig<TData>`: `{ title?, component, data?: TData, modal? (default true), closable?, draggable?, closeIconName?, closeIconTemplate?, width?, maxWidth?, role? ('dialog' default | 'alertdialog'), zIndex? }`.
|
|
429
|
-
`open<TResult, TData>()` returns `{ close(result?), afterClosed: Promise<TResult | undefined> }`. Also:
|
|
430
|
-
`closeAll(result?)`, `updatePosition(id, offsetX, offsetY)` (for `draggable` dialogs).
|
|
431
|
-
|
|
432
|
-
**`open<TResult, TData>()` type-checks `data` against `TData` when you supply both type
|
|
433
|
-
arguments** — supplying only `TResult` (the common case above) leaves `TData` as `unknown`,
|
|
434
|
-
exactly as before:
|
|
435
|
-
|
|
436
|
-
```ts
|
|
437
|
-
interface EditUserData {
|
|
438
|
-
userId: string;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const handle = this.dialogService.open<{ saved: boolean }, EditUserData>({
|
|
442
|
-
component: EditDialogComponent,
|
|
443
|
-
data: { userId: user.id }, // checked against EditUserData here
|
|
444
|
-
});
|
|
445
|
-
```
|
|
446
|
-
|
|
447
|
-
This checks only the call site. `EditDialogComponent` still reads its data via `inject(DIALOG_DATA)`
|
|
448
|
-
— an `InjectionToken<unknown>` shared by every dialog, so it still needs its own cast
|
|
449
|
-
(`inject<EditUserData>(DIALOG_DATA)`, shown below). Angular's DI has no way to carry a
|
|
450
|
-
per-call-site type through one shared token, so the receiving half of the round trip is still on
|
|
451
|
-
trust — this closes only the half that can be closed.
|
|
452
|
-
|
|
453
|
-
The library ships a ready-made `ConfirmationDialogComponent` for yes/no prompts — pass it as
|
|
454
|
-
`component` with `data: { title, description, confirmText, cancelText }`; it resolves the
|
|
455
|
-
dialog's result to `true`/`false`.
|
|
456
|
-
|
|
457
|
-
**Wiring a custom component into a dialog** — it reads its data via `DIALOG_DATA` and closes
|
|
458
|
-
itself via `DIALOG_REF`:
|
|
459
|
-
|
|
460
|
-
```ts
|
|
461
|
-
import { Component, inject } from '@angular/core';
|
|
462
|
-
import { DIALOG_DATA, DIALOG_REF } from '@guildofgleks/ui';
|
|
463
|
-
|
|
464
|
-
@Component({ selector: 'app-edit-dialog', template: `…` })
|
|
465
|
-
export class EditDialogComponent {
|
|
466
|
-
protected readonly data = inject<{ userId: string }>(DIALOG_DATA);
|
|
467
|
-
private readonly ref = inject(DIALOG_REF);
|
|
468
|
-
|
|
469
|
-
save(): void {
|
|
470
|
-
this.ref.close({ saved: true });
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
---
|
|
476
|
-
|
|
477
|
-
## Component reference
|
|
478
|
-
|
|
479
|
-
Every component below is exported from `@guildofgleks/ui`'s root — `import { X } from '@guildofgleks/ui'`.
|
|
480
|
-
"CVA" = implements `ControlValueAccessor` (works with `[formControl]`/`formControlName`).
|
|
481
|
-
|
|
482
|
-
### Buttons & choices
|
|
483
|
-
|
|
484
|
-
#### `gog-button`
|
|
485
|
-
|
|
486
|
-
| Input | Type | Default | Notes |
|
|
487
|
-
| ----------- | --------------------------------- | ----------- | ----------------------------------------------------- |
|
|
488
|
-
| `variant` | `GogVariant` | `'primary'` | |
|
|
489
|
-
| `severity` | `GogSeverity` | `'accent'` | what the action means; orthogonal to `variant` — see below |
|
|
490
|
-
| `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
|
|
491
|
-
| `disabled` | `boolean` | `false` | |
|
|
492
|
-
| `fullWidth` | `boolean` | `false` | |
|
|
493
|
-
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | |
|
|
494
|
-
| `loading` | `boolean` | `false` | shows an inline `gog-spinner`, blocks clicks |
|
|
495
|
-
| `debounce` | `number \| undefined` | `300` | ms; via `GOG_CONFIG.button.debounce` — see note below |
|
|
496
|
-
| `ariaLabel` | `string \| null` | `null` | **use this, not a raw `aria-label` attribute** |
|
|
497
|
-
| `ariaPressed` | `boolean \| 'mixed' \| null` | `null` | toggle button; `false` renders `aria-pressed="false"` |
|
|
498
|
-
| `ariaExpanded` | `boolean \| null` | `null` | disclosure / popup trigger |
|
|
499
|
-
| `ariaControls` | `string \| null` | `null` | id of the controlled element; pairs with `ariaExpanded` |
|
|
500
|
-
| `ariaHasPopup` | `GogAriaHasPopup \| null` | `null` | `boolean \| 'menu' \| 'listbox' \| 'tree' \| 'grid' \| 'dialog'` |
|
|
501
|
-
| `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
|
|
502
|
-
|
|
503
|
-
Outputs: `gogClick: MouseEvent`.
|
|
504
|
-
|
|
505
|
-
**`severity` says what the action means; `variant` says how loudly it is drawn** (21.9.0). The
|
|
506
|
-
two are orthogonal, so this is not a fifth variant — it re-points the colours all four are built
|
|
507
|
-
from, and every combination is real: `variant="ghost" severity="danger"` is a quiet delete,
|
|
508
|
-
`variant="primary" severity="danger"` a loud one. `'accent'` is the default and the absence of a
|
|
509
|
-
claim, so nothing has to opt out of a severity it does not have. `GogSeverity` is shared with
|
|
510
|
-
`gog-progressbar`, whose `GogProgressbarVariant` is now an alias of it.
|
|
511
|
-
|
|
512
|
-
```html
|
|
513
|
-
<gog-button severity="danger" (gogClick)="deleteAccount()">Delete account</gog-button>
|
|
514
|
-
<gog-button variant="outline" severity="warning">Discard draft</gog-button>
|
|
515
|
-
<a gogButton severity="success" routerLink="/done">Finish</a>
|
|
516
|
-
```
|
|
517
|
-
|
|
518
|
-
Two colour rules are worth knowing before you override anything. A **filled** severity button's
|
|
519
|
-
label is `--gog-<status>-text-color`, which each theme states for its own hue — `material` and
|
|
520
|
-
`primeng` put near-black on their bright ones, the rest white — and hover and press deepen the
|
|
521
|
-
fill *away* from that label (`--gog-<status>-shade`), so a state always makes the label easier to
|
|
522
|
-
read rather than harder. A **transparent** one's label is `--gog-button-<status>-ink`: the status
|
|
523
|
-
hue mixed halfway toward the page's ink, because the raw hue is legible body text in only five of
|
|
524
|
-
the eleven shipped themes. Override `--gog-button-<status>-ink` if your own theme wants more
|
|
525
|
-
colour there, and check it: all four severities across all four variants and all their states are
|
|
526
|
-
gated by `npm run check:contrast`.
|
|
527
|
-
|
|
528
|
-
**Every ARIA attribute this button needs has an input, and a raw attribute is not a
|
|
529
|
-
substitute.** `<gog-button [attr.aria-pressed]="on()">` compiles, throws nothing, and does
|
|
530
|
-
nothing: the attribute lands on the `<gog-button>` custom element, which has no role, while the
|
|
531
|
-
real `<button>` inside stays unmarked. The failure is invisible — the control looks right and is
|
|
532
|
-
simply not a toggle to a screen reader. Use `[ariaPressed]`, `[ariaExpanded]`, `[ariaControls]`,
|
|
533
|
-
`[ariaHasPopup]` and `ariaLabel`.
|
|
534
|
-
|
|
535
|
-
`false` is not the same as unset. `null` omits the attribute; `false` renders
|
|
536
|
-
`aria-pressed="false"` / `aria-expanded="false"`, which is what an off toggle or a closed
|
|
537
|
-
disclosure has to say — a button with no `aria-pressed` at all is not a toggle button.
|
|
538
|
-
|
|
539
|
-
**A toggle button now looks toggled** (21.9.0). `aria-pressed="true"` (or `"mixed"`) draws an
|
|
540
|
-
inset ring — `--gog-button-<variant>-toggled-shadow`, overridable per instance with
|
|
541
|
-
`--gog-button-toggled-shadow`. A ring rather than a fill because hover and press already own the
|
|
542
|
-
background: the state has to survive both, and until 21.9.0 it did not exist at all, so a button
|
|
543
|
-
could announce itself as on to a screen reader and look identical to an off one. `[gogButton]`
|
|
544
|
-
gets the same look from the attribute you write on your own element.
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
|
599
|
-
|
|
|
600
|
-
| `
|
|
601
|
-
| `
|
|
602
|
-
| `
|
|
603
|
-
| `
|
|
604
|
-
| `
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
|
628
|
-
|
|
|
629
|
-
| `
|
|
630
|
-
| `
|
|
631
|
-
| `
|
|
632
|
-
| `
|
|
633
|
-
| `
|
|
634
|
-
| `
|
|
635
|
-
| `
|
|
636
|
-
| `
|
|
637
|
-
| `
|
|
638
|
-
| `
|
|
639
|
-
| `
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
`
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
[
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
|
674
|
-
|
|
|
675
|
-
| `
|
|
676
|
-
| `
|
|
677
|
-
| `
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
|
699
|
-
|
|
|
700
|
-
| `
|
|
701
|
-
| `
|
|
702
|
-
| `
|
|
703
|
-
| `
|
|
704
|
-
| `
|
|
705
|
-
| `
|
|
706
|
-
| `
|
|
707
|
-
| `
|
|
708
|
-
| `
|
|
709
|
-
| `
|
|
710
|
-
| `
|
|
711
|
-
| `
|
|
712
|
-
| `
|
|
713
|
-
| `
|
|
714
|
-
|
|
715
|
-
`
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
[
|
|
734
|
-
[
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
|
754
|
-
|
|
|
755
|
-
|
|
|
756
|
-
| `
|
|
757
|
-
| `
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
`
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
|
823
|
-
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
|
844
|
-
|
|
|
845
|
-
| `
|
|
846
|
-
| `
|
|
847
|
-
| `
|
|
848
|
-
| `
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
```
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
`
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
|
884
|
-
|
|
|
885
|
-
| `
|
|
886
|
-
| `
|
|
887
|
-
| `
|
|
888
|
-
| `
|
|
889
|
-
| `
|
|
890
|
-
| `
|
|
891
|
-
| `
|
|
892
|
-
| `
|
|
893
|
-
| `
|
|
894
|
-
| `
|
|
895
|
-
| `
|
|
896
|
-
| `
|
|
897
|
-
| `
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
`
|
|
903
|
-
`
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
`
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
`
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
`
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
|
941
|
-
|
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
|
947
|
-
|
|
|
948
|
-
|
|
|
949
|
-
|
|
950
|
-
`
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
`
|
|
955
|
-
|
|
956
|
-
`
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
<
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
```
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
`
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
|
1052
|
-
|
|
|
1053
|
-
| `
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
|
1072
|
-
|
|
|
1073
|
-
| `
|
|
1074
|
-
| `
|
|
1075
|
-
| `
|
|
1076
|
-
| `
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
no
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
|
1134
|
-
|
|
1135
|
-
`
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
|
1176
|
-
|
|
|
1177
|
-
| `
|
|
1178
|
-
| `
|
|
1179
|
-
| `
|
|
1180
|
-
| `
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
```
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
|
1192
|
-
|
|
|
1193
|
-
| `
|
|
1194
|
-
| `
|
|
1195
|
-
| `
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
|
1286
|
-
|
|
|
1287
|
-
| `
|
|
1288
|
-
| `
|
|
1289
|
-
| `
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
|
1310
|
-
|
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
`
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
|
1379
|
-
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
`
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
`
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
`
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
|
1536
|
-
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
`
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
`
|
|
1545
|
-
`
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
`
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
```
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
`
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
[
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
></gog-table>
|
|
1619
|
-
```
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
[
|
|
1637
|
-
|
|
1638
|
-
[
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
**
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
`
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
|
1876
|
-
|
|
|
1877
|
-
|
|
|
1878
|
-
|
|
|
1879
|
-
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
|
1894
|
-
|
|
|
1895
|
-
| `
|
|
1896
|
-
| `
|
|
1897
|
-
| `
|
|
1898
|
-
| `
|
|
1899
|
-
| `
|
|
1900
|
-
| `
|
|
1901
|
-
| `
|
|
1902
|
-
|
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
|
1915
|
-
|
|
|
1916
|
-
| `
|
|
1917
|
-
| `
|
|
1918
|
-
| `
|
|
1919
|
-
| `
|
|
1920
|
-
| `
|
|
1921
|
-
| `
|
|
1922
|
-
| `
|
|
1923
|
-
| `
|
|
1924
|
-
| `
|
|
1925
|
-
| `
|
|
1926
|
-
| `
|
|
1927
|
-
| `
|
|
1928
|
-
| `
|
|
1929
|
-
| `
|
|
1930
|
-
| `
|
|
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.9.0`** (in progress — the
|
|
9
|
+
released version is 21.8.0; see `CHANGELOG.md` for what 21.9.0 adds). 21.7.0 removed the three
|
|
10
|
+
abbreviated token prefixes and 21.5.0 removed a batch of deprecated API — see **Removed in 21.7.0**
|
|
11
|
+
and **Removed in 21.5.0** near the end of this file, which exist so code written against an older
|
|
12
|
+
version can be migrated — and `CHANGELOG.md` has the rest. `README.md` covers the same ground at a
|
|
13
|
+
higher level — install, setup, theming, global configuration — and is accurate; this file goes
|
|
14
|
+
further, into per-component input tables, and is the one to trust for exact names, types and
|
|
15
|
+
defaults.
|
|
16
|
+
|
|
17
|
+
> **Maintainers:** this file ships inside the npm package and is the API reference an agent reads
|
|
18
|
+
> while writing code against it, so a stale table here becomes wrong code in someone else's app —
|
|
19
|
+
> silently, because nothing fails a build. **Any change to an input, output, slot, type, service
|
|
20
|
+
> method or default updates this file in the same change**, and moves the version marker in the
|
|
21
|
+
> paragraph above. See `.github/instructions/gleks-ui-library.instructions.md`, definition of
|
|
22
|
+
> done, step 9.
|
|
23
|
+
|
|
24
|
+
## Quick facts
|
|
25
|
+
|
|
26
|
+
- Angular **v21+** only (`peerDependencies` require `^21.2.0` for `@angular/core`,
|
|
27
|
+
`@angular/common`, `@angular/forms`, `@angular/platform-browser`). No support for older
|
|
28
|
+
Angular.
|
|
29
|
+
- No Angular CDK, no Material. Only runtime dependency is `tslib`.
|
|
30
|
+
- Every component is **standalone**, `ChangeDetectionStrategy.OnPush`, and built with signals —
|
|
31
|
+
`input()` / `output()` / `model()`, never `@Input()`/`@Output()` decorators, never `ngClass`/
|
|
32
|
+
`ngStyle`.
|
|
33
|
+
- **Reactive Forms only.** Every form control implements `ControlValueAccessor` and is built
|
|
34
|
+
and tested against `[formControl]` / `formControlName`. The library never imports
|
|
35
|
+
`FormsModule` and `[(ngModel)]` is untested — don't suggest it.
|
|
36
|
+
- Theming is 100% CSS custom properties (`--gog-*`) — no Sass config, no JS theme objects, no
|
|
37
|
+
build step to restyle anything.
|
|
38
|
+
- Tree-shakeable: `"sideEffects": false` and every component is a separate standalone import, so
|
|
39
|
+
importing `ButtonComponent` alone does not pull in the rest of the library.
|
|
40
|
+
- SSR-safe: anything touching `window`/`document` is guarded with `isPlatformBrowser`/
|
|
41
|
+
`afterNextRender`.
|
|
42
|
+
|
|
43
|
+
## Install & setup
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @guildofgleks/ui
|
|
47
|
+
# or
|
|
48
|
+
yarn add @guildofgleks/ui
|
|
49
|
+
# or — installs it and adds the stylesheet below to angular.json automatically
|
|
50
|
+
ng add @guildofgleks/ui
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Add the baseline stylesheet once — it carries every token the components read plus their
|
|
54
|
+
utility classes, so without it components render unstyled:
|
|
55
|
+
|
|
56
|
+
```jsonc
|
|
57
|
+
// angular.json → projects.<app>.architect.build.options
|
|
58
|
+
"styles": [
|
|
59
|
+
"node_modules/@guildofgleks/ui/styles/index.css",
|
|
60
|
+
"src/styles.scss", // your own styles, after the baseline so they win
|
|
61
|
+
],
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Import components where you use them — every one is standalone:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import { Component } from '@angular/core';
|
|
68
|
+
import { ButtonComponent, SelectComponent } from '@guildofgleks/ui';
|
|
69
|
+
|
|
70
|
+
@Component({
|
|
71
|
+
selector: 'app-example',
|
|
72
|
+
imports: [ButtonComponent, SelectComponent],
|
|
73
|
+
template: `
|
|
74
|
+
<gog-select label="Region" [options]="regions" [(value)]="region" />
|
|
75
|
+
<gog-button (gogClick)="save()">Save</gog-button>
|
|
76
|
+
`,
|
|
77
|
+
})
|
|
78
|
+
export class ExampleComponent {}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Core conventions (read once, applies everywhere)
|
|
82
|
+
|
|
83
|
+
These hold for essentially every component in the library. Knowing them means you can guess a
|
|
84
|
+
new component's API correctly instead of guessing wrong and hallucinating an input that doesn't
|
|
85
|
+
exist.
|
|
86
|
+
|
|
87
|
+
- **Selector prefix `gog-`** for components (`gog-button`, `gog-select`, …), attribute selectors
|
|
88
|
+
for directives (`gogTooltip`, `[gogBadge]`).
|
|
89
|
+
- **Outputs are prefixed `gog`** so they never collide with native DOM events —
|
|
90
|
+
`gogClick`, `gogToggle`, `gogSearch`, `gogTabChange`, `gogRemove`, `gogScroll`, `gogLoadMore`,
|
|
91
|
+
`gogDateSelect`. **Inputs keep their natural name** (`variant`, `size`, `disabled`).
|
|
92
|
+
- **Two-way binding via `model()`.** Wherever a component holds a value the consumer drives, it's
|
|
93
|
+
a `model()` input — bind with `[(value)]="signal"` / `[(checked)]="signal"` /
|
|
94
|
+
`[(open)]="signal"` etc., or split into `[value]` + `(valueChange)`.
|
|
95
|
+
- **Every input has a zero-config default.** Nothing requires configuration to render something
|
|
96
|
+
reasonable.
|
|
97
|
+
- **`size` is `GogSize = 'xsm' | 'sm' | 'md' | 'lg' | 'slg'`**, shared by every sized component.
|
|
98
|
+
Default is `'md'` almost everywhere — exceptions: `gog-accordion` and `gog-table` default to
|
|
99
|
+
`'lg'` (their `size` means row/section density, not form-control size), `gog-paginator`
|
|
100
|
+
defaults to `'sm'`.
|
|
101
|
+
- **`variant` is `GogVariant = 'primary' | 'secondary' | 'outline' | 'ghost'`** on `gog-button`.
|
|
102
|
+
Status-colored components (`gog-tag`, `gog-badge`) use a different, four-value
|
|
103
|
+
`GogTagVariant = 'success' | 'danger' | 'warning' | 'info'` instead — don't confuse the two.
|
|
104
|
+
- **`errorDisplay: GogErrorDisplay = 'auto' | 'manual'`** (default `'manual'`) on every control
|
|
105
|
+
that shows a validation message (inputfield, textarea, select, multiselect, autocomplete,
|
|
106
|
+
radio-group, slider, datepicker). `'manual'`: the field shows `errorMessage` whenever it's
|
|
107
|
+
non-empty — you own the timing (`errorMessage="control.invalid && control.touched ? 'Required' : ''"`).
|
|
108
|
+
`'auto'`: shown once the attached `[formControl]`/`formControlName` is touched _and_ invalid —
|
|
109
|
+
you only supply the message text. `'auto'` silently behaves like `'manual'` if there's no real
|
|
110
|
+
form control attached.
|
|
111
|
+
- **`inputId` is optional everywhere.** Every form control renders a real `id` — its own if you
|
|
112
|
+
pass one, a generated one otherwise — so the `<label for>` and the error message's
|
|
113
|
+
`aria-describedby` are always wired up. Pass `inputId` only when something outside the
|
|
114
|
+
component needs to reference the field by a known id; never pass one just to get a label.
|
|
115
|
+
- **User-visible chrome strings come from `GOG_CONFIG.labels`**, not from an input per string —
|
|
116
|
+
"Clear", "Close dialog", "Go to page 4" and the rest. Per-instance label inputs exist where a
|
|
117
|
+
single control realistically differs and win over the config. See
|
|
118
|
+
[`labels`](#labels--translating-the-library).
|
|
119
|
+
- **`floatLabel: GogFloatLabelVariant = 'none' | 'in' | 'on' | 'over'`** (default `'none'`) on
|
|
120
|
+
the six field controls: inputfield, textarea, select, multiselect, autocomplete, datepicker.
|
|
121
|
+
`'in'` floats up but stays inside the border, `'on'` floats to sit centered on the top border
|
|
122
|
+
line, `'over'` floats fully above the field. Pair with `floatLabelShowPlaceholder` (default
|
|
123
|
+
`false`) to reveal the field's own `placeholder` once the label has floated clear.
|
|
124
|
+
- **`clearable`** (default varies) on inputfield, textarea, select, multiselect, autocomplete,
|
|
125
|
+
datepicker — shows a clear (×) button once the field has content. Off by default everywhere
|
|
126
|
+
except `gog-multiselect`, which had one before the input existed.
|
|
127
|
+
- **Generic option accessors, not a fixed DTO.** Any collection-driven control (`gog-select`,
|
|
128
|
+
`gog-multiselect`, `gog-autocomplete`, `gog-button-toggle-group`) takes **your own object
|
|
129
|
+
shape** through `optionLabel` / `optionValue` / `optionDisabled` — each is a property path
|
|
130
|
+
(`'name'`, dot-paths like `'profile.title'` work) **or** a function
|
|
131
|
+
`(option: T) => TResult`. Defaults are `'name'` / `'id'` / `'disabled'`. Set
|
|
132
|
+
`[optionValue]="null"` to emit **the option object itself** instead of a plucked id — the
|
|
133
|
+
control then round-trips your own object with no lookup table needed:
|
|
134
|
+
```html
|
|
135
|
+
<gog-select [options]="members" [optionLabel]="nameOf" [optionValue]="null" [(value)]="member" />
|
|
136
|
+
```
|
|
137
|
+
- **Global defaults via `GOG_CONFIG` / `provideGogConfig(...)`** — see its own section below.
|
|
138
|
+
Precedence is always: the instance's own input (if set) → `GOG_CONFIG` → the component's
|
|
139
|
+
built-in default.
|
|
140
|
+
- **Don't bind both a `model()` and a form directive on the same instance.** Every CVA control
|
|
141
|
+
(checkbox, toggle, radio-group, inputfield, textarea, select, multiselect, autocomplete,
|
|
142
|
+
slider, datepicker) exposes its value as both a two-way `model()` (`[(checked)]`, `[(value)]`)
|
|
143
|
+
and, separately, `ControlValueAccessor` for `[formControl]`/`formControlName`. Pick one per
|
|
144
|
+
instance — wiring both gives the value two competing sources of truth.
|
|
145
|
+
- **The custom-content slot pattern.** Wherever a component needs custom markup for a specific
|
|
146
|
+
part of itself, it's an attribute directive read with `contentChild()`, given a **typed**
|
|
147
|
+
context via `let-` variables — never a plain `TemplateRef` input, never a string-keyed lookup.
|
|
148
|
+
Recognize the shape:
|
|
149
|
+
```html
|
|
150
|
+
<gog-accordion [items]="items">
|
|
151
|
+
<ng-template gogAccordionHeader let-item let-open="open">{{ item.title }}</ng-template>
|
|
152
|
+
</gog-accordion>
|
|
153
|
+
```
|
|
154
|
+
See the per-component tables below for which slot directives exist on which component.
|
|
155
|
+
- **Legacy `TemplateRef` inputs and string-keyed lookups still exist on a few components and
|
|
156
|
+
still work, but are `@deprecated` — do not use them in new code.** See
|
|
157
|
+
[Deprecated patterns — do not use in new code](#deprecated-patterns--do-not-use-in-new-code).
|
|
158
|
+
- **Accessibility is built in**, not optional: keyboard navigation (roving tabindex, arrow keys,
|
|
159
|
+
Home/End), ARIA roles/states, `:focus-visible` styling, `prefers-reduced-motion` handling, and
|
|
160
|
+
WCAG AA contrast are already implemented — you don't need to add any of this yourself, just
|
|
161
|
+
supply `ariaLabel`/`label` inputs where a component has no visible text of its own (icon-only
|
|
162
|
+
buttons, `gog-progressbar`, `gog-scroll`).
|
|
163
|
+
- **`aria-label` on the host tag does nothing.** Several components (`gog-button` chief among
|
|
164
|
+
them) render their real interactive element (a `<button>`) _inside_ the component's own host
|
|
165
|
+
tag. An `aria-label` attribute placed directly on `<gog-button>` in a template lands on the
|
|
166
|
+
custom element wrapper, not on the inner `<button>`, so assistive tech never sees it — always
|
|
167
|
+
use the component's own `ariaLabel` input instead.
|
|
168
|
+
|
|
169
|
+
## Theming
|
|
170
|
+
|
|
171
|
+
Full model is in `README.md`'s Theming section; short version:
|
|
172
|
+
|
|
173
|
+
- Every visual value (color, spacing, radius, shadow, duration) is a `--gog-*` CSS custom
|
|
174
|
+
property, layered **foundation** (`--gog-accent-color`, `--gog-space-md`, …, restyles
|
|
175
|
+
everything) → **component** (`--gog-button-primary-bg`, …, one block per component, named after
|
|
176
|
+
the component's own element) → **instance** (`--gog-button-bg`, …, deliberately undeclared
|
|
177
|
+
escape hatch for one element).
|
|
178
|
+
- **Foundation includes a small character layer** (since 21.7.0, `docs/themes.md` iteration 1):
|
|
179
|
+
`--gog-radius` (corner rounding), `--gog-control-border-*`/`--gog-panel-border-*`/`--gog-border-*`
|
|
180
|
+
(border weight — form fields, raised surfaces, everything smaller and inline, respectively),
|
|
181
|
+
`--gog-text-transform`/`--gog-letter-spacing` (emphasis casing/tracking). Component tokens in
|
|
182
|
+
the categories these cover derive from them by default; setting one in a `[data-theme]` block
|
|
183
|
+
restyles every component that reads it, with nothing to re-list per component.
|
|
184
|
+
- **The type scale is `--gog-text-xs | sm | md | lg | slg | xl | 2xl | 3xl`.** `slg` (1.25rem)
|
|
185
|
+
fills the gap between `lg` and `xl` and is named for the control size that needed it. Every
|
|
186
|
+
component font size that is one of these reads the token, so retuning the scale retunes the
|
|
187
|
+
library; the handful that do not are off-scale on purpose (an 11px chip, the accordion
|
|
188
|
+
chevron's px ramp, the toggle's own micro-ramp).
|
|
189
|
+
|
|
190
|
+
- **Weight is `--gog-font-weight-medium | semibold | bold | heavy`** (500/600/700/900). Every
|
|
191
|
+
component weight reads one of them, so a lighter or heavier house style is four declarations.
|
|
192
|
+
|
|
193
|
+
- **`--gog-z-base` moves the whole stacking order.** Badge `+1`, toast `+100`, dropdowns, dialogs
|
|
194
|
+
and menus `+300`, tooltip `+400`, the blocking spinner overlay `+8000`. Set the base to lift
|
|
195
|
+
the library above your own chrome without disturbing its internal order.
|
|
196
|
+
|
|
197
|
+
- **`--gog-density` is the character layer for spacing** (since 21.7.0, `docs/themes.md`
|
|
198
|
+
iteration 6). It multiplies the fourteen-step scale `--gog-space-2` … `--gog-space-48`, named
|
|
199
|
+
for their pixel value at density 1, and every padding and gap in the library derives from a
|
|
200
|
+
step. `--gog-density: 0.9` in a `[data-theme]` block makes the whole library tighter; nothing
|
|
201
|
+
else needs to be named. `--gog-space-xs|sm|md|lg|2xl` are aliases for steps 4/8/16/24/48 and
|
|
202
|
+
still work. Icon offsets, dropdown panel gaps, error-line offsets and the badge's overhang
|
|
203
|
+
follow density; the glyph box, the focus-ring offset, the float-label reserve and the
|
|
204
|
+
scrollbar/toggle thumb insets deliberately do not — those are legibility or geometry fitted to
|
|
205
|
+
a fixed-width track, not spacing. Since 21.9.0 the split is enforced rather than trusted:
|
|
206
|
+
`check-tokens` rule H fails the build on a length token that restates a scale step's value as
|
|
207
|
+
a bare literal, with the three exceptions named in the script.
|
|
208
|
+
- **Component prefixes are spelled out** since 21.5.0: `--gog-button-*`, `--gog-multiselect-*`,
|
|
209
|
+
`--gog-confirmation-dialog-*`. The abbreviated `--gog-btn-*`, `--gog-ms-*` and `--gog-confirm-*`
|
|
210
|
+
were removed in 21.7.0 — if you're reading a codebase or an example that still uses one, rename
|
|
211
|
+
it; it no longer resolves. The exception is `--gog-input-*`, which is not an abbreviation: it is
|
|
212
|
+
the shared text-field block that `gog-inputfield` and `gog-textarea` both render, and it keeps
|
|
213
|
+
that name.
|
|
214
|
+
- **The package does not need the app's `box-sizing` reset** (since 21.6.0): `utilities.css`
|
|
215
|
+
sets `border-box` on every element carrying a `gog-*` class, including the ones the library
|
|
216
|
+
puts on a consumer's own element. Do not add a reset "so the components line up" — they
|
|
217
|
+
already do, and a `* { box-sizing: content-box }` in an app is the only thing that undoes it.
|
|
218
|
+
- Theme switch is a `data-theme` attribute, usually on `<html>`, toggled through the
|
|
219
|
+
`ThemeService` (`inject(ThemeService).setTheme('dark')` / `.toggleTheme()` / `.theme` signal).
|
|
220
|
+
Ships `light` and `dark`, plus nine importable presets at
|
|
221
|
+
`@guildofgleks/ui/styles/presets/<name>.css`. **All nine set palette and character** (since
|
|
222
|
+
21.7.0 — before it, three were palette-only, which made them recoloured defaults):
|
|
223
|
+
|
|
224
|
+
| Preset | Radius | Density | Identity |
|
|
225
|
+
| ---------------------- | ------ | ------- | ------------------------------------------------ |
|
|
226
|
+
| `slate` | 12px | 1.05 | soft modern — hairline borders, roomy |
|
|
227
|
+
| `one-dark`/`one-light` | 4px | 0.9 | editor chrome; identical character, two tones |
|
|
228
|
+
| `material` | 4px | 1.1 | Material Design 3, pill buttons |
|
|
229
|
+
| `primeng` | 6px | 0.95 | PrimeNG Aura |
|
|
230
|
+
| `ledger` | 0 | 0.9 | administrative — hard offset shadow, no motion |
|
|
231
|
+
| `terminal` | 0 | 0.85 | green phosphor, monospaced throughout, no motion |
|
|
232
|
+
| `bevel` | 0 | 0.9 | early-web desktop — `outset`/`inset` borders |
|
|
233
|
+
| `parchment` | 0 | 1.1 | ink on paper — old-style serif, oxblood |
|
|
234
|
+
|
|
235
|
+
`material`, `primeng` and `bevel` also set a few genuinely per-component things the character
|
|
236
|
+
layer has no vocabulary for (a pill button, a table's header font, a button bevel that has to
|
|
237
|
+
disagree with a field's); see their own file headers.
|
|
238
|
+
|
|
239
|
+
- **A preset never makes a network request.** Each sets a font _stack_ resolving to a real system
|
|
240
|
+
face. Where a webfont is worth offering, it is a separate opt-in file — `terminal.fonts.css`
|
|
241
|
+
(IBM Plex Mono), `parchment.fonts.css` (EB Garamond) — imported **after** the preset, since it
|
|
242
|
+
re-points the same tokens and later wins. Do not add an `@import url(…)` to a preset itself; put
|
|
243
|
+
it in a companion file, or the import becomes a download nobody asked for.
|
|
244
|
+
- Restyle one instance without touching a theme: `<gog-button style="--gog-button-bg: #ff4edb">`.
|
|
245
|
+
- Build a custom theme by declaring a palette **and a character** against a new `data-theme`
|
|
246
|
+
value (see `README.md`'s Theming section for the full worked example) — component tokens
|
|
247
|
+
re-derive automatically, you don't restate them.
|
|
248
|
+
|
|
249
|
+
## Right-to-left
|
|
250
|
+
|
|
251
|
+
Supported since 21.5.0. `dir="rtl"` on `<html>` or on any wrapper mirrors every component —
|
|
252
|
+
you write nothing per component. Portaled overlays (select/multiselect panels, tooltip bubbles)
|
|
253
|
+
copy a _scoped_ `dir` onto themselves, so an RTL region inside an LTR page works too.
|
|
254
|
+
|
|
255
|
+
Physical by design, in both directions: `gogTooltip [position]="'left' | 'right'"` and
|
|
256
|
+
`ToastConfig.position` (`'top-right'`, …). Use the tooltip's `'auto'` for direction-aware
|
|
257
|
+
placement; a toast corner is a deliberate choice, so it is not mirrored.
|
|
258
|
+
|
|
259
|
+
## Global configuration — `GOG_CONFIG` / `provideGogConfig(...)`
|
|
260
|
+
|
|
261
|
+
For the handful of inputs an app typically wants to set once (a size for every form control, a
|
|
262
|
+
locale for every datepicker) rather than repeat on every instance:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
import { provideGogConfig } from '@guildofgleks/ui';
|
|
266
|
+
|
|
267
|
+
bootstrapApplication(App, {
|
|
268
|
+
providers: [
|
|
269
|
+
provideGogConfig({
|
|
270
|
+
control: { size: 'sm', errorDisplay: 'auto', clearable: true },
|
|
271
|
+
dropdown: { appendToBody: true, filter: true },
|
|
272
|
+
datepicker: { locale: 'de-DE', firstDayOfWeek: 1, format: 'dd.MM.yyyy' },
|
|
273
|
+
toast: { position: 'top-right', duration: 4000 },
|
|
274
|
+
}),
|
|
275
|
+
],
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Precedence, always: **instance input → `GOG_CONFIG` → component's built-in default.** A nested
|
|
280
|
+
`provideGogConfig(...)` (in a route's or component's own `providers`) **layers onto the
|
|
281
|
+
parent's config**, one level deep per key — it does not replace it.
|
|
282
|
+
|
|
283
|
+
| Key | Fields | Applies to |
|
|
284
|
+
| -------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
285
|
+
| `control` | `size`, `errorDisplay`, `clearable` | `size`: button, `[gogButton]`, button-toggle-group, checkbox, toggle, radio-group, inputfield, textarea, select, multiselect, autocomplete, datepicker. `errorDisplay`: inputfield, textarea, select, multiselect, autocomplete, datepicker, radio-group, slider. `clearable`: inputfield, textarea, select, multiselect, autocomplete, datepicker. Not table/accordion/paginator (density, not form size), not spinner/skeleton/tag/chip. |
|
|
286
|
+
| `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). |
|
|
287
|
+
| `floatLabel` | `variant`, `showPlaceholder` | inputfield, textarea, select, multiselect, autocomplete, datepicker. |
|
|
288
|
+
| `datepicker` | `locale`, `firstDayOfWeek`, `format` | `gog-datepicker`, `gog-calendar`. |
|
|
289
|
+
| `autocomplete` | `searchDebounce`, `minLength`, `openOnFocus` | `gog-autocomplete`. |
|
|
290
|
+
| `tooltip` | `position`, `showDelay`, `hideDelay` | the `gogTooltip` directive. |
|
|
291
|
+
| `spinner` | `component`, `variant` | every spinner the library draws — `gog-spinner`, `gog-spinner-overlay`, and the ones inside `gog-button`, `gog-autocomplete` and `gog-table`, which have no input of their own. `component` takes **your** component and renders it in place of the built-in look. The overlay honoured neither key until 21.10.0, and `gog-table` was simply never listed. |
|
|
292
|
+
| `scroll` | `autoHide`, `hideDelay`, `size`, `overscrollBehavior`, `showTrack`, `horizontalWheel` | `gog-scroll` (and every component that uses one internally). |
|
|
293
|
+
| `button` | `debounce` | `gog-button`. |
|
|
294
|
+
| `ripple` | `enabled` | the press ripple on `gog-button`, `[gogButton]`, `gog-button-toggle-group`, `gog-chip`, `gog-tabs`, `gog-accordion`, `gogCollapsibleTrigger`, `gogMenuItem` and the `gog-select`/`gog-multiselect`/`gog-autocomplete` options. **Off by default.** Each of those takes a `ripple` input that wins over it. Not the `gogRipple` directive — writing that attribute is already the per-element decision. |
|
|
295
|
+
| `inputfield` | `showSpinButtons` | `gog-inputfield`. |
|
|
296
|
+
| `textarea` | `resize` | `gog-textarea`. |
|
|
297
|
+
| `paginator` | `showPageSizeSelect`, `pageSizeOptions` | `gog-paginator`, and through it `gog-table`'s built-in pagination. |
|
|
298
|
+
| `toast` | `position`, `duration` | `ToastService`. |
|
|
299
|
+
| `theme` | `storageKey`, `defaultTheme`, `followSystem`, `lightTheme`, `darkTheme` | `ThemeService`. All off/neutral by default — see below. |
|
|
300
|
+
| `labels` | every fixed string the library renders — see below | inputfield, textarea, select, multiselect, autocomplete, datepicker, calendar, paginator, table, `DialogService`, `ToastService`. |
|
|
301
|
+
|
|
302
|
+
Anything visual does **not** belong here — override the `--gog-*` token instead.
|
|
303
|
+
|
|
304
|
+
### `labels` — translating the library
|
|
305
|
+
|
|
306
|
+
Every string a component renders that the consumer never writes markup for. An app that isn't
|
|
307
|
+
in English sets these once rather than on every control:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
provideGogConfig({
|
|
311
|
+
labels: {
|
|
312
|
+
clear: 'Löschen', // inputfield / textarea clear button
|
|
313
|
+
clearSelection: 'Auswahl löschen', // select / multiselect / autocomplete
|
|
314
|
+
clearDate: 'Datum löschen', // datepicker
|
|
315
|
+
selectAll: 'Alle auswählen', // multiselect panel
|
|
316
|
+
clearAll: 'Alle löschen', // multiselect panel
|
|
317
|
+
increment: 'Erhöhen', // number spin buttons
|
|
318
|
+
decrement: 'Verringern',
|
|
319
|
+
showPassword: 'Passwort anzeigen',
|
|
320
|
+
hidePassword: 'Passwort verbergen',
|
|
321
|
+
closeDialog: 'Schließen',
|
|
322
|
+
closeToast: 'Schließen',
|
|
323
|
+
pagination: 'Seitennavigation',
|
|
324
|
+
previousPage: 'Vorherige Seite',
|
|
325
|
+
nextPage: 'Nächste Seite',
|
|
326
|
+
openCalendar: 'Kalender öffnen',
|
|
327
|
+
togglePanel: 'Bereich umschalten', // gog-panel's toggle, only when it has no heading
|
|
328
|
+
rowsPerPage: 'Zeilen pro Seite', // gog-paginator's size select
|
|
329
|
+
total: 'Gesamt', // gog-table's row-count label
|
|
330
|
+
tablePagination: 'Tabellennavigation',
|
|
331
|
+
selectRow: 'Zeile auswählen',
|
|
332
|
+
selectAllRows: 'Alle Zeilen auswählen',
|
|
333
|
+
today: 'Heute',
|
|
334
|
+
thisMonth: 'Aktueller Monat',
|
|
335
|
+
previousMonth: 'Vorheriger Monat',
|
|
336
|
+
nextMonth: 'Nächster Monat',
|
|
337
|
+
previousYear: 'Vorheriges Jahr',
|
|
338
|
+
nextYear: 'Nächstes Jahr',
|
|
339
|
+
hours: 'Stunden',
|
|
340
|
+
minutes: 'Minuten',
|
|
341
|
+
seconds: 'Sekunden',
|
|
342
|
+
// The one non-string field: it interpolates the page number, and word order and
|
|
343
|
+
// agreement around a number vary by language, so it takes a formatter.
|
|
344
|
+
page: (page, isCurrent) => (isCurrent ? `Seite ${page}, aktuell` : `Zu Seite ${page} wechseln`),
|
|
345
|
+
},
|
|
346
|
+
});
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Strings that describe **one** control rather than library chrome — `gog-checkbox`'s `ariaLabel`,
|
|
350
|
+
`gog-button`'s `ariaLabel`, any field's `label`/`placeholder` — are deliberately **not** here.
|
|
351
|
+
Those stay per instance. Where a per-instance label input exists (`clearAriaLabel`, `todayLabel`,
|
|
352
|
+
…) it still wins over the configured value.
|
|
353
|
+
|
|
354
|
+
## Services
|
|
355
|
+
|
|
356
|
+
### `ThemeService`
|
|
357
|
+
|
|
358
|
+
```ts
|
|
359
|
+
private readonly theme = inject(ThemeService);
|
|
360
|
+
this.theme.theme(); // Signal<string>, READ-ONLY — current data-theme
|
|
361
|
+
this.theme.setTheme('dark'); // any theme name, including a custom one you declared in CSS
|
|
362
|
+
this.theme.toggleTheme(); // flips between the configured light and dark names
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
`theme` is read-only on purpose: writing to it would move the signal without touching the
|
|
366
|
+
`data-theme` attribute the styles actually read. Never suggest `theme.set(...)` — it does not
|
|
367
|
+
exist.
|
|
368
|
+
|
|
369
|
+
Zero-config behaviour: adopt whatever `data-theme` is already on `<html>`, else `'light'`.
|
|
370
|
+
Persistence and following the OS setting are **opt-in**, so upgrading cannot change which theme
|
|
371
|
+
an existing app opens in:
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
provideGogConfig({
|
|
375
|
+
theme: {
|
|
376
|
+
storageKey: 'app-theme', // persist the choice in localStorage; unset = no persistence
|
|
377
|
+
followSystem: true, // open in the OS prefers-color-scheme, and keep following it
|
|
378
|
+
// until the app calls setTheme/toggleTheme
|
|
379
|
+
lightTheme: 'light', // the two names followSystem maps to and toggleTheme alternates
|
|
380
|
+
darkTheme: 'one-dark', // between
|
|
381
|
+
defaultTheme: 'light', // used when nothing else decides
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
Resolution order at startup: existing `data-theme` on the document → persisted value →
|
|
387
|
+
OS setting (if `followSystem`) → `defaultTheme` → `'light'`.
|
|
388
|
+
|
|
389
|
+
### `ToastService`
|
|
390
|
+
|
|
391
|
+
Root-provided singleton. Requires a `<gog-toast-container />` placed once in your app (see
|
|
392
|
+
[gog-toast](#gog-toast--gog-toast-container) below — it is **not** wired up automatically).
|
|
393
|
+
|
|
394
|
+
```ts
|
|
395
|
+
private readonly toast = inject(ToastService);
|
|
396
|
+
|
|
397
|
+
this.toast.success('Saved');
|
|
398
|
+
this.toast.error('Could not save', {
|
|
399
|
+
isSticky: true,
|
|
400
|
+
actions: [{ label: 'Retry', onClick: () => this.save() }],
|
|
401
|
+
});
|
|
402
|
+
// also: .warning(msg, config?), .info(msg, config?), .show(config), .dismiss(id), .dismissAll()
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
`ToastConfig`: `{ message, type?, iconName?, iconTemplate?, actions?, dedupeKey?, isSticky?, duration?, position? }`.
|
|
406
|
+
Repeated calls with the same (explicit or inferred) `dedupeKey` replace the existing toast in
|
|
407
|
+
place instead of stacking a duplicate.
|
|
408
|
+
|
|
409
|
+
### `DialogService`
|
|
410
|
+
|
|
411
|
+
Root-provided singleton, imperative dynamic-component dialogs. Requires a `<gog-dialog />`
|
|
412
|
+
placed once in your app (see [gog-dialog](#gog-dialog) below — also **not** automatic).
|
|
413
|
+
|
|
414
|
+
```ts
|
|
415
|
+
private readonly dialogService = inject(DialogService);
|
|
416
|
+
|
|
417
|
+
async confirmDelete(): Promise<void> {
|
|
418
|
+
const handle = this.dialogService.open<boolean>({
|
|
419
|
+
component: ConfirmationDialogComponent, // or your own component
|
|
420
|
+
title: 'Delete this item?',
|
|
421
|
+
role: 'alertdialog',
|
|
422
|
+
data: { message: 'This cannot be undone.' },
|
|
423
|
+
});
|
|
424
|
+
const confirmed = await handle.afterClosed; // boolean | undefined
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
`DialogConfig<TData>`: `{ title?, component, data?: TData, modal? (default true), closable?, draggable?, closeIconName?, closeIconTemplate?, width?, maxWidth?, role? ('dialog' default | 'alertdialog'), zIndex? }`.
|
|
429
|
+
`open<TResult, TData>()` returns `{ close(result?), afterClosed: Promise<TResult | undefined> }`. Also:
|
|
430
|
+
`closeAll(result?)`, `updatePosition(id, offsetX, offsetY)` (for `draggable` dialogs).
|
|
431
|
+
|
|
432
|
+
**`open<TResult, TData>()` type-checks `data` against `TData` when you supply both type
|
|
433
|
+
arguments** — supplying only `TResult` (the common case above) leaves `TData` as `unknown`,
|
|
434
|
+
exactly as before:
|
|
435
|
+
|
|
436
|
+
```ts
|
|
437
|
+
interface EditUserData {
|
|
438
|
+
userId: string;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
const handle = this.dialogService.open<{ saved: boolean }, EditUserData>({
|
|
442
|
+
component: EditDialogComponent,
|
|
443
|
+
data: { userId: user.id }, // checked against EditUserData here
|
|
444
|
+
});
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
This checks only the call site. `EditDialogComponent` still reads its data via `inject(DIALOG_DATA)`
|
|
448
|
+
— an `InjectionToken<unknown>` shared by every dialog, so it still needs its own cast
|
|
449
|
+
(`inject<EditUserData>(DIALOG_DATA)`, shown below). Angular's DI has no way to carry a
|
|
450
|
+
per-call-site type through one shared token, so the receiving half of the round trip is still on
|
|
451
|
+
trust — this closes only the half that can be closed.
|
|
452
|
+
|
|
453
|
+
The library ships a ready-made `ConfirmationDialogComponent` for yes/no prompts — pass it as
|
|
454
|
+
`component` with `data: { title, description, confirmText, cancelText }`; it resolves the
|
|
455
|
+
dialog's result to `true`/`false`.
|
|
456
|
+
|
|
457
|
+
**Wiring a custom component into a dialog** — it reads its data via `DIALOG_DATA` and closes
|
|
458
|
+
itself via `DIALOG_REF`:
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import { Component, inject } from '@angular/core';
|
|
462
|
+
import { DIALOG_DATA, DIALOG_REF } from '@guildofgleks/ui';
|
|
463
|
+
|
|
464
|
+
@Component({ selector: 'app-edit-dialog', template: `…` })
|
|
465
|
+
export class EditDialogComponent {
|
|
466
|
+
protected readonly data = inject<{ userId: string }>(DIALOG_DATA);
|
|
467
|
+
private readonly ref = inject(DIALOG_REF);
|
|
468
|
+
|
|
469
|
+
save(): void {
|
|
470
|
+
this.ref.close({ saved: true });
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Component reference
|
|
478
|
+
|
|
479
|
+
Every component below is exported from `@guildofgleks/ui`'s root — `import { X } from '@guildofgleks/ui'`.
|
|
480
|
+
"CVA" = implements `ControlValueAccessor` (works with `[formControl]`/`formControlName`).
|
|
481
|
+
|
|
482
|
+
### Buttons & choices
|
|
483
|
+
|
|
484
|
+
#### `gog-button`
|
|
485
|
+
|
|
486
|
+
| Input | Type | Default | Notes |
|
|
487
|
+
| ----------- | --------------------------------- | ----------- | ----------------------------------------------------- |
|
|
488
|
+
| `variant` | `GogVariant` | `'primary'` | |
|
|
489
|
+
| `severity` | `GogSeverity` | `'accent'` | what the action means; orthogonal to `variant` — see below |
|
|
490
|
+
| `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
|
|
491
|
+
| `disabled` | `boolean` | `false` | |
|
|
492
|
+
| `fullWidth` | `boolean` | `false` | |
|
|
493
|
+
| `type` | `'button' \| 'submit' \| 'reset'` | `'button'` | |
|
|
494
|
+
| `loading` | `boolean` | `false` | shows an inline `gog-spinner`, blocks clicks |
|
|
495
|
+
| `debounce` | `number \| undefined` | `300` | ms; via `GOG_CONFIG.button.debounce` — see note below |
|
|
496
|
+
| `ariaLabel` | `string \| null` | `null` | **use this, not a raw `aria-label` attribute** |
|
|
497
|
+
| `ariaPressed` | `boolean \| 'mixed' \| null` | `null` | toggle button; `false` renders `aria-pressed="false"` |
|
|
498
|
+
| `ariaExpanded` | `boolean \| null` | `null` | disclosure / popup trigger |
|
|
499
|
+
| `ariaControls` | `string \| null` | `null` | id of the controlled element; pairs with `ariaExpanded` |
|
|
500
|
+
| `ariaHasPopup` | `GogAriaHasPopup \| null` | `null` | `boolean \| 'menu' \| 'listbox' \| 'tree' \| 'grid' \| 'dialog'` |
|
|
501
|
+
| `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
|
|
502
|
+
|
|
503
|
+
Outputs: `gogClick: MouseEvent`.
|
|
504
|
+
|
|
505
|
+
**`severity` says what the action means; `variant` says how loudly it is drawn** (21.9.0). The
|
|
506
|
+
two are orthogonal, so this is not a fifth variant — it re-points the colours all four are built
|
|
507
|
+
from, and every combination is real: `variant="ghost" severity="danger"` is a quiet delete,
|
|
508
|
+
`variant="primary" severity="danger"` a loud one. `'accent'` is the default and the absence of a
|
|
509
|
+
claim, so nothing has to opt out of a severity it does not have. `GogSeverity` is shared with
|
|
510
|
+
`gog-progressbar`, whose `GogProgressbarVariant` is now an alias of it.
|
|
511
|
+
|
|
512
|
+
```html
|
|
513
|
+
<gog-button severity="danger" (gogClick)="deleteAccount()">Delete account</gog-button>
|
|
514
|
+
<gog-button variant="outline" severity="warning">Discard draft</gog-button>
|
|
515
|
+
<a gogButton severity="success" routerLink="/done">Finish</a>
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Two colour rules are worth knowing before you override anything. A **filled** severity button's
|
|
519
|
+
label is `--gog-<status>-text-color`, which each theme states for its own hue — `material` and
|
|
520
|
+
`primeng` put near-black on their bright ones, the rest white — and hover and press deepen the
|
|
521
|
+
fill *away* from that label (`--gog-<status>-shade`), so a state always makes the label easier to
|
|
522
|
+
read rather than harder. A **transparent** one's label is `--gog-button-<status>-ink`: the status
|
|
523
|
+
hue mixed halfway toward the page's ink, because the raw hue is legible body text in only five of
|
|
524
|
+
the eleven shipped themes. Override `--gog-button-<status>-ink` if your own theme wants more
|
|
525
|
+
colour there, and check it: all four severities across all four variants and all their states are
|
|
526
|
+
gated by `npm run check:contrast`.
|
|
527
|
+
|
|
528
|
+
**Every ARIA attribute this button needs has an input, and a raw attribute is not a
|
|
529
|
+
substitute.** `<gog-button [attr.aria-pressed]="on()">` compiles, throws nothing, and does
|
|
530
|
+
nothing: the attribute lands on the `<gog-button>` custom element, which has no role, while the
|
|
531
|
+
real `<button>` inside stays unmarked. The failure is invisible — the control looks right and is
|
|
532
|
+
simply not a toggle to a screen reader. Use `[ariaPressed]`, `[ariaExpanded]`, `[ariaControls]`,
|
|
533
|
+
`[ariaHasPopup]` and `ariaLabel`.
|
|
534
|
+
|
|
535
|
+
`false` is not the same as unset. `null` omits the attribute; `false` renders
|
|
536
|
+
`aria-pressed="false"` / `aria-expanded="false"`, which is what an off toggle or a closed
|
|
537
|
+
disclosure has to say — a button with no `aria-pressed` at all is not a toggle button.
|
|
538
|
+
|
|
539
|
+
**A toggle button now looks toggled** (21.9.0). `aria-pressed="true"` (or `"mixed"`) draws an
|
|
540
|
+
inset ring — `--gog-button-<variant>-toggled-shadow`, overridable per instance with
|
|
541
|
+
`--gog-button-toggled-shadow`. A ring rather than a fill because hover and press already own the
|
|
542
|
+
background: the state has to survive both, and until 21.9.0 it did not exist at all, so a button
|
|
543
|
+
could announce itself as on to a screen reader and look identical to an off one. `[gogButton]`
|
|
544
|
+
gets the same look from the attribute you write on your own element.
|
|
545
|
+
|
|
546
|
+
**A `disabled` toggle keeps the ring** (21.10.0), dimmed by `--gog-button-disabled-opacity` like
|
|
547
|
+
the rest of the button. `disabled` on a real `<button>` does not remove `aria-pressed`, so "on,
|
|
548
|
+
and unavailable" is announced either way and has to be visible; the rule had excluded
|
|
549
|
+
`:disabled` until then, copied from the hover and press rules where the guard belongs.
|
|
550
|
+
`gog-chip`'s `selected` ring has always behaved this way, and the two are now the same.
|
|
551
|
+
|
|
552
|
+
**`[gogButton]` needs none of these inputs.** It styles an element you own, so write the ARIA
|
|
553
|
+
attributes on your own `<button>`/`<a>` directly. Same for `[gogMenuTrigger]`, which sets
|
|
554
|
+
`aria-haspopup`/`aria-expanded`/`aria-controls` on its host — put it on your own `<button
|
|
555
|
+
gogButton>`, as its own example shows, not on a `<gog-button>`.
|
|
556
|
+
|
|
557
|
+
```html
|
|
558
|
+
<gog-button [ariaPressed]="mirrored()" (gogClick)="toggleMirror()">Mirror</gog-button>
|
|
559
|
+
|
|
560
|
+
<gog-button [ariaExpanded]="open()" ariaControls="filters" ariaHasPopup="dialog"
|
|
561
|
+
(gogClick)="open.set(!open())">Filters</gog-button>
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
**The press is a colour, not only a movement.** `:active` deepens the button's background (and
|
|
565
|
+
the label where the fill demands it) as well as scaling it by `--gog-button-active-scale`. Under
|
|
566
|
+
`prefers-reduced-motion: reduce` the scale is dropped and the colour stays, so the press is still
|
|
567
|
+
visible to a reader who has switched animations off — before 21.9.0 that reader got no feedback at
|
|
568
|
+
all, since the ripple is off by default and is itself suppressed under reduced motion. Override
|
|
569
|
+
per instance with `--gog-button-press-bg` / `--gog-button-press-color`, or per theme with
|
|
570
|
+
`--gog-button-<variant>-active-bg`.
|
|
571
|
+
|
|
572
|
+
Every other pressable surface in the library does the same thing since 21.9.0 — menu items,
|
|
573
|
+
chips, tab and accordion headers, button-toggle options and the three dropdowns' option rows —
|
|
574
|
+
each through its own `--gog-<block>-press-bg`. `gogCollapsibleTrigger` is the exception: the
|
|
575
|
+
library paints nothing on that element in any state, because it is yours.
|
|
576
|
+
|
|
577
|
+
**`debounce` is a spam guard, not a delay before the first click.** The first click in a window
|
|
578
|
+
fires immediately (leading edge); further clicks within `debounce` ms are silently dropped.
|
|
579
|
+
|
|
580
|
+
**Use `(gogClick)`, never `(click)`, on `gog-button`.** The click handler that drives `debounce`
|
|
581
|
+
and emits `gogClick` is bound on the `<button>` inside the component's own template, not on the
|
|
582
|
+
host — a native click still bubbles up through `<gog-button>`, so a `(click)` listener written
|
|
583
|
+
there fires on every press, silently bypassing the debounce entirely. This is specific to the
|
|
584
|
+
component: `[gogButton]` on your own `<a>`/`<button>` has no debounce to bypass, so `(click)` on
|
|
585
|
+
it works exactly as written.
|
|
586
|
+
|
|
587
|
+
```html
|
|
588
|
+
<gog-button variant="primary" [loading]="saving()" (gogClick)="save()">Save</gog-button>
|
|
589
|
+
<gog-button variant="ghost" ariaLabel="Close" (gogClick)="close()"
|
|
590
|
+
><gog-icon name="close"
|
|
591
|
+
/></gog-button>
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
#### `gog-button-toggle-group`
|
|
595
|
+
|
|
596
|
+
A row of buttons, single- or multi-select, built from your own option objects.
|
|
597
|
+
|
|
598
|
+
| Input | Type | Default | Notes |
|
|
599
|
+
| ------------------------------------ | ------------------------------------------ | ---------------------- | --------------------------------------------- |
|
|
600
|
+
| `options` | `TOption[]` | `[]` | |
|
|
601
|
+
| `optionLabel` | accessor | `'name'` | |
|
|
602
|
+
| `optionValue` | accessor \| `null` | `'id'` | `null` emits the option object |
|
|
603
|
+
| `optionDisabled` | accessor | `'disabled'` | |
|
|
604
|
+
| `optionIcon` | accessor → `GogIconName \| null` \| `null` | `null` | optional leading icon per option |
|
|
605
|
+
| `multiple` | `boolean` | `false` | changes ARIA role entirely — see note |
|
|
606
|
+
| `appearance` | `'joined' \| 'separated'` | `'joined'` | |
|
|
607
|
+
| `orientation` | `GogOrientation` | `'horizontal'` | |
|
|
608
|
+
| `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
|
|
609
|
+
| `disabled`, `fullWidth`, `ariaLabel` | | `false`, `false`, `''` | |
|
|
610
|
+
| `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
|
|
611
|
+
|
|
612
|
+
Model: `value: TValue | TValue[] | null` (single value, or array in `multiple` mode). CVA: yes.
|
|
613
|
+
Slot: `<ng-template gogButtonToggleOption let-opt let-selected="selected">` for custom button
|
|
614
|
+
markup. **Single mode is a radio group** (`role="radiogroup"`, arrows move _and_ select);
|
|
615
|
+
**multiple mode is a toolbar of independent toggles** (`role="group"`, arrows only move, Space
|
|
616
|
+
toggles) — this is a real ARIA distinction, not cosmetic.
|
|
617
|
+
|
|
618
|
+
```html
|
|
619
|
+
<gog-button-toggle-group [options]="alignments" [(value)]="align" />
|
|
620
|
+
<gog-button-toggle-group [options]="tools" [multiple]="true" [(value)]="activeTools" />
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Form fields
|
|
624
|
+
|
|
625
|
+
#### `gog-inputfield`
|
|
626
|
+
|
|
627
|
+
| Input | Type | Default | Notes |
|
|
628
|
+
| ----------------------------------------- | ---------------------- | ----------------------------------- | -------------------------------------------------------------------------------------- |
|
|
629
|
+
| `label`, `placeholder` | `string` | `''` | |
|
|
630
|
+
| `type` | `GogInputType` | `'text'` | `text`/`password`/`email`/`number`/`search`/`tel`/`url`/`date`/`time`/`datetime-local` |
|
|
631
|
+
| `readonly` | `boolean` | `false` | value stays focusable and submitted, edits blocked; hides the clear button and stepper |
|
|
632
|
+
| `maxlength`, `minlength` | `number \| null` | `null` | native attributes |
|
|
633
|
+
| `pattern` | `string` | `''` | native attribute, regex source |
|
|
634
|
+
| `inputMode` | `GogInputMode \| null` | `null` | on-screen keyboard hint (`numeric`, `tel`, …) |
|
|
635
|
+
| `spellcheck` | `boolean \| null` | `null` | unset = browser default |
|
|
636
|
+
| `inputId` | `string` | `''` → generated | a real id is always rendered; pass one only to reference the field externally |
|
|
637
|
+
| `min`, `max`, `step` | `number \| null` | `null` | `type="number"` only |
|
|
638
|
+
| `showSpinButtons` | `boolean \| undefined` | `true` | own +/- glyphs on `type="number"`; via `GOG_CONFIG.inputfield.showSpinButtons` |
|
|
639
|
+
| `errorMessage`, `errorDisplay` | | `''`, `'manual'` | see conventions |
|
|
640
|
+
| `disabled`, `size`, `fullWidth` | | `false`, `'md'`, `true` | |
|
|
641
|
+
| `iconStart` / `iconEnd` | `GogIconName \| ''` | `''` | bare leading/trailing icon |
|
|
642
|
+
| `clearable`, `clearAriaLabel` | | `false`, `'Clear'` | on `type="number"` the clear button renders alongside the stepper |
|
|
643
|
+
| `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
|
|
644
|
+
| `showPasswordLabel` / `hidePasswordLabel` | `string \| undefined` | `'Show password'`/`'Hide password'` | `type="password"` reveal toggle aria-labels; via `GOG_CONFIG.labels` |
|
|
645
|
+
| `incrementLabel` / `decrementLabel` | `string \| undefined` | `'Increment'`/`'Decrement'` | spin button aria-labels; via `GOG_CONFIG.labels` |
|
|
646
|
+
|
|
647
|
+
Model: `value: string` (always a string, even for `type="number"` — the _form control_ value is
|
|
648
|
+
`number | null`, but the `[(value)]` model mirrors the raw text). CVA: yes.
|
|
649
|
+
|
|
650
|
+
Slots: project `<span gogInputAddonStart>`/`<span gogInputAddonEnd>` (or a `<button>`) for
|
|
651
|
+
custom leading/trailing markup — a normal DOM element with its own `aria-label`, click handler
|
|
652
|
+
and disabled state, not a component-managed slot. This is the **current, non-deprecated**
|
|
653
|
+
replacement for the old icon-template/icon-fn/icon-label input quartet — see
|
|
654
|
+
[Deprecated patterns](#deprecated-patterns--do-not-use-in-new-code).
|
|
655
|
+
|
|
656
|
+
```html
|
|
657
|
+
<gog-inputfield
|
|
658
|
+
label="Email"
|
|
659
|
+
type="email"
|
|
660
|
+
formControlName="email"
|
|
661
|
+
errorDisplay="auto"
|
|
662
|
+
errorMessage="Enter a valid email"
|
|
663
|
+
[clearable]="true"
|
|
664
|
+
/>
|
|
665
|
+
|
|
666
|
+
<gog-inputfield label="Amount" [fullWidth]="false">
|
|
667
|
+
<span gogInputAddonStart>€</span>
|
|
668
|
+
</gog-inputfield>
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
#### `gog-textarea`
|
|
672
|
+
|
|
673
|
+
| Input | Type | Default |
|
|
674
|
+
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ---------------------------------------------- |
|
|
675
|
+
| `label`, `placeholder` | `string` | `''` |
|
|
676
|
+
| `rows` | `number` | `4` |
|
|
677
|
+
| `readonly` | `boolean` | `false` |
|
|
678
|
+
| `maxlength`, `minlength` | `number \| null` | `null` |
|
|
679
|
+
| `spellcheck` | `boolean \| null` | `null` |
|
|
680
|
+
| `inputId` | `string` | `''` → generated, same as inputfield |
|
|
681
|
+
| `resize` | `GogTextareaResize \| undefined` (`'vertical'\|'horizontal'\|'both'\|'none'`) | `'vertical'`; via `GOG_CONFIG.textarea.resize` |
|
|
682
|
+
| `errorMessage`, `errorDisplay`, `disabled`, `size`, `fullWidth` | | same shape as inputfield |
|
|
683
|
+
| `clearable`, `clearAriaLabel`, `floatLabel`, `floatLabelShowPlaceholder` | | same shape as inputfield |
|
|
684
|
+
|
|
685
|
+
Model: `value: string`. CVA: yes.
|
|
686
|
+
|
|
687
|
+
```html
|
|
688
|
+
<gog-textarea label="Notes" formControlName="notes" [rows]="6" resize="vertical" />
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
#### `gog-select`
|
|
692
|
+
|
|
693
|
+
Extends the shared listbox behaviour (`GogDropdownBase`) that also backs `gog-multiselect` and
|
|
694
|
+
partly `gog-autocomplete` — placement, the append-to-body overlay, click-outside, keyboard nav,
|
|
695
|
+
and CVA all come from there. Full shared input surface (documented once, applies to both select
|
|
696
|
+
and multiselect unless noted otherwise):
|
|
697
|
+
|
|
698
|
+
| Input | Type | Default | Notes |
|
|
699
|
+
| ------------------------------------------------------ | --------------------------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
|
|
700
|
+
| `label`, `ariaLabel`, `placeholder` | `string` | `''`, `''`, `'Select...'` | |
|
|
701
|
+
| `options` | `TOption[]` | `[]` | your own objects |
|
|
702
|
+
| `optionLabel` | accessor | `'name'` | path or fn |
|
|
703
|
+
| `optionValue` | accessor \| `null` | `'id'` | `null` = emit the option object |
|
|
704
|
+
| `optionDisabled` | accessor | `'disabled'` | |
|
|
705
|
+
| `clearable`, `clearAriaLabel` | | `false` (select) / `true` (multiselect), `'Clear selection'` | |
|
|
706
|
+
| `minWidth` | `string \| null` | `null` | only with `[fullWidth]="false"` |
|
|
707
|
+
| `filter` | `boolean \| undefined` | `false` | search box in the panel; via `GOG_CONFIG.dropdown.filter` |
|
|
708
|
+
| `filterPlaceholder`, `filterEmptyMessage` | `string` | `'Search...'`, `'No matches'` | |
|
|
709
|
+
| `filterPosition` | `'top' \| 'bottom' \| undefined` | `'top'` | via `GOG_CONFIG.dropdown.filterPosition` |
|
|
710
|
+
| `filterMatch` | `((option, query) => boolean) \| null` | `null` | custom matcher, else case-insensitive substring on the resolved label |
|
|
711
|
+
| `errorMessage`, `errorDisplay` | | `''`, `'manual'` | |
|
|
712
|
+
| `size` | `GogSize \| undefined` | `'md'` | |
|
|
713
|
+
| `dropdownDirection` | `'auto' \| 'up' \| 'down' \| undefined` | `'auto'` | |
|
|
714
|
+
| `dropdownZIndex`, `dropdownWidth`, `dropdownMaxHeight` | | `null` | only meaningful with `appendToBody` |
|
|
715
|
+
| `appendToBody` | `boolean \| undefined` | `false` | renders the panel into `<body>` — needed inside a scroll/overflow-clipped container |
|
|
716
|
+
| `disabled`, `fullWidth` | | `false`, `true` | |
|
|
717
|
+
| `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
|
|
718
|
+
| `inputId` (select/autocomplete only) | `string` | `''` | |
|
|
719
|
+
| `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
|
|
720
|
+
|
|
721
|
+
`gog-select`-specific: `value: model<TValue>(null)`.
|
|
722
|
+
`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`).
|
|
723
|
+
|
|
724
|
+
CVA: yes, both. Slots (shared): `<ng-template gogDropdownChevron>` (custom chevron markup),
|
|
725
|
+
`<ng-template gogDropdownOption let-opt let-selected="selected" let-label="label">` (custom
|
|
726
|
+
option row). Multiselect adds `<ng-template gogMultiselectClearIcon>`.
|
|
727
|
+
|
|
728
|
+
```html
|
|
729
|
+
<gog-select
|
|
730
|
+
label="Region"
|
|
731
|
+
[options]="regions"
|
|
732
|
+
optionLabel="title"
|
|
733
|
+
[(value)]="regionId"
|
|
734
|
+
[filter]="true"
|
|
735
|
+
/>
|
|
736
|
+
|
|
737
|
+
<gog-multiselect
|
|
738
|
+
label="Tags"
|
|
739
|
+
[options]="tags"
|
|
740
|
+
[(value)]="selectedTagIds"
|
|
741
|
+
[showControls]="true"
|
|
742
|
+
formControlName="tags"
|
|
743
|
+
errorDisplay="auto"
|
|
744
|
+
/>
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
#### `gog-autocomplete`
|
|
748
|
+
|
|
749
|
+
Shares `GogDropdownBase` too, but the trigger is a real `<input>` (combobox pattern,
|
|
750
|
+
`aria-activedescendant`), not a listbox button — so it does **not** reuse the base's built-in
|
|
751
|
+
panel-filter box; it filters/searches off what's typed in the field itself.
|
|
752
|
+
|
|
753
|
+
| Input | Type | Default | Notes |
|
|
754
|
+
| ------------------------------------------------------------------------------------------------------ | ---------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
755
|
+
| _(all the shared `GogDropdownBase` inputs above except `filter`/`filterPlaceholder`/`filterPosition`)_ | | | |
|
|
756
|
+
| `filterLocal` | `boolean` | `true` | narrow `options` client-side as you type; turn **off** when `gogSearch` already returns a filtered server list (avoids double-filtering) |
|
|
757
|
+
| `minLength` | `number \| undefined` | `1` | via `GOG_CONFIG.autocomplete.minLength` |
|
|
758
|
+
| `openOnFocus` | `boolean \| undefined` | `true` | via `GOG_CONFIG.autocomplete.openOnFocus` |
|
|
759
|
+
| `searchDebounce` | `number \| undefined` | `300` | ms before `gogSearch` fires; via `GOG_CONFIG.autocomplete.searchDebounce` |
|
|
760
|
+
| `loading` | `boolean` | `false` | shows a spinner in the trailing slot |
|
|
761
|
+
| `emptyMessage` | `string` | `'No matches'` | |
|
|
762
|
+
| `forceSelection` | `boolean` | `true` | see note below |
|
|
763
|
+
| `ripple` | `boolean \| undefined` | `false` | press ripple; via `GOG_CONFIG.ripple.enabled` |
|
|
764
|
+
|
|
765
|
+
Outputs: `gogSearch: string` (debounced query — wire your server lookup here),
|
|
766
|
+
`gogLoadMore: void` (panel scrolled to the end — fetch the next page).
|
|
767
|
+
|
|
768
|
+
Model: `value: TValue | null`. CVA: yes.
|
|
769
|
+
|
|
770
|
+
**`forceSelection` matters.** On (default): the field always ends up reflecting a real
|
|
771
|
+
selection — free-typed text that matches nothing snaps back on blur/Escape. Off: what the user
|
|
772
|
+
typed is itself meaningful (a create-as-you-type flow) — read the typed text from `gogSearch`,
|
|
773
|
+
not from `value`, since `value` clears the moment the text stops matching the selection.
|
|
774
|
+
|
|
775
|
+
```html
|
|
776
|
+
<gog-autocomplete
|
|
777
|
+
[options]="users"
|
|
778
|
+
optionLabel="profile.fullName"
|
|
779
|
+
[optionValue]="null"
|
|
780
|
+
[(value)]="user"
|
|
781
|
+
[loading]="searching()"
|
|
782
|
+
(gogSearch)="search($event)"
|
|
783
|
+
/>
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
#### `gog-checkbox`
|
|
787
|
+
|
|
788
|
+
| Input | Type | Default |
|
|
789
|
+
| ---------------------------------------- | ---------------------- | ------- |
|
|
790
|
+
| `label`, `ariaLabel` | `string` | `''` |
|
|
791
|
+
| `size` | `GogSize \| undefined` | `'md'` |
|
|
792
|
+
| `indeterminate`, `disabled`, `fullWidth` | `boolean` | `false` |
|
|
793
|
+
|
|
794
|
+
Model: `checked: boolean`. CVA: yes. Slot: `<ng-template gogCheckboxIcon>` for a custom tick
|
|
795
|
+
icon.
|
|
796
|
+
|
|
797
|
+
```html
|
|
798
|
+
<gog-checkbox label="I agree to the terms" formControlName="agree" />
|
|
799
|
+
```
|
|
800
|
+
|
|
801
|
+
#### `gog-toggle`
|
|
802
|
+
|
|
803
|
+
An on/off switch (`role="switch"`) — semantically different from a checkbox ("is this setting
|
|
804
|
+
on", not "is this one of the things you selected").
|
|
805
|
+
|
|
806
|
+
| Input | Type | Default | Notes |
|
|
807
|
+
| ----------------------- | ---------------------- | ------- | ------------------------------------- |
|
|
808
|
+
| `label`, `ariaLabel` | `string` | `''` | |
|
|
809
|
+
| `size` | `GogSize \| undefined` | `'md'` | via `GOG_CONFIG.control.size` |
|
|
810
|
+
| `disabled`, `fullWidth` | `boolean` | `false` | |
|
|
811
|
+
| `labelPosition` | `'start' \| 'end'` | `'end'` | |
|
|
812
|
+
| `onLabel`, `offLabel` | `string` | `''` | text rendered inside the track itself |
|
|
813
|
+
|
|
814
|
+
Model: `checked: boolean`. CVA: yes.
|
|
815
|
+
|
|
816
|
+
```html
|
|
817
|
+
<gog-toggle label="Notifications" formControlName="notificationsOn" onLabel="ON" offLabel="OFF" />
|
|
818
|
+
```
|
|
819
|
+
|
|
820
|
+
#### `gog-radio-group`
|
|
821
|
+
|
|
822
|
+
| Input | Type | Default |
|
|
823
|
+
| ------------------------------ | ----------------------------------------------- | ---------------- |
|
|
824
|
+
| `options` | `GogRadioOption[]` (`{ id, label, disabled? }`) | `[]` |
|
|
825
|
+
| `label`, `ariaLabel`, `name` | `string` | `''` |
|
|
826
|
+
| `size` | `GogSize \| undefined` | `'md'` |
|
|
827
|
+
| `disabled`, `fullWidth` | `boolean` | `false` |
|
|
828
|
+
| `orientation` | `GogOrientation` | `'vertical'` |
|
|
829
|
+
| `errorMessage`, `errorDisplay` | | `''`, `'manual'` |
|
|
830
|
+
|
|
831
|
+
Model: `value: string | number | null`. CVA: yes. Fixed `{ id, label, disabled? }` shape (not
|
|
832
|
+
a generic accessor, unlike select/multiselect/button-toggle).
|
|
833
|
+
|
|
834
|
+
```html
|
|
835
|
+
<gog-radio-group
|
|
836
|
+
[options]="[{id:'m',label:'Male'},{id:'f',label:'Female'}]"
|
|
837
|
+
formControlName="gender"
|
|
838
|
+
/>
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
#### `gog-slider`
|
|
842
|
+
|
|
843
|
+
| Input | Type | Default |
|
|
844
|
+
| -------------------------------- | ---------------------- | ---------------------------------------------- |
|
|
845
|
+
| `label`, `ariaLabel` | `string` | `''` |
|
|
846
|
+
| `min`, `max`, `step` | `number` | `0`, `100`, `1` |
|
|
847
|
+
| `showValue`, `showThumb` | `boolean` | `true` |
|
|
848
|
+
| `errorMessage`, `errorDisplay` | | `''`, `'manual'` |
|
|
849
|
+
| `disabled` | `boolean` | `false` |
|
|
850
|
+
| `fullWidth` | `boolean` | `true` (ignored when `orientation="vertical"`) |
|
|
851
|
+
| `orientation` | `GogSliderOrientation` | `'horizontal'` |
|
|
852
|
+
| `range` | `boolean` | `false` — two thumbs; see below |
|
|
853
|
+
| `startDisabled`, `endDisabled` | `boolean` | `false` — `range` only |
|
|
854
|
+
| `startAriaLabel`, `endAriaLabel` | `string` | `'Minimum'` / `'Maximum'`, prefixed by `label` |
|
|
855
|
+
|
|
856
|
+
Models: `value: number`, and `rangeValue: GogSliderRange` (`{ start: number; end: number }`).
|
|
857
|
+
CVA: yes. Backed by a real `<input type="range">` (rotated via `writing-mode` for vertical), so
|
|
858
|
+
dragging/touch/keyboard all come from the platform.
|
|
859
|
+
|
|
860
|
+
```html
|
|
861
|
+
<gog-slider label="Volume" [min]="0" [max]="100" formControlName="volume" />
|
|
862
|
+
```
|
|
863
|
+
|
|
864
|
+
**Range mode.** `[range]="true"` puts a second thumb on the track and switches which model is
|
|
865
|
+
live: bind `[(rangeValue)]` instead of `[(value)]`. The two are **mutually exclusive** — `value`
|
|
866
|
+
(and a form control's `writeValue`) is ignored while `range` is on, and vice versa.
|
|
867
|
+
|
|
868
|
+
```html
|
|
869
|
+
<gog-slider label="Price" [range]="true" [(rangeValue)]="price" startAriaLabel="Lowest" />
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
Each thumb needs its own accessible name, because one `<label>` cannot be associated with two
|
|
873
|
+
inputs through `for`; unset, they fall back to `'Minimum'`/`'Maximum'` prefixed with `label`
|
|
874
|
+
(`'Price Minimum'`). `startDisabled`/`endDisabled` pin one end while the other stays movable —
|
|
875
|
+
they are ORed with `disabled` rather than overriding it, and unlike it they do not dim the whole
|
|
876
|
+
control or cut pointer events over the track, which would take the still-enabled thumb with them.
|
|
877
|
+
|
|
878
|
+
#### `gog-datepicker` / `gog-calendar`
|
|
879
|
+
|
|
880
|
+
`gog-datepicker` is a field + panel; `gog-calendar` is the month grid alone (what `inline` mode
|
|
881
|
+
renders). Native `Date` only — no date library, no adapter.
|
|
882
|
+
|
|
883
|
+
| Input | Type | Default | Notes |
|
|
884
|
+
| ----------------------------------------------------- | -------------------------------------------- | ----------------------------- | ---------------------------------------------------------------------------- |
|
|
885
|
+
| `inputId`, `label`, `ariaLabel`, `placeholder` | `string` | `''` | |
|
|
886
|
+
| `selectionMode` | `GogDateSelectionMode` (`'single'\|'range'`) | `'single'` | |
|
|
887
|
+
| `min`, `max` | `Date \| null` | `null` | |
|
|
888
|
+
| `disabledDates` | `((date: Date) => boolean) \| null` | `null` | predicate, not a list |
|
|
889
|
+
| `defaultMonth` | `Date \| null` | `null` | which month opens when nothing is selected |
|
|
890
|
+
| `numberOfMonths` | `number` | `1` | `2` is what makes a range picker usable |
|
|
891
|
+
| `showTime`, `hourFormat`, `minuteStep`, `showSeconds` | | `false`, `'24'`, `1`, `false` | |
|
|
892
|
+
| `showTodayButton` | `boolean` | `true` | **selects** today |
|
|
893
|
+
| `showThisMonthButton` | `boolean` | `false` | only moves the _view_, leaves selection alone |
|
|
894
|
+
| `format` | `string \| null` | `null` | display/parse pattern (`'dd.MM.yyyy'`); derived from `showTime` when unset |
|
|
895
|
+
| `locale` | `string \| undefined` | `'en-US'` | via `GOG_CONFIG.datepicker.locale` |
|
|
896
|
+
| `firstDayOfWeek` | `number \| undefined` | locale's own | via `GOG_CONFIG.datepicker.firstDayOfWeek` |
|
|
897
|
+
| `allowTextInput` | `boolean` | `true` | typed text parsed against `format`; unparseable drafts don't clear the value |
|
|
898
|
+
| `inline` | `boolean` | `false` | renders the calendar with no field/panel |
|
|
899
|
+
| `disabled`, `fullWidth` | | `false`, `true` | |
|
|
900
|
+
| `clearable`, `clearAriaLabel` | | `false`, `'Clear date'` | |
|
|
901
|
+
| `errorMessage`, `errorDisplay`, `size` | | `''`, `'manual'`, `'md'` | |
|
|
902
|
+
| `floatLabel`, `floatLabelShowPlaceholder` | | `'none'`, `false` | |
|
|
903
|
+
| `appendToBody`, `dropdownDirection`, `dropdownZIndex` | | `false`, `'auto'`, `null` | |
|
|
904
|
+
|
|
905
|
+
Model: `value: Date | GogDateRange | null` (`GogDateRange = { start: Date | null; end: Date | null }`).
|
|
906
|
+
CVA: yes.
|
|
907
|
+
|
|
908
|
+
`gog-calendar` (usable standalone) takes most of the same date/range/time inputs directly, plus
|
|
909
|
+
`gogDateSelect: output<GogDatepickerValue>()` fired only on a _complete_ selection. It resolves
|
|
910
|
+
`locale` and `firstDayOfWeek` from `GOG_CONFIG.datepicker` itself, so a standalone calendar
|
|
911
|
+
honours an app-wide locale without being handed one; its navigation, shortcut and time labels
|
|
912
|
+
(`todayLabel`, `thisMonthLabel`, `previousMonthLabel`, `nextMonthLabel`, `previousYearLabel`,
|
|
913
|
+
`nextYearLabel`, `hoursLabel`, `minutesLabel`, `secondsLabel`) resolve through
|
|
914
|
+
`GOG_CONFIG.labels` the same way.
|
|
915
|
+
|
|
916
|
+
Also exported for direct reuse: `formatDate(date, pattern)`, `parseDate(text, pattern)`, and a
|
|
917
|
+
family of date-math helpers (`addDays`, `addMonths`, `isSameDay`, `isWithinBounds`, …) from
|
|
918
|
+
`date-utils`.
|
|
919
|
+
|
|
920
|
+
**Sizing.** `gog-calendar` caps itself at its own month grid — you do not need to give it a
|
|
921
|
+
width. `--gog-calendar-max-width` (default `max-content`) is the cap, and it covers the size
|
|
922
|
+
variants, `numberOfMonths`, `showTime` and wider locales on its own; set it to `100%` for a
|
|
923
|
+
calendar that fills its container. This is also what sizes `inline` mode, because `inline` is
|
|
924
|
+
`gog-calendar` with a border and nothing else. The dropdown panel is separate:
|
|
925
|
+
`--gog-datepicker-panel-width`, also `max-content`.
|
|
926
|
+
|
|
927
|
+
```html
|
|
928
|
+
<gog-datepicker label="Birth date" [(value)]="birthDate" [max]="today" />
|
|
929
|
+
<gog-datepicker selectionMode="range" [(value)]="stayRange" [numberOfMonths]="2" />
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
### Display, feedback & status
|
|
933
|
+
|
|
934
|
+
#### `gog-icon`
|
|
935
|
+
|
|
936
|
+
| Input | Type | Default |
|
|
937
|
+
| ------------ | --------------------- | -------------------------------------------------- |
|
|
938
|
+
| `name` | `GogIconName` | `'close'` |
|
|
939
|
+
| `template` | `TemplateRef \| null` | `null` — custom markup instead of the built-in SVG |
|
|
940
|
+
| `title` | `string` | `''` |
|
|
941
|
+
| `ariaHidden` | `boolean` | `true` |
|
|
942
|
+
|
|
943
|
+
The package ships **41** glyphs (`GogBuiltinIconName`), all from [Lucide](https://lucide.dev)
|
|
944
|
+
and inlined so the package keeps zero runtime dependencies:
|
|
945
|
+
|
|
946
|
+
| Group | Names |
|
|
947
|
+
| ----------------- | ------------------------------------------------------------------------------------------------------ |
|
|
948
|
+
| Chevrons & arrows | `chevron-up`, `chevron-down`, `chevron-left`, `chevron-right`, `arrow-left`, `arrow-right` |
|
|
949
|
+
| Confirm & dismiss | `check`, `close`, `checkbox`, `checkbox-checked` |
|
|
950
|
+
| Status | `success`, `error`, `warning`, `info` |
|
|
951
|
+
| Sorting | `sort`, `sort-up`, `sort-down`, `filter` |
|
|
952
|
+
| Actions | `search`, `plus`, `minus`, `trash`, `pencil`, `copy`, `download`, `upload`, `refresh`, `external-link` |
|
|
953
|
+
| Chrome | `menu`, `more-horizontal`, `more-vertical`, `settings` |
|
|
954
|
+
| Objects & state | `user`, `lock`, `mail`, `calendar`, `clock`, `eye`, `eye-off`, `star`, `star-filled` |
|
|
955
|
+
|
|
956
|
+
`star` / `star-filled` is the one outline/filled pair, for a rating or favourite **toggle** —
|
|
957
|
+
the same reason `checkbox` / `checkbox-checked` exists. The set is otherwise outline-only on
|
|
958
|
+
purpose; a blanket solid duplicate of every glyph would double the payload for a distinction
|
|
959
|
+
almost nothing needs. If you want a filled variant of something else, register it with
|
|
960
|
+
`provideGogIcons`.
|
|
961
|
+
|
|
962
|
+
`Object.keys(ICON_DEFS)` is the runtime list, if you need to enumerate them (an icon picker, a
|
|
963
|
+
gallery). Do not hand-copy the names into an array — that is what goes stale.
|
|
964
|
+
|
|
965
|
+
```html
|
|
966
|
+
<gog-icon name="calendar" />
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
##### Registering your own icons — `provideGogIcons(...)`
|
|
970
|
+
|
|
971
|
+
`name` is typed `GogIconName = GogBuiltinIconName | (string & {})`: the built-ins autocomplete,
|
|
972
|
+
and any name you register is accepted. **This is the supported way to use your own icon set** —
|
|
973
|
+
prefer it over the `template` input, which costs an `<ng-template>` at every use site and is for
|
|
974
|
+
one-offs.
|
|
975
|
+
|
|
976
|
+
```ts
|
|
977
|
+
// app.config.ts
|
|
978
|
+
import { provideGogIcons } from '@guildofgleks/ui';
|
|
979
|
+
|
|
980
|
+
providers: [
|
|
981
|
+
provideGogIcons({
|
|
982
|
+
cart: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">…</svg>',
|
|
983
|
+
rocket: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">…</svg>',
|
|
984
|
+
}),
|
|
985
|
+
];
|
|
986
|
+
```
|
|
987
|
+
|
|
988
|
+
```html
|
|
989
|
+
<gog-icon name="cart" />
|
|
990
|
+
<gog-tag iconName="cart">In basket</gog-tag>
|
|
991
|
+
<!-- works anywhere an icon *name* is taken -->
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
- **Registered names win over built-ins of the same name** — that is how you replace the
|
|
995
|
+
library's checkmark or chevrons across every component at once, without touching any of them.
|
|
996
|
+
- **Nested `provideGogIcons(...)` layers onto the parent set** rather than replacing it, the same
|
|
997
|
+
as `provideGogConfig`: a lazy route can register only what it uses.
|
|
998
|
+
- **An unknown name renders nothing and warns in dev mode; it never throws.** An icon is
|
|
999
|
+
decoration — failing the render over a typo would be the worse outcome.
|
|
1000
|
+
- **`GOG_ICONS`** is the `InjectionToken<Readonly<Record<string, string>>>` behind it, exported
|
|
1001
|
+
for the one case `provideGogIcons` does not cover: reading the registered set back
|
|
1002
|
+
(`inject(GOG_ICONS)`) to enumerate it in an icon picker. Provide it through
|
|
1003
|
+
`provideGogIcons(...)` rather than directly — the helper is what layers a child injector's
|
|
1004
|
+
icons onto the parent's instead of replacing them.
|
|
1005
|
+
- **Write the SVG for inheritance:** a `viewBox`, `stroke="currentColor"` (or `fill`), and no
|
|
1006
|
+
width/height — `gog-icon` drives size and stroke width from the `--gog-icon-*` tokens, so a
|
|
1007
|
+
registered icon scales and colours like a built-in.
|
|
1008
|
+
- **Security:** the markup is inserted with `bypassSecurityTrustHtml` (Angular's HTML sanitizer
|
|
1009
|
+
strips SVG, so there is no alternative). That is fine for static icon markup you authored;
|
|
1010
|
+
**never** build a registered icon string from user input or fetch it at runtime unsanitized.
|
|
1011
|
+
|
|
1012
|
+
#### `[gogButton]` — the link-flavoured button
|
|
1013
|
+
|
|
1014
|
+
`gog-button` renders its own `<button>`, so it can never _be_ a link. `[gogButton]` inverts that:
|
|
1015
|
+
the element stays yours and the directive only gives it the look.
|
|
1016
|
+
|
|
1017
|
+
```html
|
|
1018
|
+
<a gogButton routerLink="/pricing">See pricing</a>
|
|
1019
|
+
<a gogButton variant="ghost" href="https://example.com" target="_blank" rel="noreferrer">Docs</a>
|
|
1020
|
+
<button gogButton variant="outline" size="sm" type="submit">Save</button>
|
|
1021
|
+
<a gogButton fullWidth routerLink="/checkout">Checkout</a>
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
| Input | Type | Default |
|
|
1025
|
+
| ----------- | ------------------------ | ---------------------------------------- |
|
|
1026
|
+
| `variant` | `GogVariant` | `'primary'` |
|
|
1027
|
+
| `severity` | `GogSeverity` | `'accent'`; same as `gog-button` |
|
|
1028
|
+
| `size` | `GogSize \| undefined` | `'md'`; via `GOG_CONFIG.control.size` |
|
|
1029
|
+
| `fullWidth` | `boolean` (bare attr ok) | `false` |
|
|
1030
|
+
| `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
|
|
1031
|
+
|
|
1032
|
+
Selector is `a[gogButton], button[gogButton]` — deliberately not a bare `[gogButton]`, because on
|
|
1033
|
+
a `<div>` the result looks like a button and is invisible to the keyboard and to assistive tech.
|
|
1034
|
+
|
|
1035
|
+
**Which to reach for.** `gog-button` for a button that acts on the page: it owns `loading` (a
|
|
1036
|
+
centred spinner it projects), `debounce` click throttling and the `gogClick` output, none of which
|
|
1037
|
+
a bare element can provide. `[gogButton]` when the element must be a link, or when you need to
|
|
1038
|
+
keep directives of your own on it — `routerLink`, `href`, `target`, `download`, `type="submit"`
|
|
1039
|
+
and anything else keep working because they were never brokered through an input in the first
|
|
1040
|
+
place. That is also why the library still has no `@angular/router` dependency.
|
|
1041
|
+
|
|
1042
|
+
Two things it deliberately does not do: no `disabled` on an `<a>` (there is no such thing — drop
|
|
1043
|
+
the `href` or render a real `<button>`), and no loading state (the spinner is a projected child a
|
|
1044
|
+
directive cannot add without taking over the element's content).
|
|
1045
|
+
|
|
1046
|
+
#### `[gogBadge]` — directive, not a component
|
|
1047
|
+
|
|
1048
|
+
Decorates an existing element (a button, an icon, an avatar) with a count/status dot — it never
|
|
1049
|
+
wraps its host.
|
|
1050
|
+
|
|
1051
|
+
| Input | Type | Default |
|
|
1052
|
+
| ---------------- | --------------------------------------------------------------------------- | -------------------------------- |
|
|
1053
|
+
| `gogBadge` | `string \| number \| null` | `null` — the content |
|
|
1054
|
+
| `badgePosition` | `GogBadgePosition` (`'top-end'\|'top-start'\|'bottom-end'\|'bottom-start'`) | `'top-end'` |
|
|
1055
|
+
| `badgeVariant` | `GogTagVariant` | `'danger'` |
|
|
1056
|
+
| `badgeDot` | `boolean` | `false` — bare dot, no text |
|
|
1057
|
+
| `badgeMax` | `number` | `99` — beyond this, renders `N+` |
|
|
1058
|
+
| `badgeHidden` | `boolean` | `false` |
|
|
1059
|
+
| `badgeAriaLabel` | `string` | `''` |
|
|
1060
|
+
|
|
1061
|
+
Renders **nothing** when the value is `0`, `null` or empty and `badgeDot` is off — "0" badges
|
|
1062
|
+
are impossible by design.
|
|
1063
|
+
|
|
1064
|
+
```html
|
|
1065
|
+
<gog-button gogBadge="12" badgeAriaLabel="12 unread">Inbox</gog-button>
|
|
1066
|
+
<gog-icon name="info" gogBadge badgeDot />
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
#### `gog-chip`
|
|
1070
|
+
|
|
1071
|
+
| Input | Type | Default |
|
|
1072
|
+
| ------------------------------ | ----------------------------------- | ---------------------------------------- |
|
|
1073
|
+
| `size` | `GogSize` | `'md'` |
|
|
1074
|
+
| `shape` | `GogTagShape` (`'rounded'\|'pill'`) | `'rounded'` |
|
|
1075
|
+
| `disabled`, `clickable` | `boolean` | `false`, `true` |
|
|
1076
|
+
| `selected` | `boolean \| null` (two-way) | `null` — see below |
|
|
1077
|
+
| `removable` | `boolean` | `false` |
|
|
1078
|
+
| `fullWidth` | `boolean` | `false` |
|
|
1079
|
+
| `ariaLabel`, `removeAriaLabel` | `string` | `''`, `'Remove chip'` |
|
|
1080
|
+
| `avatarUrl`, `avatarAlt` | `string \| null` / `string` | `null`, `''` |
|
|
1081
|
+
| `iconName` | `GogIconName \| null` | `null` |
|
|
1082
|
+
| `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
|
|
1083
|
+
|
|
1084
|
+
Outputs: `gogClick: MouseEvent | KeyboardEvent`, `gogRemove: void`.
|
|
1085
|
+
|
|
1086
|
+
```html
|
|
1087
|
+
<gog-chip [avatarUrl]="user.photo" [removable]="true" (gogRemove)="removeUser(user)"
|
|
1088
|
+
>{{ user.name }}</gog-chip
|
|
1089
|
+
>
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
**`selected` makes it a filter chip** (21.9.0) — a chip you toggle on and off rather than press.
|
|
1093
|
+
It is tri-state, and `null` is the default so nothing about an existing chip changes: no
|
|
1094
|
+
`aria-pressed`, no selected look, activation only emits `gogClick`. Set it to `false` and the chip
|
|
1095
|
+
is a toggle that is off (`aria-pressed="false"` — a chip with no `aria-pressed` at all is not a
|
|
1096
|
+
toggle to a screen reader, so "off" has to be stated); `true` and it is on, which draws an inset
|
|
1097
|
+
ring from `--gog-chip-selected-shadow`. A ring rather than a fill because `:hover` and `:active`
|
|
1098
|
+
already own the chip's background and the selection has to survive both.
|
|
1099
|
+
|
|
1100
|
+
It is a two-way `model`, so the chip flips it on click, Enter and Space — a row of filters needs
|
|
1101
|
+
no click handler:
|
|
1102
|
+
|
|
1103
|
+
```html
|
|
1104
|
+
@for (f of filters; track f.label) {
|
|
1105
|
+
<gog-chip [(selected)]="f.on">{{ f.label }}</gog-chip>
|
|
1106
|
+
}
|
|
1107
|
+
```
|
|
1108
|
+
|
|
1109
|
+
`gogClick` still fires, **after** the flip, so a handler reading `selected()` sees the new value.
|
|
1110
|
+
Drive the state from that handler instead and you want a one-way `[selected]`, or the two writes
|
|
1111
|
+
cancel out. A `disabled` chip keeps the ring but drops `aria-pressed`, which needs the
|
|
1112
|
+
`role="button"` a disabled chip does not carry — "selected, and currently unavailable" is a real
|
|
1113
|
+
state and hiding it would leave it announced and invisible.
|
|
1114
|
+
|
|
1115
|
+
#### `gog-tag`
|
|
1116
|
+
|
|
1117
|
+
| Input | Type | Default |
|
|
1118
|
+
| ----------- | --------------------- | ----------- |
|
|
1119
|
+
| `variant` | `GogTagVariant` | `'info'` |
|
|
1120
|
+
| `size` | `GogSize` | `'md'` |
|
|
1121
|
+
| `shape` | `GogTagShape` | `'rounded'` |
|
|
1122
|
+
| `iconName` | `GogIconName \| null` | `null` |
|
|
1123
|
+
| `fullWidth` | `boolean` | `false` |
|
|
1124
|
+
|
|
1125
|
+
Slot: `<ng-template gogTagIcon>` for custom icon markup.
|
|
1126
|
+
|
|
1127
|
+
```html
|
|
1128
|
+
<gog-tag variant="success">Active</gog-tag>
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
#### `gog-spinner` / `gog-spinner-overlay`
|
|
1132
|
+
|
|
1133
|
+
| Input | Type | Default |
|
|
1134
|
+
| -------------------------------- | ------------------------------------------------- | ------------------------------------------- |
|
|
1135
|
+
| `size` | `GogSize` | `'md'` |
|
|
1136
|
+
| `variant` | `GogSpinnerVariant` (`'runic'\|'ring'\|'custom'`) | unset — see below |
|
|
1137
|
+
| `ariaLabel` | `string` | `'Loading'` |
|
|
1138
|
+
| `overlay` (spinner only) | `boolean` | `false` |
|
|
1139
|
+
| `loading` (spinner-overlay only) | `boolean` | `false` — toggles the overlay + `aria-busy` |
|
|
1140
|
+
|
|
1141
|
+
`variant="custom"` renders your own projected markup, still inheriting the size wrapper and
|
|
1142
|
+
`--gog-spinner-color` theming.
|
|
1143
|
+
|
|
1144
|
+
**To replace the spinner everywhere at once, pass a component to `GOG_CONFIG`** — including the
|
|
1145
|
+
three places you cannot reach with an input: `gog-button`'s and `gog-autocomplete`'s loading
|
|
1146
|
+
states, and the spinner `gog-table` draws in place of its rows.
|
|
1147
|
+
|
|
1148
|
+
```ts
|
|
1149
|
+
provideGogConfig({ spinner: { component: HouseLoaderComponent } });
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
It renders inside the same size wrapper as the built-ins, so it keeps the sizing, the overlay
|
|
1153
|
+
behaviour, `role="status"` and the accessible name — only the visual is yours. An instance's own
|
|
1154
|
+
`variant` still wins over it, so `<gog-spinner variant="ring">` is a ring in an app that has set
|
|
1155
|
+
a component: a default does not overrule something asked for explicitly.
|
|
1156
|
+
|
|
1157
|
+
**Neither component's `variant` has a default value**, and on `gog-spinner-overlay` that is the
|
|
1158
|
+
whole of the 21.10.0 fix: the overlay forwards its `variant` to the spinner it wraps, so a default
|
|
1159
|
+
there would have been an instance overruling the config on every overlay ever rendered — which is
|
|
1160
|
+
exactly what happened before, leaving the one spinner that covers a whole region on the built-in
|
|
1161
|
+
look while every other spinner in the app was the house one. `size` and `ariaLabel` keep their
|
|
1162
|
+
defaults: neither has a config key to fall through to.
|
|
1163
|
+
|
|
1164
|
+
```html
|
|
1165
|
+
<gog-spinner-overlay [loading]="isLoading()">
|
|
1166
|
+
<app-content-that-loads />
|
|
1167
|
+
</gog-spinner-overlay>
|
|
1168
|
+
```
|
|
1169
|
+
|
|
1170
|
+
#### `gog-skeleton`
|
|
1171
|
+
|
|
1172
|
+
| Input | Type | Default |
|
|
1173
|
+
| ----------------- | -------------------------------------------------- | ---------------------------------------------------- |
|
|
1174
|
+
| `shape` | `GogSkeletonShape` (`'text'\|'circle'\|'rect'`) | `'text'` |
|
|
1175
|
+
| `size` | `GogSize` | `'md'` |
|
|
1176
|
+
| `animation` | `GogSkeletonAnimation` (`'pulse'\|'wave'\|'none'`) | `'pulse'` |
|
|
1177
|
+
| `width`, `height` | `string \| null` | `null` |
|
|
1178
|
+
| `lines` | `number` | `1` — `shape="text"` only, last line renders shorter |
|
|
1179
|
+
| `rounded` | `boolean` | `true` |
|
|
1180
|
+
| `ariaLabel` | `string \| null` | `null` — decorative (no `role`) unless set |
|
|
1181
|
+
|
|
1182
|
+
```html
|
|
1183
|
+
<gog-skeleton shape="text" [lines]="3" /> <gog-skeleton shape="circle" width="48px" />
|
|
1184
|
+
```
|
|
1185
|
+
|
|
1186
|
+
#### `gog-progressbar`
|
|
1187
|
+
|
|
1188
|
+
| Input | Type | Default |
|
|
1189
|
+
| ----------------- | ---------------------------------------------------------------------------- | --------------- |
|
|
1190
|
+
| `value`, `buffer` | `number` (0–100, clamped) | `0` |
|
|
1191
|
+
| `mode` | `GogProgressbarMode` (`'determinate'\|'indeterminate'\|'buffer'`) | `'determinate'` |
|
|
1192
|
+
| `variant` | `GogProgressbarVariant` (`'accent'\|'success'\|'danger'\|'warning'\|'info'`) | `'accent'` |
|
|
1193
|
+
| `size` | `GogSize` | `'md'` |
|
|
1194
|
+
| `showValue` | `boolean` | `false` |
|
|
1195
|
+
| `ariaLabel` | `string` | `''` |
|
|
1196
|
+
|
|
1197
|
+
```html
|
|
1198
|
+
<gog-progressbar mode="indeterminate" ariaLabel="Loading" />
|
|
1199
|
+
<gog-progressbar mode="buffer" [value]="42" [buffer]="70" />
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
**The fill's end is marked by two hairlines** (21.10.0), `--gog-progressbar-edge-color` over
|
|
1203
|
+
`--gog-progressbar-edge-backing-color`, each `--gog-progressbar-edge-width` wide. That boundary is
|
|
1204
|
+
the value — `showValue` is off by default — and the fill and the track cannot carry it themselves:
|
|
1205
|
+
in every shipped theme the five fills straddle mid-luminance, so no one track colour clears WCAG
|
|
1206
|
+
1.4.11's 3:1 against all of them. Two tones always do, and `check:contrast` gates the pair. Retint
|
|
1207
|
+
them per theme if you like; keep them a *pair* whose tones sit on opposite sides of the middle, or
|
|
1208
|
+
the marker disappears on whichever fill it happens to match.
|
|
1209
|
+
|
|
1210
|
+
#### `gog-divider`
|
|
1211
|
+
|
|
1212
|
+
| Input | Type | Default |
|
|
1213
|
+
| ------------- | --------------------------------------------------- | -------------- |
|
|
1214
|
+
| `orientation` | `GogOrientation` | `'horizontal'` |
|
|
1215
|
+
| `variant` | `GogDividerVariant` (`'solid'\|'dashed'\|'dotted'`) | `'solid'` |
|
|
1216
|
+
| `inset` | `boolean` | `false` |
|
|
1217
|
+
|
|
1218
|
+
Label is projected content, not an input — put an icon or a `gog-tag` inside it if needed.
|
|
1219
|
+
|
|
1220
|
+
```html
|
|
1221
|
+
<gog-divider>OR</gog-divider>
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
#### `gogRipple` — directive, not a component
|
|
1225
|
+
|
|
1226
|
+
A pointer-position wash that grows from where you pressed and fades when you let go. Drop it on
|
|
1227
|
+
any element you already have — it adds no wrapper and changes no layout.
|
|
1228
|
+
|
|
1229
|
+
| Input | Type | Default |
|
|
1230
|
+
| ---------------- | --------- | ----------------------------------------------------------- |
|
|
1231
|
+
| `rippleDisabled` | `boolean` | `false` |
|
|
1232
|
+
| `rippleCentred` | `boolean` | `false` — start from the middle instead of from the pointer |
|
|
1233
|
+
|
|
1234
|
+
```html
|
|
1235
|
+
<button gogRipple>Press me</button>
|
|
1236
|
+
<div gogRipple rippleCentred class="tile">A tile</div>
|
|
1237
|
+
```
|
|
1238
|
+
|
|
1239
|
+
Four things suppress it, none of which you have to wire up: `rippleDisabled`, a host carrying
|
|
1240
|
+
`disabled`, a host carrying `aria-disabled="true"`, and `prefers-reduced-motion: reduce` — the
|
|
1241
|
+
last one **suppressed outright, not shortened**. Keyboard activation (`Enter`/`Space`) is always
|
|
1242
|
+
centred, because a key press carries no coordinates.
|
|
1243
|
+
|
|
1244
|
+
**Put it on the element that paints the surface.** The wash lives in its own layer that clips
|
|
1245
|
+
itself — the host is never given `overflow: hidden`, so a `gogBadge` on the same element is not
|
|
1246
|
+
clipped — and that layer takes its corner radius from its host with `border-radius: inherit`. On a
|
|
1247
|
+
wrapper whose _child_ paints the rounded background, the layer inherits the wrapper's radius (very
|
|
1248
|
+
often `0`) and the wash squares off at the corners.
|
|
1249
|
+
|
|
1250
|
+
Tokens: `--gog-ripple-color` (`currentColor`, so the wash reads as the surface's own foreground on
|
|
1251
|
+
a filled surface and a ghost one alike), `--gog-ripple-opacity`, `--gog-ripple-enter-duration`,
|
|
1252
|
+
`--gog-ripple-exit-duration`, `--gog-ripple-easing`. All five are ordinary inherited custom
|
|
1253
|
+
properties, so setting one anywhere above the host is the per-instance override.
|
|
1254
|
+
|
|
1255
|
+
#### Turning the ripple on for the library's own components
|
|
1256
|
+
|
|
1257
|
+
You do **not** add `gogRipple` to a `gog-*` component: each one already owns the element that
|
|
1258
|
+
paints its surface, so it wires its own. What you do is switch it on, once:
|
|
1259
|
+
|
|
1260
|
+
```ts
|
|
1261
|
+
provideGogConfig({ ripple: { enabled: true } });
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
That covers `gog-button`, `[gogButton]`, `gog-button-toggle-group`, `gog-chip`, `gog-tabs`
|
|
1265
|
+
headers, `gog-accordion` headers, `gogCollapsibleTrigger`, `gogMenuItem`, and the options inside
|
|
1266
|
+
`gog-select` / `gog-multiselect` / `gog-autocomplete`. `gog-paginator` follows because its page
|
|
1267
|
+
buttons are `gog-button`s.
|
|
1268
|
+
|
|
1269
|
+
**Off by default**, so adding the ripple to the library changed the look of nothing. Every one of
|
|
1270
|
+
those takes a `ripple` input that beats the config in both directions: `[ripple]="false"` opts one
|
|
1271
|
+
control out of an app-wide on, `[ripple]="true"` opts one in without switching the app over.
|
|
1272
|
+
|
|
1273
|
+
Not covered, and deliberately: `gog-table` rows and `gogCardLink`. A row and a card are hundreds
|
|
1274
|
+
of pixels wide, so the wave has to travel the whole surface and reads as a flash rather than as
|
|
1275
|
+
feedback at the point you pressed — and a table renders one directive per row, with no
|
|
1276
|
+
virtualization in this library yet. Put `gogRipple` on them yourself if you disagree.
|
|
1277
|
+
|
|
1278
|
+
A chip that is not `clickable`, or is `disabled`, never ripples whatever the config says: a label
|
|
1279
|
+
answering a press is a promise it cannot keep.
|
|
1280
|
+
|
|
1281
|
+
#### `gogTooltip` — directive, not a component
|
|
1282
|
+
|
|
1283
|
+
Drop on any element — a `gog-*` component's host tag or a plain native one.
|
|
1284
|
+
|
|
1285
|
+
| Input | Type | Default |
|
|
1286
|
+
| --------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
1287
|
+
| `gogTooltip` | `string \| TemplateRef \| null` | `null` — content |
|
|
1288
|
+
| `gogTooltipPosition` | `GogTooltipPosition` (`'auto'\|'top'\|'bottom'\|'left'\|'right'`) | `'auto'`; via `GOG_CONFIG.tooltip.position` |
|
|
1289
|
+
| `gogTooltipShowDelay` | `number \| undefined` | `300`; via `GOG_CONFIG.tooltip.showDelay` |
|
|
1290
|
+
| `gogTooltipHideDelay` | `number \| undefined` | `100`; via `GOG_CONFIG.tooltip.hideDelay` |
|
|
1291
|
+
| `gogTooltipDisabled` | `boolean` | `false` |
|
|
1292
|
+
| `gogTooltipClass` | `string` | `''` — class on the bubble itself, since it's portaled to `<body>` |
|
|
1293
|
+
|
|
1294
|
+
```html
|
|
1295
|
+
<button gogTooltip="Save changes">💾</button> <gog-chip [gogTooltip]="hintTemplate">Beta</gog-chip>
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
### Layout & navigation
|
|
1299
|
+
|
|
1300
|
+
#### `gog-accordion`
|
|
1301
|
+
|
|
1302
|
+
| Input | Type | Default |
|
|
1303
|
+
| --------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
1304
|
+
| `items` | `GogAccordionItem[]` (`{ id, title, disabled?, [key: string]: unknown }`) | `[]` |
|
|
1305
|
+
| `size` | `GogSize` | `'lg'` (not `'md'` — see conventions) |
|
|
1306
|
+
| `expandFirst`, `multi`, `loading` | `boolean` | `false` |
|
|
1307
|
+
| `skeletonCount` | `number` | `3` — rows shown while `loading` and `items` is still empty |
|
|
1308
|
+
| `showChevron` | `boolean` | `true` |
|
|
1309
|
+
| `headingLevel` | `2\|3\|4\|5\|6 \| undefined` | `undefined` — wraps headers in `role="heading"` when set |
|
|
1310
|
+
| `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
|
|
1311
|
+
|
|
1312
|
+
Model: `openIds: ReadonlySet<string | number>`. Output: `gogToggle: { item, open }`.
|
|
1313
|
+
|
|
1314
|
+
Slots: `<ng-template gogAccordionHeader let-item let-open="open">`,
|
|
1315
|
+
`<ng-template gogAccordionContent let-item>`, `<ng-template gogAccordionChevron let-item let-open="open">`.
|
|
1316
|
+
This is the library's canonical example of the slot pattern — copy its shape for anything similar.
|
|
1317
|
+
|
|
1318
|
+
```html
|
|
1319
|
+
<gog-accordion [items]="faqItems" [multi]="true">
|
|
1320
|
+
<ng-template gogAccordionContent let-item>{{ item.answer }}</ng-template>
|
|
1321
|
+
</gog-accordion>
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
#### `gog-collapsible` + `gogCollapsibleTrigger` / `gogCollapsibleContent`
|
|
1325
|
+
|
|
1326
|
+
**Headless primitive** — owns no markup at all, just open/close state plus two attribute
|
|
1327
|
+
directives you place on your own elements. Use this when `gog-accordion`'s opinionated markup
|
|
1328
|
+
doesn't fit (e.g. a sidebar nav group).
|
|
1329
|
+
|
|
1330
|
+
| Input (on `gog-collapsible`) | Type | Default |
|
|
1331
|
+
| ---------------------------- | --------- | ---------------------------------------------------------- |
|
|
1332
|
+
| `disabled` | `boolean` | `false` |
|
|
1333
|
+
| `collapseOnFocusOut` | `boolean` | `false` — close once focus leaves both trigger and content |
|
|
1334
|
+
|
|
1335
|
+
Model: `open: boolean`.
|
|
1336
|
+
|
|
1337
|
+
```html
|
|
1338
|
+
<gog-collapsible [(open)]="isOpen">
|
|
1339
|
+
<button gogCollapsibleTrigger>Advanced options</button>
|
|
1340
|
+
<div gogCollapsibleContent>
|
|
1341
|
+
<!-- any markup -->
|
|
1342
|
+
</div>
|
|
1343
|
+
</gog-collapsible>
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
**The trigger can be any element.** On a `<button>` or `<a href>` the directive adds only the
|
|
1347
|
+
ARIA wiring, because the browser already handles focus and keys. On anything else — a `<div>`, a
|
|
1348
|
+
`<span>` — it also supplies `role="button"`, `tabindex="0"` and Enter/Space, so the control it
|
|
1349
|
+
announces is one a keyboard can actually reach. If you set `role` or `tabindex` yourself, the
|
|
1350
|
+
directive leaves both alone: you have said what the element is.
|
|
1351
|
+
|
|
1352
|
+
`gogCollapsibleTrigger` takes a **`ripple`** input of its own (`boolean | undefined`, `false`, via
|
|
1353
|
+
`GOG_CONFIG.ripple.enabled`) — the trigger is your element, but the directive owns the ripple so
|
|
1354
|
+
you do not have to add `gogRipple` beside it.
|
|
1355
|
+
|
|
1356
|
+
An open panel is as tall as its content — `--gog-collapsible-max-height` defaults to
|
|
1357
|
+
`max-content`. Set it to a length on an instance to cap one deliberately; the panel is
|
|
1358
|
+
`overflow: hidden`, so a cap **clips** rather than scrolls. (Before 21.4.4 that default was
|
|
1359
|
+
`480px`, which clipped taller panels silently.)
|
|
1360
|
+
|
|
1361
|
+
#### `gog-tabs` + `gog-tab`
|
|
1362
|
+
|
|
1363
|
+
| Input (on `gog-tabs`) | Type | Default |
|
|
1364
|
+
| ------------------------ | ------------------------------------------------------ | ---------------------------------------------------- |
|
|
1365
|
+
| `align` | `GogTabsAlign` (`'start'\|'center'\|'end'\|'stretch'`) | `'start'` |
|
|
1366
|
+
| `orientation` | `GogOrientation` | `'horizontal'` |
|
|
1367
|
+
| `size` | `GogSize` | `'md'` |
|
|
1368
|
+
| `fullWidth`, `ariaLabel` | | `false`, `''` |
|
|
1369
|
+
| `scrollActiveIntoView` | `boolean` | `true` |
|
|
1370
|
+
| `showScrollTrack` | `boolean \| undefined` | follows `scrollActiveIntoView` (hidden when it's on) |
|
|
1371
|
+
| `ripple` | `boolean \| undefined` | `false`; via `GOG_CONFIG.ripple.enabled` |
|
|
1372
|
+
|
|
1373
|
+
Model: `activeIndex: number`. Output: `gogTabChange: number`.
|
|
1374
|
+
|
|
1375
|
+
| Input (on `gog-tab`) | Type | Default |
|
|
1376
|
+
| -------------------- | --------------------- | ------- |
|
|
1377
|
+
| `label` | `string` | `''` |
|
|
1378
|
+
| `iconName` | `GogIconName \| null` | `null` |
|
|
1379
|
+
| `disabled` | `boolean` | `false` |
|
|
1380
|
+
|
|
1381
|
+
Slots: `<ng-template gogTabHeader let-tab let-active="active">` on `gog-tabs` for custom header
|
|
1382
|
+
markup; `<ng-template gogTabContent>` **inside** a `gog-tab` to make that tab's content **lazy**
|
|
1383
|
+
(built on first activation, then kept alive) instead of the default (rendered immediately,
|
|
1384
|
+
hidden via `[hidden]` while inactive — preserves scroll/input state).
|
|
1385
|
+
|
|
1386
|
+
```html
|
|
1387
|
+
<gog-tabs [(activeIndex)]="tabIndex">
|
|
1388
|
+
<gog-tab label="Profile"><app-profile /></gog-tab>
|
|
1389
|
+
<gog-tab label="Report" iconName="info">
|
|
1390
|
+
<ng-template gogTabContent><app-expensive-report /></ng-template>
|
|
1391
|
+
</gog-tab>
|
|
1392
|
+
</gog-tabs>
|
|
1393
|
+
```
|
|
1394
|
+
|
|
1395
|
+
#### `gog-card` + `gogCardHeader` / `gogCardMedia` / `gogCardFooter` / `gogCardLink`
|
|
1396
|
+
|
|
1397
|
+
A surface for one self-contained thing — a product tile, a summary, a search result.
|
|
1398
|
+
|
|
1399
|
+
| Input | Type | Default |
|
|
1400
|
+
| --------------- | -------------------------------------------------------- | --------------------------------------- |
|
|
1401
|
+
| `variant` | `GogSurfaceVariant` (`'outlined'\|'elevated'\|'filled'`) | `'outlined'` |
|
|
1402
|
+
| `size` | `GogSize` | `'md'` — drives padding and the row gap |
|
|
1403
|
+
| `disabled` | `boolean` (bare attribute works) | `false` |
|
|
1404
|
+
| `loading` | `boolean` (bare attribute works) | `false` |
|
|
1405
|
+
| `skeletonLines` | `number` | `2` — body lines shown while `loading` |
|
|
1406
|
+
|
|
1407
|
+
No outputs. Slots, all **attribute** directives on your own elements (not `ng-template`):
|
|
1408
|
+
`gogCardHeader`, `gogCardMedia`, `gogCardFooter`, `gogCardLink`. Layout order is fixed by the
|
|
1409
|
+
component — media, heading, body (the default slot), footer — not by the order you write them.
|
|
1410
|
+
|
|
1411
|
+
```html
|
|
1412
|
+
<gog-card>
|
|
1413
|
+
<img gogCardMedia [src]="person.photo" alt="" />
|
|
1414
|
+
<h3 gogCardHeader><a gogCardLink [routerLink]="['/people', person.id]">{{ person.name }}</a></h3>
|
|
1415
|
+
<p>{{ person.role }}</p>
|
|
1416
|
+
<div gogCardFooter>
|
|
1417
|
+
<gog-button size="xsm" (gogClick)="shortlist(person)">Shortlist</gog-button>
|
|
1418
|
+
</div>
|
|
1419
|
+
</gog-card>
|
|
1420
|
+
```
|
|
1421
|
+
|
|
1422
|
+
- **`gogCardHeader` names the card.** The card reads that element's `id` (minting one if it has
|
|
1423
|
+
none) and points its own `aria-labelledby` at it, with `role="group"`. A card with no header
|
|
1424
|
+
gets neither — an unnamed group is noise, not structure. The heading level is yours; the visual
|
|
1425
|
+
size comes from `--gog-card-heading-font-size` regardless of it.
|
|
1426
|
+
- **There is no `interactive` input, and no `gogClick` output.** A card becomes interactive by
|
|
1427
|
+
_containing_ a `gogCardLink`, which stretches that link's hit area over the whole surface. The
|
|
1428
|
+
link stays yours: `routerLink`, `href`, `target`, middle-click, "open in new tab" and Enter all
|
|
1429
|
+
behave normally, and the focus ring is drawn around the card. `gogCardLink` only applies to
|
|
1430
|
+
`<a>` and `<button>` — on a `<div>` it does nothing, deliberately.
|
|
1431
|
+
- **Other controls inside an interactive card still get their own clicks.** A footer button, a
|
|
1432
|
+
checkbox, a second link: each sits above the stretched hit area automatically.
|
|
1433
|
+
- Two costs of the pattern, inherent to it: text in the card cannot be selected by dragging, and
|
|
1434
|
+
a second link is reachable by keyboard but not by clicking the surface around it.
|
|
1435
|
+
- **`loading`** replaces the content with a title bar plus `skeletonLines` text lines and sets
|
|
1436
|
+
`aria-busy`; **`disabled`** dims the card, sets `aria-disabled`, and takes the card link out of
|
|
1437
|
+
the tab order. Both make the link non-clickable. For a _refresh_ of a card that already has
|
|
1438
|
+
content, project a `gog-spinner-overlay` instead — `loading` is the first-paint treatment.
|
|
1439
|
+
- `gogCardMedia` runs full-bleed to the card's edges, and rounds into its top corners when it is
|
|
1440
|
+
the first element in the card.
|
|
1441
|
+
|
|
1442
|
+
#### `gog-panel` + `gogPanelHeader` / `gogPanelFooter`
|
|
1443
|
+
|
|
1444
|
+
A titled region of a page — a settings section, a dashboard area, a form group.
|
|
1445
|
+
|
|
1446
|
+
| Input | Type | Default |
|
|
1447
|
+
| --------------- | -------------------------------- | ------------ |
|
|
1448
|
+
| `variant` | `GogSurfaceVariant` | `'elevated'` |
|
|
1449
|
+
| `size` | `GogSize` | `'lg'` |
|
|
1450
|
+
| `collapsible` | `boolean` (bare attribute works) | `false` |
|
|
1451
|
+
| `disabled` | `boolean` (bare attribute works) | `false` |
|
|
1452
|
+
| `loading` | `boolean` (bare attribute works) | `false` |
|
|
1453
|
+
| `skeletonLines` | `number` | `3` |
|
|
1454
|
+
|
|
1455
|
+
Model: `open: boolean` (default `true`, ignored while `collapsible` is off). No outputs beyond
|
|
1456
|
+
`openChange`. Slots: `gogPanelHeader`, `gogPanelFooter` — attribute directives on your elements.
|
|
1457
|
+
|
|
1458
|
+
```html
|
|
1459
|
+
<gog-panel [collapsible]="true" [(open)]="notificationsOpen">
|
|
1460
|
+
<h2 gogPanelHeader>Notifications</h2>
|
|
1461
|
+
<gog-checkbox label="Email digest" [(checked)]="emailDigest" />
|
|
1462
|
+
<div gogPanelFooter><gog-button size="xsm">Save</gog-button></div>
|
|
1463
|
+
</gog-panel>
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
- **It is a landmark.** With a `gogPanelHeader` it renders `role="region"` named by that heading —
|
|
1467
|
+
which is why the panel gets one and `gog-card` gets `role="group"`: a handful of named regions
|
|
1468
|
+
is how a page is navigated, a landmark per card would bury that list.
|
|
1469
|
+
- **Collapsing composes `gog-collapsible`**, so the state, the id wiring and the animation are the
|
|
1470
|
+
library's existing ones. The heading stays a heading: the toggle is a separate `<button>` named
|
|
1471
|
+
by it through `aria-labelledby`, with its hit area stretched across the header row so clicking
|
|
1472
|
+
the title works for the pointer. Without a header the toggle falls back to
|
|
1473
|
+
`GOG_CONFIG.labels.togglePanel` (default `'Toggle section'`).
|
|
1474
|
+
- **A non-collapsible panel does not clip.** It undoes the collapse geometry it inherits,
|
|
1475
|
+
`overflow` included, so a dropdown or menu opened inside it escapes the panel's box. A
|
|
1476
|
+
_collapsible_ one does clip while animating, exactly like `gog-collapsible` — prefer
|
|
1477
|
+
`[appendToBody]` for an overlay inside one.
|
|
1478
|
+
- **`loading` keeps the heading and the footer** and replaces only the body: a page section is
|
|
1479
|
+
titled before its content arrives, and blanking the title would move the layout twice.
|
|
1480
|
+
- **The surface is never itself a link** — there is no `gogPanelLink`. Controls live inside a
|
|
1481
|
+
panel, and a region that is a link cannot hold them. Use `gog-card` for that.
|
|
1482
|
+
|
|
1483
|
+
#### `gog-paginator`
|
|
1484
|
+
|
|
1485
|
+
| Input | Type | Default |
|
|
1486
|
+
| ------------------------------- | ------------------------------------------------ | -------------------------------------------------- |
|
|
1487
|
+
| `fullWidth`, `totalPages` | `boolean`, `number` | `true`, `1` |
|
|
1488
|
+
| `rangeMode` | `GogPaginatorRangeMode` (`'window'\|'ellipsis'`) | `'window'` — see note |
|
|
1489
|
+
| `visiblePages` | `number` | `5` — `'window'` mode only |
|
|
1490
|
+
| `showFirstPage`, `showLastPage` | `boolean` | `false` — `'window'` mode only |
|
|
1491
|
+
| `siblingCount` | `number` | `2` — `'ellipsis'` mode only |
|
|
1492
|
+
| `size` | `GogSize` | `'sm'` |
|
|
1493
|
+
| `disabled`, `ariaLabel` | | `false`, `'Pagination'` |
|
|
1494
|
+
| `totalRecords` | `number \| null` | `null` — see below |
|
|
1495
|
+
| `pageSize` | `model<number>` | `10` — two-way bindable |
|
|
1496
|
+
| `showPageSizeSelect` | `boolean \| undefined` | `false`; via `GOG_CONFIG.paginator` |
|
|
1497
|
+
| `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; via `GOG_CONFIG.paginator` |
|
|
1498
|
+
|
|
1499
|
+
The step buttons (`'Previous page'`/`'Next page'`) and the per-page names are configured, not
|
|
1500
|
+
input-driven: `GOG_CONFIG.labels.previousPage`/`nextPage`, and `labels.page`, a
|
|
1501
|
+
`(page: number, isCurrent: boolean) => string` formatter defaulting to
|
|
1502
|
+
`` `Page ${page}, current page` `` / `` `Go to page ${page}` ``.
|
|
1503
|
+
|
|
1504
|
+
Models: `page: number` (1-based, self-clamps) and `pageSize: number`.
|
|
1505
|
+
|
|
1506
|
+
**Give it `totalRecords` instead of `totalPages` when you know the row count** — it then derives
|
|
1507
|
+
the page count from `pageSize` itself, which is what removes the
|
|
1508
|
+
`computed(() => Math.ceil(total / size))` a consumer would otherwise have to write _and_ keep in
|
|
1509
|
+
sync with the rows-per-page select:
|
|
1510
|
+
|
|
1511
|
+
```html
|
|
1512
|
+
<gog-paginator
|
|
1513
|
+
[(page)]="page"
|
|
1514
|
+
[(pageSize)]="size"
|
|
1515
|
+
[totalRecords]="items().length"
|
|
1516
|
+
[showPageSizeSelect]="true"
|
|
1517
|
+
/>
|
|
1518
|
+
```
|
|
1519
|
+
|
|
1520
|
+
`totalPages` still works and is the right input when the server tells you a page count directly;
|
|
1521
|
+
`totalRecords` wins when both are set. Changing the page size always returns to page 1 — "page 5"
|
|
1522
|
+
of 10-row pages is not "page 5" of 50-row ones, so clamping alone would leave the user somewhere
|
|
1523
|
+
they never asked to be.
|
|
1524
|
+
|
|
1525
|
+
`'window'`: a fixed number of page buttons that slides to keep the current page centered.
|
|
1526
|
+
`'ellipsis'`: first/last pinned, `siblingCount` around the current page, "…" fills the gap
|
|
1527
|
+
(what `gog-table`'s built-in pagination uses).
|
|
1528
|
+
|
|
1529
|
+
```html
|
|
1530
|
+
<gog-paginator [(page)]="page" [totalPages]="totalPages" />
|
|
1531
|
+
```
|
|
1532
|
+
|
|
1533
|
+
#### `gog-table<T>`
|
|
1534
|
+
|
|
1535
|
+
| Input | Type | Default |
|
|
1536
|
+
| ----------------------------- | ----------------------------- | ----------------------------------- |
|
|
1537
|
+
| `value` | `T[]` | `[]` |
|
|
1538
|
+
| `fullWidth` | `boolean` | `true` |
|
|
1539
|
+
| `pageSize` | `model<number>` | `0` (no pagination) — two-way |
|
|
1540
|
+
| `showPageSizeSelect` | `boolean \| undefined` | `false`; forwarded to the paginator |
|
|
1541
|
+
| `pageSizeOptions` | `number[] \| undefined` | `[10, 20, 30, 40, 50]`; forwarded |
|
|
1542
|
+
| `showRowNumbers`, `showTotal` | `boolean` | `true`, `false` |
|
|
1543
|
+
| `emptyPlaceholder` | `string` | `'-'` |
|
|
1544
|
+
| `paginatorPosition` | `'left'\|'center'\|'right'` | `'center'` |
|
|
1545
|
+
| `totalPosition` | `'left'\|'right'\|'opposite'` | `'opposite'` |
|
|
1546
|
+
| `loading` | `boolean` | `false` |
|
|
1547
|
+
| `showColumnBorders` | `boolean` | `false` |
|
|
1548
|
+
| `stickyHeader` | `boolean` | `false` — pair with `maxHeight` |
|
|
1549
|
+
| `maxHeight` | `string \| null` | `null` — any CSS length |
|
|
1550
|
+
| `size` | `GogSize` | `'lg'` (row density — not `'md'`) |
|
|
1551
|
+
| `lazy` | `boolean` | `false` — see below |
|
|
1552
|
+
| `totalRecords` | `number \| null` | `null` — `lazy` only |
|
|
1553
|
+
| `selectionMode` | `GogTableSelectionMode` | `'none'` |
|
|
1554
|
+
| `selection` | `model<T[]>` | `[]` — two-way bindable |
|
|
1555
|
+
| `dataKey` | `string` | `''` — row identity field |
|
|
1556
|
+
| `showSelectionColumn` | `boolean` | `true` (once selection is on) |
|
|
1557
|
+
| `interactiveRows` | `boolean` | `false` |
|
|
1558
|
+
|
|
1559
|
+
Outputs: `gogSortChange: GogTableSortEvent` (`{ field, direction }`, `{ field: '', direction:
|
|
1560
|
+
null }` when the third click clears it), `gogPageChange: number` (1-based; **does not fire** on
|
|
1561
|
+
first render, nor for the page reset a new sort causes — that reset belongs to the sort),
|
|
1562
|
+
`gogRowClick: GogTableRowClickEvent<T>` (`{ row, index, originalEvent }`).
|
|
1563
|
+
|
|
1564
|
+
**`fullWidth` also picks the layout algorithm.** Left at its default the table is `100%` wide with
|
|
1565
|
+
`table-layout: fixed`; since 21.6.0 `[fullWidth]="false"` makes it `fit-content` with
|
|
1566
|
+
`table-layout: auto`, so the columns are measured against their content instead of splitting the
|
|
1567
|
+
total evenly. Before 21.6.0 that split clipped the widest header, and a `width` on the column was
|
|
1568
|
+
the workaround — under auto layout a stated `width` is a suggestion weighed against content
|
|
1569
|
+
rather than a hard split, so those can usually go.
|
|
1570
|
+
|
|
1571
|
+
**`stickyHeader` needs `maxHeight`** (both since 21.6.0 for the pairing). A sticky element
|
|
1572
|
+
resolves against its nearest scroll container, and the table wraps itself in a `gog-scroll`;
|
|
1573
|
+
once that scroller moves on either axis it is a scroll container on _both_, because CSS coerces
|
|
1574
|
+
`overflow-y: visible` to `auto` beside a scrolling `overflow-x` (and `clip` to `hidden`). So the
|
|
1575
|
+
header can only ever stick to something inside the table — and without `maxHeight` that viewport
|
|
1576
|
+
is exactly as tall as its content and never scrolls, so there is nothing to stick to.
|
|
1577
|
+
|
|
1578
|
+
```html
|
|
1579
|
+
<gog-table [value]="rows" maxHeight="260px" [stickyHeader]="true">…</gog-table>
|
|
1580
|
+
```
|
|
1581
|
+
|
|
1582
|
+
`maxHeight` takes any CSS length and is what makes the table own its vertical scrolling. Left
|
|
1583
|
+
`null`, the table grows to its content and an ancestor scrolls it — the header then follows that
|
|
1584
|
+
ancestor's scroll like everything else, which is the pre-21.6.0 behaviour and is fine as long as
|
|
1585
|
+
you are not asking for a sticky header.
|
|
1586
|
+
|
|
1587
|
+
Columns are declared as **projected `gog-column` children**, not an input array:
|
|
1588
|
+
|
|
1589
|
+
```html
|
|
1590
|
+
<gog-table [value]="rows">
|
|
1591
|
+
<gog-column field="name" header="Name" sortable="true" />
|
|
1592
|
+
<gog-column field="email" header="Email" />
|
|
1593
|
+
<gog-column field="status" header="Status">
|
|
1594
|
+
<ng-template gogColumnBody let-row let-value="value">
|
|
1595
|
+
<gog-tag [variant]="row.active ? 'success' : 'danger'">{{ value }}</gog-tag>
|
|
1596
|
+
</ng-template>
|
|
1597
|
+
</gog-column>
|
|
1598
|
+
</gog-table>
|
|
1599
|
+
```
|
|
1600
|
+
|
|
1601
|
+
##### Server-driven tables — `lazy`
|
|
1602
|
+
|
|
1603
|
+
By default the table owns the whole data set: it sorts `value` and slices the page itself. With
|
|
1604
|
+
`[lazy]="true"` it does neither — `value` **is** the current page, already sorted, and the table
|
|
1605
|
+
renders it untouched. Supply `totalRecords` (without it the table cannot know how many pages
|
|
1606
|
+
exist, so pagination stays hidden and it warns in dev), then refetch from the two outputs:
|
|
1607
|
+
|
|
1608
|
+
```html
|
|
1609
|
+
<gog-table
|
|
1610
|
+
[value]="page()"
|
|
1611
|
+
[lazy]="true"
|
|
1612
|
+
[totalRecords]="total()"
|
|
1613
|
+
[pageSize]="20"
|
|
1614
|
+
[loading]="loading()"
|
|
1615
|
+
dataKey="id"
|
|
1616
|
+
(gogSortChange)="sort.set($event); reload()"
|
|
1617
|
+
(gogPageChange)="pageNumber.set($event); reload()"
|
|
1618
|
+
></gog-table>
|
|
1619
|
+
```
|
|
1620
|
+
|
|
1621
|
+
Row numbers still count from the current page (`(page - 1) * pageSize + i + 1`), and `showTotal`
|
|
1622
|
+
reports `totalRecords` rather than `value.length`. **Do not** sort or slice `value` yourself in
|
|
1623
|
+
addition — that is what the flag turns off.
|
|
1624
|
+
|
|
1625
|
+
##### Rows per page
|
|
1626
|
+
|
|
1627
|
+
`pageSize` is a **`model`**, not an input: `[pageSize]="20"` works exactly as before, and
|
|
1628
|
+
`[(pageSize)]="size"` becomes possible. That is what makes the rows-per-page select work with no
|
|
1629
|
+
wiring — the table binds its own model straight to the paginator's, the select writes back
|
|
1630
|
+
through it, and there is no intermediate signal to keep in sync in either direction.
|
|
1631
|
+
|
|
1632
|
+
```html
|
|
1633
|
+
<!-- off by default; turn it on per table, or app-wide via GOG_CONFIG.paginator -->
|
|
1634
|
+
<gog-table
|
|
1635
|
+
[value]="rows"
|
|
1636
|
+
[(pageSize)]="size"
|
|
1637
|
+
[showPageSizeSelect]="true"
|
|
1638
|
+
[pageSizeOptions]="[5, 10, 20]"
|
|
1639
|
+
></gog-table>
|
|
1640
|
+
```
|
|
1641
|
+
|
|
1642
|
+
Changing the size returns to page 1 and does **not** emit `gogPageChange` — the consumer already
|
|
1643
|
+
knows from `pageSizeChange`, and firing both would make a lazy table fetch twice. In `lazy` mode
|
|
1644
|
+
`pageSizeChange` is the refetch signal; bind `[pageSize]` + `(pageSizeChange)` rather than the
|
|
1645
|
+
banana-box if you need to act on it.
|
|
1646
|
+
|
|
1647
|
+
The footer stays visible at a single page whenever the select is on — hiding it would strand the
|
|
1648
|
+
user on whatever size produced that one page, with no control left to pick a smaller one.
|
|
1649
|
+
|
|
1650
|
+
##### Selection
|
|
1651
|
+
|
|
1652
|
+
`selectionMode` turns it on; `[(selection)]` is always a `T[]`, including in `'single'` mode
|
|
1653
|
+
where it holds zero or one row — one shape rather than a union to narrow on every read.
|
|
1654
|
+
|
|
1655
|
+
```html
|
|
1656
|
+
<gog-table
|
|
1657
|
+
[value]="rows"
|
|
1658
|
+
selectionMode="multiple"
|
|
1659
|
+
[(selection)]="selected"
|
|
1660
|
+
dataKey="id"
|
|
1661
|
+
></gog-table>
|
|
1662
|
+
```
|
|
1663
|
+
|
|
1664
|
+
- **Set `dataKey`.** Without it rows are matched by object identity, so any refetch that produces
|
|
1665
|
+
new objects silently drops the selection. It is also the `@for` track key, which is what lets
|
|
1666
|
+
the DOM survive a refetch instead of being rebuilt.
|
|
1667
|
+
- The checkbox column renders automatically (`showSelectionColumn` to turn it off, e.g. for a
|
|
1668
|
+
table that selects by row click — pair that with `interactiveRows`).
|
|
1669
|
+
- The header select-all appears only in `'multiple'` mode and covers **the current page**, never
|
|
1670
|
+
the whole data set: in `lazy` mode the table has never seen the other pages, and a control that
|
|
1671
|
+
behaved differently between the two modes would be worse than either.
|
|
1672
|
+
|
|
1673
|
+
##### Clickable rows
|
|
1674
|
+
|
|
1675
|
+
`gogRowClick` fires on a click regardless, but a `<tr>` is not focusable, so on its own that is a
|
|
1676
|
+
mouse-only affordance. `interactiveRows` makes rows focusable and styles them as clickable, and
|
|
1677
|
+
Enter/Space then activate the focused row. If the action is really "open this one thing", a link
|
|
1678
|
+
or button inside a cell is better than a whole-row target.
|
|
1679
|
+
|
|
1680
|
+
`gog-column` inputs: `field` (required, dot-paths ok), `header`, `sortable` (default `false`),
|
|
1681
|
+
`width`/`minWidth`/`maxWidth`, `comparator` (custom `(a, b) => number`, defaults to a
|
|
1682
|
+
locale-aware collator for strings). Slots inside a column: `<ng-template gogColumnBody let-row let-value="value" let-index="index">`,
|
|
1683
|
+
`<ng-template gogColumnHeader let-header let-field="field">`.
|
|
1684
|
+
|
|
1685
|
+
**Sorting, empty/loading states and pagination are all built in** — sortable columns toggle
|
|
1686
|
+
asc → desc → unsorted on click, `loading` shows a spinner in place of rows, an empty `value`
|
|
1687
|
+
shows `emptyPlaceholder`, and `pageSize > 0` turns on the internal paginator automatically. You
|
|
1688
|
+
don't need to hand-roll any of this.
|
|
1689
|
+
|
|
1690
|
+
There is **no typed row-selection API** in the current version — if you need it, track
|
|
1691
|
+
selection yourself (e.g. a `Set` keyed by row id) and render a `gogColumnBody` checkbox column.
|
|
1692
|
+
|
|
1693
|
+
#### `gog-scroll`
|
|
1694
|
+
|
|
1695
|
+
Drop-in replacement for `overflow: auto` — content still scrolls natively (wheel, touch,
|
|
1696
|
+
keyboard); only the browser's own scrollbar chrome is replaced with a themeable overlay thumb.
|
|
1697
|
+
Used internally by several other components (`gog-dialog`'s body, `gog-select`'s panel,
|
|
1698
|
+
`gog-tabs`' header row) and equally usable directly in your own markup for any scrollable
|
|
1699
|
+
region — the library's official recommendation over a raw `overflow-x`/`overflow-y`.
|
|
1700
|
+
|
|
1701
|
+
| Input | Type | Default |
|
|
1702
|
+
| -------------------- | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- |
|
|
1703
|
+
| `axis` | `GogScrollAxis` (`'vertical'\|'horizontal'\|'both'`) | `'vertical'` |
|
|
1704
|
+
| `size` | `GogScrollSize \| undefined` (`'normal'\|'thin'`) | `'normal'`; via `GOG_CONFIG.scroll.size` |
|
|
1705
|
+
| `autoHide` | `boolean \| undefined` | `true`; via `GOG_CONFIG.scroll.autoHide` |
|
|
1706
|
+
| `hideDelay` | `number \| undefined` | `800`; via `GOG_CONFIG.scroll.hideDelay` |
|
|
1707
|
+
| `reachThreshold` | `number` | `0` |
|
|
1708
|
+
| `focusable` | `boolean` | `true` — turn off when the parent already owns focus (a dialog with its own focus trap) |
|
|
1709
|
+
| `ariaLabel` | `string` | `''` |
|
|
1710
|
+
| `overscrollBehavior` | `GogScrollOverscrollBehavior \| undefined` (`'auto'\|'contain'\|'none'`) | `'auto'`; via `GOG_CONFIG.scroll.overscrollBehavior` |
|
|
1711
|
+
| `showTrack` | `boolean \| undefined` | `true`; via `GOG_CONFIG.scroll.showTrack` |
|
|
1712
|
+
| `horizontalWheel` | `boolean \| undefined` | `false`; via `GOG_CONFIG.scroll.horizontalWheel` |
|
|
1713
|
+
|
|
1714
|
+
**`horizontalWheel` turns a vertical wheel into horizontal scrolling** (21.9.0), for the case a
|
|
1715
|
+
consumer hits first: hover a horizontal-only row, turn the wheel, and the *page* moves. That is
|
|
1716
|
+
the browser's own behaviour and the component deliberately did nothing about it until now.
|
|
1717
|
+
|
|
1718
|
+
It is off by default because it changes what an existing instance does with a gesture it
|
|
1719
|
+
currently passes on; `provideGogConfig({ scroll: { horizontalWheel: true } })` turns it on
|
|
1720
|
+
app-wide. It only acts when the viewport cannot scroll vertically (checked against live
|
|
1721
|
+
geometry, so `axis="both"` scrolls down while there is down to go), the event carries no
|
|
1722
|
+
horizontal delta of its own (a trackpad swipe and `Shift`+wheel already work), `ctrlKey` is
|
|
1723
|
+
clear (pinch-zoom), and there is room left in the direction of the turn. **That last condition is
|
|
1724
|
+
the point:** at the content's end the event is left alone and the page picks it up, so the wheel
|
|
1725
|
+
never goes dead over a scrolled-to-the-end region. `overscrollBehavior: 'contain'` still
|
|
1726
|
+
contains — that boundary is the browser's and this never reaches past it.
|
|
1727
|
+
|
|
1728
|
+
Outputs: `gogScroll: GogScrollMetrics`, `gogReachStart`/`gogReachEnd: 'vertical'|'horizontal'`.
|
|
1729
|
+
Methods (via template ref): `scrollTo(options)`, `scrollToTop()`, `scrollToBottom()`,
|
|
1730
|
+
`scrollToLeft()`, `scrollToRight()`.
|
|
1731
|
+
|
|
1732
|
+
```html
|
|
1733
|
+
<gog-scroll size="thin" [focusable]="false" overscrollBehavior="contain" style="max-height: 320px">
|
|
1734
|
+
<!-- content that might overflow -->
|
|
1735
|
+
</gog-scroll>
|
|
1736
|
+
```
|
|
1737
|
+
|
|
1738
|
+
### Overlays
|
|
1739
|
+
|
|
1740
|
+
**Overlays and the viewport — the caveat that bites once per project.** `gog-dialog`'s backdrop,
|
|
1741
|
+
`gog-toast-container` and `gog-spinner [overlay]` are `position: fixed`, which covers the viewport
|
|
1742
|
+
only while no ancestor establishes a containing block. `contain`, `transform`, `filter`,
|
|
1743
|
+
`backdrop-filter` or `will-change` anywhere above retargets them to that element's box — and
|
|
1744
|
+
**`gog-scroll` sets `contain: layout style`**, so a dialog opened inside a scroller dims the
|
|
1745
|
+
scroller rather than the page. Place the dialog and toast outlets in the root component. The
|
|
1746
|
+
dropdown panels and `gog-menu` sidestep it by rendering into `<body>`.
|
|
1747
|
+
|
|
1748
|
+
#### `gog-menu` + `gogMenuTrigger` / `gogMenuItem`
|
|
1749
|
+
|
|
1750
|
+
A command menu. The trigger is a directive on **your own button** — usually the icon button you
|
|
1751
|
+
already styled — and the items are your own buttons too, so an item can hold an icon, a label and
|
|
1752
|
+
a shortcut hint without an input per piece:
|
|
1753
|
+
|
|
1754
|
+
```html
|
|
1755
|
+
<button gogButton variant="ghost" [gogMenuTrigger]="rowMenu" aria-label="Row actions">
|
|
1756
|
+
<gog-icon name="more-vertical" />
|
|
1757
|
+
</button>
|
|
1758
|
+
|
|
1759
|
+
<gog-menu #rowMenu ariaLabel="Row actions">
|
|
1760
|
+
<button gogMenuItem (click)="edit(row)"><gog-icon name="check" /> Edit</button>
|
|
1761
|
+
<button gogMenuItem disabled>Transfer ownership</button>
|
|
1762
|
+
<button gogMenuItem (click)="remove(row)"><gog-icon name="close" /> Remove</button>
|
|
1763
|
+
</gog-menu>
|
|
1764
|
+
```
|
|
1765
|
+
|
|
1766
|
+
| Input | Type | Default | Notes |
|
|
1767
|
+
| ----------- | -------------------------- | -------- | ---------------------------------------------------------------------------- |
|
|
1768
|
+
| `direction` | `'auto' \| 'up' \| 'down'` | `'auto'` | `'auto'` drops down whenever the panel fits and flips up only when it cannot |
|
|
1769
|
+
| `ariaLabel` | `string` | `''` | Names the panel itself |
|
|
1770
|
+
|
|
1771
|
+
**There is no `appendToBody`.** The panel always renders into `<body>` and is placed from the
|
|
1772
|
+
trigger's measured rect, so a menu inside `gog-scroll`, `gog-table` or any `overflow: hidden`
|
|
1773
|
+
ancestor is not clipped and needs no configuration. It also takes the `--gog-dropdown-z` its
|
|
1774
|
+
trigger inherits, so a menu opened inside a `gog-dialog` stacks above the dialog.
|
|
1775
|
+
|
|
1776
|
+
`gogMenuItem` takes a **`ripple`** input (`boolean | undefined`, `false`, via
|
|
1777
|
+
`GOG_CONFIG.ripple.enabled`). The item is your own `<button>`, but the directive owns the ripple,
|
|
1778
|
+
so there is no `gogRipple` to add.
|
|
1779
|
+
|
|
1780
|
+
Output: `gogClosed` — fires after every close, whatever caused it.
|
|
1781
|
+
|
|
1782
|
+
Public methods, for driving it yourself: `open(trigger, 'first' | 'last')`, `close(restoreFocus?)`,
|
|
1783
|
+
`toggle(trigger)`, and the `isOpen` signal.
|
|
1784
|
+
|
|
1785
|
+
**Keyboard**, the WAI-ARIA menu button pattern: Enter/Space/ArrowDown open with the first item
|
|
1786
|
+
focused, ArrowUp opens with the last, arrows and Home/End move between items and step over
|
|
1787
|
+
disabled ones, Escape closes and returns focus to the trigger, Tab closes and lets focus move on.
|
|
1788
|
+
A press outside closes without pulling focus back.
|
|
1789
|
+
|
|
1790
|
+
**Disabling an item** is the native `disabled` attribute on your own button — static or bound,
|
|
1791
|
+
there is no input for it:
|
|
1792
|
+
|
|
1793
|
+
```html
|
|
1794
|
+
<button gogMenuItem disabled>Transfer ownership</button>
|
|
1795
|
+
<button gogMenuItem [disabled]="isLocked()" (click)="edit()">Edit</button>
|
|
1796
|
+
```
|
|
1797
|
+
|
|
1798
|
+
A disabled item stays in the list rather than disappearing (removing it would shift the others
|
|
1799
|
+
under the pointer), the arrow keys step over it, and clicking it does nothing.
|
|
1800
|
+
|
|
1801
|
+
**A long menu scrolls itself**, using `gog-scroll` — the same thin, auto-hiding scroller as
|
|
1802
|
+
everywhere else in the package, with `overscrollBehavior="contain"` so a wheel at the end of the
|
|
1803
|
+
list does not scroll the page behind it. Arrowing past the last visible item scrolls it into view.
|
|
1804
|
+
|
|
1805
|
+
The panel's height is the smallest of three: its own content, `--gog-menu-max-height` (320px by
|
|
1806
|
+
default), and the room between the trigger and the viewport edge. Lower the token to make a menu
|
|
1807
|
+
scroll sooner. **In 21.5.0 the token did nothing** — the measured room was written onto the panel
|
|
1808
|
+
as an inline `max-height`, which beat it; fixed in 21.5.1.
|
|
1809
|
+
|
|
1810
|
+
A closed menu renders nothing at all, so its commands are not in the accessibility tree until it
|
|
1811
|
+
opens.
|
|
1812
|
+
|
|
1813
|
+
#### `gog-dialog`
|
|
1814
|
+
|
|
1815
|
+
A **single** `<gog-dialog />` renders **every** dialog `DialogService.open(...)` creates —
|
|
1816
|
+
place it once, typically in your root app component's template, not per-page and not per-dialog
|
|
1817
|
+
call:
|
|
1818
|
+
|
|
1819
|
+
```html
|
|
1820
|
+
<!-- app.html -->
|
|
1821
|
+
<router-outlet />
|
|
1822
|
+
<gog-dialog />
|
|
1823
|
+
```
|
|
1824
|
+
|
|
1825
|
+
It has no inputs of its own — everything is driven through `DialogService` (see
|
|
1826
|
+
[Services](#services) above). Supports nesting, dragging (when `draggable !== false` and the
|
|
1827
|
+
dialog has a title or close button), a focus trap for modal dialogs, `Escape` to close (when
|
|
1828
|
+
`closable !== false`), and click-outside-to-close on the backdrop.
|
|
1829
|
+
|
|
1830
|
+
#### `gog-toast` / `gog-toast-container`
|
|
1831
|
+
|
|
1832
|
+
Same pattern — place **one** `<gog-toast-container />`, typically in the root component:
|
|
1833
|
+
|
|
1834
|
+
```html
|
|
1835
|
+
<gog-toast-container [maxVisiblePerPosition]="5" />
|
|
1836
|
+
```
|
|
1837
|
+
|
|
1838
|
+
`maxVisiblePerPosition` (default `5`) caps how many toasts stack at once per corner; the rest
|
|
1839
|
+
queue. Individual `gog-toast` instances are rendered internally by the container from
|
|
1840
|
+
`ToastService.toasts()` — you don't place these yourself. Toasts auto-dismiss after their
|
|
1841
|
+
`duration` unless `isSticky`; hovering pauses the countdown (front-of-stack toast only).
|
|
1842
|
+
|
|
1843
|
+
Announcements come from two permanently-mounted, visually-hidden live regions the container
|
|
1844
|
+
owns — polite, and assertive for `error`/`warning`. The toasts themselves carry no
|
|
1845
|
+
`role`/`aria-live`: a live region created in the same tick as its text is routinely skipped by
|
|
1846
|
+
screen readers, and a second region would announce everything twice. Don't add either back.
|
|
1847
|
+
|
|
1848
|
+
---
|
|
1849
|
+
|
|
1850
|
+
## Reading the deprecations at runtime — `GOG_DEPRECATIONS`
|
|
1851
|
+
|
|
1852
|
+
Everything the package currently deprecates, as data:
|
|
1853
|
+
|
|
1854
|
+
```ts
|
|
1855
|
+
import { GOG_DEPRECATIONS, type GogDeprecation } from '@guildofgleks/ui';
|
|
1856
|
+
|
|
1857
|
+
GOG_DEPRECATIONS; // []
|
|
1858
|
+
```
|
|
1859
|
+
|
|
1860
|
+
`kind` is `'symbol'` for an export or input and `'token'` for a `--gog-*` custom property. The
|
|
1861
|
+
list is generated from the library's source — tags for symbols, stylesheets for tokens — so it
|
|
1862
|
+
matches what actually still resolves in the version you installed.
|
|
1863
|
+
|
|
1864
|
+
**As of 21.7.0 the list is empty on both halves.** Nothing in the TypeScript API is deprecated, and
|
|
1865
|
+
the three abbreviated token prefixes that used to fill the token half are gone rather than
|
|
1866
|
+
deprecated — see the removal table below. An empty list here means exactly that: nothing to
|
|
1867
|
+
migrate away from right now.
|
|
1868
|
+
|
|
1869
|
+
## Removed in 21.7.0
|
|
1870
|
+
|
|
1871
|
+
**Nothing in this table exists any more.** Three CSS custom-property prefixes, abbreviations of a
|
|
1872
|
+
component's own name, are gone — each was honoured only as a fallback the spelled-out token wrapped
|
|
1873
|
+
(`--gog-button-x: var(--gog-btn-x, value)`), never declared on its own.
|
|
1874
|
+
|
|
1875
|
+
| Removed | Replacement |
|
|
1876
|
+
| ----------------- | ----------------------------- |
|
|
1877
|
+
| `--gog-btn-*` | `--gog-button-*` |
|
|
1878
|
+
| `--gog-ms-*` | `--gog-multiselect-*` |
|
|
1879
|
+
| `--gog-confirm-*` | `--gog-confirmation-dialog-*` |
|
|
1880
|
+
|
|
1881
|
+
A consumer's CSS that still sets one of the left-hand names doesn't fail their build — an
|
|
1882
|
+
unresolved `var()` just stops matching anything, silently. If a themed surface stopped picking up
|
|
1883
|
+
an override after upgrading to 21.7.0, this table is the first thing to check.
|
|
1884
|
+
|
|
1885
|
+
## Removed in 21.5.0
|
|
1886
|
+
|
|
1887
|
+
**Nothing in this table exists any more.** It is here so that code written against 21.4.x — or
|
|
1888
|
+
generated from a stale copy of this file — can be migrated: each row names what a call site must
|
|
1889
|
+
become. If you are writing new code, ignore this section entirely and use the right-hand column,
|
|
1890
|
+
which is documented in full above.
|
|
1891
|
+
|
|
1892
|
+
| Removed | Replacement |
|
|
1893
|
+
| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
1894
|
+
| `gog-select`/`gog-multiselect` `chevronTemplate` input | `<ng-template gogDropdownChevron>` |
|
|
1895
|
+
| `gog-checkbox` `checkIconTemplate` input | `<ng-template gogCheckboxIcon>` |
|
|
1896
|
+
| `gog-tag` `iconTemplate` input | `<ng-template gogTagIcon>` |
|
|
1897
|
+
| `gog-multiselect` `clearIconTemplate` input | `<ng-template gogMultiselectClearIcon>` |
|
|
1898
|
+
| `gog-inputfield` `iconStartTemplate`/`iconEndTemplate`/`iconStartFn`/`iconEndFn`/`iconStartLabel`/`iconEndLabel` | `<span gogInputAddonStart>`/`<span gogInputAddonEnd>` (or a `<button>` with its own handler) |
|
|
1899
|
+
| `gog-table`'s `[template]` attribute (`<ng-template template="field" type="body">`) | `<ng-template gogColumnBody>` / `<ng-template gogColumnHeader>` declared **inside** the matching `<gog-column>` |
|
|
1900
|
+
| `<column>` selector / `Column` export | `<gog-column>` / `GogColumn` |
|
|
1901
|
+
| `GogSelectOption` / `GogMultiselectOption` types | `GogDropdownOption` (the same type — they were aliases of it) |
|
|
1902
|
+
| `@guildofgleks/ui/src/styles/…` asset path | `@guildofgleks/ui/styles/…` |
|
|
1903
|
+
|
|
1904
|
+
The general rule they all followed: a `TemplateRef` **input** or a string-keyed lookup was the old
|
|
1905
|
+
shape; a **projected content directive with a typed context**, declared where it's used, is the
|
|
1906
|
+
current one. If you're about to write `fooTemplate` next to an existing `foo` input, or key
|
|
1907
|
+
something off a string that has to match another string elsewhere, that's this exact
|
|
1908
|
+
anti-pattern — reach for a slot directive instead.
|
|
1909
|
+
|
|
1910
|
+
## Full type reference
|
|
1911
|
+
|
|
1912
|
+
Shared enum-like types (`import type { ... } from '@guildofgleks/ui'`):
|
|
1913
|
+
|
|
1914
|
+
| Type | Values |
|
|
1915
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
1916
|
+
| `GogSize` | `'xsm' \| 'sm' \| 'md' \| 'lg' \| 'slg'` |
|
|
1917
|
+
| `GogVariant` | `'primary' \| 'secondary' \| 'outline' \| 'ghost'` |
|
|
1918
|
+
| `GogSurfaceVariant` | `'outlined' \| 'elevated' \| 'filled'` — `gog-card` and `gog-panel` |
|
|
1919
|
+
| `GogAriaHasPopup` | `boolean \| 'menu' \| 'listbox' \| 'tree' \| 'grid' \| 'dialog'` — `gog-button`'s `ariaHasPopup` |
|
|
1920
|
+
| `GogTagVariant` | `'success' \| 'danger' \| 'warning' \| 'info'` |
|
|
1921
|
+
| `GogOrientation` | `'horizontal' \| 'vertical'` |
|
|
1922
|
+
| `GogTagShape` | `'rounded' \| 'pill'` |
|
|
1923
|
+
| `GogSpinnerVariant` | `'runic' \| 'ring' \| 'custom'` |
|
|
1924
|
+
| `GogSkeletonShape` | `'text' \| 'circle' \| 'rect'` |
|
|
1925
|
+
| `GogSkeletonAnimation` | `'pulse' \| 'wave' \| 'none'` |
|
|
1926
|
+
| `GogPaginatorRangeMode` | `'window' \| 'ellipsis'` |
|
|
1927
|
+
| `GogScrollAxis` | `'vertical' \| 'horizontal' \| 'both'` |
|
|
1928
|
+
| `GogScrollSize` | `'normal' \| 'thin'` |
|
|
1929
|
+
| `GogScrollOverscrollBehavior` | `'auto' \| 'contain' \| 'none'` |
|
|
1930
|
+
| `GogTooltipPosition` | `'auto' \| 'top' \| 'bottom' \| 'left' \| 'right'` |
|
|
1931
|
+
| `GogFloatLabelVariant` | `'none' \| 'in' \| 'on' \| 'over'` |
|
|
1932
|
+
| `GogDropdownFilterPosition` | `'top' \| 'bottom'` |
|
|
1933
|
+
| `GogDividerVariant` | `'solid' \| 'dashed' \| 'dotted'` |
|
|
1934
|
+
| `GogBadgePosition` | `'top-end' \| 'top-start' \| 'bottom-end' \| 'bottom-start'` |
|
|
1935
|
+
| `GogProgressbarMode` | `'determinate' \| 'indeterminate' \| 'buffer'` |
|
|
1936
|
+
| `GogProgressbarVariant` | `'accent' \| 'success' \| 'danger' \| 'warning' \| 'info'` |
|
|
1937
|
+
| `GogButtonToggleAppearance` | `'joined' \| 'separated'` |
|
|
1938
|
+
| `GogTabsAlign` | `'start' \| 'center' \| 'end' \| 'stretch'` |
|
|
1939
|
+
| `GogDateSelectionMode` | `'single' \| 'range'` |
|
|
1940
|
+
| `GogHourFormat` | `'12' \| '24'` |
|
|
1941
|
+
| `GogTextareaResize` | `'vertical' \| 'horizontal' \| 'both' \| 'none'` |
|
|
1942
|
+
| `GogInputType` | `'text' \| 'password' \| 'email' \| 'number' \| 'search' \| 'tel' \| 'url' \| 'date' \| 'time' \| 'datetime-local'` |
|
|
1943
|
+
| `GogInputMode` | `'none' \| 'text' \| 'decimal' \| 'numeric' \| 'tel' \| 'search' \| 'email' \| 'url'` |
|
|
1944
|
+
| `GogTableSelectionMode` | `'none' \| 'single' \| 'multiple'` |
|
|
1945
|
+
| `GogTableSortEvent` | `{ field: string; direction: SortDirection }` |
|
|
1946
|
+
| `GogTableRowClickEvent<T>` | `{ row: T; index: number; originalEvent: MouseEvent \| KeyboardEvent }` |
|
|
1947
|
+
| `GogErrorDisplay` | `'auto' \| 'manual'` |
|
|
1948
|
+
| `GogDropdownDirection` | `'auto' \| 'up' \| 'down'` |
|
|
1949
|
+
| `GogTooltipSide` | `'top' \| 'bottom' \| 'left' \| 'right'` (resolved form of `GogTooltipPosition`, no `'auto'`) |
|
|
1950
|
+
| `GogBuiltinIconName` | the 20 glyphs the package ships — see [`gog-icon`](#gog-icon) |
|
|
1951
|
+
| `GogIconName` | `GogBuiltinIconName \| (string & {})` — built-ins plus anything registered via `provideGogIcons` |
|