diff --git a/src/components/NewTaskPlaceholder.tsx b/src/components/NewTaskPlaceholder.tsx index 33a2db41..e64c2f0c 100644 --- a/src/components/NewTaskPlaceholder.tsx +++ b/src/components/NewTaskPlaceholder.tsx @@ -1,5 +1,5 @@ import { onMount, onCleanup } from 'solid-js'; -import { toggleNewTaskDialog, createTerminal, store, unfocusPlaceholder } from '../store/store'; +import { toggleNewTaskDialog, createTerminal, unfocusPlaceholder } from '../store/store'; import { registerFocusFn, unregisterFocusFn } from '../store/focus'; import { theme } from '../lib/theme'; import { mod } from '../lib/platform'; @@ -17,18 +17,6 @@ export function NewTaskPlaceholder() { }); }); - const isFocused = (btn: 'add-task' | 'add-terminal') => - store.placeholderFocused && store.placeholderFocusedButton === btn; - - const focusedBorder = (btn: 'add-task' | 'add-terminal') => - isFocused(btn) ? `2px dashed ${theme.accent}` : `2px dashed ${theme.border}`; - - const focusedColor = (btn: 'add-task' | 'add-terminal') => - isFocused(btn) ? theme.accent : theme.fgSubtle; - - const focusedBg = (btn: 'add-task' | 'add-terminal') => - isFocused(btn) ? `color-mix(in srgb, ${theme.accent} 8%, transparent)` : undefined; - return (
{ + it('uses a low-specificity app-wide default that components can override', () => { + const rule = css.match(/(?:^|\n):where\(([\s\S]*?)\):focus-visible\s*\{([^}]*)\}/); + + expect(rule).not.toBeNull(); + expect(rule?.[1].split(',').map((selector) => selector.trim())).toEqual(globalFocusSelectors); + expect(rule?.[2]).toMatch(/outline:\s*2px solid var\(--accent\)/); + expect(css).not.toMatch(/(?:^|\n):is\([\s\S]*?\):focus-visible/); + }); + + it('uses the DOM focus indicator as the placeholder focus state', () => { + expect(css).not.toMatch(/\.new-task-placeholder:focus-visible\s*\{[^}]*outline:\s*none/); + expect(placeholder).not.toContain('store.placeholderFocused'); + expect(placeholder).not.toMatch(/focused(?:Border|Color|Bg)/); + }); +}); diff --git a/src/styles.css b/src/styles.css index 227949a2..8cb12594 100644 --- a/src/styles.css +++ b/src/styles.css @@ -69,31 +69,28 @@ body.font-smoothing { text-rendering: geometricPrecision; } -/* Visible keyboard focus indicators inside dialogs. - Scoped to .dialog-panel (a class hook) so it works through Solid - mounting, where descendant selectors rooted at the app shell would not - match. The selector list mirrors the spec's interactive-role enumeration - so icon buttons, toggles, and ARIA widgets all pick up the indicator. */ -.dialog-panel - :is( - button, - input, - select, - textarea, - a[href], - [tabindex]:not([tabindex='-1']), - [role='button'], - [role='switch'], - [role='checkbox'], - [role='link'], - [role='menuitem'], - [role='menuitemradio'], - [role='menuitemcheckbox'], - [role='tab'], - [role='option'], - [role='combobox'], - [role='radio'] - ):focus-visible { +/* Visible keyboard focus indicators for interactive controls. The selector + includes native elements and ARIA widgets so keyboard focus remains visible + across the app, including content mounted through Solid . */ +:where( + button, + input, + select, + textarea, + a[href], + [tabindex]:not([tabindex='-1']), + [role='button'], + [role='switch'], + [role='checkbox'], + [role='link'], + [role='menuitem'], + [role='menuitemradio'], + [role='menuitemcheckbox'], + [role='tab'], + [role='option'], + [role='combobox'], + [role='radio'] +):focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } @@ -1510,10 +1507,6 @@ textarea::placeholder { background 0.15s ease; } -.new-task-placeholder:focus-visible { - outline: none; -} - .new-task-placeholder:hover { border-color: var(--accent) !important; color: var(--accent) !important;