@apliteni/apliteni-ui 0.97.0 → 0.100.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/package.json +1 -1
- package/react/dist/index.css +19 -3
- package/react/dist/index.js +33 -8
- package/src/components/dropdown.js +77 -4
- package/src/index.d.ts +15 -0
- package/src/index.js +1 -1
- package/src/logic/dropdown.js +23 -0
- package/src/styles/dropdown.css +49 -3
- package/src/styles/layout.css +4 -1
- package/src/styles/table.css +68 -1
- package/src/styles/topbar.css +56 -8
- package/src/tokens/tokens.css +2 -0
package/package.json
CHANGED
package/react/dist/index.css
CHANGED
|
@@ -820,7 +820,7 @@
|
|
|
820
820
|
.rx-toast-stack {
|
|
821
821
|
position: fixed;
|
|
822
822
|
right: var(--space-4);
|
|
823
|
-
bottom: var(--space-4);
|
|
823
|
+
bottom: calc(var(--space-4) + var(--ui-app-bottom-clearance, 0px));
|
|
824
824
|
max-width: calc(100% - 2 * var(--space-4));
|
|
825
825
|
z-index: var(--z-toast);
|
|
826
826
|
pointer-events: none;
|
|
@@ -846,15 +846,28 @@
|
|
|
846
846
|
.ui-react-app__bottom .ui-nav__item {
|
|
847
847
|
min-height: 48px;
|
|
848
848
|
}
|
|
849
|
+
.ui-react-app:not(:has(> .ui-app__rail)) {
|
|
850
|
+
grid-template-columns: minmax(0, 1fr);
|
|
851
|
+
}
|
|
852
|
+
.ui-react-app .ui-app__bar > .ui-app__brand {
|
|
853
|
+
flex: none;
|
|
854
|
+
margin-inline-end: var(--space-2);
|
|
855
|
+
}
|
|
849
856
|
@media (max-width: 560px) {
|
|
857
|
+
:root:has(.ui-react-app__bottom) {
|
|
858
|
+
--ui-app-bottom-clearance: calc(96px + env(safe-area-inset-bottom));
|
|
859
|
+
}
|
|
860
|
+
:root:has(.ui-react-app) {
|
|
861
|
+
scroll-padding-bottom: calc(var(--space-5) + var(--ui-app-bottom-clearance, 0px));
|
|
862
|
+
}
|
|
850
863
|
.ui-react-app {
|
|
851
864
|
display: block;
|
|
852
865
|
}
|
|
853
866
|
.ui-react-app > .ui-app__rail {
|
|
854
867
|
display: none;
|
|
855
868
|
}
|
|
856
|
-
.ui-react-app .ui-app__main {
|
|
857
|
-
padding-bottom:
|
|
869
|
+
:root:has(.ui-react-app__bottom) .ui-react-app .ui-app__main {
|
|
870
|
+
padding-bottom: var(--ui-app-bottom-clearance, 0px);
|
|
858
871
|
}
|
|
859
872
|
.ui-react-app .ui-app__bar {
|
|
860
873
|
padding-inline: var(--space-4);
|
|
@@ -865,6 +878,9 @@
|
|
|
865
878
|
.ui-react-app .ui-app__search kbd {
|
|
866
879
|
display: none;
|
|
867
880
|
}
|
|
881
|
+
.ui-react-app .ui-app__bar > .ui-app__brand {
|
|
882
|
+
display: none;
|
|
883
|
+
}
|
|
868
884
|
.ui-react-app__bottom {
|
|
869
885
|
display: block;
|
|
870
886
|
position: fixed;
|
package/react/dist/index.js
CHANGED
|
@@ -1892,7 +1892,8 @@ import {
|
|
|
1892
1892
|
dropdownFiltering,
|
|
1893
1893
|
filterPanelFit,
|
|
1894
1894
|
transitionMs as transitionMs2,
|
|
1895
|
-
dropdownViewportFit
|
|
1895
|
+
dropdownViewportFit,
|
|
1896
|
+
dropdownHeightFit
|
|
1896
1897
|
} from "@apliteni/apliteni-ui";
|
|
1897
1898
|
import { Fragment as Fragment5, jsx as jsx20, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
1898
1899
|
var EXIT_SLACK_MS2 = 50;
|
|
@@ -2102,6 +2103,27 @@ function Dropdown({
|
|
|
2102
2103
|
doc.removeEventListener("keydown", onKey2);
|
|
2103
2104
|
};
|
|
2104
2105
|
}, [open]);
|
|
2106
|
+
useIsoLayoutEffect(() => {
|
|
2107
|
+
if (!open) return;
|
|
2108
|
+
const el = panel.current;
|
|
2109
|
+
if (!el) return;
|
|
2110
|
+
const size = () => {
|
|
2111
|
+
const avail = dropdownHeightFit(root.current, el);
|
|
2112
|
+
if (avail == null) return;
|
|
2113
|
+
if (Number.isFinite(avail)) el.style.setProperty("--ui-dropdown-avail", `${avail}px`);
|
|
2114
|
+
else el.style.removeProperty("--ui-dropdown-avail");
|
|
2115
|
+
const cap2 = el.ownerDocument.defaultView?.getComputedStyle(el).getPropertyValue("--ui-dropdown-cap").trim();
|
|
2116
|
+
el.classList.toggle("is-unbounded", !Number.isFinite(avail) && !cap2);
|
|
2117
|
+
};
|
|
2118
|
+
size();
|
|
2119
|
+
const view = el.ownerDocument?.defaultView ?? window;
|
|
2120
|
+
view.addEventListener("scroll", size, true);
|
|
2121
|
+
view.addEventListener("resize", size);
|
|
2122
|
+
return () => {
|
|
2123
|
+
view.removeEventListener("scroll", size, true);
|
|
2124
|
+
view.removeEventListener("resize", size);
|
|
2125
|
+
};
|
|
2126
|
+
}, [open, direction]);
|
|
2105
2127
|
useIsoLayoutEffect(() => {
|
|
2106
2128
|
const want = landOn.current;
|
|
2107
2129
|
landOn.current = null;
|
|
@@ -2328,7 +2350,7 @@ function Dropdown({
|
|
|
2328
2350
|
section.label ?? `s${si}`
|
|
2329
2351
|
);
|
|
2330
2352
|
}) : (items || []).map((entry, i) => renderRow(entry, `i${i}`));
|
|
2331
|
-
const cap = scroll && scroll !== true ? {
|
|
2353
|
+
const cap = scroll && scroll !== true ? { "--ui-dropdown-cap": typeof scroll === "number" ? `${scroll}px` : scroll } : void 0;
|
|
2332
2354
|
const none = sx && filtering && !visible.length ? /* @__PURE__ */ jsxs16(Fragment5, { children: [
|
|
2333
2355
|
/* @__PURE__ */ jsx20("span", { className: "ui-dropdown__none-title", children: sx.empty.replace("{q}", () => query.trim()) }),
|
|
2334
2356
|
/* @__PURE__ */ jsx20("span", { className: "ui-dropdown__none-hint", children: sx.hint })
|
|
@@ -3466,15 +3488,17 @@ function AppShell({
|
|
|
3466
3488
|
}
|
|
3467
3489
|
);
|
|
3468
3490
|
const foldLabel = collapsed ? "Expand sidebar" : "Collapse sidebar";
|
|
3491
|
+
const navigable = sections.length > 1 || Boolean(back);
|
|
3469
3492
|
const visible = sections.length > 4 ? sections.slice(0, 3) : sections;
|
|
3470
3493
|
const remaining = sections.slice(visible.length);
|
|
3471
3494
|
const initials = account.name.trim().split(/\s+/).map((part) => part[0]).slice(0, 2).join("");
|
|
3495
|
+
const lockup = /* @__PURE__ */ jsxs29("a", { className: "ui-app__brand", href: brandHref, "aria-label": word, children: [
|
|
3496
|
+
brand ?? /* @__PURE__ */ jsx34("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(uid, 24) } }),
|
|
3497
|
+
/* @__PURE__ */ jsx34("span", { children: word })
|
|
3498
|
+
] });
|
|
3472
3499
|
return /* @__PURE__ */ jsxs29("div", { className: `ui-app ui-app--topbar ui-react-app${collapsed ? " is-collapsed" : ""}`, children: [
|
|
3473
|
-
/* @__PURE__ */ jsxs29("div", { className: "ui-app__rail", children: [
|
|
3474
|
-
/* @__PURE__ */ jsx34("div", { className: "ui-app__head", children:
|
|
3475
|
-
brand ?? /* @__PURE__ */ jsx34("i", { "aria-hidden": "true", className: "ui-react-app__mark", dangerouslySetInnerHTML: { __html: prism(uid, 24) } }),
|
|
3476
|
-
/* @__PURE__ */ jsx34("span", { children: word })
|
|
3477
|
-
] }) }),
|
|
3500
|
+
navigable && /* @__PURE__ */ jsxs29("div", { className: "ui-app__rail", children: [
|
|
3501
|
+
/* @__PURE__ */ jsx34("div", { className: "ui-app__head", children: lockup }),
|
|
3478
3502
|
sidebar(sections, "Sections"),
|
|
3479
3503
|
/* @__PURE__ */ jsx34("div", { className: "ui-app__foot", children: /* @__PURE__ */ jsx34("div", { className: "ui-app__fold-row", children: /* @__PURE__ */ jsxs29("button", { type: "button", className: "ui-nav__item ui-app__fold", "aria-expanded": !collapsed, "aria-label": foldLabel, onClick: fold, children: [
|
|
3480
3504
|
/* @__PURE__ */ jsx34("span", { className: "ui-nav__ic", children: /* @__PURE__ */ jsxs29("svg", { className: "ui-icon", viewBox: "0 0 24 24", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
|
|
@@ -3487,6 +3511,7 @@ function AppShell({
|
|
|
3487
3511
|
] }),
|
|
3488
3512
|
/* @__PURE__ */ jsxs29("div", { className: "ui-app__well", children: [
|
|
3489
3513
|
/* @__PURE__ */ jsxs29("header", { className: "ui-app__bar", children: [
|
|
3514
|
+
!navigable && lockup,
|
|
3490
3515
|
/* @__PURE__ */ jsxs29("button", { type: "button", className: "ui-app__search", "aria-haspopup": "dialog", onClick: () => setSearch(true), children: [
|
|
3491
3516
|
/* @__PURE__ */ jsx34("span", { className: "ui-app__search-ic", children: /* @__PURE__ */ jsx34(Icon, { name: "search" }) }),
|
|
3492
3517
|
/* @__PURE__ */ jsx34("span", { className: "ui-app__search-txt", children: "Search or run a command\u2026" }),
|
|
@@ -3522,7 +3547,7 @@ function AppShell({
|
|
|
3522
3547
|
/* @__PURE__ */ jsx34("div", { className: "ui-app__body", children })
|
|
3523
3548
|
] })
|
|
3524
3549
|
] }),
|
|
3525
|
-
/* @__PURE__ */ jsx34("nav", { className: "ui-nav ui-react-app__bottom", "aria-label": "Sections on mobile", children: /* @__PURE__ */ jsxs29("ul", { className: "ui-nav__list", children: [
|
|
3550
|
+
navigable && /* @__PURE__ */ jsx34("nav", { className: "ui-nav ui-react-app__bottom", "aria-label": "Sections on mobile", children: /* @__PURE__ */ jsxs29("ul", { className: "ui-nav__list", children: [
|
|
3526
3551
|
links(visible),
|
|
3527
3552
|
remaining.length > 0 && /* @__PURE__ */ jsx34("li", { children: /* @__PURE__ */ jsxs29(
|
|
3528
3553
|
"button",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { dropdownMatch, dropdownFiltering } from '../logic/dropdown.js';
|
|
2
|
-
export { dropdownMatch, dropdownFiltering } from '../logic/dropdown.js';
|
|
1
|
+
import { dropdownMatch, dropdownFiltering, dropdownAvail } from '../logic/dropdown.js';
|
|
2
|
+
export { dropdownMatch, dropdownFiltering, dropdownAvail } from '../logic/dropdown.js';
|
|
3
3
|
// Dropdown — the kit's one popover-list primitive. A trigger opens a panel of
|
|
4
4
|
// item rows; two flavours share the same panel and the same open/close JS:
|
|
5
5
|
//
|
|
@@ -120,7 +120,10 @@ function ddSearchBody({ items, sections }, sx, name, scroll) {
|
|
|
120
120
|
const rows = ddBody({ items, sections }, true, sx);
|
|
121
121
|
const flat = (sections ? sections.flatMap((s) => s.items || []) : (items || [])).filter(ddIsRow);
|
|
122
122
|
const shown = flat.some((it) => dropdownMatch(it.label, sx.q));
|
|
123
|
-
|
|
123
|
+
// The cap property and not `max-height`: an inline height outranks the sheet's
|
|
124
|
+
// min() and would put the panel back past the viewport edge. #489
|
|
125
|
+
const cap = scroll && scroll !== true
|
|
126
|
+
? ` style="--ui-dropdown-cap:${typeof scroll === 'number' ? scroll + 'px' : esc(scroll)}"` : '';
|
|
124
127
|
const input = [
|
|
125
128
|
'class="ui-dropdown__search-input"', 'type="text"', 'role="combobox"',
|
|
126
129
|
'aria-autocomplete="list"', 'aria-expanded="true"', `aria-controls="${esc(listId)}"`,
|
|
@@ -202,7 +205,10 @@ export function dropdown({
|
|
|
202
205
|
// With search the panel holds a field and a list, which a listbox may not.
|
|
203
206
|
`role="${sx ? 'dialog' : listRole}"`,
|
|
204
207
|
sx ? `aria-label="${esc(name)}"` : (ariaLabel ? `aria-label="${esc(ariaLabel)}"` : ''),
|
|
205
|
-
|
|
208
|
+
// The cap property and not `max-height`: an inline height outranks the
|
|
209
|
+
// sheet's min() and would put the panel back past the viewport edge. #489
|
|
210
|
+
scroll && scroll !== true && !sx
|
|
211
|
+
? `style="--ui-dropdown-cap:${typeof scroll === 'number' ? scroll + 'px' : esc(scroll)}"` : '',
|
|
206
212
|
].filter(Boolean).join(' ');
|
|
207
213
|
|
|
208
214
|
// The block the sheet bleeds to the panel's bottom edge. It sits OUTSIDE the
|
|
@@ -278,6 +284,16 @@ function ddEdge(panel) {
|
|
|
278
284
|
return Number.isFinite(declared) ? declared : DD_EDGE;
|
|
279
285
|
}
|
|
280
286
|
|
|
287
|
+
// The least height a capped panel keeps, below which it would rather spend the
|
|
288
|
+
// edge inset than shrink further. The number is --ui-dropdown-min in
|
|
289
|
+
// src/styles/dropdown.css. #489
|
|
290
|
+
const DD_MIN = 120;
|
|
291
|
+
|
|
292
|
+
function ddMin(panel) {
|
|
293
|
+
const declared = parseFloat(ddViewOf(panel).getComputedStyle(panel).getPropertyValue('--ui-dropdown-min'));
|
|
294
|
+
return Number.isFinite(declared) ? declared : DD_MIN;
|
|
295
|
+
}
|
|
296
|
+
|
|
281
297
|
/** The width a panel has to stay inside: the LAYOUT viewport, which is what both
|
|
282
298
|
* `position: fixed` and a page's scrollable width are measured against. A classic
|
|
283
299
|
* scrollbar is in `innerWidth` and not in the box either of those means, so an
|
|
@@ -515,10 +531,65 @@ function ddUnwatchRow(dd) {
|
|
|
515
531
|
* taken for the middle of an animation, is stated with the rule.
|
|
516
532
|
* why: docs/components.md#the-dropdown-panel */
|
|
517
533
|
|
|
534
|
+
/**
|
|
535
|
+
* How tall a panel may be: the room between its trigger and the viewport edge,
|
|
536
|
+
* from dropdownAvail() in src/logic/dropdown.js — one calculation, so the
|
|
537
|
+
* vanilla wiring and the React `<Dropdown>` cannot disagree about where a panel
|
|
538
|
+
* ends. Measured from the trigger, not the panel, the way dropdownViewportFit()
|
|
539
|
+
* is measured from the panel and not the trigger: each is the box the number it
|
|
540
|
+
* answers actually depends on. `null` when there is nothing to measure.
|
|
541
|
+
* why: docs/components.md#the-dropdown-panel
|
|
542
|
+
*
|
|
543
|
+
* @param {Element} dd the `[data-dropdown]` container the trigger lives in
|
|
544
|
+
* @param {Element} panel a `[data-dropdown-panel]`, for its gap/edge/min and its
|
|
545
|
+
* `is-up` direction
|
|
546
|
+
*/
|
|
547
|
+
export function dropdownHeightFit(dd, panel) {
|
|
548
|
+
const trigger = dd?.querySelector?.('[data-dropdown-trigger]');
|
|
549
|
+
if (!trigger || typeof trigger.getBoundingClientRect !== 'function' || !panel) return null;
|
|
550
|
+
const t = trigger.getBoundingClientRect();
|
|
551
|
+
const view = ddViewOf(panel);
|
|
552
|
+
return dropdownAvail({
|
|
553
|
+
anchorTop: t.top,
|
|
554
|
+
anchorBottom: t.bottom,
|
|
555
|
+
viewport: view.innerHeight,
|
|
556
|
+
gap: ddGap(panel),
|
|
557
|
+
inset: ddEdge(panel),
|
|
558
|
+
min: ddMin(panel),
|
|
559
|
+
up: panel.classList.contains('is-up'),
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function ddSizeHeight(dd, panel) {
|
|
564
|
+
if (!panel?.style) return;
|
|
565
|
+
const avail = dropdownHeightFit(dd, panel);
|
|
566
|
+
if (avail == null) return;
|
|
567
|
+
// Infinity means the floor could not be met even by spending the whole inset:
|
|
568
|
+
// the property is removed rather than written, so the sheet's own fallback — the
|
|
569
|
+
// viewport less the edge inset, not a trigger-sized sliver — takes over. why:
|
|
570
|
+
// dropdownAvail(), src/logic/dropdown.js
|
|
571
|
+
//
|
|
572
|
+
// `.is-unbounded` is only added where that leaves NO cap at all: a consumer's
|
|
573
|
+
// own `--ui-dropdown-cap` (`.is-scroll` or a numeric `scroll`) still clamps the
|
|
574
|
+
// panel, and that clamp is still real room to scroll. why: src/styles/dropdown.css
|
|
575
|
+
const cap = ddViewOf(panel).getComputedStyle(panel).getPropertyValue('--ui-dropdown-cap').trim();
|
|
576
|
+
panel.classList.toggle('is-unbounded', !Number.isFinite(avail) && !cap);
|
|
577
|
+
// A number that has not changed is not written again, the way the width fit's
|
|
578
|
+
// own write is guarded: a mutation inside the row that moved nothing vertically
|
|
579
|
+
// would otherwise answer its own write, over and over, through the document
|
|
580
|
+
// observer below. why: dropdownViewportFit()'s own "moved" guard, same file
|
|
581
|
+
const next = Number.isFinite(avail) ? `${avail}px` : '';
|
|
582
|
+
if (panel.style.getPropertyValue('--ui-dropdown-avail') === next) return;
|
|
583
|
+
if (next) panel.style.setProperty('--ui-dropdown-avail', next);
|
|
584
|
+
else panel.style.removeProperty('--ui-dropdown-avail');
|
|
585
|
+
}
|
|
586
|
+
|
|
518
587
|
/** Fit a panel again where it now is, whichever placement it uses. */
|
|
519
588
|
function ddPlace(dd) {
|
|
589
|
+
const panel = dd.__ddPanel || ddPanelOf(dd);
|
|
520
590
|
if (dd.__ddPanel) positionPortalPanel(dd, dd.__ddPanel);
|
|
521
591
|
else ddFitViewport(dd, ddPanelOf(dd));
|
|
592
|
+
ddSizeHeight(dd, panel);
|
|
522
593
|
}
|
|
523
594
|
|
|
524
595
|
/* A few frames past the sheet's own end, so the timer never cuts the fade's last
|
|
@@ -778,6 +849,7 @@ function openDropdown(dd, focusIdx) {
|
|
|
778
849
|
is null inside a filter row, whose own row already bounds its panels.
|
|
779
850
|
why: docs/components.md#a-filter-row-holds-its-panels */
|
|
780
851
|
else { ddFitFilterPanel(dd, panel); ddWatchRow(dd, panel); ddFitViewport(dd, panel); }
|
|
852
|
+
ddSizeHeight(dd, panel);
|
|
781
853
|
}
|
|
782
854
|
dd.classList.add('open');
|
|
783
855
|
/* After `open`, and after the fit: ddResetSearch() pins the width it reads off
|
|
@@ -949,6 +1021,7 @@ export function wireDropdown(root = document) {
|
|
|
949
1021
|
ddFitFilterPanel(dd, panel);
|
|
950
1022
|
ddWatchRow(dd, panel);
|
|
951
1023
|
}
|
|
1024
|
+
if (panel && dd.classList.contains('open')) ddSizeHeight(dd, panel);
|
|
952
1025
|
|
|
953
1026
|
trigger.addEventListener('click', (e) => {
|
|
954
1027
|
e.stopPropagation();
|
package/src/index.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export declare function wireDropdown(root?: Document | Element): void;
|
|
|
34
34
|
/** The kit's own match, asked rather than re-implemented by <Dropdown>. */
|
|
35
35
|
export declare function dropdownMatch(label: unknown, query: unknown): boolean;
|
|
36
36
|
export declare function dropdownFiltering(query: unknown): boolean;
|
|
37
|
+
/** How tall a floating panel may be: the room between its anchor and the
|
|
38
|
+
* viewport edge, less `gap` and `inset`. `min` is a floor that spends `inset`,
|
|
39
|
+
* down to nothing, rather than room past the edge, which does not exist. */
|
|
40
|
+
export declare function dropdownAvail(opts: {
|
|
41
|
+
anchorTop: number; anchorBottom: number; viewport: number;
|
|
42
|
+
gap: number; inset: number; min: number; up?: boolean;
|
|
43
|
+
}): number;
|
|
37
44
|
/** The kit's menu floor, the width `.ui-dropdown__panel` writes. */
|
|
38
45
|
export declare const DD_MENU_FLOOR: number;
|
|
39
46
|
/** Where a filter chip's open menu can sit: the room from the edge it is
|
|
@@ -58,6 +65,14 @@ export declare function dropdownViewportFit(
|
|
|
58
65
|
panel: Element | null | undefined,
|
|
59
66
|
at?: { left: number; width: number },
|
|
60
67
|
): { shift: number; left: number; width: number; view: number; edge: number; max: number } | null;
|
|
68
|
+
/** How tall a panel may be: the room between its trigger (inside `dd`) and the
|
|
69
|
+
* viewport edge, from the same dropdownAvail() dropdownViewportFit's `max`
|
|
70
|
+
* shares its calculation with. `null` when there is nothing to measure.
|
|
71
|
+
* why: docs/components.md#the-dropdown-panel */
|
|
72
|
+
export declare function dropdownHeightFit(
|
|
73
|
+
dd: Element | null | undefined,
|
|
74
|
+
panel: Element | null | undefined,
|
|
75
|
+
): number | null;
|
|
61
76
|
export declare function backLink(opts?: Record<string, unknown>): string;
|
|
62
77
|
export declare function statBand(opts?: Record<string, unknown>): string;
|
|
63
78
|
export declare function commandPalette(opts?: Record<string, unknown>): string;
|
package/src/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export * from './components/table-values.js';
|
|
|
27
27
|
export * from './components/segmented.js';
|
|
28
28
|
export * from './components/filter-bar.js';
|
|
29
29
|
|
|
30
|
-
export { dropdownMatch, dropdownFiltering } from './logic/dropdown.js';
|
|
30
|
+
export { dropdownMatch, dropdownFiltering, dropdownAvail } from './logic/dropdown.js';
|
|
31
31
|
export { SCORE, rankGroups, rankCommands, scoreCommand, paletteHotkey } from './logic/command-palette.js';
|
|
32
32
|
export { segmentedNextIndex } from './logic/segmented.js';
|
|
33
33
|
export { filterChipText, filterChipName, filterChipUnset, filterChipItems, nextFocusStop, focusNextStop } from './logic/filter-bar.js';
|
package/src/logic/dropdown.js
CHANGED
|
@@ -14,3 +14,26 @@ export const dropdownFiltering = (query) => fold(query).trim() !== '';
|
|
|
14
14
|
|
|
15
15
|
/** Does `label` match `query`? A substring, anywhere, ignoring case and accents. */
|
|
16
16
|
export const dropdownMatch = (label, query) => fold(label).includes(fold(query).trim());
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* How tall a floating panel may be: the room between its anchor and the viewport
|
|
20
|
+
* edge, less the gap it keeps from the anchor and the edge inset it keeps from
|
|
21
|
+
* that edge. `up` measures the room above the anchor, otherwise below it.
|
|
22
|
+
*
|
|
23
|
+
* Public for the same reason the two above are: the vanilla wiring and the React
|
|
24
|
+
* <Dropdown> both have to arrive at the same number, and a panel that ends inside
|
|
25
|
+
* the viewport in one and past it in the other is the bug this answers (#489).
|
|
26
|
+
*
|
|
27
|
+
* `min` is a floor: a trigger near the edge has little room, and a panel reduced
|
|
28
|
+
* to a sliver is worse than one left to its content height. The floor never
|
|
29
|
+
* spends room PAST the edge to reach itself, because that room does not exist.
|
|
30
|
+
* Once even the whole inset cannot reach it, there is no cap a reader could
|
|
31
|
+
* use, so this returns `Infinity`: "nothing measured," the word a caller
|
|
32
|
+
* already reads from an unmeasured panel. The panel then keeps its content
|
|
33
|
+
* height, the document grows, and a reader reaches every row by scrolling the
|
|
34
|
+
* page — a 27px sliver hiding 446px of rows was danger 1 in #641's review.
|
|
35
|
+
*/
|
|
36
|
+
export const dropdownAvail = ({ anchorTop, anchorBottom, viewport, gap, inset, min, up = false }) => {
|
|
37
|
+
const room = Math.max(up ? anchorTop - gap : viewport - anchorBottom - gap, 0);
|
|
38
|
+
return room < min ? Infinity : Math.max(room - inset, min);
|
|
39
|
+
};
|
package/src/styles/dropdown.css
CHANGED
|
@@ -62,6 +62,12 @@
|
|
|
62
62
|
the screen's edge sets it to 0.
|
|
63
63
|
why: docs/components.md#the-dropdown-panel */
|
|
64
64
|
--ui-dropdown-edge: 8px;
|
|
65
|
+
/* The least height a capped panel keeps before it would rather spend the edge
|
|
66
|
+
inset above than shrink further — three rows or so. Below it the panel
|
|
67
|
+
still ends at or before the edge; it never spends room past the edge to
|
|
68
|
+
reach this floor, because that room does not exist. #489
|
|
69
|
+
why: docs/components.md#the-dropdown-panel */
|
|
70
|
+
--ui-dropdown-min: 120px;
|
|
65
71
|
/* Named for the same reason the offset is: a block pinned to an edge has to
|
|
66
72
|
bleed back through it, and #306 is a page copying the number by hand.
|
|
67
73
|
why: docs/components.md#the-dropdown-panel */
|
|
@@ -107,6 +113,24 @@
|
|
|
107
113
|
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
108
114
|
border-radius: var(--radius-lg);
|
|
109
115
|
padding: var(--ui-dropdown-pad);
|
|
116
|
+
/* The cap, and the whole of #489: a panel took its content's height until that
|
|
117
|
+
height ran off the screen, and then it took what was left. Two numbers decide
|
|
118
|
+
it and the smaller wins — --ui-dropdown-cap is what the consumer asked for
|
|
119
|
+
(the `scroll` option writes it), --ui-dropdown-avail is the room between the
|
|
120
|
+
trigger and the viewport edge, which the wiring measures on every open and
|
|
121
|
+
every scroll. Neither is declared here: an unset cap means the consumer wants
|
|
122
|
+
the whole list, and an unset avail means nothing has measured yet, which is a
|
|
123
|
+
server-rendered page before hydration — so the fallback promises the same
|
|
124
|
+
thing at a coarser grain, the viewport less the edge inset at each end.
|
|
125
|
+
why: docs/components.md#the-dropdown-panel */
|
|
126
|
+
max-height: min(
|
|
127
|
+
var(--ui-dropdown-cap, 100dvh),
|
|
128
|
+
var(--ui-dropdown-avail, calc(100dvh - 2 * var(--ui-dropdown-edge)))
|
|
129
|
+
);
|
|
130
|
+
overflow-y: auto;
|
|
131
|
+
/* The wheel stops at the panel's own end instead of carrying on down the page
|
|
132
|
+
underneath it — the second half of #489. */
|
|
133
|
+
overscroll-behavior: contain;
|
|
110
134
|
opacity: 0;
|
|
111
135
|
visibility: hidden;
|
|
112
136
|
/* Drawn for the whole of the fade out, so the rows do not vanish mid-fade — and a
|
|
@@ -123,7 +147,21 @@
|
|
|
123
147
|
z-index: var(--z-dropdown);
|
|
124
148
|
}
|
|
125
149
|
.ui-dropdown__panel.is-end { left: auto; right: 0; }
|
|
126
|
-
|
|
150
|
+
/* `.is-scroll` asks for 300px specifically; the panel above caps every panel at
|
|
151
|
+
the room the viewport left either way. #489 */
|
|
152
|
+
.ui-dropdown__panel.is-scroll { --ui-dropdown-cap: 300px; }
|
|
153
|
+
|
|
154
|
+
/* The wiring adds this where the floor (above) left the panel uncapped: below
|
|
155
|
+
120px even spending the whole edge inset, there is no usable cap, so the
|
|
156
|
+
panel keeps its content height and the page grows to hold it instead —
|
|
157
|
+
#641's danger 1. That panel has nothing of its own to scroll, so the
|
|
158
|
+
`overflow-y: auto` above still claims a wheel over it for nothing, and
|
|
159
|
+
`overscroll-behavior: contain` then stops the gesture from reaching the
|
|
160
|
+
page that is now the only way to the rows below the fold. `visible` lets it
|
|
161
|
+
through. A panel that also carries `.is-scroll` or a numeric cap keeps
|
|
162
|
+
`auto`: that cap is still real room to scroll inside. why: dropdownAvail(),
|
|
163
|
+
src/logic/dropdown.js */
|
|
164
|
+
.ui-dropdown__panel.is-unbounded { overflow-y: visible; }
|
|
127
165
|
|
|
128
166
|
/* Chrome makes a scroll container a keyboard stop on its own — no tabindex, no
|
|
129
167
|
author rule — so `.is-scroll` took the browser's outline, black in light mode.
|
|
@@ -286,7 +324,13 @@
|
|
|
286
324
|
while the list scrolls. Every rule below keys on a class only the search
|
|
287
325
|
variant emits, so the plain dropdown renders as it did.
|
|
288
326
|
why: docs/components.md#a-dropdown-with-a-search-field */
|
|
289
|
-
|
|
327
|
+
/* A column, so the panel's own avail-based max-height (above) lands on the rows
|
|
328
|
+
and not on the field: the field and the empty-state block keep their content
|
|
329
|
+
height, and the list — the child that can overflow — gives up the room they
|
|
330
|
+
take. #489 */
|
|
331
|
+
.ui-dropdown__panel--search { min-width: 280px; display: flex; flex-direction: column; }
|
|
332
|
+
.ui-dropdown__panel--search > * { flex: none; }
|
|
333
|
+
.ui-dropdown__panel--search > .ui-dropdown__list { flex: 1 1 auto; min-height: 0; }
|
|
290
334
|
|
|
291
335
|
.ui-dropdown__search { position: relative; display: flex; align-items: center; margin-bottom: 6px; }
|
|
292
336
|
.ui-dropdown__search-ic {
|
|
@@ -324,7 +368,9 @@
|
|
|
324
368
|
.ui-dropdown__search-input:hover { border-color: var(--field-edge-hover); }
|
|
325
369
|
.ui-dropdown__search-input:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
|
|
326
370
|
|
|
327
|
-
|
|
371
|
+
/* The rows' own cap, read from the same property the panel reads: the `scroll`
|
|
372
|
+
option lands on the list when there is a field above it. #489 */
|
|
373
|
+
.ui-dropdown__list { max-height: var(--ui-dropdown-cap, 300px); overflow-y: auto; overscroll-behavior: contain; }
|
|
328
374
|
/* Rows are `role="option" tabindex="-1"`, so the LIST takes the keyboard stop and
|
|
329
375
|
answered with the native outline. The band is drawn INWARD, on the list: an outset
|
|
330
376
|
one 6px inside a --radius-lg corner leaves the panel, and the panel is not the box
|
package/src/styles/layout.css
CHANGED
|
@@ -422,7 +422,10 @@
|
|
|
422
422
|
letter-spacing: var(--tracking-tight);
|
|
423
423
|
margin-bottom: 8px;
|
|
424
424
|
}
|
|
425
|
-
|
|
425
|
+
/* The head keeps the kit's second step below it whether or not a lede is there to
|
|
426
|
+
carry it: with no lede the title's own margin takes the step instead. */
|
|
427
|
+
.ui-app__main h1:not(.ui-card h1):not(:has(+ .ui-app__sub)) { margin-bottom: var(--space-8); }
|
|
428
|
+
.ui-app__sub { color: var(--text); font-size: calc(var(--text-base) + 0.5px); line-height: 1.6; max-width: var(--prose-body); margin-bottom: var(--space-8); }
|
|
426
429
|
.ui-app__body { display: flex; flex-direction: column; gap: var(--space-5); }
|
|
427
430
|
/* A flex child's min-width is its content by default, so one wide block — a
|
|
428
431
|
ledger, a code sample — pushes the whole page sideways instead of scrolling
|
package/src/styles/table.css
CHANGED
|
@@ -204,7 +204,35 @@
|
|
|
204
204
|
.ui-table--compact th { padding: var(--space-2) var(--space-3); }
|
|
205
205
|
.ui-table--compact th:last-child,
|
|
206
206
|
.ui-table--compact td:last-child { padding-right: var(--space-3); }
|
|
207
|
-
|
|
207
|
+
/* Columns past the box's edge, said in paint. Two shades, one per side, under two
|
|
208
|
+
cover layers that ride the content: `local` moves a cover with the scroll, so
|
|
209
|
+
reaching an end slides that cover over its shade. A cover is solid for its first
|
|
210
|
+
half, so at 24px it hides a 12px shade outright — which is why the two sizes are
|
|
211
|
+
not one. why: guidelines/dense-tables.md */
|
|
212
|
+
.ui-table-scroll {
|
|
213
|
+
overflow: auto; max-height: var(--ui-table-height, 70vh); padding: 0 var(--space-1);
|
|
214
|
+
background-color: var(--table-bg);
|
|
215
|
+
background-image:
|
|
216
|
+
linear-gradient(to right, var(--table-bg) 50%, transparent),
|
|
217
|
+
linear-gradient(to left, var(--table-bg) 50%, transparent),
|
|
218
|
+
radial-gradient(farthest-side at 0 50%, var(--table-edge), transparent),
|
|
219
|
+
radial-gradient(farthest-side at 100% 50%, var(--table-edge), transparent);
|
|
220
|
+
background-position: 0 0, 100% 0, 0 0, 100% 0;
|
|
221
|
+
background-size: var(--space-6) 100%, var(--space-6) 100%, var(--space-3) 100%, var(--space-3) 100%;
|
|
222
|
+
background-repeat: no-repeat;
|
|
223
|
+
background-attachment: local, local, scroll, scroll;
|
|
224
|
+
}
|
|
225
|
+
/* The edge darkens on a light ground and lightens on a dark one: ink has nowhere to
|
|
226
|
+
go on a near-black page, which is why --elev-drop splits its spend and a raised
|
|
227
|
+
dark surface takes a light --elev-edge. */
|
|
228
|
+
.ui-table-scroll { --table-edge: color-mix(in srgb, var(--sheen) 12%, transparent); }
|
|
229
|
+
:root[data-theme="light"] .ui-table-scroll { --table-edge: color-mix(in srgb, var(--shadow-ink) 13%, transparent); }
|
|
230
|
+
/* The surface belongs to the box that scrolls, so the shades land under the cells
|
|
231
|
+
rather than behind them; the row fills repeat that same surface and step aside.
|
|
232
|
+
The sticky header and pinned column keep their paint: they hide what scrolls
|
|
233
|
+
beneath them. */
|
|
234
|
+
.ui-table-scroll > .ui-table,
|
|
235
|
+
.ui-table-scroll > .ui-table tbody td:not(.ui-table__identity, .ui-table__selection) { background-color: transparent; }
|
|
208
236
|
/* The kit's first ringed scroller, and a scroll region inside a card like the rest, so
|
|
209
237
|
it draws the band inward too rather than spending a halo across the card around it.
|
|
210
238
|
It had no room of its own to draw in — the padding above is horizontal — which is the
|
|
@@ -216,6 +244,45 @@
|
|
|
216
244
|
.ui-table--sticky thead th { position: sticky; top: 0; z-index: var(--z-sticky); background: var(--table-bg); padding-top: var(--space-2); }
|
|
217
245
|
.ui-table--pinned .ui-table__identity, .ui-table--pinned .ui-table__selection { position: sticky; left: 0; z-index: var(--z-base); background: var(--table-bg); border-right: 1px solid var(--border); }
|
|
218
246
|
.ui-table--pinned thead .ui-table__identity, .ui-table--pinned thead .ui-table__selection { z-index: calc(var(--z-sticky) + 1); }
|
|
247
|
+
/* The divider is the pin's edge: it says the column stayed while the rest moved under it.
|
|
248
|
+
Columns that all fit never move, so the line divides nothing and reads as an arbitrary
|
|
249
|
+
rule. The resting rule keeps it, so an engine that cannot answer the query is unchanged;
|
|
250
|
+
scroll-state makes a query container, not a size one. why: guidelines/dense-tables.md */
|
|
251
|
+
.ui-table-scroll { container-type: scroll-state; }
|
|
252
|
+
/* A shared grid cell puts the edge shade over the opaque sticky header, on both
|
|
253
|
+
sides: the radial-gradient shades below answer only the table's own background
|
|
254
|
+
and fall to nothing where the sticky header paints over them, so an engine that
|
|
255
|
+
can ask whether the scrollport still has room sticks a shade of its own above
|
|
256
|
+
it instead and the base radial pair is sized to 0 here. #434 review round 2 */
|
|
257
|
+
@supports (container-type: scroll-state) {
|
|
258
|
+
.ui-table-scroll {
|
|
259
|
+
display: grid;
|
|
260
|
+
grid-template-columns: minmax(0, 1fr);
|
|
261
|
+
background-size: var(--space-6) 100%, var(--space-6) 100%, 0 100%, 0 100%;
|
|
262
|
+
}
|
|
263
|
+
.ui-table-scroll > .ui-table { grid-area: 1 / 1; }
|
|
264
|
+
.ui-table-scroll::before,
|
|
265
|
+
.ui-table-scroll::after {
|
|
266
|
+
content: '';
|
|
267
|
+
grid-area: 1 / 1;
|
|
268
|
+
position: sticky;
|
|
269
|
+
top: 0;
|
|
270
|
+
width: var(--space-3);
|
|
271
|
+
z-index: calc(var(--z-sticky) + 2);
|
|
272
|
+
pointer-events: none;
|
|
273
|
+
}
|
|
274
|
+
.ui-table-scroll::before { left: 0; right: calc(100% - var(--space-3)); justify-self: start; }
|
|
275
|
+
.ui-table-scroll::after { left: calc(100% - var(--space-3)); right: 0; justify-self: end; }
|
|
276
|
+
@container scroll-state(scrollable: left) {
|
|
277
|
+
.ui-table-scroll::before { background: linear-gradient(to right, var(--table-edge), transparent); }
|
|
278
|
+
}
|
|
279
|
+
@container scroll-state(scrollable: right) {
|
|
280
|
+
.ui-table-scroll::after { background: linear-gradient(to left, var(--table-edge), transparent); }
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
@container not scroll-state(scrollable: x) {
|
|
284
|
+
.ui-table--pinned :is(.ui-table__identity, .ui-table__selection) { border-right-color: transparent; }
|
|
285
|
+
}
|
|
219
286
|
.ui-value, .ui-delta { white-space: nowrap; font-variant-numeric: tabular-nums; }
|
|
220
287
|
.ui-value__unit { margin-left: var(--space-1); font-size: var(--text-xs); color: var(--text); }
|
|
221
288
|
.ui-delta--success { color: var(--chip-success-ink); }
|
package/src/styles/topbar.css
CHANGED
|
@@ -141,6 +141,13 @@
|
|
|
141
141
|
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
142
142
|
border-radius: 16px;
|
|
143
143
|
padding: 6px;
|
|
144
|
+
/* The same cap `.ui-dropdown__panel` writes itself, in bespoke clothes: this
|
|
145
|
+
menu is `wireDropdown()` too, so wireDropdown() measures it the same way and
|
|
146
|
+
writes the same two properties. #489
|
|
147
|
+
why: docs/components.md#the-dropdown-panel */
|
|
148
|
+
max-height: min(var(--ui-dropdown-cap, 100dvh), var(--ui-dropdown-avail, calc(100dvh - 16px)));
|
|
149
|
+
overflow-y: auto;
|
|
150
|
+
overscroll-behavior: contain;
|
|
144
151
|
opacity: 0;
|
|
145
152
|
visibility: hidden;
|
|
146
153
|
/* Drawn for the whole of the fade out, so the rows do not vanish mid-fade — and a box
|
|
@@ -157,6 +164,13 @@
|
|
|
157
164
|
visibility var(--dur-med) linear;
|
|
158
165
|
z-index: var(--z-dropdown);
|
|
159
166
|
}
|
|
167
|
+
/* Chrome makes a scroll container a keyboard stop on its own — no tabindex, no
|
|
168
|
+
author rule — and #489's cap is what gave this menu the `overflow-y` that
|
|
169
|
+
makes it one, the same way `.ui-dropdown__panel` got it. `.vopt` is
|
|
170
|
+
`tabindex="-1"` and reached only by the arrow keys, so Tab lands on the menu
|
|
171
|
+
itself rather than a row inside it, and the ring belongs here for the same
|
|
172
|
+
reason `.ui-dropdown__panel` keeps it. why: docs/foundations.md#elevation */
|
|
173
|
+
.vsw__menu:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
|
|
160
174
|
/* Open, the menu is clickable and visible AT ONCE. Both menus in this file are
|
|
161
175
|
`wireDropdown()` in bespoke clothes — same hooks, same keyboard, same fade — so they
|
|
162
176
|
owe a panel's two rules as much as `.ui-dropdown__panel` does: `visibility` off the
|
|
@@ -192,10 +206,10 @@
|
|
|
192
206
|
transition: background var(--dur-fast) var(--ease);
|
|
193
207
|
}
|
|
194
208
|
/* Menu rows use the reading surface and an edge on hover — see
|
|
195
|
-
src/styles/dropdown.css:
|
|
209
|
+
src/styles/dropdown.css:245 `.ui-dropdown__item:hover`.
|
|
196
210
|
why: docs/foundations.md#elevation */
|
|
197
211
|
.vopt:hover { outline: 1px solid var(--control-edge); outline-offset: -1px; background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
|
|
198
|
-
/* The row is a keyboard stop: src/components/dropdown.js:
|
|
212
|
+
/* The row is a keyboard stop: src/components/dropdown.js:1088
|
|
199
213
|
`items[next < 0 ? 0 : next]?.focus()` moves focus onto a `[data-dd-item]` with
|
|
200
214
|
the arrow keys, and this is one. It takes the ring on its own 10px radius, the
|
|
201
215
|
same way `.amenu a` above does. #482 */
|
|
@@ -277,10 +291,24 @@
|
|
|
277
291
|
border: 1px solid var(--float-edge);
|
|
278
292
|
box-shadow: inset 0 0 0 1px var(--elev-edge, var(--float-edge-inner)), var(--elev-drop);
|
|
279
293
|
border-radius: 14px;
|
|
280
|
-
/*
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
294
|
+
/* #519 chose no clip over padding: `overflow: hidden` with no room cut the ring down to
|
|
295
|
+
the one pixel of border this panel had, so the rule took the clip away instead and let
|
|
296
|
+
the ring cross the edge. #489's cap takes that choice away — `overflow-y: auto` clips
|
|
297
|
+
again, because a menu that scrolls has to stop its content at its own edge — so this
|
|
298
|
+
reserves the room instead, the same 6px `.vsw__menu` and `.ui-dropdown__panel` already
|
|
299
|
+
keep rather than a new figure. why: docs/components.md#a-menu-panel-does-not-cut-off-its-rows-ring */
|
|
300
|
+
padding: 6px;
|
|
301
|
+
/* The same cap `.ui-dropdown__panel` writes itself — see .vsw__menu above. #489 */
|
|
302
|
+
max-height: min(var(--ui-dropdown-cap, 100dvh), var(--ui-dropdown-avail, calc(100dvh - 16px)));
|
|
303
|
+
overflow-y: auto;
|
|
304
|
+
overscroll-behavior: contain;
|
|
305
|
+
/* Chromium scrolls a focused row flush with the scrollport edge, which is 3px
|
|
306
|
+
short of the row's own ring: 1px of gap plus a 2px band. The padding above
|
|
307
|
+
reserves that room while the menu stands still; this keeps it reserved once
|
|
308
|
+
scrolling the menu — reaching the first row this PR made it do — puts a
|
|
309
|
+
focused row at either end. #641's danger 2.
|
|
310
|
+
why: docs/components.md#a-menu-panel-does-not-cut-off-its-rows-ring */
|
|
311
|
+
scroll-padding-block: 3px;
|
|
284
312
|
opacity: 0;
|
|
285
313
|
visibility: hidden;
|
|
286
314
|
pointer-events: none; /* see .vsw__menu above */
|
|
@@ -292,6 +320,10 @@
|
|
|
292
320
|
visibility var(--dur-med) linear;
|
|
293
321
|
z-index: var(--z-dropdown);
|
|
294
322
|
}
|
|
323
|
+
/* The same gap as `.vsw__menu` above, from the same cause: #489's cap. `.amenu
|
|
324
|
+
a` is `tabindex="-1"` too, so Tab still lands on the menu and not a row, and
|
|
325
|
+
the ring belongs here. why: docs/foundations.md#elevation */
|
|
326
|
+
.amenu:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); }
|
|
295
327
|
/* Clickable and visible at once — see .vsw.open .vsw__menu above. The row this one
|
|
296
328
|
costs is `.aout`: a stray click in the fade out ends the reader's session. */
|
|
297
329
|
.acct.open .amenu {
|
|
@@ -301,12 +333,28 @@
|
|
|
301
333
|
transform: translateY(0);
|
|
302
334
|
transition-property: opacity, transform;
|
|
303
335
|
}
|
|
304
|
-
|
|
336
|
+
/* `margin` bleeds the head back through the 6px panel padding above, the way
|
|
337
|
+
`.ui-dropdown__panel > .ui-dropdown__head` already does for its own panel:
|
|
338
|
+
the border-bottom hairline reaches the panel's edge instead of stopping a
|
|
339
|
+
notch short of it. #641's warning.
|
|
340
|
+
why: docs/components.md#the-dropdown-panel */
|
|
341
|
+
.amenu .ahead {
|
|
342
|
+
padding: 13px 15px;
|
|
343
|
+
margin: -6px -6px 0;
|
|
344
|
+
border-bottom: 1px solid var(--border);
|
|
345
|
+
border-radius: 14px 14px 0 0;
|
|
346
|
+
display: flex; align-items: center; gap: 11px;
|
|
347
|
+
}
|
|
305
348
|
.amenu .ahead .avatar { cursor: default; }
|
|
306
349
|
.amenu .ahead .aw { min-width: 0; display: flex; flex-direction: column; gap: 1px; line-height: 1.35; }
|
|
307
350
|
.amenu .ahead .anm { color: var(--strong); font-size: var(--text-sm); font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
308
351
|
.amenu .ahead .aem { color: var(--text); font-size: calc(var(--text-xs) + 0.5px); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
309
|
-
|
|
352
|
+
/* `scroll-margin-block` on the row, beside `scroll-padding-block` on the panel
|
|
353
|
+
above: Chromium's own focus-follow scroll honoured the padding scrolling
|
|
354
|
+
toward the end of the list and not the start, so the row carries its own
|
|
355
|
+
reserve too rather than leaving the start of the list short. #641's danger 2.
|
|
356
|
+
why: docs/components.md#a-menu-panel-does-not-cut-off-its-rows-ring */
|
|
357
|
+
.amenu a { display: flex; align-items: center; gap: 11px; padding: 10px 15px; color: var(--text); text-decoration: none; font-family: var(--font-sans); font-size: var(--text-sm); cursor: pointer; scroll-margin-block: 3px; }
|
|
310
358
|
.amenu a:hover { outline: 1px solid var(--control-edge); outline-offset: -1px; background: var(--surface); /* code-bg: inherit — control paint; the containing surface is the ground, for a chip and for a band alike. */ }
|
|
311
359
|
.amenu a:focus-visible { outline: var(--ring); outline-offset: var(--ring-offset); border-radius: var(--radius-xs); }
|
|
312
360
|
.amenu a.cur { color: var(--accent); }
|
package/src/tokens/tokens.css
CHANGED
|
@@ -534,6 +534,7 @@
|
|
|
534
534
|
.ui-snippet--reveal,
|
|
535
535
|
.ui-sx,
|
|
536
536
|
.ui-table,
|
|
537
|
+
.ui-table-scroll,
|
|
537
538
|
.ui-table--sticky thead th,
|
|
538
539
|
.ui-table--pinned .ui-table__identity, .ui-table--pinned .ui-table__selection,
|
|
539
540
|
.ui-table--hover:not(.ui-table--zebra) tbody tr:hover td,
|
|
@@ -557,6 +558,7 @@
|
|
|
557
558
|
* toward the page and leaves the page 1.02 away, while the card it is a variant of stays
|
|
558
559
|
* 1.07-1.14 off in both themes. Measured in stories/code-chip.test.js. */
|
|
559
560
|
.ui-table,
|
|
561
|
+
.ui-table-scroll,
|
|
560
562
|
.ui-table--sticky thead th,
|
|
561
563
|
.ui-table--pinned .ui-table__identity, .ui-table--pinned .ui-table__selection,
|
|
562
564
|
.ui-table--hover:not(.ui-table--zebra) tbody tr:hover td,
|