@guildofgleks/ui 21.12.0 → 21.13.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 +179 -28
- package/CHANGELOG.md +612 -0
- package/README.md +23 -3
- package/TOKENS.md +52 -51
- package/fesm2022/guildofgleks-ui-datepicker.mjs +20 -0
- package/fesm2022/guildofgleks-ui-datepicker.mjs.map +1 -0
- package/fesm2022/guildofgleks-ui-dialog.mjs +20 -0
- package/fesm2022/guildofgleks-ui-dialog.mjs.map +1 -0
- package/fesm2022/guildofgleks-ui-shared.mjs +4692 -0
- package/fesm2022/guildofgleks-ui-shared.mjs.map +1 -0
- package/fesm2022/guildofgleks-ui-table.mjs +20 -0
- package/fesm2022/guildofgleks-ui-table.mjs.map +1 -0
- package/fesm2022/guildofgleks-ui.mjs +607 -3760
- package/fesm2022/guildofgleks-ui.mjs.map +1 -1
- package/package.json +17 -1
- package/styles/button.css +5 -5
- package/styles/presets/parchment.css +89 -86
- package/styles/presets/terminal.css +107 -102
- package/styles/theme.css +128 -23
- package/types/guildofgleks-ui-datepicker.d.ts +1 -0
- package/types/guildofgleks-ui-dialog.d.ts +1 -0
- package/types/guildofgleks-ui-shared.d.ts +2000 -0
- package/types/guildofgleks-ui-table.d.ts +1 -0
- package/types/guildofgleks-ui.d.ts +279 -1226
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,618 @@ All notable changes to `@guildofgleks/ui` are documented here. Format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.0.0/); this project has not yet
|
|
5
5
|
reached 1.0, so breaking changes may land in minor versions.
|
|
6
6
|
|
|
7
|
+
## [21.13.0] - 13.09.2026
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`gog-table` windows its rows — `virtualize`, off by default, and it needed a second
|
|
12
|
+
primitive.** The dropdowns' `GogVirtualWindow` takes one row height, and a table cannot supply
|
|
13
|
+
one: **a table row's height cannot be pinned**, because `height` on a `<tr>` _and_ on a `<td>` is
|
|
14
|
+
a **minimum** in table layout. One cell taken from 40 to 600 characters measured 39px → 173.75px
|
|
15
|
+
under `table-layout: fixed`, with the column width unchanged, and neither `height` on the row nor
|
|
16
|
+
on its cells moved it.
|
|
17
|
+
|
|
18
|
+
So `GogVariableWindow` (internal, `lib/shared`): a height per row — measured where a row has
|
|
19
|
+
rendered, estimated everywhere else — with a prefix sum, so the range is a binary search rather
|
|
20
|
+
than a division. The three dropdowns keep the fixed-pitch one, which is exact where this is only
|
|
21
|
+
ever as right as the rows it has seen.
|
|
22
|
+
|
|
23
|
+
**It requires `maxHeight` and `fullWidth`, does nothing without either, and says which is missing
|
|
24
|
+
in a dev-mode warning.** Both are measured constraints rather than preferences: without
|
|
25
|
+
`maxHeight` the table never scrolls vertically on its own, so there is no viewport to window
|
|
26
|
+
against; and `fullWidth="false"` means `table-layout: auto`, where the browser sizes columns from
|
|
27
|
+
the rows that are _rendered_ — measured, rendering 2 of 24 rows moved columns by up to 7.8px, so
|
|
28
|
+
a windowed table would shift its own columns as you scroll.
|
|
29
|
+
|
|
30
|
+
The `<tbody>` spacers are `<tr>`s, since a table body takes rows and nothing else; a row honours
|
|
31
|
+
an explicit height exactly, including at 400 000px. **The ceiling is Chrome's, not the
|
|
32
|
+
library's:** an element clamps at 33 554 426px, about 745 000 rows at 45px — and a dev-mode
|
|
33
|
+
warning says so once when a table passes it, because past that the rows stay correct while the
|
|
34
|
+
scrollbar stops reaching the end of the data, which is not a symptom anyone traces back to a row
|
|
35
|
+
count.
|
|
36
|
+
|
|
37
|
+
**Three indices would have changed meaning silently, and all three are public promises**:
|
|
38
|
+
`gogRowClick`'s `index` (documented as the index within the page), the `showRowNumbers` column,
|
|
39
|
+
and `GogColumnBodyContext.index` in every consumer's cell template. Under a window `$index` is
|
|
40
|
+
the position in the rendered slice, so each keeps its name, its type and its documentation while
|
|
41
|
+
meaning something else. Specs fail without the fix with `expected +0 to be 196` and
|
|
42
|
+
`expected '1' to be '197'`. Checked rather than assumed on the other side: `toggleAllOnPage` and
|
|
43
|
+
the header checkbox still read the page, not the window — select-all would otherwise have
|
|
44
|
+
selected the twenty rows on screen while claiming a thousand.
|
|
45
|
+
|
|
46
|
+
**Two of the three things the plan called hard were not.** The sticky header lives in `<thead>`,
|
|
47
|
+
which a window over `<tbody>` never touches — pinned exactly at the viewport top across 200 000px
|
|
48
|
+
of scroll past a 400 000px spacer. The selection column is an ordinary `<td>` per row. That is
|
|
49
|
+
the third release running where the predicted hard part cost nothing.
|
|
50
|
+
|
|
51
|
+
**And one bug came out of the live pass that no spec would have suggested.** The effect that
|
|
52
|
+
clears cached heights on a new page or sort calls `reset()`, and `reset()` _reads_ the
|
|
53
|
+
measurement signal to decide whether it has anything to clear — so, called bare inside an effect,
|
|
54
|
+
that read became one of the effect's dependencies. Measuring wrote the signal, the effect re-ran,
|
|
55
|
+
and it cleared the measurements that had just been taken. **Nothing looked wrong**: the right
|
|
56
|
+
rows rendered at the right heights, and only the scroll height was quietly the estimate times the
|
|
57
|
+
row count, for ever. `untracked` is the fix and a regression spec now asserts it — it fails
|
|
58
|
+
without it on a spacer that is exactly `982 × 30`, a whole number of estimates.
|
|
59
|
+
|
|
60
|
+
The estimate is seeded from the **median** of the first rendered batch rather than its first row.
|
|
61
|
+
Taking row 0 was the first version, and the showcase's own demo is why it is wrong: it makes
|
|
62
|
+
every seventh row wrap, row 0 among them, so the estimate came out 65% high and all 10 000 rows
|
|
63
|
+
were sized from the one row that least resembles them.
|
|
64
|
+
|
|
65
|
+
- **`gog-select` windows its option list — `virtualize`, off by default.** Measured in Chrome on
|
|
66
|
+
one page holding two selects over the same 10 000 options: the eager panel takes **512ms** to
|
|
67
|
+
appear and builds **10 000 rows to show six**; the windowed one takes **21ms** and holds **10**.
|
|
68
|
+
Both scroll through an identical 450 008px of content, because spacers stand in for the rows
|
|
69
|
+
that are not there. A per-field input, with `GOG_CONFIG.dropdown.virtualize` as the app-wide
|
|
70
|
+
default.
|
|
71
|
+
|
|
72
|
+
**Never automatic above some row count**, which is the same call `GOG_CONFIG.ripple.enabled`
|
|
73
|
+
makes: a windowed list differs from a plain one under `Ctrl+F`, under a screen reader's "list
|
|
74
|
+
all items", and under consumer CSS targeting `:last-child`, so flipping it when enough rows
|
|
75
|
+
happen to arrive is behaviour that depends on how much data turned up — it works in development
|
|
76
|
+
and surprises in production.
|
|
77
|
+
|
|
78
|
+
Under it, `GogVirtualWindow` in `lib/shared` (added below, internal): the arithmetic and nothing
|
|
79
|
+
that touches the page, so the component keeps its own scroller. The four things
|
|
80
|
+
`docs/virtualization.md` said were easy to get wrong all needed work, and three of them landed
|
|
81
|
+
differently from the plan:
|
|
82
|
+
|
|
83
|
+
- **The count stays honest.** `aria-setsize` and `aria-posinset` carry the real list and the
|
|
84
|
+
real index, so a listbox holding twenty rows is announced as ten thousand items. Set only
|
|
85
|
+
while windowing — an unwindowed list has every row present and the browser's own count is
|
|
86
|
+
both right and free.
|
|
87
|
+
- **The keyboard moves an index, not an element.** ArrowUp from the trigger means option 10 000,
|
|
88
|
+
which is not in the DOM to be focused: the index moves first, the scroll follows, and the row
|
|
89
|
+
is focused after the render that stamps it. `nextRovingFocusIndex` already existed as the
|
|
90
|
+
index half of the roving-focus helper, so the navigation rules — wrapping, skipping disabled,
|
|
91
|
+
Home/End meaning first/last _reachable_ — did not have to be written twice.
|
|
92
|
+
- **The panel's height is read from the scroller, not from a token.** The plan asked for a
|
|
93
|
+
`ResizeObserver`; `gog-scroll` already runs one and already coalesces scroll and resize into
|
|
94
|
+
one rAF-batched emission carrying both `scrollTop` and `clientHeight`, so `(gogScroll)` is the
|
|
95
|
+
whole answer and a second observer would have measured the same element a frame later. A
|
|
96
|
+
reported height of **zero** is ignored rather than believed: the scroller's first emission can
|
|
97
|
+
land before the panel has a height, and taking it literally renders the whole list for a frame
|
|
98
|
+
— the one thing the seed exists to prevent.
|
|
99
|
+
- **Filtering resets the window and the scroller together** — and this one is narrower than the
|
|
100
|
+
plan claimed. `GogVirtualWindow` already clamps a scroll position past the end of its own
|
|
101
|
+
list, so filtering 10 000 options down to three cannot render rows 400–420 of a three-row
|
|
102
|
+
list; the plan's own example is handled for free. The real case is a filtered list still long
|
|
103
|
+
enough to scroll, where the old position clamps to a **valid** position in the new list and
|
|
104
|
+
the search shows the end of its results. The first spec written for this passed with the reset
|
|
105
|
+
removed, which is how that was found.
|
|
106
|
+
|
|
107
|
+
One thing the plan did not have at all. **A row scrolled out of the window is unmounted, and an
|
|
108
|
+
unmounted element holding focus drops it on `<body>`** — where an open panel has no keyboard:
|
|
109
|
+
Escape does not close it and the arrows scroll the page. So a mouse scroll that would take the
|
|
110
|
+
focused row away hands focus back to the trigger first, which Escape and ArrowDown both work
|
|
111
|
+
from. It is checked before the re-render rather than after, because once the row is gone there
|
|
112
|
+
is nothing left to ask whether it was the focused one. This is a cost a plain list does not pay
|
|
113
|
+
and part of why windowing is opt-in.
|
|
114
|
+
|
|
115
|
+
`gog-multiselect`, `gog-autocomplete` and `gog-table` do not window yet.
|
|
116
|
+
|
|
117
|
+
- **`npm run check:layering` — the library's own layering, as a build failure.** Two rules:
|
|
118
|
+
nothing in `lib/shared/` may import a component or a service, and no two units may import each
|
|
119
|
+
other. It exists because both were being broken and neither hurt anything: **a cycle is
|
|
120
|
+
invisible until something tries to cut along it.**
|
|
121
|
+
|
|
122
|
+
`shared/config.ts` imported `ToastPosition` from `services/toast-service`, which imports
|
|
123
|
+
`shared/config`. `shared/tooltip-overlay.ts` imported a component out of `components/tooltip/`,
|
|
124
|
+
which imports `shared/`. In one flat bundle, harmless. To a secondary entry point, fatal —
|
|
125
|
+
ng-packagr refuses a cycle between entry points outright — which is what makes this the
|
|
126
|
+
prerequisite for that work rather than tidying.
|
|
127
|
+
|
|
128
|
+
Both are fixed: `ToastPosition` moved to `shared/types.ts`, where
|
|
129
|
+
`gleks-ui-library.instructions.md` already said public types live, and is re-exported from the
|
|
130
|
+
service so the package's surface is unchanged; `tooltip-overlay.ts` moved into
|
|
131
|
+
`components/tooltip/`, which is its only consumer and the component it stamps. 36 units, no
|
|
132
|
+
cycles.
|
|
133
|
+
|
|
134
|
+
- **`npm run check:glyph-box` — the first check in this repo that measures a rendering.** Every
|
|
135
|
+
other one reads source; this rule cannot be read honestly from source, because a glyph is
|
|
136
|
+
`--gog-icon-size` (1.2em) of its element's resolved font-size and **an `em` attaches to the
|
|
137
|
+
element carrying the property, not the one the value was written for**. Guessing that produced a
|
|
138
|
+
"fix" 25% worse at `slg` in this very release. So it serves the prerendered `ui-showcase`, walks
|
|
139
|
+
all 46 routes in Playwright against the installed Chrome, and compares every `<gog-icon>`'s
|
|
140
|
+
`<svg>` to the element holding it. 580 icons.
|
|
141
|
+
|
|
142
|
+
The rule has **no exemption list**, which no other check here can say: a box roomier than its
|
|
143
|
+
mark is fine, smaller never is.
|
|
144
|
+
|
|
145
|
+
**Two things went wrong in the check before it was right, and both are the lesson.** It first
|
|
146
|
+
walked every route on one page, so findings depended on visit order — the showcase persists
|
|
147
|
+
theme and density, and a route measured after the themes page inherited whatever it had left
|
|
148
|
+
set. And it compared against the _content_ box, which reported `gog-checkbox`: a 12px tick
|
|
149
|
+
spanning its own 2px outline, which is what a checkbox is. **The border box is the box.**
|
|
150
|
+
|
|
151
|
+
- **`npm run check:tokens` rule K — a token `theme.css` declares that nothing reads.** The mirror
|
|
152
|
+
of rule F, which has always caught the opposite direction: a `var()` read with no declaration.
|
|
153
|
+
Nothing caught a declaration with no reader, so a component could ship a documented knob wired to
|
|
154
|
+
nothing and every gate this project has would pass. `--gog-menu-panel-gap` was exactly that for
|
|
155
|
+
the whole life of `gog-menu` (fixed below).
|
|
156
|
+
|
|
157
|
+
Reads are collected from component stylesheets, the global ones, `theme.css` itself **and
|
|
158
|
+
TypeScript**, because a token read only from script is still read. That last half took two
|
|
159
|
+
passes: TypeScript spells a token two ways — a bare name handed to `resolveLengthToken`, and a
|
|
160
|
+
whole declaration built as a string (`'var(--gog-control-checkbox-box-size-lg, 32px)'`, which a
|
|
161
|
+
host binding writes) — and the first version matched only the first shape, reporting **eight
|
|
162
|
+
live tokens as dead**. A check that cannot read half its inputs is worse than no check, because
|
|
163
|
+
its findings are what you act on.
|
|
164
|
+
|
|
165
|
+
Five exemptions, all steps of a complete public scale (`--gog-elevation-0`, two spacing aliases,
|
|
166
|
+
two type steps): a scale is offered whole or it is not a scale, and the consumer is the reader.
|
|
167
|
+
**Nothing belonging to a component may be exempted** — that is the defect the rule exists for.
|
|
168
|
+
|
|
169
|
+
It cannot see a dead _chain_: if A is read only by B's declaration and nothing reads B, both look
|
|
170
|
+
live. The leaf case is the one that has ever happened here.
|
|
171
|
+
|
|
172
|
+
- **`gog-multiselect` and `gog-autocomplete` window too — the same `virtualize`, same default.**
|
|
173
|
+
Verified live in Chrome on 10 000 options each. Multiselect: 8 rows in the DOM, a 490 004px
|
|
174
|
+
scroll height matching the unwindowed list to the pixel, and row 2 036 sitting at 99 768px,
|
|
175
|
+
which is `2036 × 49 + 4` exactly. Autocomplete: 12 rows, `End` reaching option 10 000 with
|
|
176
|
+
`aria-activedescendant` on it and the panel scrolled to match.
|
|
177
|
+
|
|
178
|
+
Two things were specific to these two rather than shared, and both were real:
|
|
179
|
+
|
|
180
|
+
- **A spacer in a list that declares a row `gap` takes that gap on both sides of itself**, while
|
|
181
|
+
the window's padding already stands in for every gap between the rows it replaces. So the
|
|
182
|
+
spacer's height is the padding less one gap. `gog-multiselect` is the only one of the three
|
|
183
|
+
lists with a row gap, and uncorrected the panel was two gaps too tall and every row sat one
|
|
184
|
+
gap below where its index said. **A constant error, not an accumulating one**, which is why it
|
|
185
|
+
would have survived a reading: at the shipped 4px nothing looks wrong, it is just 4px wrong
|
|
186
|
+
everywhere.
|
|
187
|
+
- **A combobox names a row by id, and the id has to be the index in the whole list.** Focus
|
|
188
|
+
never leaves `gog-autocomplete`'s input, so the highlight travels by `aria-activedescendant`.
|
|
189
|
+
With the id keyed to the rendered slice it restarts at zero on every scroll, so the input
|
|
190
|
+
points at option 0 while the highlight paints a row in the middle — the two agreeing only
|
|
191
|
+
while the window happens to be at the top. Its spec fails that way without the fix
|
|
192
|
+
(`expected 'gog-autocomplete-35-option-10' to contain '-option-999'`).
|
|
193
|
+
|
|
194
|
+
`gog-autocomplete`'s keyboard needed nothing else: its active row was already an index into the
|
|
195
|
+
full list rather than an element, because the combobox pattern had put it there years before
|
|
196
|
+
windowing existed. Only `scrollIntoView` had to go — it needs an element, and the window's whole
|
|
197
|
+
point is that most of them are not rendered; the arithmetic knows where the row would be.
|
|
198
|
+
|
|
199
|
+
**`virtualize` and `gogLoadMore` are different halves and compose.** One keeps the records the
|
|
200
|
+
server sends small, the other keeps the rows the browser builds small; a `gogLoadMore` list that
|
|
201
|
+
has loaded 10 000 records still stamps 10 000 rows without this. The spacers are also what keep
|
|
202
|
+
`gogReachEnd` meaning the end of the data rather than the end of the window.
|
|
203
|
+
|
|
204
|
+
`gog-table` still does not window.
|
|
205
|
+
|
|
206
|
+
- **`gog-alert` — a persistent, in-flow message**, and the thing `gog-toast` cannot be. No timer,
|
|
207
|
+
no queue, no overlay, no service: it renders where you write it and stays until your app removes
|
|
208
|
+
it. `severity` (the shared `GogSeverity`, defaulting to `'accent'`), an optional `heading`, a
|
|
209
|
+
projected body, `dismissible` with a `dismissed` output, `iconName` with a `gogAlertIcon` slot,
|
|
210
|
+
and `GOG_CONFIG.labels.closeAlert`.
|
|
211
|
+
|
|
212
|
+
**`dismissed` means pressed, not removed.** The alert stays in the DOM and the app decides. A
|
|
213
|
+
component that deleted itself would take the focused element with it and drop a keyboard reader
|
|
214
|
+
onto `<body>`.
|
|
215
|
+
|
|
216
|
+
**It announces through `live`** — `'assertive' | 'polite' | 'off'`, defaulting from the severity,
|
|
217
|
+
with `GogAlertLive` exported. The announcement is a copy of the message in a **separate
|
|
218
|
+
visually-hidden region**, empty until one render after the alert mounts, because a live region
|
|
219
|
+
filled in the same pass as its own creation announces nothing — the trap
|
|
220
|
+
`gog-toast-container`'s permanently-mounted regions exist to avoid.
|
|
221
|
+
|
|
222
|
+
**Whether the component could pick `live` itself was measured, and it cannot.** The question was
|
|
223
|
+
whether it can detect having been created during the application's first render, which is the
|
|
224
|
+
one case the severity-derived default gets wrong. `@angular/core` exposes no stability member on
|
|
225
|
+
`ApplicationRef` a component can read synchronously at construction, and `afterNextRender`
|
|
226
|
+
reports its _own_ first render — which every alert has, whenever it mounts. So `live` is the
|
|
227
|
+
consumer's call with a loud note: **set `'off'` for a message that is on the page when it
|
|
228
|
+
loads.**
|
|
229
|
+
|
|
230
|
+
One thing the plan called for and the code refused: **there is no `variant` input.** It was to be
|
|
231
|
+
`GogSurfaceVariant` defaulting to `'filled'`, but `filled` in this library means _a tint_ and
|
|
232
|
+
there is no per-status tint to draw it with — `--gog-accent-pale` exists, `--gog-success-pale`
|
|
233
|
+
does not. Inventing that family here would make the alert the twelfth place a theme restates its
|
|
234
|
+
red, so the severity is a leading edge and the icon over the ordinary surface, which is the shape
|
|
235
|
+
`gog-toast` already reached for the same reason.
|
|
236
|
+
|
|
237
|
+
- **`--gog-button-focus-ring-color`**, and it is a fix as much as an addition. `gog-button` had a
|
|
238
|
+
focus-ring width and a focus-ring offset but no colour, so `button.css` reached for
|
|
239
|
+
`--gog-button-variant-hover-bg` — the _hover_ fill doing focus duty, which is the same defect
|
|
240
|
+
eight other indicators had corrected in 21.12.0. It passed on `primary` and `secondary`, whose
|
|
241
|
+
hover fill is a saturated accent, and failed wherever that fill is a wash: **`ghost` at
|
|
242
|
+
1.07–1.16:1** and the **severity `outline`** combinations at **1.79–2.65:1**, eleven failures
|
|
243
|
+
across `light`, `material`, `primeng` and `terminal`.
|
|
244
|
+
|
|
245
|
+
It defaults to `--gog-accent-color` — one colour, not one per variant. A focus ring's job is to
|
|
246
|
+
be visible against the page the button sits on, which does not change with the button's fill,
|
|
247
|
+
and this ring already sits a pixel further out than the foundation's so it reads as separate
|
|
248
|
+
even on a filled accent button. That is the answer the library's other fourteen rings give.
|
|
249
|
+
|
|
250
|
+
### Changed
|
|
251
|
+
|
|
252
|
+
- **The package has a second entry point, `@guildofgleks/ui/shared`, and it is internal.** Shared
|
|
253
|
+
types, configuration and helpers moved out of the root bundle into their own, which the root now
|
|
254
|
+
imports by package path. Nothing an app imports changes: every symbol the root exported, it still
|
|
255
|
+
exports, now named one by one instead of re-exported wholesale.
|
|
256
|
+
|
|
257
|
+
This is phase 1 of `docs/entry-points.md` in the repository, and the reason for it is a
|
|
258
|
+
measurement: **the package tree-shakes but does not code-split.** On the real CLI a button costs
|
|
259
|
+
about 10 kB gzip over an empty app, but a lazy route holding `gog-table`, `gog-datepicker`,
|
|
260
|
+
`gog-calendar` and `gog-dialog` produced a 442-byte chunk and left all four in the initial bundle
|
|
261
|
+
— 40 kB heavier than it needs to be. Splitting the heavy components out needs `GOG_CONFIG` to live
|
|
262
|
+
in exactly one bundle, which is what this entry point is; the split itself comes in the next
|
|
263
|
+
minors.
|
|
264
|
+
|
|
265
|
+
`@guildofgleks/ui/shared` resolves, but it is plumbing the package's own entry points share.
|
|
266
|
+
Import from `@guildofgleks/ui`.
|
|
267
|
+
|
|
268
|
+
- **`public-api.ts` names what it exports instead of re-exporting two modules wholesale.** The
|
|
269
|
+
defect was never which symbols are public — it was that **adding one published it silently**. A
|
|
270
|
+
helper written into `date-utils.ts` or `option-accessor.ts` became part of the package's `.d.ts`
|
|
271
|
+
the moment it was saved, with nobody deciding that and no diff showing it.
|
|
272
|
+
|
|
273
|
+
**Nothing is dropped from `date-utils`.** All twenty of its helpers and `GogDateRange` are
|
|
274
|
+
listed by name, because `AGENTS.md` already advertises `formatDate`, `parseDate` "and a family
|
|
275
|
+
of date-math helpers" — the set is supported on purpose. The list changes nothing a consumer can
|
|
276
|
+
import; it changes who decides the next one, which is the whole point.
|
|
277
|
+
|
|
278
|
+
`option-accessor` is the module where the accident had consequences, and its three functions are
|
|
279
|
+
deprecated above.
|
|
280
|
+
|
|
281
|
+
- **A warning toast and an info toast were both the accent, and an info toast was pixel-identical
|
|
282
|
+
to a plain one.** `--gog-toast-warning-color` read `--gog-accent-bright` and
|
|
283
|
+
`--gog-toast-info-color` read `--gog-accent-color` — which is also what a _typeless_ toast
|
|
284
|
+
paints. That token is the whole signal: it draws the leading stripe, the icon and the countdown
|
|
285
|
+
bar. Three of the five toast states were the accent, in every theme, since the component
|
|
286
|
+
shipped. Both now read their own role.
|
|
287
|
+
|
|
288
|
+
**Surveyed rather than assumed, after the palette fix below raised the question.** All 45
|
|
289
|
+
severity-named colour tokens across every component were resolved against their own role in all
|
|
290
|
+
eleven themes; `gog-toast` is the only one that was wrong, and it was wrong twice. `gog-alert`,
|
|
291
|
+
`gog-button`, `gogBadge`, `gog-tag` and `gog-progressbar` all derive correctly — which is what
|
|
292
|
+
made this invisible, since nothing compared a token's _name_ against the root it reads.
|
|
293
|
+
|
|
294
|
+
- **`check:tokens` rule J — a token named for a severity resolves to that severity.** A text rule
|
|
295
|
+
rather than a sweep: the declaration either names its own role or it does not, and that needs no
|
|
296
|
+
colour maths. Scoped to the four suffixes whose job is to _be_ the role's colour (`-color`,
|
|
297
|
+
`-bg`, `-fill`, `-border`); `--gog-badge-warning-color` is the _label_ on the warning fill and is
|
|
298
|
+
in the exception list with that reason, beside `-wash`, `-ink` and `-buffer-bg`, which are
|
|
299
|
+
percentages of a role rather than the role. Verified by putting the old value back and watching
|
|
300
|
+
it fail.
|
|
301
|
+
|
|
302
|
+
- **`README.md` and `AGENTS.md` say where `gog-table` stops.** No column resizing or reordering by
|
|
303
|
+
the reader, no frozen columns, no expandable rows, no grouping — stated where someone evaluating
|
|
304
|
+
the table will read it rather than discovered halfway into a project. Documentation only;
|
|
305
|
+
nothing about the component changes.
|
|
306
|
+
|
|
307
|
+
**This list said "no virtualization" when it was written, and this release removed that line by
|
|
308
|
+
building the thing.** Both entries are in this changelog, days apart, and for a while the release
|
|
309
|
+
both added `virtualize` and advertised its absence. A limitations list is a claim with a shelf
|
|
310
|
+
life, and the shelf can be one release.
|
|
311
|
+
|
|
312
|
+
Each claim was verified against the code first, and one in `docs/backlog.md`'s filing was wrong:
|
|
313
|
+
it said "no sticky columns" while the table has shipped `stickyHeader` since 21.6.0. Those are
|
|
314
|
+
different axes — the header pins while rows scroll under it; the absent one is freezing a column
|
|
315
|
+
against _horizontal_ scroll — and both documents now draw that line, because a limitations list
|
|
316
|
+
that looks wrong on its first line teaches a reader to distrust the rest.
|
|
317
|
+
|
|
318
|
+
- **`check:oklch`'s R3 compares five severities, not four — and three themes were painting two of
|
|
319
|
+
them as one colour.** `GogSeverity` is `'accent' | 'success' | 'danger' | 'warning' | 'info'`
|
|
320
|
+
and the library paints all five as a set (`gog-button`'s `severity`, `gog-progressbar`'s
|
|
321
|
+
`variant`, `gogBadge`, and now `gog-alert`, whose entire signal is the colour of one edge). R3
|
|
322
|
+
was written against "the four status colours" and left the accent out, so four of the ten pairs
|
|
323
|
+
went unmeasured.
|
|
324
|
+
|
|
325
|
+
Two of the three findings are not close calls: the **dark** theme declared
|
|
326
|
+
`--gog-warning-color: #fbbf24`, the same hex as its accent, and **terminal** declared
|
|
327
|
+
`--gog-success-color: #3ddc5c`, the same hex as its. Nobody writes one value twice for two roles
|
|
328
|
+
on purpose — it is what a palette typed role-by-role produces when nothing compares them.
|
|
329
|
+
**parchment**'s danger sat 6.9° of hue and 0.052 of lightness from its oxblood accent, which is
|
|
330
|
+
not a distance a reader can use.
|
|
331
|
+
|
|
332
|
+
All three moved the **status**, never the accent: the accent is the theme's identity and the
|
|
333
|
+
status is the role that has to be read. `dark`'s warning is ember orange `#ffac4e` — moved by
|
|
334
|
+
hue rather than lightness, because darkening a status on a dark ground trades one defect for
|
|
335
|
+
another. `terminal`'s success is `#00b330`, moved along its own hue because that theme's other
|
|
336
|
+
three statuses already occupy yellow, red and cyan and the room left is in lightness.
|
|
337
|
+
`parchment`'s danger is `#a24439`. `check:contrast` stays green on all eleven themes.
|
|
338
|
+
|
|
339
|
+
- **A disabled control is exempt from `check:contrast`, consistently and on purpose.** WCAG carves
|
|
340
|
+
out "an inactive user interface component" in both SC 1.4.3 and 1.4.11 — a disabled control is
|
|
341
|
+
meant to look unavailable, and holding it to 4.5:1 makes "unavailable" impossible to draw. The
|
|
342
|
+
script had simply never had `:disabled` in one of its state regexes, with nothing saying why, so
|
|
343
|
+
the omission read as an oversight.
|
|
344
|
+
|
|
345
|
+
It also was not the exemption it looked like: **eight pairs reached the sweeps through compound
|
|
346
|
+
selectors** — `.gog-accordion__item--disabled .gog-accordion__header:hover` enters on its
|
|
347
|
+
`:hover` — and were gated. One predicate governs all three sweeps now, and such pairs are
|
|
348
|
+
printed rather than dropped. Nothing in the library changes; all eight already passed.
|
|
349
|
+
|
|
350
|
+
- **`npm run check:oklch` gates that a raised surface has an edge (R4).** `*-shadow` colours were
|
|
351
|
+
the last thing the palette gate did not read. `check:elevation` requires a theme to declare all
|
|
352
|
+
ten elevation knobs, but a theme may declare all ten at zero and pass it — rendering a dialog
|
|
353
|
+
with no boundary against the page behind it, with every check green.
|
|
354
|
+
|
|
355
|
+
The rule is a disjunction, because four different things can mark that edge and the eleven
|
|
356
|
+
shipped themes split on which: **six are carried by their shadow and five by their hairline
|
|
357
|
+
ring**, so gating any single carrier would have failed half the catalogue for a choice it made
|
|
358
|
+
deliberately. Whichever is strongest must clear ΔL ≥ 0.03 — the same threshold R1 already uses,
|
|
359
|
+
because it is the same question. Observed 0.0852 (`light`) to 0.3465 (`material`): the weakest
|
|
360
|
+
shipped theme clears it by 2.8x, and both figures are now printed per theme.
|
|
361
|
+
|
|
362
|
+
Nothing in the library changes. This one is a gate against a theme a consumer writes, which is
|
|
363
|
+
where the failure is silent.
|
|
364
|
+
|
|
365
|
+
- **`npm run check:contrast` resolves a boundary through the variant layer, and `.gog-btn` is
|
|
366
|
+
gated by it.** The button was the one control deliberately outside the boundary sweep, because
|
|
367
|
+
what identifies a button depends on its variant and the sweep resolved each painting rule once —
|
|
368
|
+
on `.gog-btn` that meant reading `--gog-button-primary-border`, `transparent` in the base theme.
|
|
369
|
+
It now resolves every boundary under each variant chain, the same machinery the variant sweep
|
|
370
|
+
already used for fills and labels, so `outline`'s border is measured as `outline`'s and
|
|
371
|
+
`ghost`'s transparent one is skipped. That is what found the focus-ring failures above.
|
|
372
|
+
|
|
373
|
+
Two smaller corrections came out of building it. **`boundaryBlock` matched the first gated
|
|
374
|
+
prefix rather than the longest**, and `.gog-ms` is a prefix of `.gog-ms__filter-input`: the
|
|
375
|
+
multiselect's filter input was being measured against the _page_ rather than the panel it sits
|
|
376
|
+
in, so the pair it reported was one nobody sees. And **the sweep now asserts that every block it
|
|
377
|
+
gates actually matched a declaration** — the same discipline as the pattern self-test added in
|
|
378
|
+
21.12.0, and it is what surfaced the prefix bug. A gated list whose entries match nothing looks
|
|
379
|
+
exactly like a library with no defects.
|
|
380
|
+
|
|
381
|
+
### Deprecated
|
|
382
|
+
|
|
383
|
+
- **`gog-table`, `gog-datepicker`/`gog-calendar` and `gog-dialog` move to their own entry points —
|
|
384
|
+
import them from `@guildofgleks/ui/table`, `/datepicker` and `/dialog`.** The root keeps
|
|
385
|
+
exporting all 25 symbols until **21.14.0** and stops then, when the code moves. The split is the
|
|
386
|
+
point: a lazy route using these components currently ships them in the initial bundle anyway,
|
|
387
|
+
and only an entry point the root does not re-export can change that — measured in
|
|
388
|
+
`docs/entry-points.md` in the repository.
|
|
389
|
+
|
|
390
|
+
- `@guildofgleks/ui/table` — `TableComponent`, `GogColumn`, `GogColumnBodyDirective`,
|
|
391
|
+
`GogColumnHeaderDirective`, `defaultCompare`, and the types `GogColumnBodyContext`,
|
|
392
|
+
`GogColumnHeaderContext`, `GogTableRowClickEvent`, `GogTableSelectionMode`,
|
|
393
|
+
`GogTableSortEvent`, `SortDirection`.
|
|
394
|
+
- `@guildofgleks/ui/datepicker` — `DatepickerComponent`, `CalendarComponent`, and the types
|
|
395
|
+
`GogCalendarDay`, `GogDatepickerValue`. The date helpers and `GogDateRange` stay in the root.
|
|
396
|
+
- `@guildofgleks/ui/dialog` — `DialogService`, `DialogComponent`,
|
|
397
|
+
`ConfirmationDialogComponent`, `DIALOG_DATA`, `DIALOG_REF`, and the types `DialogRef`,
|
|
398
|
+
`DialogConfig`, `DialogHandle`, `OpenDialog`, `ConfirmDialogData`.
|
|
399
|
+
|
|
400
|
+
**Your editor will not strike the old imports through**, and that is measured rather than
|
|
401
|
+
overlooked: ng-packagr bundles the root's types into a single export statement and drops the
|
|
402
|
+
deprecation tag on every re-export. A tag on the class itself would survive — and would strike
|
|
403
|
+
through the new subpath too, since it is the same class. This entry, `AGENTS.md` and
|
|
404
|
+
`GOG_DEPRECATIONS` are the notice.
|
|
405
|
+
|
|
406
|
+
Switching an import today changes nothing at runtime; both paths reach the same class.
|
|
407
|
+
|
|
408
|
+
- **`getByPath`, `readOption` and `isSameOptionValue` are deprecated, removed in 21.14.0.** They
|
|
409
|
+
are this library's own plumbing for reading a field off a consumer's object, and they became
|
|
410
|
+
public API because `public-api.ts` re-exported their module wholesale. Nothing in `README.md` or
|
|
411
|
+
`AGENTS.md` has ever mentioned them. `GogOptionAccessor` — the type every collection control's
|
|
412
|
+
`optionLabel` / `optionValue` / `optionDisabled` input is declared with — stays, and is the
|
|
413
|
+
reason the module was exported at all.
|
|
414
|
+
|
|
415
|
+
- **`--gog-slider-thumb-shadow` becomes `--gog-slider-thumb-glow-color`, removed in 21.14.0.**
|
|
416
|
+
The thumb composes it as `box-shadow: 0 0 var(--gog-slider-thumb-glow-size) <this>`, so the
|
|
417
|
+
token holds a **colour** and always has. Of the 31 `*-shadow` tokens the elevation audit
|
|
418
|
+
classified for 21.12.0, it was the only one whose name was simply wrong — and the wrongness is
|
|
419
|
+
not decorative: a consumer overriding it with a colour got what they expected, and one
|
|
420
|
+
overriding it with a shadow got a declaration the browser silently dropped.
|
|
421
|
+
|
|
422
|
+
`check-elevation.mjs` has carried it in `NOT_ELEVATION` with that reason since it was found;
|
|
423
|
+
that entry now explains why it is still listed (the sweep keys on the `-shadow`/`-glow`
|
|
424
|
+
families, not on what a token holds) rather than promising a rename.
|
|
425
|
+
|
|
426
|
+
- **`--gog-select-panel-offset` and `--gog-multiselect-panel-offset` become `*-panel-gap`,
|
|
427
|
+
removed in 21.14.0.** Five components place a panel with `calc(100% + <token>)` and split three
|
|
428
|
+
ways on what to call the value: `gog-autocomplete` and `gog-datepicker` said `-panel-gap`,
|
|
429
|
+
`gog-select` and `gog-multiselect` said `-panel-offset`, and `gog-menu` said `-offset`. A
|
|
430
|
+
consumer who learned one spelling guessed wrong on the next component.
|
|
431
|
+
|
|
432
|
+
`-gap` wins because it is the true one: an offset is a displacement from where a thing would
|
|
433
|
+
otherwise be, and this is the space between two things. `gog-menu`'s is renamed outright in the
|
|
434
|
+
same release with no window, because that one was never read (see Fixed) — a deprecation cycle
|
|
435
|
+
protects working consumer code, and there was none.
|
|
436
|
+
|
|
437
|
+
**Both old names keep resolving until 21.14.0.** `theme.css` declares each new token as
|
|
438
|
+
`var(<old name>, <value>)`, which is the mechanism the 21.7.0 prefix removals used: an override
|
|
439
|
+
on the old name still wins, one on the new name wins over it, and everyone else gets the value.
|
|
440
|
+
One minor rather than two, per `api-design.instructions.md` — the migration is a find-and-replace
|
|
441
|
+
in a theme.
|
|
442
|
+
|
|
443
|
+
**The ratchet had to grow a second half to hold this.** `DEPRECATED_NAMESPACES` can express
|
|
444
|
+
`--gog-btn-*` becoming `--gog-button-*`, because the prefix moves and the suffix is carried
|
|
445
|
+
through — it cannot express a rename, where the suffix itself moves and the replacement has to be
|
|
446
|
+
named. `DEPRECATED_TOKENS` is that map, `check:deprecations` fails on an overdue entry the same
|
|
447
|
+
way, and `GOG_DEPRECATIONS` now ships two entries where it shipped none. Verified by dating both
|
|
448
|
+
to the current version and watching the check fail.
|
|
449
|
+
|
|
450
|
+
### Fixed
|
|
451
|
+
|
|
452
|
+
- **Six tokens were declared and read by nothing — one wired up, five removed.** Everything rule K
|
|
453
|
+
found on its first run, each given a verdict rather than a blanket fix:
|
|
454
|
+
|
|
455
|
+
**`gog-inputfield`'s clear mark was the wrong size**, and this is the one a consumer can see.
|
|
456
|
+
Six controls offer a clear button and all six declare `--gog-<block>-clear-icon-ratio` at `0.7`;
|
|
457
|
+
five read it and `gog-inputfield` did not, so its `×` rendered at the field's full type size —
|
|
458
|
+
about **43% larger** than the identical mark on a select, multiselect, autocomplete, datepicker
|
|
459
|
+
or textarea standing next to it. It now reads its own token, like its five siblings.
|
|
460
|
+
|
|
461
|
+
**Five leftovers removed**, none of which any stylesheet could reach:
|
|
462
|
+
`--gog-accordion-hover-ring` (the header's hover paints a colour and a background, never a ring),
|
|
463
|
+
`--gog-multiselect-checkbox-bg` and `--gog-multiselect-checkbox-checked-color` (the option's mark
|
|
464
|
+
is a glyph, so a background and a label colour have nothing to paint — the two tokens the mark
|
|
465
|
+
_does_ read are untouched), `--gog-panel-elevated-shadow` (21.12.0 deliberately pointed the
|
|
466
|
+
elevated variant at the foundation's own `--gog-panel-shadow`, which left this behind), and
|
|
467
|
+
`--gog-toast-gap` (the stack uses `--gog-toast-stack-expanded-gap` and the row
|
|
468
|
+
`--gog-toast-content-gap`).
|
|
469
|
+
|
|
470
|
+
**No deprecation cycle for any of the five**, on the same reasoning as the menu's rename: a
|
|
471
|
+
deprecation window protects working consumer code, and a token nothing reads has none to protect.
|
|
472
|
+
Overriding any of them has always done exactly nothing, and still does.
|
|
473
|
+
|
|
474
|
+
- **Two more marks were bigger than the boxes holding them**, found by the check above rather than
|
|
475
|
+
by eye — the same defect as the chip's remove mark, the select's chevron and the multiselect's
|
|
476
|
+
arrow earlier in this release, in two places that audit did not reach.
|
|
477
|
+
|
|
478
|
+
**`gog-table`'s sort icon was 9% wider than its slot**, and it is the _fourth_ time this library
|
|
479
|
+
has paid for "a relative unit resolves against the element carrying the property".
|
|
480
|
+
`--gog-table-sort-icon-width: 1.1em` reads as "a little wider than the mark" and is not: the
|
|
481
|
+
`em` resolves against the element's own font-size, which the line below it had already reduced
|
|
482
|
+
to `0.9em`, so the slot came out `0.99em` of the header while the mark is `1.2 × 0.9 = 1.08em`
|
|
483
|
+
of it. The slot now takes a floor of `--gog-icon-size`, which states the invariant in the CSS
|
|
484
|
+
rather than leaving it to a number someone has to get right. A consumer setting the token wider
|
|
485
|
+
still wins.
|
|
486
|
+
|
|
487
|
+
**`gog-textarea`'s clear mark was 20% wider than its button**, and this one is the largest of
|
|
488
|
+
the four because its ratio is deliberately `1` — `theme.css` says why, and it is right: 0.7
|
|
489
|
+
suits a dropdown's dense single-line trigger and reads as a speck on a multi-line box. So the
|
|
490
|
+
**box grew and the mark did not move**, which is the same resolution the chip's got. The clear
|
|
491
|
+
button's hit area is about 20% larger.
|
|
492
|
+
|
|
493
|
+
- **An `interactiveRows` table row answered a click and not a press.** It had a cursor, a hover
|
|
494
|
+
tint and a focus ring, and nothing at all under the finger — 21.9.0 gave nine other pressable
|
|
495
|
+
surfaces a `:active` colour and this one was not among them. `--gog-table-row-press-bg` now
|
|
496
|
+
fills that, guarded on `--interactive` because a plain row is not a control, and a selected row
|
|
497
|
+
keeps its own tint under the finger for the reason it already keeps it on hover.
|
|
498
|
+
|
|
499
|
+
**A colour rather than a ripple, and that is the verdict on `docs/ripple.md`'s deferred table
|
|
500
|
+
rows.** That plan left them out on two arguments and gated the revisit on the weaker one —
|
|
501
|
+
"no virtualization in this library yet" — which `virtualize` has now removed. The other never
|
|
502
|
+
depended on it: a wave whose radius is an 800–1200px row reads as a flash across the table
|
|
503
|
+
rather than as feedback where the finger landed. A colour also survives
|
|
504
|
+
`prefers-reduced-motion`, which was 21.9.0's other half.
|
|
505
|
+
|
|
506
|
+
- **`gog-menu`'s gap between trigger and panel was a token nothing read.** `--gog-menu-offset` was
|
|
507
|
+
declared in `theme.css`, listed in `TOKENS.md`, and documented on the site as "gap between the
|
|
508
|
+
trigger and the panel" — and the panel is placed in script, by a function that was called
|
|
509
|
+
without its `gap` argument and fell back to its own hard-coded `4`. Setting the token did
|
|
510
|
+
nothing, in every theme, since the component shipped.
|
|
511
|
+
|
|
512
|
+
What it cost beyond the dead knob: the four other components that place a panel this way take
|
|
513
|
+
their gap from CSS and follow `--gog-density`, so **a theme changing density moved four of the
|
|
514
|
+
five and left the menu's gap at 4px.** The component resolves the token now, through
|
|
515
|
+
`resolveLengthToken` — `parseFloat` would return `NaN` on the `calc(4px * var(--gog-density))`
|
|
516
|
+
the density scale produces, which is the trap that file exists for.
|
|
517
|
+
|
|
518
|
+
**Renamed to `--gog-menu-panel-gap` in the same change, with no deprecation cycle, on purpose.**
|
|
519
|
+
A deprecation window protects working consumer code and there is none to protect: nothing a
|
|
520
|
+
consumer wrote against the old name ever had an effect. Keeping an alias alive for two releases
|
|
521
|
+
would be complexity spent guarding a promise that was never kept. The new name is also the one
|
|
522
|
+
the other four already use — `--gog-<block>-panel-gap` — which is what the
|
|
523
|
+
`-gap`/`-offset` split in `docs/backlog.md` is about.
|
|
524
|
+
|
|
525
|
+
Found by sweeping `theme.css` for tokens nothing reads, after `--gog-menu-offset` turned up while
|
|
526
|
+
auditing that naming split. **The sweep found twelve candidates out of 1482**, and no check
|
|
527
|
+
covers the category: `check:tokens` rule F is the opposite direction — a `var()` read with no
|
|
528
|
+
declaration — so a declaration with no reader passes every gate the project has.
|
|
529
|
+
|
|
530
|
+
- **A dropdown could open downward into a panel that does not fit.** The three controls on
|
|
531
|
+
`GogDropdownBase` — `gog-select`, `gog-multiselect` and `gog-autocomplete` — size their panel
|
|
532
|
+
from `--gog-*-option-height` and choose up or down from the result. That token calls itself an estimate; measured against a rendered row it is wrong in
|
|
533
|
+
**all eleven shipped themes** — low in ten of them, by up to 8.38px a row — so the estimate
|
|
534
|
+
systematically under-reported and a short list could be judged to fit below when it needed more
|
|
535
|
+
room than there was.
|
|
536
|
+
|
|
537
|
+
It only ever misfired on lists short enough to sit under the panel's max-height cap (roughly
|
|
538
|
+
five rows), which is why it went unseen: above the cap the cap dominates and the error is
|
|
539
|
+
masked. **No static token can fix it** — the same `parchment` row is 48.38px at
|
|
540
|
+
`--gog-density: 1` and 42.38px at 0.85 — so the component now measures one real row a frame
|
|
541
|
+
after the panel renders and re-places if the token disagreed, caching it so every later open is
|
|
542
|
+
right from its first frame. The token remains as the seed for that first frame and its
|
|
543
|
+
documentation is corrected in all three.
|
|
544
|
+
|
|
545
|
+
Found by `docs/virtualization.md`'s iteration 0, which existed to check exactly this before
|
|
546
|
+
anything new depended on it.
|
|
547
|
+
|
|
548
|
+
- **Two of the three dropdowns sized their panel from a gap that is not between the rows.** The
|
|
549
|
+
height estimate that decides whether a panel opens up or down sums the rows _and the gap
|
|
550
|
+
between them_, and it took that gap from `--gog-<block>-option-gap` — which on `gog-select` and
|
|
551
|
+
`gog-autocomplete` is the gap **inside** a row, between the mark or icon and the label. Neither
|
|
552
|
+
options container declares a gap between rows at all, so every row added 12px of panel that is
|
|
553
|
+
not on the page: 48px on a five-row list, which is the length at which this decision is made at
|
|
554
|
+
all.
|
|
555
|
+
|
|
556
|
+
It is the same defect as the row-height one above and the opposite sign, which is why they were
|
|
557
|
+
invisible together — one estimate ran low per row and the other ran high per gap. Fixing the
|
|
558
|
+
first is what exposed the second, and that is the argument for measuring rather than deriving:
|
|
559
|
+
**a sum of two wrong terms can place a panel correctly and does not stay lucky.**
|
|
560
|
+
|
|
561
|
+
The gap is now measured from the rendered options container, in the same frame and from the same
|
|
562
|
+
element as the row — the row's own `parentElement`, so no subclass has to declare a second
|
|
563
|
+
selector. `gog-multiselect` is unaffected: its list is the one that really does declare a row
|
|
564
|
+
gap, and it is the only one that still seeds from a token.
|
|
565
|
+
|
|
566
|
+
**The token names are unchanged and so is what they paint.** `--gog-select-option-gap` still
|
|
567
|
+
sets the space between the check mark and the label, which is what it has always done; only
|
|
568
|
+
what read it for a different purpose has changed. All three now say in `theme.css` which gap
|
|
569
|
+
they are.
|
|
570
|
+
|
|
571
|
+
- **`gog-progressbar`'s buffer had no edge, and its edge is the whole of what it says.** The
|
|
572
|
+
buffer tier marks how much is loaded; where it _ends_ was under 3:1 against the track in **55 of
|
|
573
|
+
55** shipped theme/variant combinations, worst **1.06:1**. That is a stronger result than the 51
|
|
574
|
+
of 55 that justified marking the fill's edge in 21.10.0, and it has the same cause: the two
|
|
575
|
+
tiers are the same hue by design — "the buffer tier is the same hue at low opacity, so it reads
|
|
576
|
+
as _ahead of the fill_ rather than as a second, competing colour" — which is the right choice
|
|
577
|
+
and exactly why a colour difference cannot carry the boundary.
|
|
578
|
+
|
|
579
|
+
The buffer now draws the same two hairlines the fill has drawn since 21.10.0. **No new token and
|
|
580
|
+
no palette change:** measured at the buffer's own edge across the same 55, the existing marker
|
|
581
|
+
clears 3:1 everywhere, worst 3.25:1, carried by the ink line against the bare track — the pair
|
|
582
|
+
`check:contrast` already gates. Visible wherever `mode="buffer"` is used.
|
|
583
|
+
|
|
584
|
+
- **Three icon buttons were smaller than the icon inside them** — `gog-chip`'s remove mark by 9%,
|
|
585
|
+
`gog-select`'s chevron and `gog-multiselect`'s arrow by 5%, at every size and in every theme.
|
|
586
|
+
Each set its box from one basis and let `<gog-icon>` draw the mark from another: the icon
|
|
587
|
+
renders its `<svg>` at `--gog-icon-size` (1.2em) of its own font-size, and none of the three
|
|
588
|
+
boxes was derived from that.
|
|
589
|
+
|
|
590
|
+
**Nothing painted the overflow, so nothing showed it** — the boxes carry no background at rest
|
|
591
|
+
or on hover. What it cost was the focus indicator. `:focus-visible` draws its outline on the
|
|
592
|
+
box, so on the chip the ring was drawn _inside_ the mark it indicates and cleared it only
|
|
593
|
+
because `--gog-chip-focus-ring-offset` happens to be 2px; measured at an offset of `0` — a
|
|
594
|
+
value any theme may choose, and one the theme generator offers as a slider — the ring landed
|
|
595
|
+
0.79px inside the glyph at `md`.
|
|
596
|
+
|
|
597
|
+
All three now state the mark's font-size and its box on the same element, with the box reading
|
|
598
|
+
the same `--gog-icon-size` the icon reads. The two cannot drift again for any ratio, or for any
|
|
599
|
+
icon size a consumer sets.
|
|
600
|
+
|
|
601
|
+
- **`--gog-chip-remove-scale` now means what its name says, and its default is `1`.** It was
|
|
602
|
+
`1.1`, and it multiplied the button's _font-size_ rather than the mark — so it produced a box
|
|
603
|
+
9% narrower than its own contents, and any value under `1.2` did nothing visible at all. It is
|
|
604
|
+
the ring around the mark now: `1` is the box the glyph occupies, and anything above it is
|
|
605
|
+
padding. **A theme that overrides this token gets a larger box than before at the same
|
|
606
|
+
number**, by a factor of 1.2.
|
|
607
|
+
|
|
608
|
+
`--gog-select-chevron-icon-ratio` and `--gog-multiselect-arrow-icon-ratio` were wrong in the
|
|
609
|
+
same way and are unchanged in value: both read `0.875`, which a reader takes to mean the
|
|
610
|
+
chevron is seven eighths of the field's type. It was 1.05 of it, because `--gog-icon-size`
|
|
611
|
+
multiplied on top. The mark does not move; the box around it grows 5% to contain it.
|
|
612
|
+
|
|
613
|
+
One visible consequence: **a removable chip with no avatar is ~9% of its remove box taller** —
|
|
614
|
+
1.2px at `xsm` to 2.0px at `slg`, measured at `--gog-density: 1`. The mark itself does not
|
|
615
|
+
change size at any of the five. A chip with an avatar does not move at all, because the avatar
|
|
616
|
+
is the taller element. On `gog-select` and `gog-multiselect` nothing moves: the chevron's box
|
|
617
|
+
is not what sets a field's height.
|
|
618
|
+
|
|
7
619
|
## [21.12.0] - 10.09.2026
|
|
8
620
|
|
|
9
621
|
### Added
|