@apliteni/apliteni-ui 0.82.0 → 0.85.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/guidelines/dense-tables.md +3 -1
- package/guidelines/motion.md +1 -1
- package/package.json +1 -1
- package/react/dist/index.d.ts +4 -1
- package/react/dist/index.js +12 -8
- package/src/components/stat.js +16 -18
- package/src/styles/card.css +2 -1
- package/src/styles/dropdown.css +10 -0
- package/src/styles/stat.css +37 -8
- package/src/styles/table.css +65 -0
- package/src/styles/topbar.css +5 -2
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
|
|
57
57
|
**Rule:** Keep units smaller and in body ink.
|
|
58
58
|
|
|
59
|
-
**Do:** Keep units smaller and in body ink, and explain abbreviations in reachable text.
|
|
59
|
+
**Do:** Keep units smaller and in body ink, and explain abbreviations in reachable text. A short value in a stacked row carries its own unit or count word, because the header that named it is no longer on screen.
|
|
60
60
|
|
|
61
61
|
**Don't:** Hide units in muted text or make readers guess what an abbreviation means.
|
|
62
62
|
|
|
@@ -72,6 +72,8 @@
|
|
|
72
72
|
|
|
73
73
|
**Don't:** Squeeze columns until values are unreadable or silently remove data.
|
|
74
74
|
|
|
75
|
+
**Except:** On a phone, a row whose long column is a paragraph stacks instead of scrolling: the short values on one line, the paragraph under them, and the header kept for screen readers. No column is dropped.
|
|
76
|
+
|
|
75
77
|
## Keep updates stable
|
|
76
78
|
|
|
77
79
|
<!-- rule: states -->
|
package/guidelines/motion.md
CHANGED
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
|
|
47
47
|
**Why:** Published bundles cap animations and transitions at 0.01ms under `prefers-reduced-motion`, so focus reaches a visible control without delay.
|
|
48
48
|
|
|
49
|
-
**Do:** Open drawers and
|
|
49
|
+
**Do:** Open drawers, confirmations, command palettes and dropdown panels without internal transitions.
|
|
50
50
|
|
|
51
51
|
**Don't:** Keep a transition running after reduced motion is requested.
|
|
52
52
|
|
package/package.json
CHANGED
package/react/dist/index.d.ts
CHANGED
|
@@ -97,11 +97,14 @@ interface StatDelta {
|
|
|
97
97
|
basis?: string;
|
|
98
98
|
tooltip?: string;
|
|
99
99
|
direction?: 'up' | 'down' | 'flat';
|
|
100
|
-
none?: string;
|
|
101
100
|
}
|
|
102
101
|
interface StatFigure {
|
|
103
102
|
label: string;
|
|
104
103
|
value: ReactNode;
|
|
104
|
+
/** Context that is not a change, such as "of revenue": no arrow, no tone. It
|
|
105
|
+
* takes the row a change would take, and leads that row beside one. A short
|
|
106
|
+
* phrase: the row is one line, and a longer caption is clipped. */
|
|
107
|
+
caption?: string;
|
|
105
108
|
delta?: StatDelta;
|
|
106
109
|
trend?: ReactNode;
|
|
107
110
|
}
|
package/react/dist/index.js
CHANGED
|
@@ -379,17 +379,21 @@ var directionOf = (text2) => {
|
|
|
379
379
|
return /^[-−–(]/.test(t) ? "down" : /^\+/.test(t) ? "up" : "flat";
|
|
380
380
|
};
|
|
381
381
|
var hasChange = (d) => !!d && d.value != null && d.value !== "";
|
|
382
|
-
function
|
|
383
|
-
if (!hasChange(delta)) {
|
|
384
|
-
|
|
385
|
-
|
|
382
|
+
function ContextRow({ caption, delta, basisId }) {
|
|
383
|
+
if (!hasChange(delta)) return caption ? /* @__PURE__ */ jsx11("dd", { className: "ui-stat__caption", children: caption }) : null;
|
|
384
|
+
const lead = caption ? /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
385
|
+
/* @__PURE__ */ jsx11("span", { className: "ui-stat__caption", children: caption }),
|
|
386
|
+
" "
|
|
387
|
+
] }) : null;
|
|
386
388
|
const dir = delta.direction && GLYPH[delta.direction] ? delta.direction : directionOf(delta.value);
|
|
387
|
-
|
|
389
|
+
const own = delta.basis || "";
|
|
390
|
+
return /* @__PURE__ */ jsxs7("dd", { className: "ui-stat__delta", "aria-describedby": !own && basisId ? basisId : void 0, children: [
|
|
391
|
+
lead,
|
|
388
392
|
/* @__PURE__ */ jsx11(Icon, { name: GLYPH[dir] }),
|
|
389
393
|
delta.tooltip ? /* @__PURE__ */ jsx11(Tooltip, { text: delta.tooltip, children: /* @__PURE__ */ jsx11("span", { className: "ui-stat__change", children: delta.value }) }) : /* @__PURE__ */ jsx11("span", { className: "ui-stat__change", children: delta.value }),
|
|
390
|
-
|
|
394
|
+
own ? /* @__PURE__ */ jsxs7(Fragment, { children: [
|
|
391
395
|
" ",
|
|
392
|
-
/* @__PURE__ */ jsx11("span", { className: "ui-stat__basis", children:
|
|
396
|
+
/* @__PURE__ */ jsx11("span", { className: "ui-stat__basis", children: own })
|
|
393
397
|
] }) : null
|
|
394
398
|
] });
|
|
395
399
|
}
|
|
@@ -406,7 +410,7 @@ function StatBand({ stats, variant = "tiles", basis, basisId: sharedBasisId, lab
|
|
|
406
410
|
return /* @__PURE__ */ jsxs7("div", { className: cls, children: [
|
|
407
411
|
/* @__PURE__ */ jsx11("dt", { className: "ui-stat__label", children: s.label }),
|
|
408
412
|
/* @__PURE__ */ jsx11("dd", { className: "ui-stat__value", children: s.value }),
|
|
409
|
-
|
|
413
|
+
/* @__PURE__ */ jsx11(ContextRow, { caption: s.caption, delta: s.delta, basisId }),
|
|
410
414
|
s.trend ? /* @__PURE__ */ jsx11("dd", { className: "ui-stat__trend", children: s.trend }) : null
|
|
411
415
|
] }, `${s.label}-${i}`);
|
|
412
416
|
}) })
|
package/src/components/stat.js
CHANGED
|
@@ -23,23 +23,26 @@ const directionOf = (text) => {
|
|
|
23
23
|
const GLYPH = { up: 'arrowUp', down: 'arrowDown', flat: 'minus' };
|
|
24
24
|
const hasChange = (delta) => delta && delta.value != null && delta.value !== '';
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
// One row and one line: the caption, the change, then what it is measured
|
|
27
|
+
// against. Nothing to compare draws no row. why: docs/specification.md#stat-bands
|
|
28
|
+
const contextRow = (caption, delta, basisId) => {
|
|
29
|
+
if (!hasChange(delta)) return caption ? `<dd class="ui-stat__caption">${esc(caption)}</dd>` : '';
|
|
30
|
+
// The space is read where the CSS gap is only drawn: "of income+1.2 pts" else.
|
|
31
|
+
const lead = caption ? `<span class="ui-stat__caption">${esc(caption)}</span> ` : '';
|
|
31
32
|
const text = String(delta.value);
|
|
32
33
|
const dir = GLYPH[delta.direction] ? delta.direction : directionOf(text);
|
|
34
|
+
// Never dropped: a basis is passed when the band's caption does not cover this figure.
|
|
33
35
|
const own = delta.basis ? ` <span class="ui-stat__basis">${esc(delta.basis)}</span>` : '';
|
|
34
|
-
const describedby = !
|
|
35
|
-
return `<dd class="ui-stat__delta"${describedby}>${icon(GLYPH[dir])}
|
|
36
|
+
const describedby = !own && basisId ? ` aria-describedby="${basisId}"` : '';
|
|
37
|
+
return `<dd class="ui-stat__delta"${describedby}>${lead}${icon(GLYPH[dir])}`
|
|
38
|
+
+ `<span class="ui-stat__change">${esc(text)}</span>${own}</dd>`;
|
|
36
39
|
};
|
|
37
40
|
|
|
38
41
|
// `tone` says whether the change is good news, and nothing else. It is never
|
|
39
42
|
// inferred from the direction: costs going up and unclassified rows going down
|
|
40
43
|
// are both real, and a band that paints every rise green is editorialising.
|
|
41
44
|
// A figure with no change has no news to colour.
|
|
42
|
-
const figure = ({ label = '', value = '', delta, trend = '' }, tile, basisId) => {
|
|
45
|
+
const figure = ({ label = '', value = '', caption = '', delta, trend = '' }, tile, basisId) => {
|
|
43
46
|
const tone = hasChange(delta) && STAT_TONES.includes(delta.tone) && delta.tone !== 'neutral' ? delta.tone : '';
|
|
44
47
|
const cls = cx('ui-stat', tone && `ui-stat--${tone}`, tile && 'ui-card ui-card--pad-sm');
|
|
45
48
|
// `trend` is trusted markup — an <svg> the caller drew. The kit sizes and
|
|
@@ -47,20 +50,15 @@ const figure = ({ label = '', value = '', delta, trend = '' }, tile, basisId) =>
|
|
|
47
50
|
return `<div class="${cls}">`
|
|
48
51
|
+ `<dt class="ui-stat__label">${esc(label)}</dt>`
|
|
49
52
|
+ `<dd class="ui-stat__value">${esc(value)}</dd>`
|
|
50
|
-
+
|
|
53
|
+
+ contextRow(caption, delta, basisId)
|
|
51
54
|
+ (trend ? `<dd class="ui-stat__trend">${trend}</dd>` : '')
|
|
52
55
|
+ '</div>';
|
|
53
56
|
};
|
|
54
57
|
|
|
55
|
-
// `basis` is the band's caption
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// The caption comes before the list, the way a table's <caption> does, in every
|
|
61
|
-
// layout. It is one statement about all the figures, so it is read before them
|
|
62
|
-
// and it sits outside every one of them — under a row of tiles it would read as
|
|
63
|
-
// a note on the last card. why: docs/specification.md#stat-bands
|
|
58
|
+
// `basis` is the band's caption: what every change is measured against, said
|
|
59
|
+
// once before the list the way a table's <caption> is, in every layout — under
|
|
60
|
+
// a row of tiles it would read as a note on the last card.
|
|
61
|
+
// why: docs/specification.md#stat-bands
|
|
64
62
|
export function statBand({ stats = [], variant = 'tiles', basis = '', label, id } = {}) {
|
|
65
63
|
const v = STAT_VARIANTS.includes(variant) ? variant : 'tiles';
|
|
66
64
|
const base = id ? esc(id) : `ui-stats-${++seq}`;
|
package/src/styles/card.css
CHANGED
|
@@ -82,7 +82,8 @@
|
|
|
82
82
|
display: flex;
|
|
83
83
|
align-items: center;
|
|
84
84
|
gap: 10px;
|
|
85
|
-
|
|
85
|
+
/* Title to description: the step, not the 5px it was. why: docs/specification.md#spacing-and-rhythm */
|
|
86
|
+
margin: 0 0 var(--space-2);
|
|
86
87
|
}
|
|
87
88
|
.ui-card__sub {
|
|
88
89
|
color: var(--text);
|
package/src/styles/dropdown.css
CHANGED
|
@@ -126,6 +126,16 @@
|
|
|
126
126
|
.ui-dropdown.open .ui-dropdown__panel,
|
|
127
127
|
.ui-dropdown__panel--portal.is-open { transition-property: opacity, transform; }
|
|
128
128
|
|
|
129
|
+
/* And nothing inside it is held either. Under reduced motion an element that names no
|
|
130
|
+
property of its own still transitions the inherited `visibility`, so it is hidden in
|
|
131
|
+
the frame openDropdown() focuses into it — #519. Keyed on the data hook, because the
|
|
132
|
+
topbar's two menus open on the same wiring and keep their own CSS.
|
|
133
|
+
why: docs/specification.md#the-dropdown-panel */
|
|
134
|
+
@media (prefers-reduced-motion: reduce) {
|
|
135
|
+
.open > [data-dropdown-panel] *,
|
|
136
|
+
[data-dropdown-panel].is-open * { transition: none !important; }
|
|
137
|
+
}
|
|
138
|
+
|
|
129
139
|
/* Portalled panel — wireDropdown() moves it onto <body>, clear of every
|
|
130
140
|
ancestor's overflow and every ancestor's stacking context. The descendant
|
|
131
141
|
selector above stops matching the moment it leaves the trigger's subtree, so
|
package/src/styles/stat.css
CHANGED
|
@@ -49,9 +49,25 @@
|
|
|
49
49
|
white-space: nowrap;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/* A figure's own words about its value, in the caption rank's size and weight
|
|
53
|
+
with the band's tighter leading, as the label takes, so it claims no rank.
|
|
54
|
+
Body ink on no fill; no tone reaches it, having no change to colour. */
|
|
55
|
+
.ui-stat__caption {
|
|
56
|
+
color: var(--text);
|
|
57
|
+
font-size: var(--text-sm);
|
|
58
|
+
font-weight: var(--weight-normal);
|
|
59
|
+
line-height: var(--leading-snug);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Alone it is the row a change would have taken — the same step down and line
|
|
63
|
+
box — which holds a band of unlike figures on one line. Leading a change it is
|
|
64
|
+
a span in that row, spaced off what follows. */
|
|
65
|
+
dd.ui-stat__caption { margin-top: var(--space-2); }
|
|
66
|
+
.ui-stat__delta .ui-stat__caption { margin-inline-end: var(--space-1); }
|
|
67
|
+
|
|
52
68
|
.ui-stat__delta {
|
|
53
69
|
display: flex;
|
|
54
|
-
flex-wrap:
|
|
70
|
+
flex-wrap: nowrap;
|
|
55
71
|
align-items: center;
|
|
56
72
|
gap: 2px var(--space-1);
|
|
57
73
|
margin-top: var(--space-2);
|
|
@@ -61,9 +77,21 @@
|
|
|
61
77
|
line-height: var(--leading-snug);
|
|
62
78
|
}
|
|
63
79
|
.ui-stat__delta svg { width: 14px; height: 14px; flex: none; stroke-width: 2.7; } /* 2.7 of a 24 box drawn at 14px = 1.58 CSS px. docs/specification.md#icons-and-glyphs */
|
|
64
|
-
.ui-stat__change { font-variant-numeric: tabular-nums; white-space: nowrap; }
|
|
80
|
+
.ui-stat__change { font-variant-numeric: tabular-nums; white-space: nowrap; flex: none; }
|
|
65
81
|
.ui-stat__basis { color: var(--text); font-weight: var(--weight-normal); }
|
|
66
|
-
|
|
82
|
+
|
|
83
|
+
/* The change above does not wrap: wrapped, its arrow ends one line and its number
|
|
84
|
+
starts the next, and a long caption drops its own change below the ones beside
|
|
85
|
+
it. The arrow and the number cannot give way, so the words clip — the whole
|
|
86
|
+
string stays in the markup. Only in that row: a caption standing alone has
|
|
87
|
+
nothing to orphan, so it takes a second line rather than lose words. */
|
|
88
|
+
.ui-stat__delta .ui-stat__caption,
|
|
89
|
+
.ui-stat__basis { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
90
|
+
|
|
91
|
+
/* A figure is never narrower than its content: without these a long line widens it. */
|
|
92
|
+
dd.ui-stat__caption { contain: inline-size; }
|
|
93
|
+
.ui-stat__delta .ui-stat__caption,
|
|
94
|
+
.ui-stat__basis { width: 0; flex-grow: 1; max-width: max-content; }
|
|
67
95
|
|
|
68
96
|
/* Tone is set by the caller and says good or bad news. Direction is the arrow's
|
|
69
97
|
job, so an unset tone keeps the change neutral whichever way it points. */
|
|
@@ -77,14 +105,15 @@
|
|
|
77
105
|
.ui-stat--good .ui-stat__trend { color: var(--chip-success-ink); }
|
|
78
106
|
.ui-stat--bad .ui-stat__trend { color: var(--chip-danger-ink); }
|
|
79
107
|
|
|
80
|
-
/* The band's caption, above the figures rather than in one:
|
|
81
|
-
|
|
82
|
-
|
|
108
|
+
/* The band's caption, above the figures rather than in one: what every change
|
|
109
|
+
points at. It governs the row, so it takes the caption rank itself and is
|
|
110
|
+
never set under a caption inside one figure, and its margin is below it. */
|
|
83
111
|
.ui-stats__basis {
|
|
84
112
|
margin: 0 0 var(--space-5);
|
|
85
113
|
color: var(--text);
|
|
86
|
-
|
|
87
|
-
|
|
114
|
+
/* rank: caption */
|
|
115
|
+
font-size: var(--text-sm);
|
|
116
|
+
font-weight: var(--weight-normal);
|
|
88
117
|
}
|
|
89
118
|
|
|
90
119
|
.ui-stats--tiles .ui-stats__list { gap: var(--space-4); }
|
package/src/styles/table.css
CHANGED
|
@@ -241,3 +241,68 @@ a.ui-identity:hover { text-decoration: underline; }
|
|
|
241
241
|
.ui-table--pinned { --ui-table-selection-width: var(--space-12); }
|
|
242
242
|
.ui-table--pinned .ui-table__selection { box-sizing: border-box; width: var(--ui-table-selection-width); min-width: var(--ui-table-selection-width); max-width: var(--ui-table-selection-width); padding-inline: 0; text-align: center; }
|
|
243
243
|
.ui-table--pinned .ui-table__selection + .ui-table__identity { left: var(--ui-table-selection-width); }
|
|
244
|
+
|
|
245
|
+
/* ============================================================================
|
|
246
|
+
* Stacked rows at phone width — .ui-table--stack, .ui-table__long
|
|
247
|
+
*
|
|
248
|
+
* The cell rules name both .ui-table and the modifier on purpose: --dense, --compact and
|
|
249
|
+
* __title reach a cell with two classes, and one class would lose to them and leave
|
|
250
|
+
* `nowrap` on a paragraph.
|
|
251
|
+
*
|
|
252
|
+
* The consumer's half — the roles a changed `display` drops — is in the specification.
|
|
253
|
+
* why: docs/specification.md#dense-financial-tables
|
|
254
|
+
* held by: src/styles/table-stack.test.js (layout), stories/table-stack.test.js (roles)
|
|
255
|
+
* ========================================================================== */
|
|
256
|
+
|
|
257
|
+
/* The marked column holds running text at any width. What the modifier adds below the
|
|
258
|
+
phone step is the line of its own. */
|
|
259
|
+
.ui-table td.ui-table__long { white-space: normal; }
|
|
260
|
+
|
|
261
|
+
@media (max-width: 560px) {
|
|
262
|
+
.ui-table.ui-table--stack { display: block; width: 100%; min-width: 0; padding: 0; }
|
|
263
|
+
.ui-table.ui-table--stack > thead {
|
|
264
|
+
position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%);
|
|
265
|
+
}
|
|
266
|
+
.ui-table.ui-table--stack > tbody { display: block; }
|
|
267
|
+
.ui-table.ui-table--stack > tbody > tr {
|
|
268
|
+
display: flex; flex-wrap: wrap; align-items: baseline;
|
|
269
|
+
column-gap: var(--space-3); row-gap: var(--space-1);
|
|
270
|
+
/* The inset is on the row, not the table: the hover outline draws at the row's own
|
|
271
|
+
edge, so a pad on the table would put the outline back against the text (#71). It is
|
|
272
|
+
the step a dense cell already pays sideways, so the outline and the row separator keep
|
|
273
|
+
the clearance they had at the width above this one. */
|
|
274
|
+
padding: var(--space-3); border-bottom: 1px solid var(--border);
|
|
275
|
+
}
|
|
276
|
+
.ui-table.ui-table--stack > tbody > tr:last-child { border-bottom: 0; }
|
|
277
|
+
/* `height` is in this list for the same reason `white-space` is: --compact sets a row
|
|
278
|
+
height for a one-line cell, and a block cell takes it as a fixed height rather than a
|
|
279
|
+
floor, so a wrapped paragraph spills through the separator into the next row. A stacked
|
|
280
|
+
cell is as tall as its text. */
|
|
281
|
+
.ui-table.ui-table--stack > tbody > tr > td {
|
|
282
|
+
display: block; width: auto; min-width: 0; max-width: 100%; height: auto;
|
|
283
|
+
padding: 0; border-bottom: 0; white-space: normal; overflow-wrap: break-word;
|
|
284
|
+
}
|
|
285
|
+
.ui-table.ui-table--stack > tbody > tr > td.ui-table__long { flex: 0 0 100%; text-align: left; }
|
|
286
|
+
/* A stacked row has no columns left to pin, stick or inset, and the identity's name is
|
|
287
|
+
what the row is about — the fold hides it only because a pinned column is narrow. */
|
|
288
|
+
.ui-table.ui-table--stack.ui-table--pinned > :where(thead, tbody) > tr > :where(.ui-table__identity, .ui-table__selection) {
|
|
289
|
+
position: static; width: auto; min-width: 0; max-width: none;
|
|
290
|
+
padding-inline: 0; border-right: 0; text-align: left;
|
|
291
|
+
}
|
|
292
|
+
.ui-table.ui-table--stack.ui-table--pinned .ui-identity__name {
|
|
293
|
+
position: static; width: auto; height: auto; max-width: none;
|
|
294
|
+
overflow: visible; clip-path: none;
|
|
295
|
+
}
|
|
296
|
+
/* The bleed now pays for the row's inset instead of the end cells', so the cells still
|
|
297
|
+
start on the card's own text edge and the outline keeps its room on both sides. Same
|
|
298
|
+
step, so the arithmetic is the one the sheet already does for a dense table above; these
|
|
299
|
+
rules exist because a stacked table that is not dense gets no bleed from that one. A
|
|
300
|
+
scroll wrapper owns the bleed when it is the card's child, and owes one step more than
|
|
301
|
+
the table does because the wrapper keeps its own --space-1 of focus clearance. */
|
|
302
|
+
.ui-card > .ui-table--stack {
|
|
303
|
+
width: calc(100% + 2 * var(--space-3)); margin-inline: calc(-1 * var(--space-3));
|
|
304
|
+
}
|
|
305
|
+
.ui-card > .ui-table-scroll:has(> .ui-table--stack) {
|
|
306
|
+
margin-inline: calc(-1 * var(--space-4));
|
|
307
|
+
}
|
|
308
|
+
}
|
package/src/styles/topbar.css
CHANGED
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
transition: background var(--dur-fast) var(--ease);
|
|
189
189
|
}
|
|
190
190
|
/* Menu rows use the reading surface and an edge on hover — see
|
|
191
|
-
src/styles/dropdown.css:
|
|
191
|
+
src/styles/dropdown.css:176 `.ui-dropdown__item:hover`.
|
|
192
192
|
why: docs/specification.md#elevation */
|
|
193
193
|
.vopt:hover { outline: 1px solid var(--control-edge); outline-offset: -1px; background: var(--surface); /* ring-gap: inherit — control paint; its focus indicator uses the containing surface. */ }
|
|
194
194
|
/* The row is a keyboard stop: src/components/dropdown.js:554
|
|
@@ -269,7 +269,10 @@
|
|
|
269
269
|
border: 1px solid var(--float-edge);
|
|
270
270
|
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
271
271
|
border-radius: 14px;
|
|
272
|
-
|
|
272
|
+
/* No clip. The rows reach this panel's edges, and the ring a focused row draws reaches
|
|
273
|
+
3px past itself; `overflow: hidden` here cut that down to the one pixel of border it
|
|
274
|
+
had, and the reader met two accent bars instead of a ring. The ring crosses the edge
|
|
275
|
+
now. #519 why: docs/specification.md#a-menu-panel-does-not-cut-off-its-rows-ring */
|
|
273
276
|
opacity: 0;
|
|
274
277
|
visibility: hidden;
|
|
275
278
|
pointer-events: none; /* see .vsw__menu above */
|