@guildofgleks/ui 21.4.0 → 21.4.2
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 +36 -13
- package/CHANGELOG.md +957 -0
- package/README.md +99 -361
- package/fesm2022/guildofgleks-ui.mjs +37 -10
- package/fesm2022/guildofgleks-ui.mjs.map +1 -1
- package/package.json +10 -1
- package/types/guildofgleks-ui.d.ts +2 -2
package/README.md
CHANGED
|
@@ -4,184 +4,126 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
dependencies are `@angular/core`, `@angular/common` and `@angular/forms`.
|
|
7
|
+
# @guildofgleks/ui
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
## Features
|
|
13
|
-
|
|
14
|
-
- 27 standalone components and 3 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
|
-
|
|
16
|
+
## Why this one
|
|
38
17
|
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
46
|
-
|
|
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" //
|
|
41
|
+
"src/styles.scss" // yours, after the baseline so it wins
|
|
54
42
|
]
|
|
55
43
|
```
|
|
56
44
|
|
|
57
|
-
> Up to 21.3.1
|
|
58
|
-
>
|
|
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
|
-
|
|
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
|
|
80
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
|
110
|
-
|
|
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
|
-
|
|
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:
|
|
118
|
-
|
|
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.
|
|
87
|
+
Every value the components paint with lives in `styles/theme.css`, in three layers:
|
|
122
88
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
available at runtime as `GOG_TOKEN_GROUPS`, typed by `GogTokenName`.
|
|
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.
|
|
126
91
|
|
|
127
|
-
**
|
|
128
|
-
|
|
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
|
-
**
|
|
143
|
-
|
|
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; /*
|
|
108
|
+
--gog-btn-bg: rebeccapurple; /* wins over .gog-btn--primary */
|
|
149
109
|
}
|
|
150
110
|
```
|
|
151
111
|
|
|
152
|
-
|
|
153
|
-
|
|
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).
|
|
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`.
|
|
159
114
|
|
|
160
|
-
|
|
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`.
|
|
115
|
+
### Light, dark and your own
|
|
165
116
|
|
|
166
|
-
The
|
|
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.
|
|
170
|
-
|
|
171
|
-
### Light and dark
|
|
172
|
-
|
|
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();
|
|
179
|
-
this.theme.setTheme('cyberpunk'); // any
|
|
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 `
|
|
184
|
-
Persisting the choice and following the OS setting are opt-in
|
|
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,146 +131,47 @@ provideGogConfig({
|
|
|
189
131
|
});
|
|
190
132
|
```
|
|
191
133
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
229
|
-
|
|
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.
|
|
237
|
-
|
|
238
|
-
### Optional font preset
|
|
143
|
+
Fonts are left alone on purpose (system stacks, no webfont download). Add
|
|
144
|
+
`@guildofgleks/ui/styles/fonts.css` for the showcase's typography.
|
|
239
145
|
|
|
240
|
-
|
|
241
|
-
download. For the showcase's typography (Cinzel / Inter / JetBrains Mono, pulled from
|
|
242
|
-
Google Fonts) add `@guildofgleks/ui/styles/fonts.css` as well.
|
|
146
|
+
## App-wide configuration
|
|
243
147
|
|
|
244
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
-
`
|
|
266
|
-
and
|
|
267
|
-
|
|
268
|
-
rather than replacing it, so a route can override one field and inherit the rest.
|
|
269
|
-
|
|
270
|
-
### Your own icons
|
|
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
|
-
|
|
273
|
-
the form-state set (success, error, warning, info), and the common actions (search, plus, minus,
|
|
274
|
-
trash, pencil, download, upload, refresh, filter, external-link, copy), plus `menu`,
|
|
275
|
-
`more-horizontal`/`more-vertical`, `user`, `settings`, `lock`, `mail`, `calendar`, `clock`, and
|
|
276
|
-
`star`/`star-filled` for a rating or favourite toggle.
|
|
277
|
-
|
|
278
|
-
Register your own by name and they behave like built-ins — including in every component that
|
|
279
|
-
takes an icon *name* rather than a template:
|
|
165
|
+
Icons work the same way — 41 Lucide glyphs ship with the package, and your own register by name:
|
|
280
166
|
|
|
281
167
|
```ts
|
|
282
|
-
|
|
283
|
-
import { provideGogIcons } from '@guildofgleks/ui';
|
|
284
|
-
|
|
285
|
-
providers: [
|
|
286
|
-
provideGogIcons({
|
|
287
|
-
cart: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">…</svg>',
|
|
288
|
-
}),
|
|
289
|
-
];
|
|
168
|
+
provideGogIcons({ cart: '<svg viewBox="0 0 24 24">…</svg>' });
|
|
290
169
|
```
|
|
291
170
|
|
|
292
171
|
```html
|
|
293
|
-
<gog-icon name="cart" />
|
|
294
|
-
<gog-tag iconName="cart">In basket</gog-tag>
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
A registered name overrides a built-in of the same name, so you can swap the library's whole
|
|
298
|
-
look — its checkmark, its chevrons — without touching a component. Nested calls layer onto the
|
|
299
|
-
parent set. An unknown name renders nothing and warns in dev mode rather than throwing.
|
|
300
|
-
|
|
301
|
-
Write the SVG with a `viewBox` and `currentColor`, and no width/height: sizing and stroke width
|
|
302
|
-
come from the `--gog-icon-*` tokens, which is what makes a registered icon scale and colour like
|
|
303
|
-
a built-in. The markup is inserted with `bypassSecurityTrustHtml` (Angular's sanitizer strips
|
|
304
|
-
SVG outright), so register only static markup you authored — never a string built from user
|
|
305
|
-
input.
|
|
306
|
-
|
|
307
|
-
### Translating the library
|
|
308
|
-
|
|
309
|
-
`labels` carries every fixed string the components render themselves — the ones you never write
|
|
310
|
-
markup for. Set them once instead of on every control:
|
|
311
|
-
|
|
312
|
-
```ts
|
|
313
|
-
provideGogConfig({
|
|
314
|
-
labels: {
|
|
315
|
-
clear: 'Очистить',
|
|
316
|
-
clearSelection: 'Очистить выбор',
|
|
317
|
-
selectAll: 'Выбрать все',
|
|
318
|
-
closeDialog: 'Закрыть',
|
|
319
|
-
previousPage: 'Предыдущая страница',
|
|
320
|
-
today: 'Сегодня',
|
|
321
|
-
// interpolating labels take a function, since word order and agreement vary by language
|
|
322
|
-
page: (page, isCurrent) =>
|
|
323
|
-
isCurrent ? `Страница ${page}, текущая` : `Перейти на страницу ${page}`,
|
|
324
|
-
},
|
|
325
|
-
});
|
|
172
|
+
<gog-icon name="cart" /> <gog-tag iconName="cart">In basket</gog-tag>
|
|
326
173
|
```
|
|
327
174
|
|
|
328
|
-
The full key list is on `GogGlobalConfig['labels']`. Strings that describe *one* control rather
|
|
329
|
-
than library chrome — a field's `label`, a button's `ariaLabel` — are deliberately not in here;
|
|
330
|
-
those stay per instance.
|
|
331
|
-
|
|
332
175
|
## Components
|
|
333
176
|
|
|
334
177
|
| Group | Components |
|
|
@@ -341,150 +184,45 @@ those stay per instance.
|
|
|
341
184
|
| Feedback | `gog-spinner`, `gog-spinner-overlay`, `gog-progressbar`, `gog-skeleton` |
|
|
342
185
|
| Content | `gog-icon` |
|
|
343
186
|
|
|
344
|
-
Directives
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
`gogAccordionHeader`, `gogAccordionContent`, `gogAccordionChevron`, `gogButtonToggleOption`,
|
|
348
|
-
`gogCheckboxIcon`, `gogColumnBody`, `gogColumnHeader`, `gogDropdownOption`,
|
|
349
|
-
`gogDropdownChevron`, `gogInputAddonStart`, `gogInputAddonEnd`, `gogMultiselectClearIcon`,
|
|
350
|
-
`gogTabHeader`, `gogTabContent`, `gogTagIcon`.
|
|
351
|
-
|
|
352
|
-
`gog-dialog` and `gog-toast-container` are the host elements the two services render into —
|
|
353
|
-
see [Services need a host element](#services-need-a-host-element).
|
|
354
|
-
|
|
355
|
-
Services: `DialogService`, `ToastService`, `ThemeService`.
|
|
356
|
-
|
|
357
|
-
`gog-collapsible` is a headless expand/collapse primitive — no owned markup, no portal.
|
|
358
|
-
Project any element as the trigger via `gogCollapsibleTrigger` and any element as the
|
|
359
|
-
panel via `gogCollapsibleContent`; `[(open)]` is two-way bindable. Useful for anything
|
|
360
|
-
that needs an inline expanding region without the overlay behavior of `gog-select`/
|
|
361
|
-
`gog-multiselect` — e.g. a collapsible group of links in a nav sidebar.
|
|
362
|
-
|
|
363
|
-
### Options come from your own objects
|
|
364
|
-
|
|
365
|
-
`gog-select` and `gog-multiselect` do not require a `{ id, name }` shape. `optionLabel`,
|
|
366
|
-
`optionValue` and `optionDisabled` each take a property path — dot-paths included — or a
|
|
367
|
-
function:
|
|
368
|
-
|
|
369
|
-
```html
|
|
370
|
-
<gog-select
|
|
371
|
-
[options]="members"
|
|
372
|
-
optionLabel="profile.fullName"
|
|
373
|
-
optionValue="uuid"
|
|
374
|
-
optionDisabled="suspended"
|
|
375
|
-
[(value)]="memberId"
|
|
376
|
-
/>
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
Set `[optionValue]="null"` and the control emits the option object itself, so a selection round-trips
|
|
380
|
-
without a lookup table:
|
|
381
|
-
|
|
382
|
-
```html
|
|
383
|
-
<gog-select [options]="members" [optionLabel]="nameOf" [optionValue]="null" [(value)]="member">
|
|
384
|
-
<ng-template gogDropdownOption let-m let-label="label">
|
|
385
|
-
{{ label }} — {{ $any(m).profile.role }}
|
|
386
|
-
</ng-template>
|
|
387
|
-
</gog-select>
|
|
388
|
-
```
|
|
389
|
-
|
|
390
|
-
The defaults are `'name'` / `'id'` / `'disabled'`, which is what `GogDropdownOption` describes —
|
|
391
|
-
so code written against that shape keeps working unchanged.
|
|
392
|
-
|
|
393
|
-
`gog-table` works two ways. By default it owns the data: give it `value` and it sorts and pages
|
|
394
|
-
in memory. With `[lazy]="true"` it hands both to the server — `value` is the current page,
|
|
395
|
-
already sorted, `totalRecords` says how many rows exist in total, and `gogSortChange` /
|
|
396
|
-
`gogPageChange` are the refetch signals. Row selection is `selectionMode` plus a two-way
|
|
397
|
-
`[(selection)]`; set `dataKey` so rows are matched by id rather than object identity, or a
|
|
398
|
-
refetch will drop the selection.
|
|
187
|
+
**Directives:** `gogButton` (a link that looks like a button), `gogTooltip`, `gogBadge`,
|
|
188
|
+
`gogCollapsibleTrigger`, `gogCollapsibleContent`.
|
|
189
|
+
**Services:** `DialogService`, `ToastService`, `ThemeService`.
|
|
399
190
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
[value]="page()"
|
|
403
|
-
[lazy]="true"
|
|
404
|
-
[totalRecords]="total()"
|
|
405
|
-
[pageSize]="20"
|
|
406
|
-
[loading]="loading()"
|
|
407
|
-
selectionMode="multiple"
|
|
408
|
-
[(selection)]="selected"
|
|
409
|
-
dataKey="id"
|
|
410
|
-
(gogSortChange)="sort.set($event); reload()"
|
|
411
|
-
(gogPageChange)="pageNumber.set($event); reload()"
|
|
412
|
-
>
|
|
413
|
-
<gog-column field="name" header="Name" [sortable]="true" />
|
|
414
|
-
<gog-column field="email" header="Email" />
|
|
415
|
-
</gog-table>
|
|
416
|
-
```
|
|
191
|
+
Fifteen more slot directives replace a component's markup rather than configuring it —
|
|
192
|
+
`gogColumnBody`, `gogInputAddonStart`, `gogDropdownOption` and friends.
|
|
417
193
|
|
|
418
|
-
|
|
419
|
-
`showPageSizeSelect` or app-wide through `GOG_CONFIG.paginator`. Give it `totalRecords` rather
|
|
420
|
-
than `totalPages` and it works out the page count from `pageSize` itself:
|
|
421
|
-
|
|
422
|
-
```html
|
|
423
|
-
<gog-paginator [(page)]="page" [(pageSize)]="size" [totalRecords]="items().length" [showPageSizeSelect]="true" />
|
|
424
|
-
```
|
|
194
|
+
A few things worth knowing before you reach for a workaround:
|
|
425
195
|
|
|
426
|
-
`
|
|
427
|
-
|
|
428
|
-
|
|
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.
|
|
429
209
|
|
|
430
|
-
|
|
431
|
-
navigates, use the `gogButton` directive on your own element instead — it applies the same look
|
|
432
|
-
and nothing is brokered through an input, so `routerLink`, `href`, `target` and any directive of
|
|
433
|
-
your own keep working:
|
|
210
|
+
## Documentation
|
|
434
211
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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 |
|
|
439
217
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
them.
|
|
443
|
-
|
|
444
|
-
`gogTooltip` is a hover/focus tooltip directive, not a component — drop it on any element,
|
|
445
|
-
a `gog-*` component's own host tag or a plain native one
|
|
446
|
-
(`<button gogTooltip="Save changes">`, `<gog-chip [gogTooltip]="hint">`). Content is a
|
|
447
|
-
string or a `TemplateRef`; `gogTooltipPosition` (`'auto'` default, or an explicit side),
|
|
448
|
-
`gogTooltipShowDelay` (`300`ms default), `gogTooltipHideDelay` (`100`ms default) and
|
|
449
|
-
`gogTooltipDisabled` inputs, the first three also configurable app-wide via
|
|
450
|
-
`GOG_CONFIG.tooltip`.
|
|
451
|
-
|
|
452
|
-
`gog-inputfield` and `gog-textarea` forward the native attribute space of the element they
|
|
453
|
-
wrap, so nothing is out of reach because it is hidden inside a component: `readonly`,
|
|
454
|
-
`maxlength`, `minlength`, `spellcheck`, plus `pattern` and `inputMode` on the input, and the
|
|
455
|
-
`type` values that render as a text field (`text`, `password`, `email`, `number`, `search`,
|
|
456
|
-
`tel`, `url`, `date`, `time`, `datetime-local` — see `GogInputType`). `readonly` keeps the
|
|
457
|
-
value focusable and submitted but blocks edits, so the clear button and a number field's
|
|
458
|
-
stepper both stand down while it is on. `autofocus` is deliberately **not** forwarded: moving
|
|
459
|
-
focus without the user asking is disorienting for keyboard and screen-reader users, and an app
|
|
460
|
-
that genuinely needs it can focus the element itself.
|
|
461
|
-
|
|
462
|
-
Every field also generates its own `id` when you don't pass `inputId`, so the label is always
|
|
463
|
-
associated with the control and the error message is always reachable through
|
|
464
|
-
`aria-describedby`. Pass `inputId` only when something outside the component has to reference
|
|
465
|
-
the field by a known id.
|
|
466
|
-
|
|
467
|
-
`gog-checkbox`, `gog-inputfield`, `gog-select`, `gog-multiselect`, `gog-slider` and
|
|
468
|
-
`gog-textarea` implement `ControlValueAccessor` and are built for Reactive Forms — use
|
|
469
|
-
`[formControl]` or `formControlName`. The library itself never imports `FormsModule` or
|
|
470
|
-
uses `ngModel`; `[(ngModel)]` is not tested against these components and isn't a
|
|
471
|
-
supported usage path. With a form control attached, the error message appears once the
|
|
472
|
-
control is both touched and invalid; without one it shows for as long as `errorMessage`
|
|
473
|
-
is non-empty, and the consumer decides when to clear it.
|
|
474
|
-
|
|
475
|
-
`gog-inputfield`, `gog-select`, `gog-multiselect` and `gog-textarea` all accept a
|
|
476
|
-
`floatLabel` input — `'in'` (floats up but stays inside the border), `'on'` (floats to sit
|
|
477
|
-
centered on the top border line) or `'over'` (floats fully above the field, outside the
|
|
478
|
-
border) — plus `floatLabelShowPlaceholder` to reveal the field's own `placeholder` once the
|
|
479
|
-
label has floated out of the way (it stays hidden the whole time otherwise, since the
|
|
480
|
-
resting label already sits where it would). Both default to off/`false` and are settable
|
|
481
|
-
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.
|
|
482
220
|
|
|
483
221
|
## License
|
|
484
222
|
|
|
485
223
|
Apache-2.0 © Roman Malitskyi
|
|
486
224
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
`src/lib/shared/icons.ts
|
|
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`.
|
|
@@ -836,6 +836,38 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
|
|
|
836
836
|
}]
|
|
837
837
|
}] });
|
|
838
838
|
|
|
839
|
+
/**
|
|
840
|
+
* Which `data-theme` an overlay rendered into `<body>` has to carry, if any.
|
|
841
|
+
*
|
|
842
|
+
* A panel or tooltip bubble is appended to `<body>`, which puts it outside whatever subtree it
|
|
843
|
+
* was opened from. `data-theme` can be scoped to any subtree — several themes rendering side by
|
|
844
|
+
* side is a documented use — so an overlay opened inside one of those has to be told which theme
|
|
845
|
+
* it belongs to, or it silently picks up the document's instead.
|
|
846
|
+
*
|
|
847
|
+
* **But only when the theme really is scoped.** When the nearest themed ancestor is the document
|
|
848
|
+
* element, copying the attribute is not merely redundant, it is actively wrong: the overlay
|
|
849
|
+
* already inherits everything from `<html>` through `<body>`, and re-stating `data-theme` on it
|
|
850
|
+
* makes it match `theme.css`'s derived layer (`:root, [data-theme]`) *locally*. That re-declares
|
|
851
|
+
* every component token on the overlay itself, resolved against the plain preset palette — which
|
|
852
|
+
* discards anything set on `<html>` that is not part of that preset. Custom properties written
|
|
853
|
+
* inline on `:root` are the case that bites: a live theme editor sets them there, the page
|
|
854
|
+
* follows, and every portal keeps rendering the un-edited theme.
|
|
855
|
+
*
|
|
856
|
+
* So: return the theme only for a genuinely scoped ancestor, and let inheritance do the work
|
|
857
|
+
* otherwise.
|
|
858
|
+
*
|
|
859
|
+
* Known limitation, currently unreachable: a *scoped* theme that is itself being edited through
|
|
860
|
+
* inline custom properties would still lose those on the overlay, since only the attribute is
|
|
861
|
+
* carried across. Copying resolved values instead would mean reading ~1200 properties on every
|
|
862
|
+
* open, which is not worth it for a case nothing does yet.
|
|
863
|
+
*/
|
|
864
|
+
function scopedOverlayTheme(themeSource, documentElement) {
|
|
865
|
+
const themedAncestor = themeSource?.closest('[data-theme]');
|
|
866
|
+
if (!themedAncestor || themedAncestor === documentElement)
|
|
867
|
+
return null;
|
|
868
|
+
return themedAncestor.getAttribute('data-theme');
|
|
869
|
+
}
|
|
870
|
+
|
|
839
871
|
/**
|
|
840
872
|
* Renders a template into `document.body` while keeping it wired to the declaring
|
|
841
873
|
* component: the view is stamped with that component's style encapsulation and reads
|
|
@@ -866,11 +898,9 @@ class GogDropdownOverlay {
|
|
|
866
898
|
this.detach();
|
|
867
899
|
this.hostEl = this.document.createElement('div');
|
|
868
900
|
this.hostEl.classList.add('gog-overlay-host');
|
|
869
|
-
//
|
|
870
|
-
//
|
|
871
|
-
|
|
872
|
-
const themedAncestor = themeSource?.closest('[data-theme]');
|
|
873
|
-
const theme = themedAncestor?.getAttribute('data-theme');
|
|
901
|
+
// Only for a genuinely scoped theme — see `scopedOverlayTheme` for why copying it when the
|
|
902
|
+
// theme sits on `<html>` breaks live-edited themes.
|
|
903
|
+
const theme = scopedOverlayTheme(themeSource, this.document.documentElement);
|
|
874
904
|
if (theme) {
|
|
875
905
|
this.hostEl.setAttribute('data-theme', theme);
|
|
876
906
|
}
|
|
@@ -6003,11 +6033,8 @@ class GogTooltipOverlay {
|
|
|
6003
6033
|
this.detach();
|
|
6004
6034
|
this.componentRef = this.viewContainerRef.createComponent(GogTooltipBubbleComponent);
|
|
6005
6035
|
const el = this.componentRef.location.nativeElement;
|
|
6006
|
-
//
|
|
6007
|
-
|
|
6008
|
-
// assumed from the document.
|
|
6009
|
-
const themedAncestor = themeSource?.closest('[data-theme]');
|
|
6010
|
-
const theme = themedAncestor?.getAttribute('data-theme');
|
|
6036
|
+
// Only for a genuinely scoped theme — see `scopedOverlayTheme`.
|
|
6037
|
+
const theme = scopedOverlayTheme(themeSource, this.document.documentElement);
|
|
6011
6038
|
if (theme) {
|
|
6012
6039
|
el.setAttribute('data-theme', theme);
|
|
6013
6040
|
}
|