Skip to content

Commit e700f9b

Browse files
authored
Merge pull request #4138 from CalamitousFelicitousness/patch-1
Fix sticking hints and tabs losing hints on click
2 parents 6cba564 + e54f514 commit e700f9b

File tree

1 file changed

+112
-3
lines changed

1 file changed

+112
-3
lines changed

javascript/setHints.js

Lines changed: 112 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ const localeData = {
99
type: 2,
1010
hint: null,
1111
btn: null,
12-
expandTimeout: null, // New property for expansion timeout
12+
expandTimeout: null, // Property for expansion timeout
1313
currentElement: null, // Track current element for expansion
14+
observer: null, // MutationObserver for DOM changes
1415
};
1516
let localeTimeout = null;
1617

@@ -62,6 +63,17 @@ async function tooltipCreate() {
6263
if (window.opts.tooltips === 'None') localeData.type = 0;
6364
if (window.opts.tooltips === 'Browser default') localeData.type = 1;
6465
if (window.opts.tooltips === 'UI tooltips') localeData.type = 2;
66+
67+
// Setup event delegation for tooltips instead of individual listeners
68+
if (localeData.type === 2) {
69+
gradioApp().addEventListener('mouseover', tooltipShowDelegated); // eslint-disable-line no-use-before-define
70+
gradioApp().addEventListener('mouseout', tooltipHideDelegated); // eslint-disable-line no-use-before-define
71+
}
72+
73+
// Initialize DOM observer for immediate hint application
74+
if (!localeData.observer) {
75+
initializeDOMObserver(); // eslint-disable-line no-use-before-define
76+
}
6577
}
6678

6779
async function expandTooltip(element, longHint) {
@@ -85,6 +97,19 @@ async function expandTooltip(element, longHint) {
8597
}
8698
}
8799

100+
async function tooltipShowDelegated(e) {
101+
// Use event delegation to handle dynamically created elements
102+
if (e.target.dataset && e.target.dataset.hint) {
103+
tooltipShow(e); // eslint-disable-line no-use-before-define
104+
}
105+
}
106+
107+
async function tooltipHideDelegated(e) {
108+
if (e.target.dataset && e.target.dataset.hint) {
109+
tooltipHide(e); // eslint-disable-line no-use-before-define
110+
}
111+
}
112+
88113
async function tooltipShow(e) {
89114
// Clear any existing expansion timeout
90115
if (localeData.expandTimeout) {
@@ -294,8 +319,6 @@ async function setHint(el, entry) {
294319
el.dataset.hint = entry.hint;
295320
if (entry.longHint && entry.longHint.length > 0) el.dataset.longHint = entry.longHint;
296321
if (entry.reload && entry.reload.length > 0) el.dataset.reload = entry.reload;
297-
el.addEventListener('mouseover', tooltipShow);
298-
el.addEventListener('mouseout', tooltipHide);
299322
} else {
300323
// tooltips disabled
301324
}
@@ -359,3 +382,89 @@ const analyzeHints = async () => {
359382
localeData.data = [];
360383
await setHints(true);
361384
};
385+
386+
// Apply hints to a single element immediately
387+
async function applyHintToElement(el) {
388+
if (!localeData.data || localeData.data.length === 0) return;
389+
if (!el.textContent) return;
390+
391+
// Check if element matches our selector criteria
392+
const isValidElement = el.tagName === 'BUTTON'
393+
|| el.tagName === 'H2'
394+
|| (el.tagName === 'SPAN' && (el.parentElement?.tagName === 'LABEL' || el.parentElement?.classList.contains('label-wrap')));
395+
396+
if (!isValidElement) return;
397+
398+
// Find matching hint data
399+
let found;
400+
if (el.dataset.original) {
401+
found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.dataset.original.toLowerCase().trim());
402+
} else {
403+
found = localeData.data.find((l) => l.label.toLowerCase().trim() === el.textContent.toLowerCase().trim());
404+
}
405+
406+
// Apply localization if found
407+
if (found?.localized?.length > 0) {
408+
if (!el.dataset.original) el.dataset.original = el.textContent;
409+
replaceTextContent(el, found.localized);
410+
}
411+
412+
// Apply hint if found
413+
if (found?.hint?.length > 0) {
414+
setHint(el, found);
415+
}
416+
}
417+
418+
// Initialize MutationObserver for immediate hint application
419+
function initializeDOMObserver() {
420+
if (localeData.observer) {
421+
localeData.observer.disconnect();
422+
}
423+
424+
localeData.observer = new MutationObserver((mutations) => {
425+
// Process added nodes immediately
426+
for (const mutation of mutations) {
427+
if (mutation.type === 'childList') {
428+
for (const node of mutation.addedNodes) {
429+
if (node.nodeType === Node.ELEMENT_NODE) {
430+
// Apply hints to the node itself
431+
applyHintToElement(node);
432+
433+
// Apply hints to all relevant children
434+
const elements = [
435+
...Array.from(node.querySelectorAll('button')),
436+
...Array.from(node.querySelectorAll('h2')),
437+
...Array.from(node.querySelectorAll('label > span')),
438+
...Array.from(node.querySelectorAll('.label-wrap > span')),
439+
];
440+
441+
// Include the node itself if it matches
442+
if (node.matches && (
443+
node.matches('button')
444+
|| node.matches('h2')
445+
|| node.matches('label > span')
446+
|| node.matches('.label-wrap > span')
447+
)) {
448+
elements.push(node);
449+
}
450+
451+
// Apply hints immediately to all found elements
452+
elements.forEach((el) => applyHintToElement(el));
453+
}
454+
}
455+
}
456+
}
457+
});
458+
459+
// Start observing the entire gradio app for changes
460+
const targetNode = gradioApp();
461+
if (targetNode) {
462+
localeData.observer.observe(targetNode, {
463+
childList: true,
464+
subtree: true,
465+
});
466+
}
467+
}
468+
469+
// Export for external use if needed
470+
const forceReapplyHints = () => setHints();

0 commit comments

Comments
 (0)