bs tooltips fix; consistent icons

3clyp50 committed Jan 6, 2026 at 01:35 UTC 48627e417245550783b43ae835570207701fe16a
9 files changed +35 -48
webui/components/chat/input/bottom-actions.html
+4 -7
@@ -26,16 +26,12 @@
26 </button>
27
28 <button class="text-button" @click="$store.chats.resetChat()" :disabled="!$store.chats.selected">
29 - <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" width="14" height="14">
30 - <path d="M19.36 2.72l1.42 1.42-5.72 5.71c1.07 1.54 1.22 3.39.32 4.59L9.06 8.12c1.2-.9 3.05-.75 4.59.32l5.71-5.72M5.93 17.57c-2.01-2.01-3.24-4.41-3.58-6.65l4.88-2.09 7.44 7.44-2.09 4.88c-2.24-.34-4.64-1.57-6.65-3.58z"/>
31 - </svg>
29 + <span class="material-symbols-outlined">delete_sweep</span>
30 <p>Clear Chat</p>
31 </button>
32
33 <button class="text-button" @click="$store.chats.saveChat()" :disabled="!$store.chats.selected">
36 - <svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 24 24" width="14" height="14">
37 - <path d="M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z"/>
38 - </svg>
34 + <span class="material-symbols-outlined">save</span>
35 <p>Save Chat</p>
36 </button>
37
@@ -88,9 +84,10 @@
84 .text-button:active:not(:disabled) { opacity: 0.5; }
85 .text-button:disabled { opacity: 0.4; cursor: not-allowed; }
86 .text-button svg { width: 14px; height: 14px; flex-shrink: 0; }
87 + .text-button .material-symbols-outlined { font-size: 14px; flex-shrink: 0; }
88 .text-button p { margin-block: 0; }
89
93 - @media (max-width: 768px) { .text-buttons-row { width: 90%; display: flex; padding-top: var(--spacing-xs); gap: var(--spacing-xs); white-space: pre-wrap; } .text-button { font-size: 0.6rem; } .text-button svg { width: 18px; height: 18px; flex-shrink: 0; } }
90 + @media (max-width: 768px) { .text-buttons-row { width: 90%; display: flex; padding-top: var(--spacing-xs); gap: var(--spacing-xs); white-space: pre-wrap; } .text-button { font-size: 0.6rem; } .text-button svg { width: 18px; height: 18px; flex-shrink: 0; } .text-button .material-symbols-outlined { font-size: 18px; } }
91 @media (max-width: 640px) { .text-buttons-row { display: flex; width: 90%; padding-top: var(--spacing-xs); gap: var(--spacing-xs); white-space: pre-wrap; } .text-button { max-height: 25px; } .text-button p { display: none; } }
92 </style>
93 </body>
webui/components/chat/input/chat-bar-input.html
+1 -1
@@ -10,7 +10,7 @@
10 <div class="input-row">
11 <!-- Attachment icon with tooltip -->
12 <div class="attachment-wrapper">
13 - <label for="file-input" class="attachment-icon" title="Add attachments to the message" data-bs-placement="top">
13 + <label for="file-input" class="attachment-icon" title="Add attachments to the message" data-bs-placement="top" data-bs-trigger="hover">
14 <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor">
15 <path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
16 </svg>
webui/components/notifications/notification-toast-stack.html
+1 -1
@@ -56,7 +56,7 @@
56 position: absolute;
57 bottom: 5px; /* Spacing from the bottom of the zero-height container */
58 padding-right: 5px;
59 - z-index: 15000;
59 + z-index: 4000;
60 display: flex;
61 flex-direction: column-reverse; /* Stack toasts upwards */
62 gap: 8px;
webui/components/sidebar/chats/chats-list.html
+4 -5
@@ -13,10 +13,8 @@
13 <div class="section-header-row">
14 <h3 class="section-header">Chats</h3>
15 <!-- New Chat Button -->
16 - <button id="newChat" @click="$store.tooltips.hideAll(); $store.chats.newChat()" aria-label="New Chat" title="New Chat" data-bs-placement="top">
17 - <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 110 135" width="32" height="32">
18 - <path d="m38.3 81.3 3.2-9.1 3.1-9v-.1c0-.2.1-.3.2-.4.1-.2.2-.3.3-.4L82.4 25c1.6-1.6 3.7-2.4 5.7-2.4h.2c2 0 4 .8 5.6 2.4l3.7 3.7.1.2c1.5 1.6 2.2 3.6 2.2 5.6v.2c0 2-.8 4-2.4 5.6l-.1.1-3.8 3.6-.1.1-.1.1-32.9 33-.1.1q-.45.45-.9.6l-9.1 3.2h-.1l-9.1 3.1c-1.2.4-2.5-.2-2.9-1.4q-.3-.75 0-1.5m7.5-7.6-1.6 4.6 4.6-1.6 5.7-2-3.4-3.4-3.3-3.3zm4.2-9.9 4.3 4.3 4.3 4.3 30-30-8.6-8.5zm35.7-35.7-2.5 2.5 8.6 8.6 2.4-2.4.1-.1c.6-.6 1-1.5 1-2.3v-.1c0-.9-.3-1.8-1-2.4l-3.7-3.7c-.6-.6-1.5-1-2.3-1h-.1c-.9-.1-1.8.3-2.5.9m.4 44.1c0-1.3 1-2.3 2.3-2.3s2.3 1 2.3 2.3V101c0 3.2-1.3 6.1-3.4 8.2s-5 3.4-8.2 3.4H21.6c-3.2 0-6.1-1.3-8.2-3.4s-3.4-5-3.4-8.2V43.4c0-3.2 1.3-6.1 3.4-8.2s5-3.4 8.2-3.4h28.8c1.3 0 2.3 1 2.3 2.3s-1 2.3-2.3 2.3H21.6c-1.9 0-3.7.8-4.9 2-1.3 1.3-2 3-2 4.9v57.6c0 1.9.8 3.6 2 4.9 1.3 1.3 3 2 4.9 2h57.6c1.9 0 3.7-.8 4.9-2 1.3-1.3 2-3 2-4.9z" style="fill:currentColor"></path>
19 - </svg>
16 + <button id="newChat" @click="$store.chats.newChat()" aria-label="New Chat" title="New Chat" data-bs-placement="top" data-bs-trigger="hover">
17 + <span class="material-symbols-outlined">add_comment</span>
18 </button>
19 </div>
20
@@ -33,7 +31,7 @@
31 <span class="chat-name" :title="context.name ? context.name : 'Chat #' + context.no"
32 x-text="context.name ? context.name : 'Chat #' + context.no"></span>
33 </div>
36 - <button class="btn-icon-action" title="Close chat" @click.stop="$store.tooltips.hideAll(); $confirmClick($event, () => $store.chats.killChat(context.id))">
34 + <button class="btn-icon-action" title="Close chat" @click.stop="$confirmClick($event, () => $store.chats.killChat(context.id))">
35 <span class="material-symbols-outlined">close</span>
36 </button>
37 </div>
@@ -55,6 +53,7 @@
53 position: sticky;
54 top: 0;
55 flex-shrink: 0;
56 + margin-bottom: var(--spacing-xs);
57 z-index: 10;
58 }
59
webui/components/sidebar/tasks/tasks-list.html
+5 -5
@@ -40,16 +40,16 @@
40 x-text="task.state || 'idle'"></span>
41 <div class="task-actions">
42 <button class="btn-icon-action"
43 - @click.stop="$store.tooltips.hideAll(); $store.tasks.openDetail(task.id)"
43 + @click.stop="$store.tasks.openDetail(task.id)"
44 title="View task details">
45 <span class="material-symbols-outlined">Info</span>
46 </button>
47 <button class="btn-icon-action" title="Clear task chat"
48 - @click.stop="$store.tooltips.hideAll(); $store.tasks.reset(task.id)">
49 - <span class="material-symbols-outlined">Refresh</span>
48 + @click.stop="$store.tasks.reset(task.id)">
49 + <span class="material-symbols-outlined">delete_sweep</span>
50 </button>
51 - <button class="btn-icon-action" title="Delete task" @click.stop="$store.tooltips.hideAll(); $confirmClick($event, () => $store.tasks.deleteTask(task.id))">
52 - <span class="material-symbols-outlined">Close</span>
51 + <button class="btn-icon-action" title="Delete task" @click.stop="$confirmClick($event, () => $store.tasks.deleteTask(task.id))">
52 + <span class="material-symbols-outlined">close</span>
53 </button>
54 </div>
55 </div>
webui/components/sidebar/top-section/quick-actions.html
+5 -2
@@ -31,7 +31,7 @@
31 </button>
32
33 <!-- Dropdown Toggle -->
34 - <button class="config-button dropdown-toggle" @click="$store.tooltips.hideAll(); dropdownOpen = !dropdownOpen" title="More options">
34 + <button class="config-button dropdown-toggle" @click="dropdownOpen = !dropdownOpen" title="More options">
35 <span class="material-symbols-outlined" :class="dropdownOpen ? 'rotated' : ''">expand_more</span>
36 </button>
37 </div>
@@ -226,7 +226,10 @@
226 }
227 #newChat:hover { opacity: 0.85; transform: scale(1.04); }
228 #newChat:active { opacity: 0.5; transform: scale(0.98); }
229 - #newChat svg { color: var(--color-text); }
229 + #newChat .material-symbols-outlined {
230 + color: var(--color-text);
231 + font-size: 24px;
232 + }
233
234 /* Light mode */
235 .light-mode .config-button {
webui/components/tooltips/tooltip-store.js
+14 -21
@@ -28,10 +28,11 @@ function ensureBootstrapTooltip(element) {
28 }
29
30 element.setAttribute("data-bs-toggle", "tooltip");
31 + element.setAttribute("data-bs-trigger", "hover");
32 element.setAttribute("data-bs-tooltip-initialized", "true");
33 new bs.Tooltip(element, {
34 delay: { show: 0, hide: 0 },
34 - trigger: "hover focus",
35 + trigger: "hover",
36 });
37 }
38
@@ -57,6 +58,18 @@ function observeBootstrapTooltips() {
58 }
59
60 if (mutation.type === "childList") {
61 + // Check removed nodes for tooltip cleanup
62 + mutation.removedNodes.forEach((node) => {
63 + if (!(node instanceof Element)) return;
64 + const tooltipElements = node.matches?.('[data-bs-tooltip-initialized]') ? [node] : Array.from(node.querySelectorAll?.('[data-bs-tooltip-initialized]') || []);
65 + tooltipElements.forEach((el) => {
66 + const instance = globalThis.bootstrap?.Tooltip?.getInstance(el);
67 + if (instance) {
68 + instance.dispose();
69 + }
70 + });
71 + });
72 +
73 mutation.addedNodes.forEach((node) => {
74 if (!(node instanceof Element)) return;
75 if (node.matches("[title]") || node.querySelector("[title]")) {
@@ -82,27 +95,7 @@ function cleanupTooltipObserver() {
95 }
96 }
97
85 -function hideAllTooltips() {
86 - const Tooltip = globalThis.bootstrap?.Tooltip;
87 - if (!Tooltip) return;
88 -
89 - document
90 - .querySelectorAll('[data-bs-tooltip-initialized="true"]')
91 - .forEach((element) => {
92 - try {
93 - const instance = Tooltip.getInstance(element);
94 - if (instance) {
95 - instance.hide();
96 - }
97 - } catch (error) {
98 - console.warn("Error hiding tooltip:", error);
99 - }
100 - });
101 -}
102 -
98 export const store = createStore("tooltips", {
104 - hideAll: hideAllTooltips,
105 -
99 init() {
100 initBootstrapTooltips();
101 observeBootstrapTooltips();
webui/index.js
+1
@@ -10,6 +10,7 @@ import { store as inputStore } from "/components/chat/input/input-store.js";
10 import { store as chatsStore } from "/components/sidebar/chats/chats-store.js";
11 import { store as tasksStore } from "/components/sidebar/tasks/tasks-store.js";
12 import { store as chatTopStore } from "/components/chat/top-section/chat-top-store.js";
13 +import { store as tooltipsStore } from "/components/tooltips/tooltip-store.js";
14
15 globalThis.fetchApi = api.fetchApi; // TODO - backward compatibility for non-modular scripts, remove once refactored to alpine
16
webui/js/modals.js
-6
@@ -1,6 +1,5 @@
1 // Import the component loader and page utilities
2 import { importComponent } from "/js/components.js";
3 -import { store as tooltipsStore } from "/components/tooltips/tooltip-store.js";
3
4 // Modal functionality
5 const modalStack = [];
@@ -107,8 +106,6 @@ function createModalElement(path) {
106 export function openModal(modalPath) {
107 return new Promise((resolve) => {
108 try {
110 - tooltipsStore.hideAll();
111 -
109 // Create new modal instance
110 const modal = createModalElement(modalPath);
111
@@ -177,9 +174,6 @@ export function openModal(modalPath) {
174 export function closeModal(modalPath = null) {
175 if (modalStack.length === 0) return;
176
180 - // Ensure any visible tooltips are hidden before removing modal DOM.
181 - tooltipsStore.hideAll();
182 -
177 let modalIndex = modalStack.length - 1; // Default to last modal
178 let modal;
179