feat: add mobile drawer auto-hide when keyboard is active

Implement optional drawer element hiding on mobile devices when the chat input is focused, providing more screen space for the on-screen keyboard. Elements smoothly animate away on focus and restore on blur. Includes new setting in Advanced Settings section and updates Anthropic SDK to 0.91.1.
This commit is contained in:
Andrew Beal 2026-04-25 21:13:59 +01:00
parent b21207adcf
commit 9ca4f6f4f8
8 changed files with 163 additions and 22 deletions

View file

@ -24,6 +24,7 @@
import type { IPrompt } from "AIPrompts/IPrompt";
import type { SettingsService } from "Services/SettingsService";
import { ChatMode, chatModeAllowsEdits, iconForChatMode } from "Enums/ChatMode";
import { hideDrawerElements, restoreDrawerElements } from "Helpers/ElementHelper";
export let attachments: Attachment[] = [];
@ -82,7 +83,8 @@
});
function checkStacked() {
if (textareaElement.textContent.trim() === "") {
// Mobile already uses the 'stacked' layout
if (Platform.isMobile || textareaElement.textContent.trim() === "") {
stacked = false;
return;
}
@ -503,8 +505,17 @@
}
}
function handleFocusIn() {
if (Platform.isMobile && settingsService.settings.hideDrawerElements) {
hideDrawerElements();
}
}
function handleFocusOut() {
searchStateStore.resetSearch();
if (Platform.isMobile && settingsService.settings.hideDrawerElements) {
restoreDrawerElements();
}
}
</script>
@ -565,6 +576,7 @@
on:dragleave={handleDragLeave}
on:click={handleCursorPositionChange}
on:keyup={handleCursorPositionChange}
on:focus={handleFocusIn}
on:focusout={handleFocusOut}
data-placeholder={inputPlaceholder}
role="textbox"

View file

@ -84,6 +84,10 @@ export enum Copy {
TooltipLearnMoreFileMonitoring = "Learn more in Plugin Guide",
TooltipAccessMemories = "View Memories",
SettingAdvancedSettings = "Advanced Settings",
SettingHideDrawerElements = "Hide Drawer Elements",
SettingHideDrawerElementsDesc = "Hide side drawer elements on mobile when typing. This provides more space when an on screen keyboard is used. The elements will reappear when the chat input is unfocused.",
SettingQuickActions = "Quick Actions",
SettingEnableContextMenuActions = "Enable Context Menu Actions",
SettingEnableContextMenuActionsDesc = "Show quick actions in the right-click editor context menu.",

View file

@ -17,4 +17,66 @@ export function setElementIcon(element: HTMLDivElement, iconName: string) {
}
}
};
}
export function hideDrawerElements() {
for (const element of getDrawerElements()) {
if ("vaultKeeperHidden" in element.dataset) continue;
const computed = getComputedStyle(element);
element.dataset.vaultKeeperHidden = String(element.scrollHeight);
element.dataset.vaultKeeperPaddingTop = computed.paddingTop;
element.dataset.vaultKeeperPaddingBottom = computed.paddingBottom;
element.setCssProps({
"--vaultKeeper-drawer-height": element.scrollHeight + "px",
"--vaultKeeper-drawer-padding-top": computed.paddingTop,
"--vaultKeeper-drawer-padding-bottom": computed.paddingBottom,
});
element.addClass("vaultKeeper-drawer-hiding");
requestAnimationFrame(() => {
element.removeClass("vaultKeeper-drawer-hiding");
element.addClass("vaultKeeper-drawer-collapsed");
});
element.addEventListener("transitionend", () => {
element.removeClass("vaultKeeper-drawer-collapsed");
element.addClass("vaultKeeper-drawer-hidden");
}, { once: true });
}
}
export function restoreDrawerElements() {
for (const element of getDrawerElements()) {
if (!("vaultKeeperHidden" in element.dataset)) continue;
const targetHeight = element.dataset.vaultKeeperHidden ?? "0";
const paddingTop = element.dataset.vaultKeeperPaddingTop ?? "0";
const paddingBottom = element.dataset.vaultKeeperPaddingBottom ?? "0";
delete element.dataset.vaultKeeperHidden;
delete element.dataset.vaultKeeperPaddingTop;
delete element.dataset.vaultKeeperPaddingBottom;
element.setCssProps({
"--vaultKeeper-drawer-height": targetHeight + "px",
"--vaultKeeper-drawer-padding-top": paddingTop,
"--vaultKeeper-drawer-padding-bottom": paddingBottom,
});
element.removeClass("vaultKeeper-drawer-hidden");
element.addClass("vaultKeeper-drawer-restoring");
requestAnimationFrame(() => {
element.removeClass("vaultKeeper-drawer-restoring");
element.addClass("vaultKeeper-drawer-expanding");
});
element.addEventListener("transitionend", () => {
element.removeClass("vaultKeeper-drawer-expanding");
}, { once: true });
}
}
function getDrawerElements(): NodeListOf<HTMLElement> {
return activeDocument.querySelectorAll<HTMLElement>(".workspace-drawer-header, .workspace-drawer-tab-options");
}

