mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
fix(app): forward ⌘L from a focused browser page to the address field (#57965)
This commit is contained in:
committed by
GitHub
Unverified
parent
f6958ddb38
commit
e8f0b3e7bf
@@ -429,6 +429,45 @@ story("⌘T and ⌘W follow focus: side tabs inside the side panel, session tabs
|
||||
await expect(sessions).toHaveText("1")
|
||||
})
|
||||
|
||||
story("⌘L selects the browser address from the page and the pane, and nowhere else", async ({ page }) => {
|
||||
const root = page.getByTestId("browser-region-fixture")
|
||||
const mod = (await page.evaluate(() => /Mac|iPhone|iPad|iPod/.test(navigator.platform))) ? "Meta" : "Control"
|
||||
const outside = root.getByRole("button", { name: "Beta", exact: true })
|
||||
const address = root.getByRole("combobox", { name: "Browser address", exact: true })
|
||||
const editor = page.frameLocator('iframe[title="Browser page"]').getByRole("textbox", { name: "Editor page input" })
|
||||
|
||||
const selected = () =>
|
||||
address.evaluate(
|
||||
(input: HTMLInputElement) =>
|
||||
document.activeElement === input &&
|
||||
input.value.length > 0 &&
|
||||
input.selectionStart === 0 &&
|
||||
input.selectionEnd === input.value.length,
|
||||
)
|
||||
|
||||
await expect(root.getByText("Registrations: 1", { exact: true })).toBeVisible()
|
||||
await outside.click()
|
||||
await root.getByRole("button", { name: "First inventory", exact: true }).click()
|
||||
await expect(root.getByTestId("selected")).toHaveText(/^browser:/)
|
||||
|
||||
// In the main column ⌘L does not reach into the side panel.
|
||||
await outside.focus()
|
||||
await outside.press(`${mod}+l`)
|
||||
await expect(address).not.toBeFocused()
|
||||
|
||||
// A focused page hands ⌘L to the pane, as it does ⌘T and ⌘W, without typing it.
|
||||
await editor.click()
|
||||
await editor.press(`${mod}+l`)
|
||||
await expect.poll(selected).toBe(true)
|
||||
await expect(editor).not.toHaveValue(/l$/)
|
||||
|
||||
// On the pane's own surface too.
|
||||
await page.locator('#browser-panel [data-component="browser-bar"]').click({ position: { x: 2, y: 2 } })
|
||||
await expect(address).not.toBeFocused()
|
||||
await page.keyboard.press(`${mod}+l`)
|
||||
await expect.poll(selected).toBe(true)
|
||||
})
|
||||
|
||||
story("keeps ⌘T in the browser pane after a click on its loading surface", async ({ page }) => {
|
||||
const root = page.getByTestId("browser-region-fixture")
|
||||
const mod = (await page.evaluate(() => /Mac|iPhone|iPad|iPod/.test(navigator.platform))) ? "Meta" : "Control"
|
||||
|
||||
@@ -535,14 +535,6 @@ story("adds the whole page to the session and keeps the draft when the button is
|
||||
await expect(root.getByTestId("fixture-comments").getByRole("listitem")).toHaveText([/: Check the header layout$/])
|
||||
})
|
||||
|
||||
story("focuses the address field when the page uses the address shortcut", async ({ page }) => {
|
||||
const root = page.getByTestId("browser-pane-fixture")
|
||||
await root.getByRole("button", { name: "Address shortcut", exact: true }).click()
|
||||
const address = root.getByRole("combobox", { name: "Browser address", exact: true })
|
||||
await expect(address).toBeFocused()
|
||||
await expect(address).toHaveJSProperty("selectionEnd", "https://alpha.example/".length)
|
||||
})
|
||||
|
||||
story("shows the page's cookies in the site information and clears them", async ({ page }) => {
|
||||
const root = page.getByTestId("browser-pane-fixture")
|
||||
await root.getByRole("button", { name: "Site information", exact: true }).click()
|
||||
|
||||
@@ -136,8 +136,6 @@ export function createBrowserPage(
|
||||
detail?: (detail: PageDetail) => void
|
||||
/** The page's zoom changed, which Chromium applies to every page of the same origin in the partition. */
|
||||
zoomed?: () => void
|
||||
/** The user pressed the address shortcut while the page had focus. */
|
||||
address?: () => void
|
||||
/** Hands a browser or shell shortcut to the app window. */
|
||||
shortcut?: (shortcut: PageShortcut) => void
|
||||
/** The page took keyboard focus. */
|
||||
@@ -214,14 +212,6 @@ export function createBrowserPage(
|
||||
return
|
||||
}
|
||||
|
||||
// The system browser's address shortcut. The app's own focus moves to the address field.
|
||||
if (!input.shift && pageLetter(input) === "l") {
|
||||
event.preventDefault()
|
||||
options.address?.()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const direction = zoomKey(input)
|
||||
|
||||
if (!direction) return
|
||||
|
||||
@@ -110,7 +110,6 @@ function fixture(strip: string[] = []) {
|
||||
preview: (path) => routed.preview.push(path),
|
||||
inspect: (event) => routed.inspect.push(event),
|
||||
page: () => undefined,
|
||||
address: () => undefined,
|
||||
shortcut: () => undefined,
|
||||
focused: () => undefined,
|
||||
})
|
||||
|
||||
@@ -58,8 +58,6 @@ export function createConnection(input: {
|
||||
inspect: (event: InspectEvent) => void
|
||||
/** A page's icon or zoom changed. */
|
||||
page: (event: PageEvent) => void
|
||||
/** The user pressed the address shortcut while the page had focus. */
|
||||
address: (tabID: Browser.TabID) => void
|
||||
/** A focused page handed a browser or shell shortcut to the window. */
|
||||
shortcut: (event: ShortcutEvent) => void
|
||||
/** The user put keyboard focus in a page. */
|
||||
@@ -162,8 +160,6 @@ export function createConnection(input: {
|
||||
|
||||
if (event.type === "page") return input.page(event)
|
||||
|
||||
if (event.type === "address") return input.address(event.tabID)
|
||||
|
||||
if (event.type === "shortcut") return input.shortcut(event)
|
||||
|
||||
if (event.type === "focused") return input.focused()
|
||||
|
||||
@@ -50,8 +50,6 @@ export const PaneEvent = Schema.Union([
|
||||
icon: Schema.optionalKey(detail(MAX_ICON_URL).check(Schema.isStartingWith("data:image/"))),
|
||||
zoom: Schema.Finite.check(Schema.isBetween({ minimum: 0.25, maximum: 5 })),
|
||||
}),
|
||||
// The page asked for the address field, with the platform's address shortcut.
|
||||
Schema.Struct({ type: Schema.Literal("address"), tabID: Browser.TabID }),
|
||||
// The user put keyboard focus in the page, outside the window's own document.
|
||||
Schema.Struct({ type: Schema.Literal("focused"), tabID: Browser.TabID }),
|
||||
// The page handed a browser or shell shortcut to the window, which runs it as its own key press.
|
||||
|
||||
@@ -45,6 +45,10 @@ test("forwards browser and shell shortcuts while leaving page editing, find, rel
|
||||
["mod", "w", true, "w"],
|
||||
["mod", "w", false, "w"],
|
||||
["mod", "\u0432", true, "w", "KeyW"],
|
||||
// The address field, as in a browser.
|
||||
["mod", "l", true, "l"],
|
||||
["mod", "l", false, "l"],
|
||||
["mod", "l", true, "l", "KeyP"],
|
||||
["mod", "n", true, "n"],
|
||||
["mod", "n", false, "n"],
|
||||
["mod", "1", true, "1"],
|
||||
@@ -98,7 +102,7 @@ test("forwards browser and shell shortcuts while leaving page editing, find, rel
|
||||
["mod", "a", false, undefined],
|
||||
["mod", "z", false, undefined],
|
||||
["mod", "y", false, undefined],
|
||||
// Find, reload, history, and the pane's own address, picker and zoom chords.
|
||||
// Find, reload, history, and the pane's own picker and zoom chords.
|
||||
["mod", "f", true, undefined],
|
||||
["mod", "g", true, undefined],
|
||||
["mod+shift", "G", true, undefined],
|
||||
@@ -106,7 +110,6 @@ test("forwards browser and shell shortcuts while leaving page editing, find, rel
|
||||
["mod", "r", false, undefined],
|
||||
["mod", "[", true, undefined],
|
||||
["mod", "]", true, undefined],
|
||||
["mod", "l", true, undefined],
|
||||
["mod+shift", "C", true, undefined],
|
||||
["mod", "=", true, undefined],
|
||||
["mod", "-", true, undefined],
|
||||
|
||||
@@ -11,8 +11,8 @@ export type PageShortcut = {
|
||||
}
|
||||
|
||||
// Unshifted chords with the platform's modifier that belong to the app shell or browser pane rather than the page:
|
||||
// opening, closing or switching tabs, opening settings, and toggling the file tree.
|
||||
const shellKeys = ["t", "w", "n", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", "\\"]
|
||||
// opening, closing or switching tabs, focusing the address field, opening settings, and toggling the file tree.
|
||||
const shellKeys = ["t", "w", "l", "n", "1", "2", "3", "4", "5", "6", "7", "8", "9", ",", "\\"]
|
||||
|
||||
// Shifted chords with the platform's modifier that belong to the app shell rather than the page: reopening a tab,
|
||||
// switching side-panel tabs, toggling the side panel, opening the command palette, or running session commands. New Window and Close Window
|
||||
|
||||
@@ -99,7 +99,6 @@ export function createModel(ctx: SetupContext<typeof definition>) {
|
||||
const live = new Map<string, Live>()
|
||||
const listeners = new Map<string, (event: PaneEvent) => void>()
|
||||
const inspectors = new Map<string, Set<(event: InspectEvent) => void>>()
|
||||
const addressed = new Map<string, Set<(tabID: Browser.TabID) => void>>()
|
||||
// Sessions where the user just opened a tab; its pane focuses the address field once the blank page shows.
|
||||
const opening = new Set<string>()
|
||||
// Each tab's last recorded page, so a state that repeats it does not write the history again.
|
||||
@@ -218,7 +217,6 @@ export function createModel(ctx: SetupContext<typeof definition>) {
|
||||
|
||||
if (visits) history.set({ visits })
|
||||
},
|
||||
address: (tabID) => addressed.get(id)?.forEach((listener) => listener(tabID)),
|
||||
// Only the pane on screen holds the page that had focus.
|
||||
shortcut: (event) => {
|
||||
if (sessions.current()?.key === id) panes()[0]?.press(event)
|
||||
@@ -498,18 +496,6 @@ export function createModel(ctx: SetupContext<typeof definition>) {
|
||||
recorded.clear()
|
||||
history.set({ visits: [] })
|
||||
},
|
||||
/** The page asking for the address field with its shortcut. */
|
||||
onAddress(session: Session, listener: (tabID: Browser.TabID) => void) {
|
||||
const set = addressed.get(session.key) ?? new Set()
|
||||
set.add(listener)
|
||||
addressed.set(session.key, set)
|
||||
|
||||
return () => {
|
||||
set.delete(listener)
|
||||
|
||||
if (!set.size) addressed.delete(session.key)
|
||||
}
|
||||
},
|
||||
pending,
|
||||
/**
|
||||
* Tab IDs to list in the side panel: the desktop's inventory, limited to tabs stored in the strip. Until the first
|
||||
|
||||
@@ -504,16 +504,10 @@ export function createBrowserPane(input: {
|
||||
if (entry.pages.has(id)) report(entry, { type: "page", tabID: id, ...value })
|
||||
},
|
||||
zoomed: () => entry.pages.forEach((page) => page.detail()),
|
||||
address: () => {
|
||||
if (!entry.pages.has(id) || entry.win.isDestroyed()) return
|
||||
// The page held keyboard focus; the app's address field needs it back.
|
||||
entry.win.webContents.focus()
|
||||
publish(entry, { type: "address", tabID: id })
|
||||
},
|
||||
shortcut: (shortcut) => {
|
||||
if (!entry.pages.has(id) || entry.focusedTabID !== id || entry.win.isDestroyed()) return
|
||||
|
||||
// As for the address shortcut: the command runs in the app, which may move focus there.
|
||||
// The page held keyboard focus; the command runs in the app, which may move focus there, as ⌘L does.
|
||||
if (entry.win.isFocused()) entry.win.webContents.focus()
|
||||
publish(entry, { type: "shortcut", ...shortcut })
|
||||
},
|
||||
|
||||
@@ -495,9 +495,6 @@ export function mountBrowserPane(input: PaneHost) {
|
||||
<button onClick={() => emit(store.session, { type: "page", tabID: current().id, icon, zoom: store.zoom })}>
|
||||
Page icon
|
||||
</button>
|
||||
<button onClick={() => emit(store.session, { type: "address", tabID: current().id })}>
|
||||
Address shortcut
|
||||
</button>
|
||||
</nav>
|
||||
<p>Captures: {store.captures}</p>
|
||||
<p>Zoom: {Math.round(store.zoom * 100)}</p>
|
||||
|
||||
@@ -451,17 +451,6 @@ export default function SessionBrowserPane(props: {
|
||||
),
|
||||
)
|
||||
|
||||
// The page's address shortcut moves focus to this field when the page is the one on screen.
|
||||
createKeyed(
|
||||
() => props.session.key,
|
||||
(sessionKey) =>
|
||||
onCleanup(
|
||||
props.model.onAddress({ key: sessionKey }, (tabID) => {
|
||||
if (tabID === state()?.id && visible()) focusAddress()
|
||||
}),
|
||||
),
|
||||
)
|
||||
|
||||
// A tab the user just opened starts in its address field, once its blank page is on screen. Syncs DOM focus.
|
||||
createKeyed(
|
||||
() => (visible() && empty() ? state()?.id : undefined),
|
||||
|
||||
@@ -92,8 +92,8 @@ const setup: Setup<typeof definition> = (ctx) => {
|
||||
}
|
||||
})
|
||||
|
||||
// The system browser's address shortcut. While the page has focus, the page claims it and main forwards it; here it
|
||||
// covers focus elsewhere in the pane. Ctrl+L focuses the composer outside the pane on Windows and Linux.
|
||||
// The system browser's address shortcut, in the pane and, forwarded like mod+t and mod+w, from a page with focus.
|
||||
// Ctrl+L focuses the composer outside the pane on Windows and Linux.
|
||||
ctx.add(Command, (): Command | undefined => {
|
||||
const pane = model()?.pane()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user