Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions openless-all/app/e2e/ask-anything.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ test('translation uses the shared popup and native headroom; save failure preser
await picker.click();
const popup = page.locator('.ol-select-popup');
await expect(popup).toBeVisible();
await expect(popup).toHaveClass(/ol-capsule-language-popup/);
await expect(popup).toHaveCSS('backdrop-filter', 'none');
await popup.evaluate((node) => node.setAttribute('data-test-retained', 'true'));
for (let i = 0; i < 8; i += 1) {
await page.evaluate((level) => (window as any).fixture.setCapsule({ level }), i / 10);
await expect(popup).toHaveAttribute('data-test-retained', 'true');
await expect(popup).toHaveCSS('opacity', '1');
}
const clipped = await popup.evaluate((node) =>
getComputedStyle(node).clipPath.startsWith('shape('),
);
Expand Down
19 changes: 17 additions & 2 deletions openless-all/app/e2e/control-geometry-regressions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,23 @@ test('history list keeps its rounded surface and native trackpad scrolling; corr
});
expect(wheelPrevented).toBe(false);
await page.screenshot({ path: test.info().outputPath('history-list.png') });
await page.getByRole('button', { name: '工具', exact: true }).click();
await page.getByRole('button', { name: '纠正规则', exact: true }).click();
const tools = page.getByRole('button', { name: '工具', exact: true });
await tools.click();
await tools.evaluate(async (node) => {
await Promise.all(
node
.parentElement!.getAnimations({ subtree: true })
.map((animation) => animation.finished.catch(() => {})),
);
});
const corrections = page.getByRole('button', { name: '纠正规则', exact: true });
await corrections.click();
await expect(corrections).toHaveAttribute('aria-current', 'page');
await expect(page.locator('[data-ol-page="corrections"]')).toHaveAttribute(
'data-transition-phase',
'settled',
{ timeout: 15_000 },
);
await settleNavigation(page);
const add = page.getByRole('button', { name: '添加', exact: true });
await expect(add).toBeVisible();
Expand Down
6 changes: 6 additions & 0 deletions openless-all/app/src/components/CapsuleStyles.css
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@
pointer-events: auto;
max-width: 174px;
}
.ol-select-popup.ol-capsule-language-popup {
background: var(--ol-select-popover-bg);
backdrop-filter: none;
-webkit-backdrop-filter: none;
transition: none;
}
.ol-translation-language .ol-select-trigger {
height: 22px;
min-width: 92px;
Expand Down
25 changes: 17 additions & 8 deletions openless-all/app/src/components/CapsuleTranslationBadge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function CapsuleTranslationBadge({ visible = true }: { visible?: boolean
const [menuOpen, setMenuOpen] = useState(false);
const menuEpoch = useRef(0);
const menuOwner = useRef<string | null>(null);
const opening = useRef(false);
const closeTimer = useRef<ReturnType<typeof setTimeout>>();
const shown = useRef(visible);
shown.current = visible;
Expand Down Expand Up @@ -96,12 +97,19 @@ export function CapsuleTranslationBadge({ visible = true }: { visible?: boolean
[i18n.language, target],
);
const selected = languages.find((language) => language.nativeName === target);
const options = languages.filter(
(language) =>
language.nativeName === target ||
!isTranslationTargetRedundant(language.nativeName, snapshot?.workingLanguages ?? []),
const options = useMemo(
() =>
languages
.filter(
(language) =>
language.nativeName === target ||
!isTranslationTargetRedundant(language.nativeName, snapshot?.workingLanguages ?? []),
)
.map((language) => ({ value: language.nativeName, label: language.displayName })),
[languages, target, snapshot?.workingLanguages],
);
const openMenu = async (next: boolean) => {
if (next && opening.current) return;
const epoch = ++menuEpoch.current;
if (closeTimer.current) clearTimeout(closeTimer.current);
if (!next) {
Expand All @@ -122,6 +130,7 @@ export function CapsuleTranslationBadge({ visible = true }: { visible?: boolean
if (!shown.current || !active?.mutable) return;
pickerSession.current = active.sessionId;
menuOwner.current = active.sessionId;
opening.current = true;
try {
await setCapsuleLanguageMenu(true, active.sessionId);
await new Promise<void>((resolve) =>
Expand All @@ -139,6 +148,8 @@ export function CapsuleTranslationBadge({ visible = true }: { visible?: boolean
}
} catch {
if (epoch === menuEpoch.current) setError(true);
} finally {
opening.current = false;
}
};
useEffect(() => {
Expand Down Expand Up @@ -207,10 +218,7 @@ export function CapsuleTranslationBadge({ visible = true }: { visible?: boolean
<div className="ol-translation-language" data-busy={busy}>
<SelectLite
value={target}
options={options.map((language) => ({
value: language.nativeName,
label: language.displayName,
}))}
options={options}
placeholder={selected?.displayName || target || '…'}
ariaLabel={t('translation.target.title')}
disabled={!visible || busy || !snapshot?.mutable}
Expand All @@ -219,6 +227,7 @@ export function CapsuleTranslationBadge({ visible = true }: { visible?: boolean
onChange={(value) => void change(value)}
side="top"
popupStyle={{ width: 240 }}
popupClassName="ol-capsule-language-popup"
/>
</div>
{error && (
Expand Down
7 changes: 6 additions & 1 deletion openless-all/app/src/components/ui/Select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ interface SelectProps {
open?: boolean;
side?: 'top' | 'bottom';
popupStyle?: CSSProperties;
popupClassName?: string;
}

/** Standard Base UI Select; keyboard navigation, typeahead and focus return stay upstream. */
Expand All @@ -29,6 +30,7 @@ export function Select({
open,
side = 'bottom',
popupStyle,
popupClassName,
}: SelectProps) {
return (
<BaseSelect.Root
Expand Down Expand Up @@ -59,7 +61,10 @@ export function Select({
collisionPadding={8}
alignItemWithTrigger={false}
>
<BaseSelect.Popup className="ol-select-popup" style={popupStyle}>
<BaseSelect.Popup
className={['ol-select-popup', popupClassName].filter(Boolean).join(' ')}
style={popupStyle}
>
<BaseSelect.List className="ol-select-list">
{options.map((option) => (
<BaseSelect.Item
Expand Down
11 changes: 10 additions & 1 deletion openless-all/app/src/components/ui/SelectLite.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface SelectLiteProps {
open?: boolean;
side?: 'top' | 'bottom';
popupStyle?: CSSProperties;
popupClassName?: string;
searchable?: boolean;
searchPlaceholder?: string;
emptyMessage?: string;
Expand All @@ -51,6 +52,7 @@ export function SelectLite({
open: controlledOpen,
side = 'bottom',
popupStyle,
popupClassName,
searchable = false,
searchPlaceholder = 'Search…',
emptyMessage = 'No matching options',
Expand Down Expand Up @@ -86,6 +88,7 @@ export function SelectLite({
open: controlledOpen,
side,
popupStyle,
popupClassName,
}}
onOpenChange={handleOpenChange}
/>
Expand All @@ -104,6 +107,7 @@ export function SelectLite({
open: controlledOpen,
side,
popupStyle,
popupClassName,
}}
onOpenChange={handleOpenChange}
/>
Expand All @@ -124,6 +128,7 @@ function SearchableSelect({
open,
side = 'bottom',
popupStyle,
popupClassName,
}: SelectLiteProps) {
const [query, setQuery] = useState('');
const inputRef = useRef<HTMLInputElement>(null);
Expand Down Expand Up @@ -181,7 +186,11 @@ function SearchableSelect({
sideOffset={8}
collisionPadding={8}
>
<Combobox.Popup className="ol-select-popup" initialFocus={inputRef} style={popupStyle}>
<Combobox.Popup
className={['ol-select-popup', popupClassName].filter(Boolean).join(' ')}
initialFocus={inputRef}
style={popupStyle}
>
<div className="ol-select-search">
<Icon name="search" size={13} />
<Combobox.Input
Expand Down
Loading