7
7
useCallback ,
8
8
useEffect ,
9
9
useMemo ,
10
+ useRef ,
10
11
useState ,
11
12
} from 'react' ;
12
13
import { OverlayProps } from 'react-aria' ;
@@ -125,6 +126,10 @@ export interface CubeItemBaseProps extends BaseProps, ContainerStyleProps {
125
126
* @default "top"
126
127
*/
127
128
defaultTooltipPlacement ?: OverlayProps [ 'placement' ] ;
129
+ /**
130
+ * Ref to access the label element directly
131
+ */
132
+ labelRef ?: RefObject < HTMLElement > ;
128
133
}
129
134
130
135
const DEFAULT_ICON_STYLES : Styles = {
@@ -387,6 +392,8 @@ export function useAutoTooltip({
387
392
// Track label overflow for auto tooltip (only when enabled)
388
393
const mergedLabelRef = useCombinedRefs ( ( labelProps as any ) ?. ref ) ;
389
394
const [ isLabelOverflowed , setIsLabelOverflowed ] = useState ( false ) ;
395
+ const observedElementRef = useRef < HTMLElement | null > ( null ) ;
396
+ const resizeObserverRef = useRef < ResizeObserver | null > ( null ) ;
390
397
391
398
const checkLabelOverflow = useCallback ( ( ) => {
392
399
const label = mergedLabelRef . current ;
@@ -396,7 +403,6 @@ export function useAutoTooltip({
396
403
}
397
404
398
405
const hasOverflow = label . scrollWidth > label . clientWidth ;
399
-
400
406
setIsLabelOverflowed ( hasOverflow ) ;
401
407
} , [ mergedLabelRef ] ) ;
402
408
@@ -406,24 +412,64 @@ export function useAutoTooltip({
406
412
}
407
413
} , [ isAutoTooltipEnabled , checkLabelOverflow ] ) ;
408
414
409
- useEffect ( ( ) => {
410
- if ( ! isAutoTooltipEnabled ) return ;
411
-
412
- const label = mergedLabelRef . current ;
413
- if ( ! label ) return ;
415
+ // Attach ResizeObserver via callback ref to handle DOM node changes
416
+ const handleLabelElementRef = useCallback (
417
+ ( element : HTMLElement | null ) => {
418
+ // Sync to combined ref so external refs receive the node
419
+ ( mergedLabelRef as any ) . current = element ;
420
+
421
+ // Disconnect previous observer
422
+ if ( resizeObserverRef . current ) {
423
+ try {
424
+ resizeObserverRef . current . disconnect ( ) ;
425
+ } catch {
426
+ // do nothing
427
+ }
428
+ resizeObserverRef . current = null ;
429
+ }
414
430
415
- const resizeObserver = new ResizeObserver ( checkLabelOverflow ) ;
416
- resizeObserver . observe ( label ) ;
431
+ observedElementRef . current = element ;
432
+
433
+ if ( element && isAutoTooltipEnabled ) {
434
+ // Create a fresh observer to capture the latest callback
435
+ const obs = new ResizeObserver ( ( ) => {
436
+ checkLabelOverflow ( ) ;
437
+ } ) ;
438
+ resizeObserverRef . current = obs ;
439
+ obs . observe ( element ) ;
440
+ // Initial check
441
+ checkLabelOverflow ( ) ;
442
+ } else {
443
+ setIsLabelOverflowed ( false ) ;
444
+ }
445
+ } ,
446
+ [ mergedLabelRef , isAutoTooltipEnabled , checkLabelOverflow ] ,
447
+ ) ;
417
448
418
- return ( ) => resizeObserver . disconnect ( ) ;
419
- } , [ isAutoTooltipEnabled , checkLabelOverflow , mergedLabelRef ] ) ;
449
+ // Cleanup on unmount
450
+ useEffect ( ( ) => {
451
+ return ( ) => {
452
+ if ( resizeObserverRef . current ) {
453
+ try {
454
+ resizeObserverRef . current . disconnect ( ) ;
455
+ } catch {
456
+ // do nothing
457
+ }
458
+ resizeObserverRef . current = null ;
459
+ }
460
+ observedElementRef . current = null ;
461
+ } ;
462
+ } , [ ] ) ;
420
463
421
464
const finalLabelProps = useMemo ( ( ) => {
422
- return {
465
+ const props = {
423
466
...( labelProps || { } ) ,
424
- ref : mergedLabelRef ,
425
- } as Props & { ref ?: any } ;
426
- } , [ labelProps , mergedLabelRef ] ) ;
467
+ } ;
468
+
469
+ delete props . ref ;
470
+
471
+ return props ;
472
+ } , [ labelProps ] ) ;
427
473
428
474
const renderWithTooltip = useCallback (
429
475
(
@@ -498,7 +544,7 @@ export function useAutoTooltip({
498
544
) ;
499
545
500
546
return {
501
- labelRef : mergedLabelRef ,
547
+ labelRef : handleLabelElementRef ,
502
548
labelProps : finalLabelProps ,
503
549
isLabelOverflowed,
504
550
isAutoTooltipEnabled,
@@ -574,6 +620,7 @@ const ItemBase = <T extends HTMLElement = HTMLDivElement>(
574
620
suffix ??
575
621
( hotkeys ? (
576
622
< HotKeys
623
+ { ...( keyboardShortcutProps as any ) }
577
624
type = { type === 'primary' ? 'primary' : 'default' }
578
625
styles = { { padding : '1x left' , opacity : finalIsDisabled ? 0.5 : 1 } }
579
626
>
@@ -633,7 +680,7 @@ const ItemBase = <T extends HTMLElement = HTMLDivElement>(
633
680
634
681
const {
635
682
labelProps : finalLabelProps ,
636
- hasTooltip ,
683
+ labelRef ,
637
684
renderWithTooltip,
638
685
} = useAutoTooltip ( { tooltip, children, labelProps } ) ;
639
686
@@ -660,7 +707,6 @@ const ItemBase = <T extends HTMLElement = HTMLDivElement>(
660
707
return (
661
708
< ItemBaseElement
662
709
ref = { handleRef }
663
- tabIndex = { 0 }
664
710
variant = {
665
711
theme && type ? ( `${ theme } .${ type } ` as ItemVariant ) : undefined
666
712
}
@@ -682,7 +728,7 @@ const ItemBase = <T extends HTMLElement = HTMLDivElement>(
682
728
) }
683
729
{ finalPrefix && < div data-element = "Prefix" > { finalPrefix } </ div > }
684
730
{ children || labelProps ? (
685
- < div data-element = "Label" { ...finalLabelProps } >
731
+ < div data-element = "Label" { ...finalLabelProps } ref = { labelRef } >
686
732
{ children }
687
733
</ div >
688
734
) : null }
0 commit comments