Add support for double-clicking to rename document tabs (#4072)

This commit is contained in:
Keavon Chambers 2026-04-28 17:54:58 -07:00 committed by GitHub
parent fc7348d08a
commit b152f46380
4 changed files with 81 additions and 4 deletions

View File

@ -83,6 +83,7 @@
data-tooltip-description={tooltipDescription}
data-tooltip-shortcut={tooltipShortcut?.shortcut ? JSON.stringify(tooltipShortcut.shortcut) : undefined}
for={forCheckbox !== undefined ? `checkbox-input-${forCheckbox}` : undefined}
on:dblclick
bind:this={self}
>
<slot />

View File

@ -36,6 +36,7 @@
export let clickAction: ((index: number) => void) | undefined = undefined;
export let closeAction: ((index: number) => void) | undefined = undefined;
export let reorderAction: ((oldIndex: number, newIndex: number) => void) | undefined = undefined;
export let renameAction: ((index: number, newName: string) => void) | undefined = undefined;
export let emptySpaceAction: (() => void) | undefined = undefined;
export let crossPanelDropAction: ((sourcePanelId: string, targetPanelId: string, insertIndex: number) => void) | undefined = undefined;
export let groupDropAction: ((sourcePanelId: string, targetPanelId: string, insertIndex: number) => void) | undefined = undefined;
@ -50,6 +51,28 @@
let tabElements: (LayoutRow | undefined)[] = [];
// Document tab rename state
let editingNameTabIndex: number | undefined = undefined;
let editingNameText = "";
let editingNameInputElement: HTMLInputElement | undefined = undefined;
async function setEditingTabName(tabIndex: number, currentName: string) {
editingNameText = currentName;
editingNameTabIndex = tabIndex;
await tick();
editingNameInputElement?.focus();
editingNameInputElement?.select();
}
function commitEditingTabName(event: Event) {
if (editingNameTabIndex === undefined || !(event.target instanceof HTMLInputElement)) return;
renameAction?.(editingNameTabIndex, event.target.value);
editingNameTabIndex = undefined;
}
// Tab drag-and-drop state
let dragStartState: { tabIndex: number; pointerX: number; pointerY: number; isGroupDrag: boolean } | undefined = undefined;
let dragging = false;
@ -392,9 +415,30 @@
bind:this={tabElements[tabIndex]}
>
<LayoutRow class="name">
<TextLabel class="text">{tabLabel.name}</TextLabel>
{#if editingNameTabIndex !== tabIndex}
<TextLabel class="text" on:dblclick={() => renameAction && setEditingTabName(tabIndex, tabLabel.name)}>{tabLabel.name}</TextLabel>
{:else}
<input
type="text"
bind:this={editingNameInputElement}
bind:value={editingNameText}
on:pointerdown|stopPropagation
on:dblclick|stopPropagation
on:blur={commitEditingTabName}
on:keydown={(e) => {
// Stop propagation when we handle the key ourselves so the global keyboard forwarder doesn't dispatch them and trigger unrelated bindings
if (e.key === "Enter") {
commitEditingTabName(e);
e.stopPropagation();
} else if (e.key === "Escape") {
editingNameTabIndex = undefined;
e.stopPropagation();
}
}}
/>
{/if}
{#if tabLabel.unsaved}
<TextLabel>*</TextLabel>
<TextLabel classes={{ hidden: editingNameTabIndex === tabIndex }}>*</TextLabel>
{/if}
</LayoutRow>
{#if tabCloseButtons}
@ -513,11 +557,31 @@
&.text {
overflow-x: hidden;
white-space: nowrap;
white-space: pre;
text-overflow: ellipsis;
flex-shrink: 1;
}
}
input {
color: inherit;
border: none;
outline: none;
margin: 0 -4px;
padding: 0 4px;
height: 20px;
border-radius: 2px;
background: var(--color-1-nearblack);
field-sizing: content;
align-self: center;
// Stack above the absolutely-positioned close button so it doesn't intercept clicks at the input's right edge.
position: relative;
z-index: 1;
}
.text-label.hidden {
visibility: hidden;
}
}
.icon-button {

View File

@ -180,6 +180,11 @@
clickAction={(tabIndex) => editor.selectDocument($portfolio.documents[tabIndex].id)}
closeAction={(tabIndex) => editor.closeDocumentWithConfirmation($portfolio.documents[tabIndex].id)}
reorderAction={(oldIndex, newIndex) => editor.reorderDocument($portfolio.documents[oldIndex].id, newIndex)}
renameAction={(tabIndex, newName) => {
// Ensure the target document is the active one before renaming, since `RenameDocument` operates on the active document
editor.selectDocument($portfolio.documents[tabIndex].id);
editor.renameDocument(newName);
}}
tabActiveIndex={$portfolio.activeDocumentIndex}
groupDropAction={groupDrop}
splitDropAction={splitDrop}

View File

@ -395,7 +395,7 @@ impl EditorWrapper {
pub fn load_document_content(&self, document_id: u64, document: String) {
let message = PersistentStateMessage::LoadDocument {
document_id: DocumentId(document_id),
document: document,
document,
};
self.dispatch(message);
}
@ -407,6 +407,13 @@ impl EditorWrapper {
self.dispatch(message);
}
/// Rename the currently active document.
#[wasm_bindgen(js_name = renameDocument)]
pub fn rename_document(&self, new_name: String) {
let message = DocumentMessage::RenameDocument { new_name };
self.dispatch(message);
}
#[wasm_bindgen(js_name = newDocumentDialog)]
pub fn new_document_dialog(&self) {
let message = DialogMessage::RequestNewDocumentDialog;