You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/14 14:23:16 UTC
(maka) branch main updated: fix(ui): expand model wheels in place without empty ends (#5305)
This is an automated email from the ASF dual-hosted git repository.
M4n5ter pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git
The following commit(s) were added to refs/heads/main by this push:
new c22768c3b0 fix(ui): expand model wheels in place without empty ends (#5305)
c22768c3b0 is described below
commit c22768c3b0dc47518f6f8584e864f86f0b1e5379
Author: Wang <m4...@gmail.com>
AuthorDate: Mon Sep 14 22:23:11 2026 +0800
fix(ui): expand model wheels in place without empty ends (#5305)
Generated-by: Codex
---
apps/desktop/e2e-budget.json | 2 +-
apps/desktop/e2e/workhub-layout.spec.ts | 19 ++++----
apps/desktop/src/renderer/styles/workhub.css | 9 ++--
apps/desktop/stories/workhub.stories.tsx | 15 ++++---
.../ui/src/__tests__/model-wheel-picker.test.tsx | 12 +++--
packages/ui/src/model-wheel-picker.tsx | 52 ++++++++++++++--------
packages/ui/src/styles.css | 6 +--
packages/ui/stories/model-picker.stories.tsx | 13 ++++++
8 files changed, 80 insertions(+), 48 deletions(-)
diff --git a/apps/desktop/e2e-budget.json b/apps/desktop/e2e-budget.json
index a86076bed8..bb6b7f6561 100644
--- a/apps/desktop/e2e-budget.json
+++ b/apps/desktop/e2e-budget.json
@@ -79,7 +79,7 @@
},
"workhub-layout.spec.ts": {
"tests": 2,
- "electron": "WorkHub model configuration and attachment sending cross the Host boundary; the compact native window grows to contain the model overlay without moving composer controls on screen, and pointer dragging scrolls the no-drag wheel instead of moving the frameless window; native floating preserves macOS Dock visibility; a held-open Host Turn verifies immediate prompt visibility, durable transcript handoff and persistence after renderer reload"
+ "electron": "WorkHub model configuration and attachment sending cross the Host boundary; the compact native window grows only with the inline composer wheel and keeps its bottom anchor, and pointer dragging scrolls the no-drag wheel instead of moving the frameless window; native floating preserves macOS Dock visibility; a held-open Host Turn verifies immediate prompt visibility, durable transcript handoff and persistence after renderer reload"
},
"workhub-reconstruction.spec.ts": {
"tests": 1,
diff --git a/apps/desktop/e2e/workhub-layout.spec.ts b/apps/desktop/e2e/workhub-layout.spec.ts
index 15158441d6..ed953639a8 100644
--- a/apps/desktop/e2e/workhub-layout.spec.ts
+++ b/apps/desktop/e2e/workhub-layout.spec.ts
@@ -20,10 +20,10 @@
import { FAKE_HOLD_OPEN_PROMPT } from '@maka/runtime/test-only/fake-backend';
import { awaitSendReady, COMPOSER_INPUT, expect, test, getWorkHubPage } from './fixtures';
-// The compact native window must contain the top-layer wheel while keeping
-// composer controls fixed on screen; native dragging must scroll the no-drag
+// The compact native window must contain the inline wheel and retain its
+// bottom anchor; native dragging must scroll the no-drag
// wheel instead of moving the frameless window. A browser has neither boundary.
-test('WorkHub uses its coordination model and shared attachment composer', async ({ sessionLocalWindow: { page, app } }) => {
+test('WorkHub uses its coordination model and shared attachment composer', async ({ sessionLocalWindow: { page, app } }, testInfo) => {
await page.evaluate(async () => {
const { connections } = await window.maka.connections.getSnapshot();
const connection = connections.find((entry) => entry.slug === 'e2e')!;
@@ -201,22 +201,22 @@ test('WorkHub uses its coordination model and shared attachment composer', async
return { x: x + rect.x, y: y + rect.y, width: rect.width, height: rect.height };
}), origin);
};
- const compactControls = await screenLayout();
await model.click();
const wheel = workhub.getByRole('listbox');
await expect(wheel).toBeVisible();
await expect(wheel.getByRole('option')).toHaveCount(3);
const modelChoices = await workhub.evaluate(async (id) => (await window.maka.connections.getSnapshot(id)).chatModelChoices, sessionId);
- await expect(wheel.locator('.maka-model-wheel-label')).toHaveText(modelChoices.map((choice) => choice.label));
+ await expect(wheel.getByRole('option').locator('.maka-model-wheel-label')).toHaveText(modelChoices.map((choice) => choice.label));
await expect(workhub.locator('.workHubHistory')).toBeHidden();
await expect.poll(() => workhub.evaluate(() => innerHeight)).toBeGreaterThan(compactHeight);
await expect.poll(floatingBottom).toBe(anchoredBottom);
- await expect.poll(screenLayout).toEqual(compactControls);
+ await expect.poll(() => workhub.evaluate(() => innerHeight)).toBeLessThanOrEqual(compactHeight + 132);
const expectWheelInsideWindow = () => expect.poll(() => wheel.evaluate((element) => {
const rect = element.getBoundingClientRect();
return rect.top >= 0 && rect.left >= 0 && rect.bottom <= innerHeight && rect.right <= innerWidth;
})).toBe(true);
await expectWheelInsideWindow();
+ await workhub.screenshot({ path: testInfo.outputPath('floating-composer-wheel.png') });
const expectSnappedSelection = async (index: number) => {
const choice = modelChoices[index]!;
const option = wheel.getByRole('option').nth(index);
@@ -229,7 +229,7 @@ test('WorkHub uses its coordination model and shared attachment composer', async
}, sessionId)).toEqual({ connectionId: choice.connectionId, model: choice.model });
return choice;
};
- const initialIndex = Math.round(await wheel.evaluate((element) => element.scrollTop) / 44);
+ const initialIndex = await wheel.getByRole('option').evaluateAll((options) => options.findIndex((option) => option.getAttribute('aria-selected') === 'true'));
const scrollDirection = initialIndex < modelChoices.length - 1 ? 1 : -1;
await wheel.hover();
await workhub.mouse.wheel(0, scrollDirection * 30);
@@ -251,11 +251,10 @@ test('WorkHub uses its coordination model and shared attachment composer', async
await workhub.mouse.up();
await expect.poll(screenLayout).toEqual(beforeDrag);
await expect(wheel).toBeVisible();
- await expect.poll(() => wheel.evaluate((element) => element.scrollTop)).toBe(Math.round((dragInitialTop - dragDistance) / 44) * 44);
- const draggedIndex = Math.round((dragInitialTop - dragDistance) / 44);
+ const draggedIndex = Math.round((dragInitialTop - dragDistance + 44) / 44) % modelChoices.length;
await expectSnappedSelection(draggedIndex);
await wheel.press('ArrowDown');
- await expectSnappedSelection(Math.min(draggedIndex + 1, modelChoices.length - 1));
+ await expectSnappedSelection((draggedIndex + 1) % modelChoices.length);
await wheel.press('Escape');
await expect(wheel).toHaveCount(0);
await expect(model).toBeFocused();
diff --git a/apps/desktop/src/renderer/styles/workhub.css b/apps/desktop/src/renderer/styles/workhub.css
index 05d5b7e2db..a05497c757 100644
--- a/apps/desktop/src/renderer/styles/workhub.css
+++ b/apps/desktop/src/renderer/styles/workhub.css
@@ -156,11 +156,10 @@
.workHubLiveError { padding: var(--space-3); }
.workHubComposerSurface { position: relative; width: 100%; }
.workHubComposerContent { position: relative; }
- /* Top-layer content still needs room inside the native window. Measuring
- this leading space grows the bottom-anchored window upward, without
- changing the composer's layout or its controls' screen positions. */
- .workHubLive[data-placement='floating'][data-conversation-expanded='false'] .workHubComposerSurface:has(.maka-model-wheel-anchor[data-open='true'])::before {
- content: ''; display: block; height: 136px;
+ /* Compact WorkHub keeps its original inline wheel footprint. Other hosts
+ keep the button footprint and paint the same wheel over that position. */
+ .workHubLive[data-placement='floating'][data-conversation-expanded='false'] .maka-model-wheel-anchor[data-open='true'] {
+ width: min(264px, 55vw); height: var(--maka-model-wheel-height);
}
.workHubComposerActions { display: flex; align-items: center; gap: 2px; margin-left: auto; }
.workHubRevealMark { position: absolute; inset: 32px 0 calc(var(--workhub-composer-height, 96px) + 12px); display: grid; place-items: center; z-index: 3; color: var(--foreground); opacity: 0; pointer-events: none; }
diff --git a/apps/desktop/stories/workhub.stories.tsx b/apps/desktop/stories/workhub.stories.tsx
index ea833934e1..eed5ef63c5 100644
--- a/apps/desktop/stories/workhub.stories.tsx
+++ b/apps/desktop/stories/workhub.stories.tsx
@@ -189,10 +189,15 @@ export const StandardComposer: Story = {
return { x, y, width, height };
});
const before = layout();
+ const anchor = trigger.getBoundingClientRect();
await userEvent.click(trigger);
const wheel = canvas.getByRole('listbox', { name: /切换当前任务模型/ });
await waitFor(() => expect(wheel).toHaveFocus());
expect(layout()).toEqual(before);
+ expect(trigger).not.toBeVisible();
+ const expanded = wheel.getBoundingClientRect();
+ const center = Math.max(expanded.height / 2, Math.min(innerHeight - expanded.height / 2, (anchor.top + anchor.bottom) / 2));
+ expect(Math.abs((expanded.top + expanded.bottom) / 2 - center)).toBeLessThanOrEqual(1);
await expect(within(wheel).getByRole('option', { name: /model-a/, selected: true })).toBeInTheDocument();
await userEvent.keyboard('{End}');
await waitFor(() => expect(writes.model).toHaveBeenCalledWith(sessionId, expect.objectContaining({ expectedRevision: 1, modelTarget: expect.objectContaining({ model: 'model-b' }) })));
@@ -212,11 +217,6 @@ export const StandardComposer: Story = {
expect(layout()).toEqual(before);
await userEvent.click(trigger);
await waitFor(() => expect(canvas.getByRole('listbox')).toHaveFocus());
- await userEvent.click(trigger);
- await waitFor(() => expect(canvas.queryByRole('listbox')).not.toBeInTheDocument());
- await waitFor(() => expect(trigger).toHaveFocus());
- await userEvent.click(trigger);
- await waitFor(() => expect(canvas.getByRole('listbox')).toHaveFocus());
await userEvent.tab();
await waitFor(() => expect(canvas.queryByRole('listbox')).not.toBeInTheDocument());
expect(canvasElement.ownerDocument.activeElement).not.toBe(canvasElement.ownerDocument.body);
@@ -241,11 +241,12 @@ export const ProgressModelPicker: Story = {
await userEvent.click(trigger);
const wheel = await canvas.findByRole('listbox');
await waitFor(() => expect(wheel).toHaveFocus());
- const surface = wheel.closest('.maka-model-wheel-popup')!;
+ const surface = wheel.closest('.maka-model-wheel-expanded')!;
const pixels = new OffscreenCanvas(1, 1).getContext('2d')!;
pixels.fillStyle = getComputedStyle(surface).backgroundColor;
pixels.fillRect(0, 0, 1, 1);
- expect(pixels.getImageData(0, 0, 1, 1).data[3]).toBe(255);
+ expect(pixels.getImageData(0, 0, 1, 1).data[3]).toBeGreaterThanOrEqual(230);
+ expect(pixels.getImageData(0, 0, 1, 1).data[3]).toBeLessThan(255);
expect(editor).toHaveTextContent('Keep this draft readable while choosing a model.');
},
};
diff --git a/packages/ui/src/__tests__/model-wheel-picker.test.tsx b/packages/ui/src/__tests__/model-wheel-picker.test.tsx
index 800ba3df4e..4d6233cd64 100644
--- a/packages/ui/src/__tests__/model-wheel-picker.test.tsx
+++ b/packages/ui/src/__tests__/model-wheel-picker.test.tsx
@@ -58,14 +58,14 @@ test('the wheel applies settled selection once and restores the saved model on f
try {
await act(() => root.render(<Harness />));
const wheel = document.querySelector<HTMLElement>('[role="listbox"]')!;
- assert.equal(wheel.scrollTop, 44, 'opening centers the current model');
+ assert.equal(wheel.scrollTop, 132, 'opening centers the current model');
await act(() => wheel.dispatchEvent(event('scroll')));
await settle();
assert.deepEqual(calls, [], 'initial positioning never changes the model');
await act(() => {
wheel.dispatchEvent(event('wheel'));
- wheel.scrollTop = 88;
+ wheel.scrollTop = 176;
wheel.dispatchEvent(event('scroll'));
});
await settle();
@@ -85,12 +85,16 @@ test('the wheel applies settled selection once and restores the saved model on f
await act(() => wheel.dispatchEvent(Object.assign(event('keydown'), { key: 'Home' })));
assert.deepEqual(calls, ['C', 'A'], 'keyboard navigation also applies immediately');
await act(async () => { finish?.(false); });
- assert.equal(wheel.scrollTop, 88, 'a failed save recenters the authoritative value');
+ assert.equal(wheel.scrollTop, 176, 'a failed save recenters the authoritative value');
assert.equal(wheel.querySelector('[aria-selected="true"]')?.textContent, 'C');
+ await act(() => wheel.dispatchEvent(Object.assign(event('keydown'), { key: 'ArrowDown' })));
+ assert.deepEqual(calls, ['C', 'A', 'A'], 'the last model wraps to its visible next neighbor');
+ await act(async () => { finish?.(true); });
+
await act(() => root.render(<Harness disabled />));
await act(() => wheel.dispatchEvent(Object.assign(event('keydown'), { key: 'Home' })));
- assert.deepEqual(calls, ['C', 'A'], 'disabled navigation does not change the model');
+ assert.deepEqual(calls, ['C', 'A', 'A'], 'disabled navigation does not change the model');
} finally {
await act(() => root.unmount());
Object.assign(globalThis, original);
diff --git a/packages/ui/src/model-wheel-picker.tsx b/packages/ui/src/model-wheel-picker.tsx
index 0c81d59d72..ef6bfb1870 100644
--- a/packages/ui/src/model-wheel-picker.tsx
+++ b/packages/ui/src/model-wheel-picker.tsx
@@ -17,7 +17,7 @@
* under the License.
*/
-import { type ReactNode, useId, useLayoutEffect, useRef, useState } from 'react';
+import { type CSSProperties, type ReactNode, useId, useLayoutEffect, useRef, useState } from 'react';
import { Button, useLayer } from '@astryxdesign/core';
import { Check, ICON_SIZE } from './icons.js';
@@ -29,7 +29,7 @@ export interface ModelWheelOption {
disabled?: boolean;
}
-/** Shared overlay picker: the model at the settled snap position takes effect. */
+/** The trigger becomes a wheel in place; the settled model takes effect. */
export function ModelWheelPicker(props: {
options: readonly ModelWheelOption[];
value?: string;
@@ -48,6 +48,7 @@ export function ModelWheelPicker(props: {
const trigger = useRef<HTMLButtonElement>(null);
const anchor = useRef<HTMLSpanElement>(null);
const open = props.open ?? internalOpen;
+ const height = ROW_HEIGHT * (props.options.length > 1 ? 3 : 1);
const setOpen = (next: boolean) => {
if (props.open === undefined) setInternalOpen(next);
props.onOpenChange?.(next);
@@ -70,21 +71,26 @@ export function ModelWheelPicker(props: {
element.style.width = `${element.getBoundingClientRect().width}px`;
return () => { element.style.removeProperty('width'); };
}, [open]);
- return <span ref={anchor} className="maka-model-wheel-anchor" data-open={open}>
- <Button ref={(element) => { trigger.current = element; layer.ref(element); }} type="button" variant="ghost" size={props.size ?? 'sm'}
+ return <span ref={(element) => { anchor.current = element; layer.ref(element); }}
+ className="maka-model-wheel-anchor" data-open={open}
+ style={{ '--maka-model-wheel-height': `${height}px` } as CSSProperties}>
+ <Button ref={trigger} type="button" variant="ghost" size={props.size ?? 'sm'}
label={props.label} icon={props.icon} tooltip={open ? undefined : props.tooltip}
isDisabled={props.disabled || props.options.length === 0}
className={props.triggerClassName} aria-label={props.ariaLabel}
aria-haspopup="listbox" aria-expanded={open} aria-controls={open ? layer.id : undefined}
- onMouseDown={(event) => { if (open) event.preventDefault(); }}
- onClick={() => open ? close(true) : setOpen(true)}
+ onClick={() => setOpen(true)}
onKeyDown={(event) => {
if (!props.disabled && props.options.length > 0 && (event.key === 'ArrowDown' || event.key === 'ArrowUp')) {
event.preventDefault(); event.stopPropagation(); setOpen(true);
}
}} />
{layer.render(open && layer.isOpen ? <ModelWheel {...props} onClose={close} /> : null,
- { placement: 'above', alignment: 'start', offset: 4, className: 'maka-model-wheel-popup' })}
+ { positioning: 'custom', className: 'maka-model-wheel-expanded', style: {
+ position: 'fixed', inset: 'auto',
+ top: `clamp(0px, calc(anchor(center) - ${height / 2}px), calc(100dvh - ${height}px))`,
+ left: 'clamp(0px, anchor(left), calc(100vw - min(264px, 55vw)))',
+ } })}
</span>;
}
@@ -101,12 +107,20 @@ function ModelWheel(props: Parameters<typeof ModelWheelPicker>[0] & {
const saving = useRef(false);
const [pending, setPending] = useState<string | null>(null);
const currentValue = pending ?? props.value;
+ const count = props.options.length;
+ const height = ROW_HEIGHT * (count > 1 ? 3 : 1);
+ const inset = (height - ROW_HEIGHT) / 2;
+ // Neighboring copies let the first and last models have real neighbors,
+ // rather than empty padding. Only the middle copy is exposed to assistive technology.
+ const copies = count > 1 ? [0, 1, 2] : [1];
+ const centeredTop = (index: number) => (count > 1 ? count + index : 0) * ROW_HEIGHT - inset;
const selectedIndex = Math.max(0, props.options.findIndex((option) => option.value === currentValue));
const [preview, setPreview] = useState(selectedIndex);
const latest = useRef(props);
latest.current = props;
const disabled = props.disabled || pending !== null;
- const indexAt = (element: HTMLDivElement) => Math.max(0, Math.min(latest.current.options.length - 1, Math.round(element.scrollTop / ROW_HEIGHT)));
+ const rowAt = (element: HTMLDivElement) => Math.round((element.scrollTop + inset) / ROW_HEIGHT);
+ const indexAt = (element: HTMLDivElement) => count ? ((rowAt(element) % count) + count) % count : 0;
const clearTimer = () => clearTimeout(timer.current);
const pick = async (index: number) => {
const option = latest.current.options[index];
@@ -122,15 +136,16 @@ function ModelWheel(props: Parameters<typeof ModelWheelPicker>[0] & {
clearTimer();
if (!interacted.current || element.dataset.dragging || saving.current) return;
const index = indexAt(element);
- if (Math.abs(element.scrollTop - index * ROW_HEIGHT) > 1) return;
+ if (Math.abs(element.scrollTop - (rowAt(element) * ROW_HEIGHT - inset)) > 1) return;
interacted.current = false;
+ element.scrollTop = centeredTop(index);
void pick(index);
};
const moveTo = (element: HTMLDivElement, index: number) => {
clearTimer();
interacted.current = false;
- const next = Math.max(0, Math.min(latest.current.options.length - 1, index));
- element.scrollTop = next * ROW_HEIGHT;
+ const next = count ? ((index % count) + count) % count : 0;
+ element.scrollTop = centeredTop(next);
setPreview(next);
void pick(next);
};
@@ -138,8 +153,8 @@ function ModelWheel(props: Parameters<typeof ModelWheelPicker>[0] & {
if (!element.dataset.dragging) return;
delete element.dataset.dragging;
// Keep `moved` until the synthetic click has been suppressed.
- const next = indexAt(element);
- element.scrollTo({ top: next * ROW_HEIGHT, behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'instant' : 'smooth' });
+ const next = rowAt(element);
+ element.scrollTo({ top: next * ROW_HEIGHT - inset, behavior: window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'instant' : 'smooth' });
clearTimer();
timer.current = setTimeout(() => settle(element), 180);
};
@@ -147,7 +162,7 @@ function ModelWheel(props: Parameters<typeof ModelWheelPicker>[0] & {
if (!viewport.current) return;
interacted.current = false;
clearTimer();
- viewport.current.scrollTop = selectedIndex * ROW_HEIGHT;
+ viewport.current.scrollTop = centeredTop(selectedIndex);
setPreview(selectedIndex);
}, [selectedIndex, pending, props.value, props.options.length]);
useLayoutEffect(() => {
@@ -159,7 +174,7 @@ function ModelWheel(props: Parameters<typeof ModelWheelPicker>[0] & {
<div ref={viewport} className="maka-model-wheel-viewport" role="listbox" tabIndex={0}
aria-label={props.ariaLabel} aria-disabled={disabled} aria-busy={pending !== null}
aria-activedescendant={props.options.length ? `${id}-${preview}` : undefined}
- style={{ height: ROW_HEIGHT * 3, paddingBlock: ROW_HEIGHT }}
+ style={{ height }}
onBlur={(event) => {
if (event.currentTarget.contains(event.relatedTarget)) return;
if (interacted.current) void pick(indexAt(event.currentTarget));
@@ -213,8 +228,9 @@ function ModelWheel(props: Parameters<typeof ModelWheelPicker>[0] & {
if (next === undefined) return;
event.preventDefault(); event.stopPropagation(); moveTo(event.currentTarget, next);
}}>
- {props.options.map((option, index) => <div key={option.value} id={`${id}-${index}`}
- className="maka-model-wheel-option" role="option" aria-selected={option.value === currentValue}
+ {copies.flatMap((copy) => props.options.map((option, index) => <div key={`${copy}-${option.value}`} id={copy === 1 ? `${id}-${index}` : undefined}
+ className="maka-model-wheel-option" role={copy === 1 ? 'option' : undefined} aria-hidden={copy !== 1 || undefined}
+ aria-selected={copy === 1 ? option.value === currentValue : undefined}
data-active={index === preview} aria-disabled={disabled || option.disabled}
style={{ height: ROW_HEIGHT }} title={[option.label, option.heading, option.description].filter(Boolean).join(' · ')}
onMouseDown={(event) => event.preventDefault()}
@@ -222,7 +238,7 @@ function ModelWheel(props: Parameters<typeof ModelWheelPicker>[0] & {
<span className="maka-model-wheel-label">{option.label}</span>
{option.heading && <span className="maka-model-wheel-provider">{option.heading}</span>}
{option.value === currentValue && <span className="maka-model-wheel-check"><Check size={ICON_SIZE.control} aria-hidden="true" /></span>}
- </div>)}
+ </div>))}
</div>
</div>;
}
diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css
index 1c72dc37fd..cc98577854 100644
--- a/packages/ui/src/styles.css
+++ b/packages/ui/src/styles.css
@@ -21,10 +21,10 @@
.maka-model-wheel-anchor { display: inline-flex; min-width: 0; max-width: 100%; }
.maka-model-wheel-anchor > button { min-width: 0; max-width: 100%; }
-.maka-model-wheel-anchor[data-open='true'] > button { width: 100%; }
-.maka-model-wheel-popup { border-radius: var(--radius-container); background: var(--background-elevated); box-shadow: var(--shadow-med); }
+.maka-model-wheel-anchor[data-open='true'] > button { visibility: hidden; }
+.maka-model-wheel-expanded { border-radius: 10px; background: color-mix(in oklch, var(--background-elevated) 94%, transparent); backdrop-filter: blur(12px); }
.maka-model-wheel { position: relative; width: min(264px, 55vw); max-width: 100%; }
-.maka-model-wheel::before { content: ''; position: absolute; inset: 44px 0; border-radius: 10px; background: color-mix(in oklch, var(--foreground) 6%, transparent); pointer-events: none; }
+.maka-model-wheel::before { content: ''; position: absolute; inset: 50% 0 auto; height: 44px; translate: 0 -50%; border-radius: 10px; background: color-mix(in oklch, var(--foreground) 6%, transparent); pointer-events: none; }
.maka-model-wheel-viewport { position: relative; box-sizing: border-box; overflow-y: auto; overscroll-behavior: contain; scroll-snap-type: y mandatory; scrollbar-width: none; mask-image: linear-gradient(transparent, black 32%, black 68%, transparent); outline: none; }
.maka-model-wheel-viewport:focus-visible { outline: var(--focus-ring-width) solid var(--focus-ring); outline-offset: calc(-1 * var(--focus-ring-width)); }
.maka-model-wheel-viewport[data-dragging='true'] { scroll-snap-type: none; cursor: grabbing; }
diff --git a/packages/ui/stories/model-picker.stories.tsx b/packages/ui/stories/model-picker.stories.tsx
index e801c397b0..ffd4221f5f 100644
--- a/packages/ui/stories/model-picker.stories.tsx
+++ b/packages/ui/stories/model-picker.stories.tsx
@@ -400,7 +400,20 @@ export const ManyConnections: Story = {
await userEvent.click(trigger);
const menu = within(document.body);
await expect(await menu.findAllByRole('option')).toHaveLength(MANY_CHOICES.length);
+ const wheel = menu.getByRole('listbox');
+ const expectFilledEdges = async () => waitFor(() => {
+ const bounds = wheel.getBoundingClientRect();
+ const rows = Array.from(wheel.querySelectorAll('.maka-model-wheel-option'))
+ .map((row) => row.getBoundingClientRect());
+ for (const y of [bounds.top + 8, bounds.bottom - 8]) {
+ expect(rows.some((row) => row.top <= y && row.bottom >= y)).toBe(true);
+ }
+ const selected = wheel.querySelector('[aria-selected="true"]')!.getBoundingClientRect();
+ expect(Math.abs((selected.top + selected.bottom - bounds.top - bounds.bottom) / 2)).toBeLessThanOrEqual(1);
+ });
+ await expectFilledEdges();
await userEvent.keyboard('{End}');
+ await expectFilledEdges();
await expect(await menu.findByRole('option', { name: /vendor\/gamma/, selected: true })).toBeInTheDocument();
},
};
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org