ext(affordances): wireFillFromTab + .glyph-btn CSS

This commit is contained in:
adlee-was-taken
2026-05-01 17:04:17 -04:00
parent 918fdef519
commit 4be0bcff83
4 changed files with 130 additions and 0 deletions

View File

@@ -10,6 +10,7 @@
--bg-page: #0d1117; --bg-page: #0d1117;
--bg-pane: #161b22; --bg-pane: #161b22;
--bg-elevated: #21262d; --bg-elevated: #21262d;
--bg-input: #161b22;
--border-subtle: #30363d; --border-subtle: #30363d;
/* Text */ /* Text */
@@ -1332,3 +1333,32 @@ textarea {
padding: 1px 5px; padding: 1px 5px;
border-radius: 3px; border-radius: 3px;
} }
/* Glyph button used by smart-input affordances. Sits inline with an input. */
.glyph-btn {
min-width: 28px;
height: 28px;
padding: 0 6px;
background: var(--bg-input);
border: 1px solid var(--border-subtle);
border-radius: 3px;
color: var(--text-muted);
font-family: inherit;
font-size: 14px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.glyph-btn:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.glyph-btn:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
.glyph-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}

View File

@@ -0,0 +1,46 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { wireFillFromTab } from '../url-tools';
describe('wireFillFromTab', () => {
let form: HTMLElement;
let sendMessage: ReturnType<typeof vi.fn>;
beforeEach(() => {
form = document.createElement('div');
form.innerHTML = `
<input id="f-title" type="text" />
<div class="inline-row">
<input id="f-url" type="text" />
<button id="fill-from-tab-btn" class="glyph-btn" type="button" title="fill from active tab">⤓</button>
</div>
`;
document.body.appendChild(form);
sendMessage = vi.fn();
});
it('fills url + title from active tab on click', async () => {
sendMessage.mockResolvedValue({ ok: true, data: { url: 'https://github.com/login', title: 'GitHub' } });
wireFillFromTab(form, { sendMessage });
(form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).click();
await Promise.resolve(); await Promise.resolve();
expect((form.querySelector('#f-url') as HTMLInputElement).value).toBe('https://github.com/login');
expect((form.querySelector('#f-title') as HTMLInputElement).value).toBe('GitHub');
});
it('does not overwrite a non-empty title', async () => {
(form.querySelector('#f-title') as HTMLInputElement).value = 'My GitHub';
sendMessage.mockResolvedValue({ ok: true, data: { url: 'https://github.com/login', title: 'GitHub' } });
wireFillFromTab(form, { sendMessage });
(form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).click();
await Promise.resolve(); await Promise.resolve();
expect((form.querySelector('#f-title') as HTMLInputElement).value).toBe('My GitHub');
});
it('disables the button if SW returns null', async () => {
sendMessage.mockResolvedValue({ ok: true, data: null });
wireFillFromTab(form, { sendMessage });
(form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).click();
await Promise.resolve(); await Promise.resolve();
expect((form.querySelector('#fill-from-tab-btn') as HTMLButtonElement).disabled).toBe(true);
});
});

View File

@@ -0,0 +1,24 @@
import { GLYPH_FILL_FROM_TAB } from '../glyphs';
export interface FillFromTabOpts {
sendMessage: (msg: { type: 'get_active_tab_url' }) => Promise<{ ok: boolean; data?: { url: string; title: string } | null }>;
}
export function wireFillFromTab(form: HTMLElement, opts: FillFromTabOpts): void {
const btn = form.querySelector<HTMLButtonElement>('#fill-from-tab-btn');
if (!btn) return;
btn.addEventListener('click', async () => {
const resp = await opts.sendMessage({ type: 'get_active_tab_url' });
if (!resp.ok || !resp.data) {
btn.disabled = true;
btn.title = 'no active tab';
return;
}
const urlEl = form.querySelector<HTMLInputElement>('#f-url');
const titleEl = form.querySelector<HTMLInputElement>('#f-title');
if (urlEl) urlEl.value = resp.data.url;
if (titleEl && !titleEl.value.trim()) titleEl.value = resp.data.title;
});
}
export const FILL_FROM_TAB_BTN_HTML = `<button id="fill-from-tab-btn" class="glyph-btn" type="button" title="fill from active tab">${GLYPH_FILL_FROM_TAB}</button>`;

View File

@@ -10,6 +10,7 @@
--bg-page: #0d1117; --bg-page: #0d1117;
--bg-pane: #161b22; --bg-pane: #161b22;
--bg-elevated: #21262d; --bg-elevated: #21262d;
--bg-input: #161b22;
--border-subtle: #30363d; --border-subtle: #30363d;
/* Text */ /* Text */
@@ -1362,3 +1363,32 @@ textarea {
.vault-lock-screen__form input { .vault-lock-screen__form input {
text-align: center; text-align: center;
} }
/* Glyph button used by smart-input affordances. Sits inline with an input. */
.glyph-btn {
min-width: 28px;
height: 28px;
padding: 0 6px;
background: var(--bg-input);
border: 1px solid var(--border-subtle);
border-radius: 3px;
color: var(--text-muted);
font-family: inherit;
font-size: 14px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
.glyph-btn:hover:not(:disabled) {
border-color: var(--accent);
color: var(--accent);
}
.glyph-btn:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
.glyph-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}