View file

@ -30,7 +30,9 @@ const DEFAULT_SETTINGS: IVaultkeeperAISettings = {
enableWebViewer: false,
enableContextMenuActions: true,
enableToolbarActions: true
enableToolbarActions: true,
hideDrawerElements: true
}
export interface IVaultkeeperAISettings {
@ -61,6 +63,8 @@ export interface IVaultkeeperAISettings {
enableContextMenuActions: boolean;
enableToolbarActions: boolean;
hideDrawerElements: boolean;
}
export class SettingsService {

View file

@ -1,12 +1,3 @@
/* ============================== */
/* Mobile Drawer Full Width */
/* ============================== */
body:not(.is-tablet) .workspace-drawer.mod-right {
max-width: 100%;
width: 100%;
}
/* ============================== */
/* Main View Customization */
/* ============================== */
@ -22,6 +13,56 @@ body:not(.is-tablet) .workspace-drawer.mod-right {
padding-top: 0;
}
/* ============================== */
/* Mobile Drawer Full Width */
/* ============================== */
body:not(.is-tablet) .workspace-drawer.mod-right {
max-width: 100%;
width: 100%;
}
/* ============================== */
/* Drawer element collapse animation */
/* ============================== */
.vaultKeeper-drawer-hiding {
overflow: hidden;
height: var(--vaultKeeper-drawer-height);
padding-top: var(--vaultKeeper-drawer-padding-top);
padding-bottom: var(--vaultKeeper-drawer-padding-bottom);
transition: height 0.25s ease, padding-top 0.25s ease, padding-bottom 0.25s ease;
}
.vaultKeeper-drawer-collapsed {
overflow: hidden;
height: 0;
padding-top: 0;
padding-bottom: 0;
transition: height 0.25s ease, padding-top 0.25s ease, padding-bottom 0.25s ease;
}
.vaultKeeper-drawer-hidden {
display: none;
}
.vaultKeeper-drawer-restoring {
overflow: hidden;
display: block;
height: 0;
padding-top: 0;
padding-bottom: 0;
transition: height 0.25s ease, padding-top 0.25s ease, padding-bottom 0.25s ease;
}
.vaultKeeper-drawer-expanding {
overflow: hidden;
height: var(--vaultKeeper-drawer-height);
padding-top: var(--vaultKeeper-drawer-padding-top);
padding-bottom: var(--vaultKeeper-drawer-padding-bottom);
transition: height 0.25s ease, padding-top 0.25s ease, padding-bottom 0.25s ease;
}
/* ============================== */
/* Quick Actions Notice */
/* ============================== */

View file

@ -303,6 +303,24 @@ export class VaultkeeperAISettingTab extends PluginSettingTab {
);
});
});
/* Advanced Settings Header */
new Setting(containerEl)
.setHeading()
.setName(Copy.SettingAdvancedSettings);
/* Hide Drawer Elements */
new Setting(containerEl)
.setName(Copy.SettingHideDrawerElements)
.setDesc(Copy.SettingHideDrawerElementsDesc)
.addToggle(toggle => {
toggle
.setValue(this.settingsService.settings.hideDrawerElements)
.onChange(async (value) => {
this.settingsService.settings.hideDrawerElements = value;
await this.settingsService.saveSettings();
});
});
}
private populateModelDropdown(dropdown: DropdownComponent, providerFilter?: AIProvider): void {

20
package-lock.json generated
View file

@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@anthropic-ai/sdk": "^0.91.0",
"@anthropic-ai/sdk": "^0.91.1",
"@google/genai": "^1.50.1",
"@shikijs/rehype": "^4.0.2",
"core-js": "^3.49.0",
@ -64,9 +64,9 @@
}
},
"node_modules/@anthropic-ai/sdk": {
"version": "0.91.0",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.0.tgz",
"integrity": "sha512-hybd/DOI3ujG4gZyqqcWnSekYxkdjr1JbZYqP2Lb4AmcsU6HCTHSrTOgqedPSsQAruBVucHNAoD1vTQnpPzedw==",
"version": "0.91.1",
"resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.91.1.tgz",
"integrity": "sha512-LAmu761tSN9r66ixvmciswUj/ZC+1Q4iAfpedTfSVLeswRwnY3n2Nb6Tsk+cLPP28aLOPWeMgIuTuCcMC6W/iw==",
"license": "MIT",
"dependencies": {
"json-schema-to-ts": "^3.1.1"
@ -4196,9 +4196,9 @@
}
},
"node_modules/eslint-plugin-json-schema-validator/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -6788,9 +6788,9 @@
}
},
"node_modules/json-schema-migrate/node_modules/ajv": {
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
"dev": true,
"license": "MIT",
"dependencies": {

View file

@ -44,7 +44,7 @@
"vitest": "^4.1.5"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.91.0",
"@anthropic-ai/sdk": "^0.91.1",
"@google/genai": "^1.50.1",
"@shikijs/rehype": "^4.0.2",
"core-js": "^3.49.0",