fix(ui): contain dropdown scroll geometry
This commit is contained in:
@@ -56,6 +56,8 @@ Dropdown владеет floating-layer поведением:
|
||||
- переворачивается вверх при недостатке места;
|
||||
- ограничивает размеры viewport;
|
||||
- обновляется при resize и scroll;
|
||||
- сохраняет border-box геометрию при собственной прокрутке и не накапливает padding в измеренной ширине;
|
||||
- удерживает wheel/trackpad overscroll внутри меню, не прокручивая лежащую под portal поверхность;
|
||||
- закрывается, когда trigger полностью уходит за границы viewport;
|
||||
- закрывается по outside pointer и Escape;
|
||||
- после Escape возвращает фокус на реальную trigger-кнопку, не закрывая родительское окно;
|
||||
|
||||
@@ -1893,10 +1893,12 @@ textarea.nodedc-field__control {
|
||||
position: fixed;
|
||||
z-index: var(--nodedc-layer-popover);
|
||||
display: grid;
|
||||
box-sizing: border-box;
|
||||
gap: 0.2rem;
|
||||
min-width: 11.25rem;
|
||||
max-width: calc(100vw - 1rem);
|
||||
overflow: auto;
|
||||
overscroll-behavior: contain;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-control);
|
||||
background: var(--nodedc-floating-surface);
|
||||
|
||||
@@ -98,10 +98,11 @@
|
||||
"domExports": ["createFloatingLayer"],
|
||||
"domContract": ["nodedc-dropdown-surface", "nodedc-dropdown-option"],
|
||||
"summary": "Shared portal/fixed floating layer for action and selection menus.",
|
||||
"behavior": ["portal to document body", "viewport clamp", "vertical flip", "outside pointer close", "Escape close with trigger focus restore", "scroll and resize reposition", "offscreen trigger close", "one open dropdown per window"],
|
||||
"behavior": ["portal to document body", "viewport clamp", "vertical flip", "outside pointer close", "Escape close with trigger focus restore", "scroll and resize reposition", "contained menu scroll without geometry growth", "offscreen trigger close", "one open dropdown per window"],
|
||||
"rules": [
|
||||
"Never render a runtime dropdown as an absolute child of a card, sticky header or scroll container.",
|
||||
"Action menus and selection menus share floating behavior even when their row content differs."
|
||||
"Action menus and selection menus share floating behavior even when their row content differs.",
|
||||
"The portal surface uses border-box sizing and contained overscroll so repeated menu scrolling cannot grow its measured width or move the underlying application surface."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
import { computeFloatingPosition } from "../packages/ui-core/dist/index.js";
|
||||
@@ -46,3 +47,15 @@ test("floating surface remains bounded when its anchor is below the viewport", (
|
||||
assert.ok(position.top + Math.min(500, position.maxHeight) <= 712);
|
||||
assert.ok(position.maxHeight <= 704);
|
||||
});
|
||||
|
||||
test("dropdown scroll keeps portal geometry stable and contained", async () => {
|
||||
const styles = await readFile(
|
||||
new URL("../packages/ui-core/styles.css", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const dropdownRule = styles.match(/\.nodedc-dropdown-surface \{(?<body>[\s\S]*?)\n\}/)?.groups?.body ?? "";
|
||||
|
||||
assert.match(dropdownRule, /box-sizing:\s*border-box;/);
|
||||
assert.match(dropdownRule, /overflow:\s*auto;/);
|
||||
assert.match(dropdownRule, /overscroll-behavior:\s*contain;/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user