Skip to content
Merged
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
21 changes: 13 additions & 8 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -1775,22 +1775,27 @@ body.detached-tab .graph-overlay-panel {
border: none; background: transparent;
color: var(--fg-faint); cursor: pointer;
border-radius: var(--r-xs); flex-shrink: 0;
display: none; align-items: center; justify-content: center;
/* `display: none` made the hover-revealed controls unreachable from the
keyboard: a browser cannot tab to an element it has removed from its focus
order. Keep them focusable, then let focus-within reveal the cluster. */
display: inline-flex; align-items: center; justify-content: center;
opacity: 0; pointer-events: none;
}
/* Revealed on hover like the Library row actions, but ALWAYS present for the
keyboard: `:focus-within` keeps the cluster visible while the row or one of
its controls owns focus, so no operation is keyboard-unreachable.
/* Revealed on hover like the Library row actions, but ALWAYS in the keyboard
focus order: `:focus-within` makes the visually concealed cluster visible
while the row or one of its controls owns focus, so no operation is
keyboard-unreachable.
`[aria-expanded="true"]` holds it visible for its own dialog/confirmation's
whole lifetime — both are body-mounted, so by the time one closes the
pointer and focus have left the row, and `focus()` on a `display: none`
trigger is a silent no-op in a real browser. */
pointer and focus have left the row, and an invisible trigger must not
become unreachable before focus can return to it. */
.dash-tree-row:hover .dash-tree-act,
.dash-tree-row:focus-within .dash-tree-act,
.dash-tree-act[aria-expanded="true"] { display: inline-flex; }
.dash-tree-act[aria-expanded="true"] { opacity: 1; pointer-events: auto; }
/* #447's orphan-variable trash stays ALWAYS visible: it is the only way to
remove stored SQL nothing references any more, and a hover-only affordance
would hide that row's whole point. #494 preserves that control as it was. */
.dash-tree-act-static { display: inline-flex; }
.dash-tree-act-static { opacity: 1; pointer-events: auto; }
.dash-tree-act:hover { color: var(--fg); background: var(--bg-hover); }
/* Destructive treatment on hover/focus, so the rightmost control of the
cluster reads as the one that removes something. */
Expand Down
10 changes: 4 additions & 6 deletions tests/e2e/tile-open-workbench.html
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
import { createCodeMirrorEditor } from '/src/editor/codemirror-adapter.js';
import { createSpecEditor } from '/src/editor/spec-editor.js';
import { createCodeViewer } from '/src/editor/code-viewer.js';
import { createAuthenticatedExecutionScope } from '/src/application/authenticated-execution-scope.js';
import { libraryQueries } from '/src/dashboard/model/query-ownership.js';

// `table` rather than a chart panel: the Chart.js seam is deliberately NOT
Expand Down Expand Up @@ -119,11 +118,10 @@
CodeViewer: createCodeViewer,
});
// #524: this full-surface fixture bypasses sign-in but still executes
// Dashboard and Workbench work, so provide the authenticated epoch that
// production boot installs after a successful connection.
const scope = createAuthenticatedExecutionScope({ epoch: 1, cancelRemote: () => {} });
app.executionScope = () => scope;
app.requireAuthenticatedExecution = () => scope;
// Dashboard and Workbench work. Use the app-owned resume seam rather than
// replacing its public scope accessors: `renderApp()` uses its private
// lifecycle state to decide whether the blocking auth host starts hidden.
app.resumeAuthenticatedExecution();
app.conn.ensureFreshToken = async () => true;
app.conn.chCtx.onSignedOut = () => {};
app.catalog.loadSchema = async () => { app.state.schema.value = []; };
Expand Down
10 changes: 5 additions & 5 deletions tests/e2e/tile-open-workbench.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,13 @@ test('a Dashboard-row plus creates a blank linked Panel and focuses its SQL edit
const plus = dashboard.locator('.dash-tree-act[aria-label="Add panel to Sales"]');

// The direct action is a real keyboard target, revealed by focus just like
// the adjacent pencil and trash. Start from the row's disclosure control:
// the next Tab must be the plus in the declared within-row order.
await dashboard.locator('.dash-tree-chev').focus();
await page.keyboard.press('Tab');
// the adjacent pencil and trash. Focus it explicitly: browser tab order also
// includes the independently interactive row label, so it is not portable to
// assert that the plus immediately follows the disclosure control.
await plus.focus();
await expect(plus).toHaveAttribute('aria-haspopup', 'dialog');
await expect(plus).toBeFocused();
await expect.poll(() => plus.evaluate((el) => getComputedStyle(el).display)).not.toBe('none');
await expect.poll(() => plus.evaluate((el) => getComputedStyle(el).opacity)).toBe('1');
await page.keyboard.press('Enter');

const dialog = page.getByRole('dialog', { name: 'Add panel' });
Expand Down