@guildofgleks/ui 21.3.2 → 21.4.1

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/README.md CHANGED
@@ -4,184 +4,126 @@
4
4
  ![NPM Downloads](https://img.shields.io/npm/dm/@guildofgleks/ui)
5
5
  ![License](https://img.shields.io/npm/l/@guildofgleks/ui)
6
6
 
7
- A lightweight Angular 21 component library. No CDK, no Material — the only runtime
8
- dependencies are `@angular/core`, `@angular/common` and `@angular/forms`.
7
+ # @guildofgleks/ui
9
8
 
10
- **Tags:** angular, angular21, components, ui-library, design-system, signals, standalone, accessible
11
-
12
- ## Features
13
-
14
- - 27 standalone components and 2 directives — see [Components](#components) for the full
15
- list — plus `DialogService`, `ToastService` and `ThemeService`.
16
- - Signal-based API throughout: `input()` / `output()` / `model()`, `OnPush` change
17
- detection, no NgModules.
18
- - No CDK, no Material — a small dependency footprint on top of `@angular/core`,
19
- `@angular/common` and `@angular/forms`.
20
- - Full theming through CSS custom properties: restyle any component, swap the whole
21
- palette, or ship light/dark and custom themes at runtime via a `data-theme` attribute.
22
- - `ControlValueAccessor` on every form control (checkbox, inputfield, select,
23
- multiselect, slider, textarea) — built and tested against Reactive Forms (`formControl` /
24
- `formControlName`). The library does not use `ngModel`/`FormsModule` anywhere itself,
25
- and template-driven usage via `[(ngModel)]` is untested — CVA makes it likely to work,
26
- but it isn't a supported or verified path.
27
- - Accessible by default: keyboard navigation, ARIA attributes, WCAG AA contrast.
28
-
29
- ## Install
30
-
31
- npm:
9
+ An Angular 21 component library with **no CDK and no Material**. 27 components, 5 directives and
10
+ 3 services, all standalone, all signal-based, themed entirely through CSS custom properties.
32
11
 
33
12
  ```bash
34
13
  npm install @guildofgleks/ui
35
14
  ```
36
15
 
37
- yarn:
16
+ ## Why this one
38
17
 
39
- ```bash
40
- yarn add @guildofgleks/ui
41
- ```
18
+ - **Small dependency footprint.** Peers are `@angular/core`, `@angular/common`, `@angular/forms`
19
+ and `@angular/platform-browser`. No router, no CDK, no animations package. `tslib` is the only
20
+ runtime dependency.
21
+ - **Signals throughout.** `input()` / `output()` / `model()`, `OnPush` everywhere, no NgModules.
22
+ - **Themeable without a build step.** Every value a component paints with is a `--gog-*` custom
23
+ property. Swap a palette, restyle one component, or override a single instance — no Sass
24
+ variables, no JS theme object.
25
+ - **Reactive Forms native.** Every form control is a `ControlValueAccessor` built and tested
26
+ against `[formControl]` / `formControlName`.
27
+ - **Your data, your shapes.** Dropdowns take your objects with accessor paths
28
+ (`optionLabel="profile.fullName"`), not a mandated `{ id, name }` DTO.
29
+ - **Accessible by default.** Keyboard navigation, ARIA wiring and generated label associations
30
+ come with the components rather than with extra attributes.
42
31
 
43
32
  ## Setup
44
33
 
45
- Add the stylesheet once. It carries the baseline theme (every token the components
46
- read) plus the utility classes the component templates use, so without it components
47
- render unstyled.
34
+ **1. Add the stylesheet.** It carries the baseline theme and the utility classes the components
35
+ use without it they render unstyled.
48
36
 
49
37
  ```jsonc
50
38
  // angular.json → projects.<app>.architect.build.options
51
39
  "styles": [
52
40
  "node_modules/@guildofgleks/ui/styles/index.css",
53
- "src/styles.scss" // your own styles, after the baseline so they win
41
+ "src/styles.scss" // yours, after the baseline so it wins
54
42
  ]
55
43
  ```
56
44
 
57
- > Up to 21.3.1 the same files shipped under `@guildofgleks/ui/src/styles/…`. That path still
58
- > works and will keep working until 21.5.0 new setups should use the shorter one above.
45
+ > Up to 21.3.1 these files shipped under `@guildofgleks/ui/src/styles/…`. That path keeps working
46
+ > until 21.5.0; new setups should use the shorter one.
59
47
 
60
- Then import a component where you need it:
48
+ **2. Import components where you use them** — each is standalone:
61
49
 
62
50
  ```ts
63
- import { Component } from '@angular/core';
64
51
  import { ButtonComponent, SelectComponent } from '@guildofgleks/ui';
65
52
 
66
53
  @Component({
67
- selector: 'app-example',
68
54
  imports: [ButtonComponent, SelectComponent],
69
55
  template: `
70
56
  <gog-select label="Region" [options]="regions" [(value)]="region" />
71
57
  <gog-button (gogClick)="save()">Save</gog-button>
72
58
  `,
73
59
  })
74
- export class ExampleComponent {
75
- /* … */
76
- }
60
+ export class ExampleComponent {}
77
61
  ```
78
62
 
79
- Outputs are prefixed with `gog` (`gogClick`, `gogToggle`) so they never collide with
80
- native DOM events. Inputs keep their natural names.
81
-
82
- ### Services need a host element
63
+ Outputs are prefixed `gog` (`gogClick`, `gogToggle`) so they never collide with native DOM
64
+ events. Inputs keep their natural names.
83
65
 
84
- `DialogService` and `ToastService` render into a host component you place once, rather than
85
- creating a detached portal of their own. **Without the host in a template, `open()` and
86
- `show()` update state and nothing appears on screen.** Put both in your root template — they
87
- render nothing until something is actually open:
66
+ **3. If you use dialogs or toasts, place their hosts once.** `DialogService.open()` and
67
+ `ToastService.show()` update state but render nothing without them:
88
68
 
89
69
  ```ts
90
- import { Component, inject } from '@angular/core';
91
- import { DialogComponent, ToastContainerComponent, ToastService } from '@guildofgleks/ui';
92
-
93
70
  @Component({
94
71
  selector: 'app-root',
95
72
  imports: [DialogComponent, ToastContainerComponent],
96
73
  template: `
97
74
  <router-outlet />
98
-
99
75
  <gog-dialog />
100
76
  <gog-toast-container />
101
77
  `,
102
78
  })
103
- export class App {
104
- private readonly toasts = inject(ToastService);
105
- notify = () => this.toasts.show({ message: 'Saved', type: 'success' });
106
- }
79
+ export class App {}
107
80
  ```
108
81
 
109
- `<gog-dialog />` hosts every dialog opened through `DialogService` (they stack, so one host is
110
- enough for the whole app). `<gog-toast-container />` hosts all four toast positions at once and
111
- takes an optional `maxVisiblePerPosition` (default `5`).
82
+ One `<gog-dialog />` hosts every dialog (they stack); one `<gog-toast-container />` hosts all
83
+ four toast corners.
112
84
 
113
85
  ## Theming
114
86
 
115
- Everything is themed through CSS custom properties, and **every value the components
116
- paint with lives in `styles/theme.css`** — no component stylesheet holds a colour, font,
117
- border, radius, shadow, spacing or duration of its own. There are three layers:
87
+ Every value the components paint with lives in `styles/theme.css`, in three layers:
118
88
 
119
- **1. Foundation tokens** — the palette, type scale, spacing, motion and control metrics.
120
- Override these to restyle everything at once; the component tokens all derive from them,
121
- so a palette swap carries through without touching anything else.
89
+ **Foundation** — palette, type scale, spacing, motion. Override these to restyle everything at
90
+ once; component tokens derive from them, so a palette swap carries through on its own.
122
91
 
123
- Every group and every token name is listed in [`TOKENS.md`](./TOKENS.md) generated from
124
- `theme.css`, so it cannot drift from what the components actually read. The same catalogue is
125
- available at runtime as `GOG_TOKEN_GROUPS`, typed by `GogTokenName`.
126
-
127
- **2. Component tokens** — `--gog-<block>-*`, one block per component in `theme.css`, for
128
- restyling a single component theme-wide. They cover every painted property, including
129
- per-variant and per-size values (`--gog-btn-ghost-hover-bg`, `--gog-tag-lg-padding-block`,
130
- `--gog-accordion-sm-chevron-size`, `--gog-skeleton-shine`, `--gog-paginator-gap`, …), plus
131
- each component's font family, so a theme can decide that e.g. buttons use the body face:
92
+ **Component** `--gog-<block>-*`, one block per component, to restyle a single component
93
+ app-wide:
132
94
 
133
95
  ```css
134
96
  :root[data-theme='mine'] {
135
97
  --gog-btn-font-family: var(--gog-font-body);
136
- --gog-btn-font-weight: 600;
137
98
  --gog-btn-ghost-hover-bg: color-mix(in srgb, var(--gog-accent-color) 20%, transparent);
138
99
  --gog-table-hover-bg: var(--gog-hover-color);
139
100
  }
140
101
  ```
141
102
 
142
- **3. Instance tokens** — a small set left deliberately _undeclared_ so that setting them
143
- anywhere always wins over the variant/size classes. This is the per-instance escape
144
- hatch:
103
+ **Instance** — a small set left deliberately undeclared, so setting one anywhere beats the
104
+ variant and size classes:
145
105
 
146
106
  ```css
147
107
  .my-form gog-button {
148
- --gog-btn-bg: rebeccapurple; /* beats .gog-btn--primary, unlike a declared token */
108
+ --gog-btn-bg: rebeccapurple; /* wins over .gog-btn--primary */
149
109
  }
150
110
  ```
151
111
 
152
- They are `--gog-btn-{bg,color,border,shadow,hover-bg,hover-color,hover-shadow,padding,font-size,spinner-color}`,
153
- `--gog-tag-{accent,bg,border,color,font-size,gap,padding-block,padding-inline,icon-size}`,
154
- `--gog-chip-{font-size,gap,padding-block,padding-inline,avatar-size,icon-size,remove-size}`,
155
- the per-size field hooks (`--gog-input-padding-y`, `--gog-select-control-font`,
156
- `--gog-ms-font-size`, `--gog-table-td-padding-v`, `--gog-accordion-padding-y`, …),
157
- `--gog-spinner-color`, and `--gog-{input,select,ms}-float-label-on-bg` (the patch masking the
158
- border behind an `'on'` label).
159
-
160
- The full list is the `INSTANCE_TOKENS` set in `scripts/check-tokens.mjs`, which is verified
161
- against the stylesheets on every CI run — an instance token that gets declared anywhere, or
162
- stops being read, fails the build. That check also enforces the other half of the contract:
163
- **no component stylesheet carries a default in a `var()` fallback**, so every value a
164
- component paints with really is discoverable in `theme.css`.
165
-
166
- The float label's _geometry_ is not instance-layer — it is themeable per component
167
- (`--gog-{input,select,ms}-float-label-{reserve,in-top,over-gap,over-reserve}`), and all three
168
- derive from the shared `--gog-field-float-label-*` scale above, so one declaration retunes
169
- every field at once while a single control can still be overridden on its own.
112
+ Every group and token name is in **[`TOKENS.md`](./TOKENS.md)**, generated from `theme.css` so it
113
+ cannot drift, and available at runtime as `GOG_TOKEN_GROUPS`.
170
114
 
171
- ### Light and dark
115
+ ### Light, dark and your own
172
116
 
173
- The theme is selected with a `data-theme` attribute on `:root`, and `ThemeService`
174
- manages it:
117
+ The active theme is a `data-theme` attribute on `:root`, managed by `ThemeService`:
175
118
 
176
119
  ```ts
177
120
  private readonly theme = inject(ThemeService);
178
- this.theme.toggleTheme(); // light ⇄ dark
179
- this.theme.setTheme('cyberpunk'); // any custom theme name
180
- this.theme.theme(); // read-only signal — go through the two methods above
121
+ this.theme.toggleTheme(); // light ⇄ dark
122
+ this.theme.setTheme('cyberpunk'); // any name you declared in CSS
181
123
  ```
182
124
 
183
- Out of the box it adopts whatever `data-theme` is already on the document, or `'light'`.
184
- Persisting the choice and following the OS setting are opt-in, through `GOG_CONFIG.theme`:
125
+ Out of the box it adopts whatever `data-theme` is already on the document, or `light`.
126
+ Persisting the choice and following the OS setting are opt-in:
185
127
 
186
128
  ```ts
187
129
  provideGogConfig({
@@ -189,108 +131,46 @@ provideGogConfig({
189
131
  });
190
132
  ```
191
133
 
192
- To add a theme, copy a palette block from `styles/theme.css` and change the attribute
193
- value. A theme only needs to declare what it actually changes the component tokens are
194
- re-derived from whatever palette is in scope, so a swapped `--gog-accent-color` reaches every
195
- component without listing any of them:
196
-
197
- ```css
198
- :root[data-theme='cyberpunk'],
199
- [data-theme='cyberpunk'] {
200
- color-scheme: dark;
201
- --gog-background-color: #050816;
202
- --gog-accent-color: #ff4edb;
203
- --gog-radius: 22px;
204
- }
205
- ```
206
-
207
- The second selector is what lets a theme apply to a _subtree_ rather than the whole page
208
- — put `data-theme="cyberpunk"` on any element and everything inside it re-derives from
209
- that palette, so one page can show several themes side by side (see the showcase's Theme
210
- lab). That works because `theme.css` re-declares its derived layer on `:root, [data-theme]`:
211
- a custom property's `var()` references are substituted where the property is _declared_,
212
- so a derived token declared only on `:root` would freeze to the root palette.
213
-
214
- Corollary worth knowing when writing your own themes: **anything you declare that reads
215
- another token must sit on the theme scope itself**, not on `:root`, or it will not follow
216
- a nested theme.
217
-
218
- ### Presets
219
-
220
- `theme.css` ships `light` and `dark`. `slate` is a third, importable separately — a
221
- palette-only theme in a cool/indigo register:
134
+ A theme only declares what it changes the derived layer re-resolves against whatever palette is
135
+ in scope, so a new palette restyles every component without listing any of them. The `slate`
136
+ preset is the worked example, palette-only:
222
137
 
223
138
  ```css
224
139
  @import '@guildofgleks/ui/styles/index.css';
225
140
  @import '@guildofgleks/ui/styles/presets/slate.css';
226
141
  ```
227
142
 
228
- ```html
229
- <html data-theme="slate"></html>
230
- ```
231
-
232
- It is worth reading as the worked example of the contract above: it declares **only palette
233
- tokens** — no `--gog-btn-*`, no `--gog-table-*` — and still restyles every component, because
234
- the derived layer re-resolves against whatever palette is in scope. If a theme of yours needs to
235
- list component tokens, that usually means the derived layer is missing something rather than
236
- that the theme needs to be longer.
143
+ Fonts are left alone on purpose (system stacks, no webfont download). Add
144
+ `@guildofgleks/ui/styles/fonts.css` for the showcase's typography.
237
145
 
238
- ### Optional font preset
146
+ ## App-wide configuration
239
147
 
240
- `styles/index.css` intentionally leaves fonts alonegeneric system stacks, no webfont
241
- download. For the showcase's typography (Cinzel / Inter / JetBrains Mono, pulled from
242
- Google Fonts) add `@guildofgleks/ui/styles/fonts.css` as well.
243
-
244
- ## Global configuration
245
-
246
- Anything visual is a CSS token (above). For the rest — the handful of settings an app would
247
- otherwise repeat on every instance — there is one provider, not an injection token per
248
- component per setting:
148
+ Anything visual is a token. Everything else the settings you would otherwise repeat on every
149
+ instance goes through one provider:
249
150
 
250
151
  ```ts
251
- // app.config.ts
252
- import { provideGogConfig } from '@guildofgleks/ui';
253
-
254
- providers: [
255
- provideGogConfig({
256
- control: { size: 'sm', errorDisplay: 'auto', clearable: true },
257
- dropdown: { appendToBody: true },
258
- datepicker: { locale: 'de-DE', format: 'dd.MM.yyyy' },
259
- button: { debounce: 500 },
260
- }),
261
- ];
152
+ provideGogConfig({
153
+ control: { size: 'sm', errorDisplay: 'auto', clearable: true },
154
+ dropdown: { appendToBody: true },
155
+ datepicker: { locale: 'de-DE', format: 'dd.MM.yyyy' },
156
+ labels: { clear: 'Очистить', selectAll: 'Выбрать все' }, // translate the library once
157
+ });
262
158
  ```
263
159
 
264
- Keys: `control`, `dropdown`, `floatLabel`, `datepicker`, `autocomplete`, `inputfield`,
265
- `textarea`, `tooltip`, `scroll`, `button`, `toast`, `theme`, `labels`. Every field is optional,
266
- and an instance's own input always wins over the configured value. Providing it again lower in
267
- the injector tree (a route, a component) **layers onto** the parent's config one level deep
268
- rather than replacing it, so a route can override one field and inherit the rest.
269
-
270
- ### Translating the library
160
+ Keys: `control`, `dropdown`, `floatLabel`, `datepicker`, `autocomplete`, `inputfield`, `textarea`,
161
+ `tooltip`, `scroll`, `button`, `paginator`, `toast`, `theme`, `labels`. An instance's own input
162
+ always wins, and providing the config again lower in the injector tree layers onto the parent
163
+ rather than replacing it.
271
164
 
272
- `labels` carries every fixed string the components render themselves the ones you never write
273
- markup for. Set them once instead of on every control:
165
+ Icons work the same way 41 Lucide glyphs ship with the package, and your own register by name:
274
166
 
275
167
  ```ts
276
- provideGogConfig({
277
- labels: {
278
- clear: 'Очистить',
279
- clearSelection: 'Очистить выбор',
280
- selectAll: 'Выбрать все',
281
- closeDialog: 'Закрыть',
282
- previousPage: 'Предыдущая страница',
283
- today: 'Сегодня',
284
- // interpolating labels take a function, since word order and agreement vary by language
285
- page: (page, isCurrent) =>
286
- isCurrent ? `Страница ${page}, текущая` : `Перейти на страницу ${page}`,
287
- },
288
- });
168
+ provideGogIcons({ cart: '<svg viewBox="0 0 24 24">…</svg>' });
289
169
  ```
290
170
 
291
- The full key list is on `GogGlobalConfig['labels']`. Strings that describe *one* control rather
292
- than library chrome a field's `label`, a button's `ariaLabel` — are deliberately not in here;
293
- those stay per instance.
171
+ ```html
172
+ <gog-icon name="cart" /> <gog-tag iconName="cart">In basket</gog-tag>
173
+ ```
294
174
 
295
175
  ## Components
296
176
 
@@ -304,94 +184,45 @@ those stay per instance.
304
184
  | Feedback | `gog-spinner`, `gog-spinner-overlay`, `gog-progressbar`, `gog-skeleton` |
305
185
  | Content | `gog-icon` |
306
186
 
307
- Directives: `gogTooltip`, `gogBadge`, `gogCollapsibleTrigger`, `gogCollapsibleContent`.
308
-
309
- Slot directives, for replacing a component's markup rather than configuring it:
310
- `gogAccordionHeader`, `gogAccordionContent`, `gogAccordionChevron`, `gogButtonToggleOption`,
311
- `gogCheckboxIcon`, `gogColumnBody`, `gogColumnHeader`, `gogDropdownOption`,
312
- `gogDropdownChevron`, `gogInputAddonStart`, `gogInputAddonEnd`, `gogMultiselectClearIcon`,
313
- `gogTabHeader`, `gogTabContent`, `gogTagIcon`.
314
-
315
- `gog-dialog` and `gog-toast-container` are the host elements the two services render into —
316
- see [Services need a host element](#services-need-a-host-element).
187
+ **Directives:** `gogButton` (a link that looks like a button), `gogTooltip`, `gogBadge`,
188
+ `gogCollapsibleTrigger`, `gogCollapsibleContent`.
189
+ **Services:** `DialogService`, `ToastService`, `ThemeService`.
317
190
 
318
- Services: `DialogService`, `ToastService`, `ThemeService`.
191
+ Fifteen more slot directives replace a component's markup rather than configuring it —
192
+ `gogColumnBody`, `gogInputAddonStart`, `gogDropdownOption` and friends.
319
193
 
320
- `gog-collapsible` is a headless expand/collapse primitive no owned markup, no portal.
321
- Project any element as the trigger via `gogCollapsibleTrigger` and any element as the
322
- panel via `gogCollapsibleContent`; `[(open)]` is two-way bindable. Useful for anything
323
- that needs an inline expanding region without the overlay behavior of `gog-select`/
324
- `gog-multiselect` — e.g. a collapsible group of links in a nav sidebar.
194
+ A few things worth knowing before you reach for a workaround:
325
195
 
326
- ### Options come from your own objects
196
+ - **`gog-table` works two ways.** By default it owns the data and sorts and pages in memory. With
197
+ `[lazy]="true"` it hands both to the server: `value` is the current page, `totalRecords` drives
198
+ the paginator, and `gogSortChange` / `gogPageChange` are your refetch signals. Row selection is
199
+ `selectionMode` + `[(selection)]`; set `dataKey` or a refetch drops it.
200
+ - **`gog-button` cannot be a link** — it renders its own `<button>`. Use `[gogButton]` on your own
201
+ `<a>` instead; nothing is brokered through inputs, so `routerLink`, `href` and `target` keep
202
+ working. That is also why this package needs no `@angular/router`.
203
+ - **`gog-inputfield` and `gog-textarea` forward the native attribute space** they wrap —
204
+ `readonly`, `maxlength`, `pattern`, `inputMode`, `spellcheck` and the text-field `type` values.
205
+ They also generate their own `id`, so labels and error messages are wired up without `inputId`.
206
+ - **`gog-collapsible` is headless** — no markup of its own. Project any element as the trigger and
207
+ any element as the panel.
208
+ - **`[(ngModel)]` is untested.** The library never imports `FormsModule`; use Reactive Forms.
327
209
 
328
- `gog-select` and `gog-multiselect` do not require a `{ id, name }` shape. `optionLabel`,
329
- `optionValue` and `optionDisabled` each take a property path — dot-paths included — or a
330
- function:
331
-
332
- ```html
333
- <gog-select
334
- [options]="members"
335
- optionLabel="profile.fullName"
336
- optionValue="uuid"
337
- optionDisabled="suspended"
338
- [(value)]="memberId"
339
- />
340
- ```
210
+ ## Documentation
341
211
 
342
- Set `[optionValue]="null"` and the control emits the option object itself, so a selection round-trips
343
- without a lookup table:
344
-
345
- ```html
346
- <gog-select [options]="members" [optionLabel]="nameOf" [optionValue]="null" [(value)]="member">
347
- <ng-template gogDropdownOption let-m let-label="label">
348
- {{ label }} — {{ $any(m).profile.role }}
349
- </ng-template>
350
- </gog-select>
351
- ```
212
+ | | |
213
+ | --- | --- |
214
+ | **[`AGENTS.md`](./AGENTS.md)** | the full API reference — every input, output, slot, type and default, per component. Ships in this package. |
215
+ | **[`TOKENS.md`](./TOKENS.md)** | every `--gog-*` token, generated from `theme.css` |
216
+ | [CHANGELOG](https://github.com/GuildOfGleks/gleks_web_ui/blob/master/projects/gleks/ui/CHANGELOG.md) | release history |
352
217
 
353
- The defaults are `'name'` / `'id'` / `'disabled'`, which is what `GogDropdownOption` describes
354
- so code written against that shape keeps working unchanged.
355
-
356
- `gogTooltip` is a hover/focus tooltip directive, not a component — drop it on any element,
357
- a `gog-*` component's own host tag or a plain native one
358
- (`<button gogTooltip="Save changes">`, `<gog-chip [gogTooltip]="hint">`). Content is a
359
- string or a `TemplateRef`; `gogTooltipPosition` (`'auto'` default, or an explicit side),
360
- `gogTooltipShowDelay` (`300`ms default), `gogTooltipHideDelay` (`100`ms default) and
361
- `gogTooltipDisabled` inputs, the first three also configurable app-wide via
362
- `GOG_CONFIG.tooltip`.
363
-
364
- `gog-inputfield` and `gog-textarea` forward the native attribute space of the element they
365
- wrap, so nothing is out of reach because it is hidden inside a component: `readonly`,
366
- `maxlength`, `minlength`, `spellcheck`, plus `pattern` and `inputMode` on the input, and the
367
- `type` values that render as a text field (`text`, `password`, `email`, `number`, `search`,
368
- `tel`, `url`, `date`, `time`, `datetime-local` — see `GogInputType`). `readonly` keeps the
369
- value focusable and submitted but blocks edits, so the clear button and a number field's
370
- stepper both stand down while it is on. `autofocus` is deliberately **not** forwarded: moving
371
- focus without the user asking is disorienting for keyboard and screen-reader users, and an app
372
- that genuinely needs it can focus the element itself.
373
-
374
- Every field also generates its own `id` when you don't pass `inputId`, so the label is always
375
- associated with the control and the error message is always reachable through
376
- `aria-describedby`. Pass `inputId` only when something outside the component has to reference
377
- the field by a known id.
378
-
379
- `gog-checkbox`, `gog-inputfield`, `gog-select`, `gog-multiselect`, `gog-slider` and
380
- `gog-textarea` implement `ControlValueAccessor` and are built for Reactive Forms — use
381
- `[formControl]` or `formControlName`. The library itself never imports `FormsModule` or
382
- uses `ngModel`; `[(ngModel)]` is not tested against these components and isn't a
383
- supported usage path. With a form control attached, the error message appears once the
384
- control is both touched and invalid; without one it shows for as long as `errorMessage`
385
- is non-empty, and the consumer decides when to clear it.
386
-
387
- `gog-inputfield`, `gog-select`, `gog-multiselect` and `gog-textarea` all accept a
388
- `floatLabel` input — `'in'` (floats up but stays inside the border), `'on'` (floats to sit
389
- centered on the top border line) or `'over'` (floats fully above the field, outside the
390
- border) — plus `floatLabelShowPlaceholder` to reveal the field's own `placeholder` once the
391
- label has floated out of the way (it stays hidden the whole time otherwise, since the
392
- resting label already sits where it would). Both default to off/`false` and are settable
393
- per instance or app-wide via `GOG_CONFIG.floatLabel`.
218
+ `AGENTS.md` is written for an AI coding assistant working in your project, but it is the most
219
+ complete API reference either way point your assistant at it and it will stop guessing.
394
220
 
395
221
  ## License
396
222
 
397
223
  Apache-2.0 © Roman Malitskyi
224
+
225
+ Built-in icons are [Lucide](https://lucide.dev) glyphs, inlined so the package keeps zero runtime
226
+ dependencies. Lucide is ISC licensed; portions are held by Cole Bemis 2013–2022 as part of
227
+ Feather (MIT), all others by Lucide Contributors 2022 — full notice in
228
+ `src/lib/shared/icons.ts`.