diff options
| author | Adam Malczewski <[email protected]> | 2026-06-07 14:49:30 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-07 14:49:30 +0900 |
| commit | 56d4841ef1b4e3c5b940b23562f54c9858d35636 (patch) | |
| tree | 9b53fa29747fae903b8c00799df948d1bdb1b779 /src/features/tabs/tabs.ts | |
| parent | 34bed91f2e0ee2484df6be9d2fc8742f64b24941 (diff) | |
| download | dispatch-web-56d4841ef1b4e3c5b940b23562f54c9858d35636.tar.gz dispatch-web-56d4841ef1b4e3c5b940b23562f54c9858d35636.zip | |
feat(tabs): polish new-chat button — stuck-only square edge, New Chat label
- isStuckToEnd (pure): square the sticky '+' right edge only while it floats
over scrolled tabs; rounded at rest. Edge-measured in TabBar via a disposed
scroll + ResizeObserver effect (RO guarded for non-browser envs).
- Show a temporary 'New Chat' title when the draft is selected, with the '+'
moved to the trailing close-button slot for consistency with real tabs.
Diffstat (limited to 'src/features/tabs/tabs.ts')
| -rw-r--r-- | src/features/tabs/tabs.ts | 20 |
1 files changed, 20 insertions, 0 deletions
diff --git a/src/features/tabs/tabs.ts b/src/features/tabs/tabs.ts index 9af522f..a4db6f3 100644 --- a/src/features/tabs/tabs.ts +++ b/src/features/tabs/tabs.ts @@ -66,6 +66,26 @@ export function activeTab(state: TabsState): Tab | null { return state.tabs.find((t) => t.conversationId === state.activeConversationId) ?? null; } +export interface ScrollMetrics { + readonly scrollLeft: number; + readonly clientWidth: number; + readonly scrollWidth: number; +} + +const STUCK_EPSILON = 1; + +/** + * True when a right-pinned sticky element is floating over scrolled content — the + * strip overflows horizontally AND is not scrolled fully to the right. When it is + * at rest (no overflow, or scrolled to the end so it sits at its natural position) + * this returns false. Pure: layout measurements in, boolean out. + */ +export function isStuckToEnd(m: ScrollMetrics): boolean { + const overflows = m.scrollWidth > m.clientWidth + STUCK_EPSILON; + const notAtEnd = m.scrollLeft + m.clientWidth < m.scrollWidth - STUCK_EPSILON; + return overflows && notAtEnd; +} + export function deriveTitle(message: string, max: number = DEFAULT_MAX_TITLE_LENGTH): string { const trimmed = message.trim().replace(/\s+/g, " "); if (trimmed.length === 0) return DEFAULT_TITLE; |
