@@ -9,8 +9,9 @@ const localeData = {
9
9
type : 2 ,
10
10
hint : null ,
11
11
btn : null ,
12
- expandTimeout : null , // New property for expansion timeout
12
+ expandTimeout : null , // Property for expansion timeout
13
13
currentElement : null , // Track current element for expansion
14
+ observer : null , // MutationObserver for DOM changes
14
15
} ;
15
16
let localeTimeout = null ;
16
17
@@ -62,6 +63,17 @@ async function tooltipCreate() {
62
63
if ( window . opts . tooltips === 'None' ) localeData . type = 0 ;
63
64
if ( window . opts . tooltips === 'Browser default' ) localeData . type = 1 ;
64
65
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
+ }
65
77
}
66
78
67
79
async function expandTooltip ( element , longHint ) {
@@ -85,6 +97,19 @@ async function expandTooltip(element, longHint) {
85
97
}
86
98
}
87
99
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
+
88
113
async function tooltipShow ( e ) {
89
114
// Clear any existing expansion timeout
90
115
if ( localeData . expandTimeout ) {
@@ -294,8 +319,6 @@ async function setHint(el, entry) {
294
319
el . dataset . hint = entry . hint ;
295
320
if ( entry . longHint && entry . longHint . length > 0 ) el . dataset . longHint = entry . longHint ;
296
321
if ( entry . reload && entry . reload . length > 0 ) el . dataset . reload = entry . reload ;
297
- el . addEventListener ( 'mouseover' , tooltipShow ) ;
298
- el . addEventListener ( 'mouseout' , tooltipHide ) ;
299
322
} else {
300
323
// tooltips disabled
301
324
}
@@ -359,3 +382,89 @@ const analyzeHints = async () => {
359
382
localeData . data = [ ] ;
360
383
await setHints ( true ) ;
361
384
} ;
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