diff --git a/changelog.md b/changelog.md index ba366da..928601a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,14 @@ # Changelog +## 2025-12-17 - 1.4.1 - fix(ui) +handle on-screen keyboard visibility to adjust layout and prevent inputs from being obscured + +- Add keyboard visibility state (isKeyboardVisible) and keyboardBlurTimeout in sio-combox.ts +- Listen for custom 'input-focus' and 'input-blur' events and toggle keyboard-visible host attribute +- Dispatch 'input-focus'/'input-blur' from sio-conversation-selector and sio-message-input on focus/blur +- Add connected/disconnected lifecycle handlers and updated() hook to manage attribute and cleanup timeouts +- Apply :host([keyboard-visible]) CSS to set height to 100vh / 100dvh when keyboard is visible + ## 2025-12-17 - 1.4.0 - feat(elements) update design tokens and sio-fab component; bump deps and update npmextra config diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 85981c3..b79eb97 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@social.io/catalog', - version: '1.4.0', + version: '1.4.1', description: 'catalog for social.io' } diff --git a/ts_web/elements/sio-combox.ts b/ts_web/elements/sio-combox.ts index fdac205..5e7ba58 100644 --- a/ts_web/elements/sio-combox.ts +++ b/ts_web/elements/sio-combox.ts @@ -42,6 +42,11 @@ export class SioCombox extends DeesElement { @state() private accessor selectedConversationId: string | null = null; + @state() + private accessor isKeyboardVisible: boolean = false; + + private keyboardBlurTimeout?: number; + @state() private accessor conversations: IConversation[] = [ { @@ -127,6 +132,50 @@ export class SioCombox extends DeesElement { domtools.DomTools.setupDomTools(); } + async connectedCallback() { + await super.connectedCallback(); + this.addEventListener('input-focus', this.handleInputFocus as EventListener); + this.addEventListener('input-blur', this.handleInputBlur as EventListener); + } + + async disconnectedCallback() { + await super.disconnectedCallback(); + this.removeEventListener('input-focus', this.handleInputFocus as EventListener); + this.removeEventListener('input-blur', this.handleInputBlur as EventListener); + if (this.keyboardBlurTimeout) { + clearTimeout(this.keyboardBlurTimeout); + } + } + + private handleInputFocus = () => { + if (this.keyboardBlurTimeout) { + clearTimeout(this.keyboardBlurTimeout); + this.keyboardBlurTimeout = undefined; + } + this.isKeyboardVisible = true; + } + + private handleInputBlur = () => { + if (this.keyboardBlurTimeout) { + clearTimeout(this.keyboardBlurTimeout); + } + this.keyboardBlurTimeout = window.setTimeout(() => { + this.isKeyboardVisible = false; + this.keyboardBlurTimeout = undefined; + }, 150); + } + + updated(changedProperties: Map) { + super.updated(changedProperties); + if (changedProperties.has('isKeyboardVisible')) { + if (this.isKeyboardVisible) { + this.setAttribute('keyboard-visible', ''); + } else { + this.removeAttribute('keyboard-visible'); + } + } + } + public static styles = [ cssManager.defaultStyles, css` @@ -242,6 +291,12 @@ export class SioCombox extends DeesElement { left: 0; opacity: 1; } + + /* Keyboard visible adjustments */ + :host([keyboard-visible]) { + height: 100vh; + height: 100dvh; + } `), ]; diff --git a/ts_web/elements/sio-conversation-selector.ts b/ts_web/elements/sio-conversation-selector.ts index 68b17f4..a525f6d 100644 --- a/ts_web/elements/sio-conversation-selector.ts +++ b/ts_web/elements/sio-conversation-selector.ts @@ -313,6 +313,8 @@ export class SioConversationSelector extends DeesElement { placeholder="Search conversations..." .value=${this.searchQuery} @input=${(e: Event) => this.searchQuery = (e.target as HTMLInputElement).value} + @focus=${this.handleInputFocus} + @blur=${this.handleInputBlur} /> @@ -371,4 +373,20 @@ export class SioConversationSelector extends DeesElement { composed: true })); } + + private handleInputFocus() { + setTimeout(() => { + this.dispatchEvent(new CustomEvent('input-focus', { + bubbles: true, + composed: true + })); + }, 50); + } + + private handleInputBlur() { + this.dispatchEvent(new CustomEvent('input-blur', { + bubbles: true, + composed: true + })); + } } \ No newline at end of file diff --git a/ts_web/elements/sio-message-input.ts b/ts_web/elements/sio-message-input.ts index 07e42c6..d132281 100644 --- a/ts_web/elements/sio-message-input.ts +++ b/ts_web/elements/sio-message-input.ts @@ -168,6 +168,8 @@ export class SioMessageInput extends DeesElement { .value=${this.messageText} @input=${this.handleInput} @keydown=${this.handleKeyDown} + @focus=${this.handleFocus} + @blur=${this.handleBlur} ?disabled=${this.disabled} rows="1" > @@ -216,6 +218,22 @@ export class SioMessageInput extends DeesElement { } } + private handleFocus() { + setTimeout(() => { + this.dispatchEvent(new CustomEvent('input-focus', { + bubbles: true, + composed: true + })); + }, 50); + } + + private handleBlur() { + this.dispatchEvent(new CustomEvent('input-blur', { + bubbles: true, + composed: true + })); + } + private sendMessage() { if (!this.messageText.trim() && this.pendingAttachments.length === 0) { return;