@mortenbrudvik/waveui 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +142 -32
  2. package/dist/components/data-display/InfoButton.cjs +1 -1
  3. package/dist/components/data-display/InfoButton.cjs.map +1 -1
  4. package/dist/components/data-display/InfoButton.mjs +90 -82
  5. package/dist/components/data-display/InfoButton.mjs.map +1 -1
  6. package/dist/components/data-display/InfoLabel.cjs +1 -1
  7. package/dist/components/data-display/InfoLabel.cjs.map +1 -1
  8. package/dist/components/data-display/InfoLabel.mjs +19 -18
  9. package/dist/components/data-display/InfoLabel.mjs.map +1 -1
  10. package/dist/components/feedback/MessageBarGroup.cjs +2 -0
  11. package/dist/components/feedback/MessageBarGroup.cjs.map +1 -0
  12. package/dist/components/feedback/MessageBarGroup.mjs +63 -0
  13. package/dist/components/feedback/MessageBarGroup.mjs.map +1 -0
  14. package/dist/components/feedback/ProgressBar.cjs +1 -1
  15. package/dist/components/feedback/ProgressBar.cjs.map +1 -1
  16. package/dist/components/feedback/ProgressBar.mjs +41 -39
  17. package/dist/components/feedback/ProgressBar.mjs.map +1 -1
  18. package/dist/components/feedback/Toaster.cjs +1 -1
  19. package/dist/components/feedback/Toaster.cjs.map +1 -1
  20. package/dist/components/feedback/Toaster.mjs +222 -216
  21. package/dist/components/feedback/Toaster.mjs.map +1 -1
  22. package/dist/components/layout/Accordion.cjs +1 -1
  23. package/dist/components/layout/Accordion.cjs.map +1 -1
  24. package/dist/components/layout/Accordion.mjs +124 -109
  25. package/dist/components/layout/Accordion.mjs.map +1 -1
  26. package/dist/components/layout/Carousel.cjs +1 -1
  27. package/dist/components/layout/Carousel.cjs.map +1 -1
  28. package/dist/components/layout/Carousel.mjs +207 -193
  29. package/dist/components/layout/Carousel.mjs.map +1 -1
  30. package/dist/components/layout/TabList.cjs +1 -1
  31. package/dist/components/layout/TabList.cjs.map +1 -1
  32. package/dist/components/layout/TabList.mjs +149 -108
  33. package/dist/components/layout/TabList.mjs.map +1 -1
  34. package/dist/components/layout/Tree.cjs.map +1 -1
  35. package/dist/components/layout/Tree.context.cjs +1 -1
  36. package/dist/components/layout/Tree.context.cjs.map +1 -1
  37. package/dist/components/layout/Tree.context.mjs +2 -1
  38. package/dist/components/layout/Tree.context.mjs.map +1 -1
  39. package/dist/components/layout/Tree.core.cjs +1 -1
  40. package/dist/components/layout/Tree.core.cjs.map +1 -1
  41. package/dist/components/layout/Tree.core.mjs +178 -165
  42. package/dist/components/layout/Tree.core.mjs.map +1 -1
  43. package/dist/components/layout/Tree.mjs.map +1 -1
  44. package/dist/components/navigation/Menu.popover.cjs +1 -1
  45. package/dist/components/navigation/Menu.popover.cjs.map +1 -1
  46. package/dist/components/navigation/Menu.popover.mjs +154 -168
  47. package/dist/components/navigation/Menu.popover.mjs.map +1 -1
  48. package/dist/components/navigation/Nav.cjs +1 -1
  49. package/dist/components/navigation/Nav.cjs.map +1 -1
  50. package/dist/components/navigation/Nav.mjs +156 -136
  51. package/dist/components/navigation/Nav.mjs.map +1 -1
  52. package/dist/components/navigation/NavDrawer.cjs +1 -1
  53. package/dist/components/navigation/NavDrawer.cjs.map +1 -1
  54. package/dist/components/navigation/NavDrawer.mjs +48 -45
  55. package/dist/components/navigation/NavDrawer.mjs.map +1 -1
  56. package/dist/components/overlays/Dialog.cjs +1 -1
  57. package/dist/components/overlays/Dialog.cjs.map +1 -1
  58. package/dist/components/overlays/Dialog.mjs +128 -122
  59. package/dist/components/overlays/Dialog.mjs.map +1 -1
  60. package/dist/components/overlays/Dialog.slots.cjs +1 -1
  61. package/dist/components/overlays/Dialog.slots.cjs.map +1 -1
  62. package/dist/components/overlays/Dialog.slots.mjs +12 -12
  63. package/dist/components/overlays/Dialog.slots.mjs.map +1 -1
  64. package/dist/components/overlays/Drawer.cjs.map +1 -1
  65. package/dist/components/overlays/Drawer.mjs.map +1 -1
  66. package/dist/components/overlays/Drawer.panel.cjs +1 -1
  67. package/dist/components/overlays/Drawer.panel.cjs.map +1 -1
  68. package/dist/components/overlays/Drawer.panel.mjs +97 -81
  69. package/dist/components/overlays/Drawer.panel.mjs.map +1 -1
  70. package/dist/components/overlays/Drawer.root.cjs +1 -1
  71. package/dist/components/overlays/Drawer.root.cjs.map +1 -1
  72. package/dist/components/overlays/Drawer.root.mjs +34 -31
  73. package/dist/components/overlays/Drawer.root.mjs.map +1 -1
  74. package/dist/components/overlays/Popover.cjs +1 -1
  75. package/dist/components/overlays/Popover.cjs.map +1 -1
  76. package/dist/components/overlays/Popover.mjs +231 -216
  77. package/dist/components/overlays/Popover.mjs.map +1 -1
  78. package/dist/components/overlays/TeachingPopover.cjs.map +1 -1
  79. package/dist/components/overlays/TeachingPopover.mjs.map +1 -1
  80. package/dist/components/overlays/TeachingPopover.parts.cjs +1 -1
  81. package/dist/components/overlays/TeachingPopover.parts.cjs.map +1 -1
  82. package/dist/components/overlays/TeachingPopover.parts.mjs +202 -173
  83. package/dist/components/overlays/TeachingPopover.parts.mjs.map +1 -1
  84. package/dist/components/overlays/TeachingPopover.root.cjs +1 -1
  85. package/dist/components/overlays/TeachingPopover.root.cjs.map +1 -1
  86. package/dist/components/overlays/TeachingPopover.root.mjs +108 -99
  87. package/dist/components/overlays/TeachingPopover.root.mjs.map +1 -1
  88. package/dist/components/overlays/Tooltip.cjs +1 -1
  89. package/dist/components/overlays/Tooltip.cjs.map +1 -1
  90. package/dist/components/overlays/Tooltip.mjs +66 -61
  91. package/dist/components/overlays/Tooltip.mjs.map +1 -1
  92. package/dist/components/provider/WaveProvider.cjs +1 -1
  93. package/dist/components/provider/WaveProvider.cjs.map +1 -1
  94. package/dist/components/provider/WaveProvider.mjs +26 -23
  95. package/dist/components/provider/WaveProvider.mjs.map +1 -1
  96. package/dist/hooks/useCollapsePresence.cjs +2 -0
  97. package/dist/hooks/useCollapsePresence.cjs.map +1 -0
  98. package/dist/hooks/useCollapsePresence.mjs +21 -0
  99. package/dist/hooks/useCollapsePresence.mjs.map +1 -0
  100. package/dist/hooks/useFocusBeforeClose.cjs +2 -0
  101. package/dist/hooks/useFocusBeforeClose.cjs.map +1 -0
  102. package/dist/hooks/useFocusBeforeClose.mjs +33 -0
  103. package/dist/hooks/useFocusBeforeClose.mjs.map +1 -0
  104. package/dist/hooks/useMotion.cjs +2 -0
  105. package/dist/hooks/useMotion.cjs.map +1 -0
  106. package/dist/hooks/useMotion.mjs +14 -0
  107. package/dist/hooks/useMotion.mjs.map +1 -0
  108. package/dist/hooks/usePresenceGroup.cjs +2 -0
  109. package/dist/hooks/usePresenceGroup.cjs.map +1 -0
  110. package/dist/hooks/usePresenceGroup.mjs +68 -0
  111. package/dist/hooks/usePresenceGroup.mjs.map +1 -0
  112. package/dist/index.cjs +1 -1
  113. package/dist/index.d.cts +721 -131
  114. package/dist/index.d.ts +721 -131
  115. package/dist/index.mjs +136 -135
  116. package/dist/lib/classNames.cjs +1 -1
  117. package/dist/lib/classNames.cjs.map +1 -1
  118. package/dist/lib/classNames.mjs +101 -90
  119. package/dist/lib/classNames.mjs.map +1 -1
  120. package/dist/lib/motion.cjs +2 -0
  121. package/dist/lib/motion.cjs.map +1 -0
  122. package/dist/lib/motion.mjs +43 -0
  123. package/dist/lib/motion.mjs.map +1 -0
  124. package/dist/styles.css +1 -1
  125. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"InfoButton.cjs","names":["React","useId","usePresence","usePopupPosition","useDismiss","useHoverIntent","isFocusInSurfaceTree","useEventCallback","getFirstTabbable","usePopoverTabOrder","useMergedRefs","_Fragment","joinIds","cn","infoButtonClassNames","focusRing","composeEventHandlers","InfoIcon","Portal","PopoverBeak","require_runtime","require_cn","require_composeEventHandlers","require_classNames","require_useEventCallback","require_useId","require_focus","require_useMergedRefs","require_aria","require_usePresence","require_usePopupPosition","require_useDismiss","require_useRestoreFocus","require_styles","require_Portal","require_icons","require_useHoverIntent","require_Popover_shared","react","react_jsx_runtime","GLYPH","HOVER_DELAY","InfoNoteClassContext","InfoButton","sizeProp","idProp","ariaLabelProp","ariaLabelledBy","ariaDescribedBy","rest","size","generatedId","id","noteClass","noteClassName","ariaLabel","noteId","infoId","reason","setReason","open","buttonRef","noteRef","arrowRef","buttonElement","setButtonElement","note","setNote","reasonRef","dismissedRef","pointerPressRef","pressTimerRef","hoverCloseRef","presenceRef","dismissReason","cancelHover","hoverTriggerHandlers","hoverSurfaceHandlers","openOnHover","disabled","current","layerId","onFocusOut","next","event","handleClick","closing","handleFocus","handleBlur","button","handlePointerPress","onButtonKeyDown","first","onNoteKeyDown","buttonRefs","ref","setReference","noteRefs","setFloating","className","onClick","onFocus","onKeyDown","onPointerEnter","onPointerMove","onPointerLeave","onBlur","onPointerDown","onMouseDown","isMounted","info","presenceProps","floatingProps","arrowStyles"],"sources":["../../../src/components/data-display/InfoButton.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { joinIds } from '../../lib/aria';\nimport { infoButtonClassNames } from '../../lib/classNames';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { getFirstTabbable } from '../../lib/focus';\nimport { InfoIcon } from '../../lib/icons';\nimport { focusRing } from '../../lib/styles';\nimport type { CoreSize } from '../../lib/types';\nimport { useDismiss } from '../../hooks/useDismiss';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { useHoverIntent } from '../../hooks/useHoverIntent';\nimport { useId } from '../../hooks/useId';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { usePopupPosition } from '../../hooks/usePopupPosition';\nimport { usePresence } from '../../hooks/usePresence';\nimport { useRestoreFocus } from '../../hooks/useRestoreFocus';\nimport { isFocusInSurfaceTree, PopoverBeak, usePopoverTabOrder } from '../overlays/Popover.shared';\nimport { Portal } from '../portal/Portal';\n\n/** Properties for the InfoButton component. */\nexport interface InfoButtonProps extends Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n 'children'\n> {\n /**\n * The information the note shows: text, links or other content. It also describes the button\n * (`aria-describedby`), from the server HTML on. It is rendered once: next to the button, not\n * displayed, while the note is closed, and inside the note while it is shown.\n */\n info: React.ReactNode;\n /**\n * Size of the glyph: 12, 16 or 20px. The button box stays 24×24px at every size, with negative\n * margins above and below, so it never makes a row that centres its items (as InfoLabel does)\n * taller.\n * @default 'medium'\n */\n size?: CoreSize;\n /**\n * Open the note when a mouse pointer rests on the button (after 250 ms; it closes 250 ms after\n * the pointer has left the button and the note, touch and pen never hover), and when the button\n * receives keyboard focus. `false` opens it only on a click, and so on Enter and Space (Fluent's\n * InfoButton opens on click only). A click always toggles the note: it pins a note that hover or\n * keyboard focus opened, and closes a pinned one.\n * @default true\n */\n openOnHover?: boolean;\n /**\n * Accessible name of the button, and of the note, which is labelled like the button. Localize it\n * here. With `aria-labelledby` listing the id of your text and the button's own `id`, the name is\n * your text followed by this one (\"Billing Information\").\n * @default 'Information'\n */\n 'aria-label'?: string;\n /** Ref to the button. */\n ref?: React.Ref<HTMLButtonElement>;\n}\n\n/** Why the note is open: a click pins it; keyboard focus owns it; hover opens it transiently. */\ntype OpenReason = 'hover' | 'focus' | 'click';\n\n/**\n * Glyph size and negative margins per size: the 24px box never makes a row that centres its items\n * taller.\n */\nconst GLYPH: Readonly<Record<CoreSize, { size: number; margin: string }>> = {\n small: { size: 12, margin: '-my-1' },\n medium: { size: 16, margin: '-my-0.5' },\n large: { size: 20, margin: '-my-px' },\n};\n\n/**\n * Milliseconds from a mouse pointer resting on the button to the opening, and from the pointer\n * leaving the button and the note to the hover close (the 0.7 InfoLabel delays).\n */\nconst HOVER_DELAY = 250;\n\n/** What InfoLabel tells the InfoButton it renders: see {@link InfoNoteClassContext}. */\ninterface InfoNoteClass {\n /** The `id` of the InfoButton the class is for. */\n buttonId: string;\n /** The class added to that InfoButton's note. */\n className: string;\n}\n\n/**\n * Internal (P3-00, D5): the class InfoLabel adds to the note of the InfoButton it renders, its\n * 0.8 `info` part. Only the InfoButton whose `id` matches takes it, so an InfoButton inside the\n * label's `info` content takes none. Not exported from the barrels.\n */\nexport const InfoNoteClassContext = React.createContext<InfoNoteClass | null>(null);\nInfoNoteClassContext.displayName = 'InfoNoteClassContext';\n\n/**\n * A button with the info glyph that shows `info` in a note next to it: text, links or other rich\n * content, placed above the button with a beak (a toggletip).\n *\n * - **Naming.** The button is named \"Information\" (`aria-label`, which localizes it). Next to a\n * heading or other text, pass `aria-labelledby` with that text's id and the button's own `id`:\n * the button is then named \"‹text› Information\", so several info buttons on a page are told\n * apart. The note (`role=\"note\"`) is labelled like the button: by the same `aria-labelledby`\n * ids, else by the button. The button has `aria-expanded`, and `aria-controls` while the note is\n * open, but no `aria-haspopup`: a note is none of the popup kinds it names.\n * - **Opening.** A click pins the note open, and a click on a pinned note closes it. With\n * `openOnHover` (the default) a mouse pointer resting on the button opens it after 250 ms, and\n * it closes 250 ms after the pointer has left the button and the note (a pointer moving\n * diagonally from the button into the note keeps it open; touch and pen never hover); keyboard\n * focus on the button opens it too, while focus that comes from a pointer press waits for the\n * click. A hover close moves no focus, and a note that keyboard focus opened or keeps never\n * closes because of pointer movement. Opening never moves focus. Escape, a press outside and\n * focus moving outside the button and the note close it; Escape returns focus to the button when\n * it was in the note, and a dismissed note opens again on keyboard focus only once focus has\n * left both.\n * - **Tab path.** The note is portaled, but it keeps its place right after the button in the\n * keyboard order: Tab from the open button enters the note's first element (a link in it), Tab\n * past its last element continues after the button, and Shift+Tab from its first element\n * returns to the button.\n * - **One copy of `info`.** While the note is closed, `info` renders next to the button in an\n * element that is not displayed and describes the button (`aria-describedby`), so it is in the\n * server HTML and is read when the button takes focus. While the note is shown, `info` renders\n * only inside it, in an unnamed element that describes the button. `info` is never in the\n * document twice, so content with ids or effects is safe; it remounts each time the note opens\n * and closes, so state inside it starts over and its effects run again.\n * - **Structure.** The button is the root element: `ref`, `className`, `style` and every other\n * attribute go to it, the consumer's handlers first. The description and the note render next\n * to it, never inside it. Its box is 24×24px at every `size` and renders `data-size`. The note\n * mounts through the presence core: it carries `data-presence`, `data-state` (`open`, and\n * `closed` while it exits, `inert`) and the `data-wave-infolabel-surface` marker; it ships no\n * motion of its own.\n *\n * Fluent's InfoButton takes the same `info` and `size`. It opens on click only\n * (`openOnHover={false}` here), renders its note in place, and moves focus into the note; this one\n * keeps focus on the button and renders the note in a portal. Fluent's `popover` props and a\n * controlled open state are not offered.\n *\n * @example\n * <h2 id=\"billing\">Billing</h2>\n * <InfoButton\n * id=\"billing-info\"\n * aria-labelledby=\"billing billing-info\"\n * info={<>Invoices are sent monthly. <a href=\"/help/billing\">Learn more</a></>}\n * />\n */\nexport const InfoButton = ({\n info,\n size: sizeProp,\n openOnHover = true,\n id: idProp,\n type = 'button',\n 'aria-label': ariaLabelProp,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n className,\n disabled,\n onClick,\n onFocus,\n onBlur,\n onKeyDown,\n onPointerDown,\n onMouseDown,\n onPointerEnter,\n onPointerMove,\n onPointerLeave,\n ref,\n ...rest\n}: InfoButtonProps) => {\n // A size from untyped code renders, and reports, the default.\n const size: CoreSize = sizeProp === 'small' || sizeProp === 'large' ? sizeProp : 'medium';\n const generatedId = useId('info-button');\n const id = idProp || generatedId;\n const noteClass = React.useContext(InfoNoteClassContext);\n const noteClassName = noteClass?.buttonId === id ? noteClass.className : undefined;\n // An empty name would leave the button and the note unnamed: the glyph is decorative.\n const ariaLabel = ariaLabelProp || 'Information';\n const noteId = useId('info-note');\n // The one copy of `info`: not displayed while the note is closed, inside the note while shown.\n const infoId = useId('info');\n const [reason, setReason] = React.useState<OpenReason | null>(null);\n const open = reason !== null;\n\n const buttonRef = React.useRef<HTMLButtonElement | null>(null);\n const noteRef = React.useRef<HTMLDivElement | null>(null);\n const arrowRef = React.useRef<HTMLDivElement | null>(null);\n // The button and the note held in state too (C-POPUPS): for the hover intent (the safe zone\n // measures both) and the focus restore.\n const [buttonElement, setButtonElement] = React.useState<HTMLButtonElement | null>(null);\n const [note, setNote] = React.useState<HTMLDivElement | null>(null);\n\n // The latest reason, for the hover close (read in the hover intent's pointer events and timers).\n const reasonRef = React.useRef(reason);\n React.useLayoutEffect(() => {\n reasonRef.current = reason;\n });\n // Set by a dismissal (Escape, outside press): keyboard focus reopens only after focus has left\n // both the button and the note (a focus restore must not reopen it). A click that closes the\n // note sets it too: in Safari a click leaves focus on <body>, and the focus restore follows. It\n // blocks only a reopening: keyboard focus still takes over a note that a click or hover opened.\n const dismissedRef = React.useRef(false);\n // A pointer press focuses the button; that focus waits for the click (0.7).\n const pointerPressRef = React.useRef(false);\n const pressTimerRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // A hover close in flight, set before its request: that close moves no focus. Every other close\n // request and every opening clears it.\n const hoverCloseRef = React.useRef(false);\n\n // The note mounts and unmounts through the presence core; the dismiss layer, the focus restore\n // and the positioning below key on `open`, so they go on close, not after an exit motion.\n // Destructured: react-hooks/refs treats an object whose member is passed to `ref` as a ref.\n const { isMounted, ref: presenceRef, presenceProps } = usePresence(open);\n const { setReference, setFloating, floatingProps, arrowStyles, side } = usePopupPosition({\n open,\n side: 'top',\n align: 'center',\n offset: 8,\n arrowRef,\n });\n const { layerId } = useDismiss({\n open,\n onDismiss: (dismissReason) => {\n // Only a focusin elsewhere dismisses with 'focus-outside' (focus lost to <body> never does):\n // focus has left both then, so keyboard focus may reopen the note at once.\n if (dismissReason !== 'focus-outside') dismissedRef.current = true;\n hoverCloseRef.current = false;\n // Declared below; the layer calls this only from its document listeners, after render.\n cancelHover();\n setReason(null);\n },\n refs: [buttonRef, noteRef],\n anchorRef: buttonRef,\n focusOutside: true,\n });\n // Focus returns to the button when the note closes with focus inside it (or lost to <body>); a\n // hover close returns only focus that is inside it, so it never moves focus.\n useRestoreFocus({\n enabled: open,\n container: note,\n triggerRef: buttonRef,\n onlyIfFocusInside: true,\n isHoverClose: () => hoverCloseRef.current,\n });\n\n const {\n triggerHandlers: hoverTriggerHandlers,\n surfaceHandlers: hoverSurfaceHandlers,\n cancel: cancelHover,\n } = useHoverIntent({\n // Browsers send pointer events to a disabled button too, and the hook skips only an\n // `aria-disabled` trigger.\n enabled: openOnHover && !disabled,\n open,\n openDelay: HOVER_DELAY,\n closeDelay: HOVER_DELAY,\n trigger: buttonElement,\n surface: note,\n onOpen: () => {\n hoverCloseRef.current = false;\n setReason((current) => current ?? 'hover');\n },\n onClose: () => {\n hoverCloseRef.current = true;\n setReason((current) => (current === 'hover' ? null : current));\n },\n // Only a note opened by hover, not pinned and not keyboard-owned, closes by hover; focus\n // inside the note (or a layer opened from it) blocks it too (Phase 2 D18).\n canClose: () => reasonRef.current === 'hover' && !isFocusInSurfaceTree(note, layerId),\n });\n\n React.useEffect(() => () => clearTimeout(pressTimerRef.current), []);\n\n // A dismissal ends once focus has left both the button and the note: at the button's blur\n // (below) and at a focusout of the note. Focus the note loses to nothing is the note closing\n // (removed, or inert while it exits): that ends nothing.\n React.useEffect(() => {\n if (!note) return undefined;\n const onFocusOut = (event: FocusEvent) => {\n const next = event.relatedTarget as Node | null;\n if (!next || note.contains(next) || buttonRef.current?.contains(next)) return;\n dismissedRef.current = false;\n };\n note.addEventListener('focusout', onFocusOut);\n return () => note.removeEventListener('focusout', onFocusOut);\n }, [note]);\n\n // The handlers below read and write refs: useEventCallback keeps them out of render (C-HOOKS).\n const handleClick = useEventCallback(() => {\n cancelHover();\n hoverCloseRef.current = false;\n // Closed, or opened by hover or keyboard focus: pinned. Pinned: closed.\n const closing = reason === 'click';\n if (closing) dismissedRef.current = true;\n setReason(closing ? null : 'click');\n });\n\n const handleFocus = useEventCallback(() => {\n // Keyboard focus opens, as hovering's keyboard counterpart.\n if (!openOnHover || pointerPressRef.current || (dismissedRef.current && !open)) return;\n hoverCloseRef.current = false;\n cancelHover();\n // A note that hover opened becomes focus-owned; a pinned one stays pinned.\n setReason((current) => (current === 'click' ? current : 'focus'));\n });\n\n const handleBlur = useEventCallback((event: React.FocusEvent<HTMLButtonElement>) => {\n const button = event.currentTarget;\n const next = event.relatedTarget as Node | null;\n // Tab from the button into the note keeps a dismissal.\n if (next && note?.contains(next)) return;\n // So does the window losing focus (a switch of window or tab): the button stays the document's\n // focused element, and the browser focuses it again, from nothing, when the window comes back.\n if (!next && button.ownerDocument.activeElement === button) return;\n // Focus lost to nothing in the page (a press on a spot that takes no focus) has left both.\n dismissedRef.current = false;\n });\n\n const handlePointerPress = useEventCallback(() => {\n pointerPressRef.current = true;\n // A press focuses the button (where it does) as the default action of `mousedown`, in the same\n // event turn. Forget the press after that turn: a press that does not focus the button (Safari\n // and Firefox on macOS, iOS) and ends without a click (dragged off, or a touch that becomes a\n // scroll) must not turn a later keyboard focus into pointer focus. A touch tap dispatches its\n // compatibility `mousedown` (and the focus) after the touch ends; that `mousedown` sets the\n // flag again.\n clearTimeout(pressTimerRef.current);\n pressTimerRef.current = setTimeout(() => {\n pressTimerRef.current = undefined;\n pointerPressRef.current = false;\n }, 0);\n });\n\n // Tab from the open button enters the note. The button's own handler runs before a modal focus\n // trap's document listener, which would wrap the Tab when the button is the trap's last element.\n const onButtonKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== 'Tab' || event.shiftKey || !open || !note) return;\n const first = getFirstTabbable(note);\n if (!first) return;\n event.preventDefault();\n first.focus();\n };\n // Tab past the note's last element continues after the button, Shift+Tab from its first element\n // returns to it, and the page order skips the note where its portal is.\n const onNoteKeyDown = usePopoverTabOrder({\n enabled: open,\n surface: note,\n anchorRef: buttonRef,\n getPreviousStop: () => buttonRef.current,\n });\n\n const buttonRefs = useMergedRefs<HTMLButtonElement>(\n ref,\n buttonRef,\n setButtonElement,\n setReference,\n );\n const noteRefs = useMergedRefs<HTMLDivElement>(noteRef, setNote, setFloating, presenceRef);\n\n return (\n <>\n <button\n type={type}\n id={id}\n data-size={size}\n {...rest}\n ref={buttonRefs}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={joinIds(ariaDescribedBy, infoId)}\n aria-expanded={open}\n aria-controls={open ? noteId : undefined}\n className={cn(\n infoButtonClassNames.root,\n 'inline-flex size-6 shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-muted-foreground',\n GLYPH[size].margin,\n 'not-disabled:not-aria-disabled:hover:text-foreground',\n focusRing,\n className,\n )}\n onClick={composeEventHandlers(onClick, handleClick)}\n onFocus={composeEventHandlers(onFocus, handleFocus)}\n onKeyDown={composeEventHandlers(onKeyDown, onButtonKeyDown)}\n onPointerEnter={composeEventHandlers(onPointerEnter, hoverTriggerHandlers.onPointerEnter)}\n onPointerMove={composeEventHandlers(onPointerMove, hoverTriggerHandlers.onPointerMove)}\n onPointerLeave={composeEventHandlers(onPointerLeave, hoverTriggerHandlers.onPointerLeave)}\n // Bookkeeping that follows the DOM (where focus went, whether a press is under way): it\n // runs even when the consumer prevents the default.\n onBlur={composeEventHandlers(onBlur, handleBlur, { checkDefaultPrevented: false })}\n onPointerDown={composeEventHandlers(onPointerDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n onMouseDown={composeEventHandlers(onMouseDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n >\n <InfoIcon size={GLYPH[size].size} />\n </button>\n {/* The same element follows the button in every phase, so a row styled by position never\n shifts: it holds `info` while the note is not mounted, and nothing while the note does. */}\n <span id={isMounted ? undefined : infoId} hidden>\n {isMounted ? null : info}\n </span>\n {isMounted && (\n <Portal layerId={layerId}>\n <div\n ref={noteRefs}\n id={noteId}\n role=\"note\"\n aria-labelledby={ariaLabelledBy || id}\n data-wave-infolabel-surface=\"\"\n data-state={open ? 'open' : 'closed'}\n {...presenceProps}\n {...floatingProps}\n onKeyDown={onNoteKeyDown}\n onPointerEnter={hoverSurfaceHandlers.onPointerEnter}\n onPointerLeave={hoverSurfaceHandlers.onPointerLeave}\n className={cn(\n infoButtonClassNames.info,\n noteClassName,\n 'w-max max-w-[min(20rem,calc(100vw-1rem))] rounded-md border border-border bg-background px-3 py-2 text-caption-1 text-foreground shadow-16',\n )}\n >\n <PopoverBeak\n ref={arrowRef}\n side={side}\n style={arrowStyles}\n data-wave-popover-arrow=\"\"\n className={infoButtonClassNames.beak}\n />\n <div id={infoId}>{info}</div>\n </div>\n </Portal>\n )}\n </>\n );\n};\nInfoButton.displayName = 'InfoButton';\n"],"mappings":"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"}
1
+ {"version":3,"file":"InfoButton.cjs","names":["React","useId","usePresence","usePopupPosition","useDismiss","useMotionClasses","popupMotion","useHoverIntent","isFocusInSurfaceTree","useEventCallback","getFirstTabbable","usePopoverTabOrder","useMergedRefs","_Fragment","joinIds","cn","infoButtonClassNames","focusRing","composeEventHandlers","InfoIcon","Portal","PopoverBeak","require_runtime","require_cn","require_composeEventHandlers","require_classNames","require_useEventCallback","require_useId","require_focus","require_useMergedRefs","require_aria","require_usePresence","require_usePopupPosition","require_useDismiss","require_useRestoreFocus","require_styles","require_Portal","require_icons","require_motion","require_useFocusBeforeClose","require_useHoverIntent","require_useMotion","require_Popover_shared","react","react_jsx_runtime","GLYPH","HOVER_DELAY","InfoNoteClassContext","InfoButton","sizeProp","idProp","ariaLabelProp","ariaLabelledBy","ariaDescribedBy","rest","size","generatedId","id","noteClass","noteClassName","ariaLabel","noteId","infoId","reason","setReason","open","buttonRef","noteRef","arrowRef","buttonElement","setButtonElement","note","setNote","reasonRef","dismissedRef","pointerPressRef","pressTimerRef","hoverCloseRef","presenceRef","dismissReason","cancelHover","motionClassName","motion","hoverTriggerHandlers","hoverSurfaceHandlers","openOnHover","disabled","current","layerId","onFocusOut","next","event","handleClick","closing","handleFocus","handleBlur","button","handlePointerPress","onButtonKeyDown","first","onNoteKeyDown","buttonRefs","ref","setReference","noteRefs","setFloating","className","onClick","onFocus","onKeyDown","onPointerEnter","onPointerMove","onPointerLeave","onBlur","onPointerDown","onMouseDown","isMounted","info","presenceProps","floatingProps","arrowStyles"],"sources":["../../../src/components/data-display/InfoButton.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { joinIds } from '../../lib/aria';\nimport { infoButtonClassNames } from '../../lib/classNames';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { getFirstTabbable } from '../../lib/focus';\nimport { InfoIcon } from '../../lib/icons';\nimport { focusRing } from '../../lib/styles';\nimport { popupMotion } from '../../lib/motion';\nimport type { CoreSize, MotionSlot } from '../../lib/types';\nimport { useDismiss } from '../../hooks/useDismiss';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { useFocusBeforeClose } from '../../hooks/useFocusBeforeClose';\nimport { useHoverIntent } from '../../hooks/useHoverIntent';\nimport { useId } from '../../hooks/useId';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { useMotionClasses } from '../../hooks/useMotion';\nimport { usePopupPosition } from '../../hooks/usePopupPosition';\nimport { usePresence } from '../../hooks/usePresence';\nimport { useRestoreFocus } from '../../hooks/useRestoreFocus';\nimport { isFocusInSurfaceTree, PopoverBeak, usePopoverTabOrder } from '../overlays/Popover.shared';\nimport { Portal } from '../portal/Portal';\n\n/** Properties for the InfoButton component. */\nexport interface InfoButtonProps extends Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n 'children'\n> {\n /**\n * The information the note shows: text, links or other content. It also describes the button\n * (`aria-describedby`), from the server HTML on. It is rendered once: next to the button, not\n * displayed, while the note is closed, and inside the note while it is shown.\n */\n info: React.ReactNode;\n /**\n * Size of the glyph: 12, 16 or 20px. The button box stays 24×24px at every size, with negative\n * margins above and below, so it never makes a row that centres its items (as InfoLabel does)\n * taller.\n * @default 'medium'\n */\n size?: CoreSize;\n /**\n * Open the note when a mouse pointer rests on the button (after 250 ms; it closes 250 ms after\n * the pointer has left the button and the note, touch and pen never hover), and when the button\n * receives keyboard focus. `false` opens it only on a click, and so on Enter and Space (Fluent's\n * InfoButton opens on click only). A click always toggles the note: it pins a note that hover or\n * keyboard focus opened, and closes a pinned one.\n * @default true\n */\n openOnHover?: boolean;\n /**\n * Accessible name of the button, and of the note, which is labelled like the button. Localize it\n * here. With `aria-labelledby` listing the id of your text and the button's own `id`, the name is\n * your text followed by this one (\"Billing Information\").\n * @default 'Information'\n */\n 'aria-label'?: string;\n /**\n * The note's motion. By default it fades in and slides 4px away from the button, on the side it\n * opened on, over 200 ms with a decelerating curve, and fades out and slides back over 100 ms\n * with an accelerating curve; under reduced motion it appears and disappears at once. `null`\n * turns the motion off (the note still renders its `data-presence` phases, which then end at\n * once); a class string or `{ className }` replaces the default classes with yours (see\n * `MotionSlot`), also under a `WaveProvider` with `motion=\"none\"`, which otherwise turns the\n * default off. Fluent's `Popover` `surfaceMotion` (its InfoButton is a Popover).\n * @default the fade and slide above\n */\n motion?: MotionSlot | null;\n /** Ref to the button. */\n ref?: React.Ref<HTMLButtonElement>;\n}\n\n/** Why the note is open: a click pins it; keyboard focus owns it; hover opens it transiently. */\ntype OpenReason = 'hover' | 'focus' | 'click';\n\n/**\n * Glyph size and negative margins per size: the 24px box never makes a row that centres its items\n * taller.\n */\nconst GLYPH: Readonly<Record<CoreSize, { size: number; margin: string }>> = {\n small: { size: 12, margin: '-my-1' },\n medium: { size: 16, margin: '-my-0.5' },\n large: { size: 20, margin: '-my-px' },\n};\n\n/**\n * Milliseconds from a mouse pointer resting on the button to the opening, and from the pointer\n * leaving the button and the note to the hover close (the 0.7 InfoLabel delays).\n */\nconst HOVER_DELAY = 250;\n\n/** What InfoLabel tells the InfoButton it renders: see {@link InfoNoteClassContext}. */\ninterface InfoNoteClass {\n /** The `id` of the InfoButton the class is for. */\n buttonId: string;\n /** The class added to that InfoButton's note. */\n className: string;\n}\n\n/**\n * Internal (P3-00, D5): the class InfoLabel adds to the note of the InfoButton it renders, its\n * 0.8 `info` part. Only the InfoButton whose `id` matches takes it, so an InfoButton inside the\n * label's `info` content takes none. Not exported from the barrels.\n */\nexport const InfoNoteClassContext = React.createContext<InfoNoteClass | null>(null);\nInfoNoteClassContext.displayName = 'InfoNoteClassContext';\n\n/**\n * A button with the info glyph that shows `info` in a note next to it: text, links or other rich\n * content, placed above the button with a beak (a toggletip).\n *\n * - **Naming.** The button is named \"Information\" (`aria-label`, which localizes it). Next to a\n * heading or other text, pass `aria-labelledby` with that text's id and the button's own `id`:\n * the button is then named \"‹text› Information\", so several info buttons on a page are told\n * apart. The note (`role=\"note\"`) is labelled like the button: by the same `aria-labelledby`\n * ids, else by the button. The button has `aria-expanded`, and `aria-controls` while the note is\n * open, but no `aria-haspopup`: a note is none of the popup kinds it names.\n * - **Opening.** A click pins the note open, and a click on a pinned note closes it. With\n * `openOnHover` (the default) a mouse pointer resting on the button opens it after 250 ms, and\n * it closes 250 ms after the pointer has left the button and the note (a pointer moving\n * diagonally from the button into the note keeps it open; touch and pen never hover); keyboard\n * focus on the button opens it too, while focus that comes from a pointer press waits for the\n * click. A hover close moves no focus, and a note that keyboard focus opened or keeps never\n * closes because of pointer movement. Opening never moves focus. Escape, a press outside and\n * focus moving outside the button and the note close it; Escape returns focus to the button when\n * it was in the note, and a dismissed note opens again on keyboard focus only once focus has\n * left both.\n * - **Tab path.** The note is portaled, but it keeps its place right after the button in the\n * keyboard order: Tab from the open button enters the note's first element (a link in it), Tab\n * past its last element continues after the button, and Shift+Tab from its first element\n * returns to the button.\n * - **One copy of `info`.** While the note is closed, `info` renders next to the button in an\n * element that is not displayed and describes the button (`aria-describedby`), so it is in the\n * server HTML and is read when the button takes focus. While the note is shown, `info` renders\n * only inside it, in an unnamed element that describes the button. `info` is never in the\n * document twice, so content with ids or effects is safe; it remounts each time the note opens\n * and closes, so state inside it starts over and its effects run again.\n * - **Structure.** The button is the root element: `ref`, `className`, `style` and every other\n * attribute go to it, the consumer's handlers first. The description and the note render next\n * to it, never inside it. Its box is 24×24px at every `size` and renders `data-size`. The note\n * mounts through the presence core: it carries `data-presence`, `data-state` (`open`, and\n * `closed` while it exits, `inert`) and the `data-wave-infolabel-surface` marker.\n * - **Motion.** The note fades in and slides toward the button from the side it opened on, and\n * fades out and slides back as it closes (see `motion`); it stays in the document, `inert`,\n * until the exit ends, with `info` still inside it (never twice in the document), while the\n * dismiss layer, the focus restore and the button's `aria-expanded` follow the open state at\n * once. Focus inside a closing note moves to the button in the closing commit. Without motion\n * (reduced motion, `motion={null}`, or a provider with `motion=\"none\"`) it unmounts as soon as\n * it closes.\n *\n * Fluent's InfoButton takes the same `info` and `size`. It opens on click only\n * (`openOnHover={false}` here), renders its note in place, and moves focus into the note; this one\n * keeps focus on the button and renders the note in a portal. Fluent's `popover` props and a\n * controlled open state are not offered.\n *\n * @example\n * <h2 id=\"billing\">Billing</h2>\n * <InfoButton\n * id=\"billing-info\"\n * aria-labelledby=\"billing billing-info\"\n * info={<>Invoices are sent monthly. <a href=\"/help/billing\">Learn more</a></>}\n * />\n */\nexport const InfoButton = ({\n info,\n size: sizeProp,\n openOnHover = true,\n id: idProp,\n type = 'button',\n 'aria-label': ariaLabelProp,\n motion,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n className,\n disabled,\n onClick,\n onFocus,\n onBlur,\n onKeyDown,\n onPointerDown,\n onMouseDown,\n onPointerEnter,\n onPointerMove,\n onPointerLeave,\n ref,\n ...rest\n}: InfoButtonProps) => {\n // A size from untyped code renders, and reports, the default.\n const size: CoreSize = sizeProp === 'small' || sizeProp === 'large' ? sizeProp : 'medium';\n const generatedId = useId('info-button');\n const id = idProp || generatedId;\n const noteClass = React.useContext(InfoNoteClassContext);\n const noteClassName = noteClass?.buttonId === id ? noteClass.className : undefined;\n // An empty name would leave the button and the note unnamed: the glyph is decorative.\n const ariaLabel = ariaLabelProp || 'Information';\n const noteId = useId('info-note');\n // The one copy of `info`: not displayed while the note is closed, inside the note while shown.\n const infoId = useId('info');\n const [reason, setReason] = React.useState<OpenReason | null>(null);\n const open = reason !== null;\n\n const buttonRef = React.useRef<HTMLButtonElement | null>(null);\n const noteRef = React.useRef<HTMLDivElement | null>(null);\n const arrowRef = React.useRef<HTMLDivElement | null>(null);\n // The button and the note held in state too (C-POPUPS): for the hover intent (the safe zone\n // measures both) and the focus restore.\n const [buttonElement, setButtonElement] = React.useState<HTMLButtonElement | null>(null);\n const [note, setNote] = React.useState<HTMLDivElement | null>(null);\n\n // The latest reason, for the hover close (read in the hover intent's pointer events and timers).\n const reasonRef = React.useRef(reason);\n React.useLayoutEffect(() => {\n reasonRef.current = reason;\n });\n // Set by a dismissal (Escape, outside press): keyboard focus reopens only after focus has left\n // both the button and the note (a focus restore must not reopen it). A click that closes the\n // note sets it too: in Safari a click leaves focus on <body>, and the focus restore follows. It\n // blocks only a reopening: keyboard focus still takes over a note that a click or hover opened.\n const dismissedRef = React.useRef(false);\n // A pointer press focuses the button; that focus waits for the click (0.7).\n const pointerPressRef = React.useRef(false);\n const pressTimerRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // A hover close in flight, set before its request: that close moves no focus. Every other close\n // request and every opening clears it.\n const hoverCloseRef = React.useRef(false);\n\n // The note mounts and unmounts through the presence core; the dismiss layer, the focus restore\n // and the positioning below key on `open`, so they go on close, not after an exit motion.\n // Destructured: react-hooks/refs treats an object whose member is passed to `ref` as a ref.\n const {\n isMounted,\n ref: presenceRef,\n presenceProps,\n } = usePresence(open, {\n appear: true,\n });\n const { setReference, setFloating, floatingProps, arrowStyles, side } = usePopupPosition({\n open,\n side: 'top',\n align: 'center',\n offset: 8,\n arrowRef,\n });\n const { layerId } = useDismiss({\n open,\n onDismiss: (dismissReason) => {\n // Only a focusin elsewhere dismisses with 'focus-outside' (focus lost to <body> never does):\n // focus has left both then, so keyboard focus may reopen the note at once.\n if (dismissReason !== 'focus-outside') dismissedRef.current = true;\n hoverCloseRef.current = false;\n // Declared below; the layer calls this only from its document listeners, after render.\n cancelHover();\n setReason(null);\n },\n refs: [buttonRef, noteRef],\n anchorRef: buttonRef,\n focusOutside: true,\n });\n // Focus returns to the button when the note closes with focus inside it (or lost to <body>); a\n // hover close returns only focus that is inside it, so it never moves focus.\n useRestoreFocus({\n enabled: open,\n container: note,\n triggerRef: buttonRef,\n onlyIfFocusInside: true,\n isHoverClose: () => hoverCloseRef.current,\n });\n\n // Focus before close: the note stays mounted while it exits, so focus inside a closing note moves\n // to the button in the closing commit, where useRestoreFocus would put it. Unmounting is left to\n // useRestoreFocus.\n useFocusBeforeClose({\n open,\n surfaceRef: noteRef,\n captureOpener: () => buttonRef.current,\n candidates: () => [buttonRef.current],\n });\n const motionClassName = useMotionClasses(motion, popupMotion);\n\n const {\n triggerHandlers: hoverTriggerHandlers,\n surfaceHandlers: hoverSurfaceHandlers,\n cancel: cancelHover,\n } = useHoverIntent({\n // Browsers send pointer events to a disabled button too, and the hook skips only an\n // `aria-disabled` trigger.\n enabled: openOnHover && !disabled,\n open,\n openDelay: HOVER_DELAY,\n closeDelay: HOVER_DELAY,\n trigger: buttonElement,\n surface: note,\n onOpen: () => {\n hoverCloseRef.current = false;\n setReason((current) => current ?? 'hover');\n },\n onClose: () => {\n hoverCloseRef.current = true;\n setReason((current) => (current === 'hover' ? null : current));\n },\n // Only a note opened by hover, not pinned and not keyboard-owned, closes by hover; focus\n // inside the note (or a layer opened from it) blocks it too (Phase 2 D18).\n canClose: () => reasonRef.current === 'hover' && !isFocusInSurfaceTree(note, layerId),\n });\n\n React.useEffect(() => () => clearTimeout(pressTimerRef.current), []);\n\n // A dismissal ends once focus has left both the button and the note: at the button's blur\n // (below) and at a focusout of the note. Focus the note loses to nothing is the note closing\n // (removed, or inert while it exits): that ends nothing.\n React.useEffect(() => {\n if (!note) return undefined;\n const onFocusOut = (event: FocusEvent) => {\n const next = event.relatedTarget as Node | null;\n if (!next || note.contains(next) || buttonRef.current?.contains(next)) return;\n dismissedRef.current = false;\n };\n note.addEventListener('focusout', onFocusOut);\n return () => note.removeEventListener('focusout', onFocusOut);\n }, [note]);\n\n // The handlers below read and write refs: useEventCallback keeps them out of render (C-HOOKS).\n const handleClick = useEventCallback(() => {\n cancelHover();\n hoverCloseRef.current = false;\n // Closed, or opened by hover or keyboard focus: pinned. Pinned: closed.\n const closing = reason === 'click';\n if (closing) dismissedRef.current = true;\n setReason(closing ? null : 'click');\n });\n\n const handleFocus = useEventCallback(() => {\n // Keyboard focus opens, as hovering's keyboard counterpart.\n if (!openOnHover || pointerPressRef.current || (dismissedRef.current && !open)) return;\n hoverCloseRef.current = false;\n cancelHover();\n // A note that hover opened becomes focus-owned; a pinned one stays pinned.\n setReason((current) => (current === 'click' ? current : 'focus'));\n });\n\n const handleBlur = useEventCallback((event: React.FocusEvent<HTMLButtonElement>) => {\n const button = event.currentTarget;\n const next = event.relatedTarget as Node | null;\n // Tab from the button into the note keeps a dismissal.\n if (next && note?.contains(next)) return;\n // So does the window losing focus (a switch of window or tab): the button stays the document's\n // focused element, and the browser focuses it again, from nothing, when the window comes back.\n if (!next && button.ownerDocument.activeElement === button) return;\n // Focus lost to nothing in the page (a press on a spot that takes no focus) has left both.\n dismissedRef.current = false;\n });\n\n const handlePointerPress = useEventCallback(() => {\n pointerPressRef.current = true;\n // A press focuses the button (where it does) as the default action of `mousedown`, in the same\n // event turn. Forget the press after that turn: a press that does not focus the button (Safari\n // and Firefox on macOS, iOS) and ends without a click (dragged off, or a touch that becomes a\n // scroll) must not turn a later keyboard focus into pointer focus. A touch tap dispatches its\n // compatibility `mousedown` (and the focus) after the touch ends; that `mousedown` sets the\n // flag again.\n clearTimeout(pressTimerRef.current);\n pressTimerRef.current = setTimeout(() => {\n pressTimerRef.current = undefined;\n pointerPressRef.current = false;\n }, 0);\n });\n\n // Tab from the open button enters the note. The button's own handler runs before a modal focus\n // trap's document listener, which would wrap the Tab when the button is the trap's last element.\n const onButtonKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== 'Tab' || event.shiftKey || !open || !note) return;\n const first = getFirstTabbable(note);\n if (!first) return;\n event.preventDefault();\n first.focus();\n };\n // Tab past the note's last element continues after the button, Shift+Tab from its first element\n // returns to it, and the page order skips the note where its portal is.\n const onNoteKeyDown = usePopoverTabOrder({\n enabled: open,\n surface: note,\n anchorRef: buttonRef,\n getPreviousStop: () => buttonRef.current,\n });\n\n const buttonRefs = useMergedRefs<HTMLButtonElement>(\n ref,\n buttonRef,\n setButtonElement,\n setReference,\n );\n const noteRefs = useMergedRefs<HTMLDivElement>(noteRef, setNote, setFloating, presenceRef);\n\n return (\n <>\n <button\n type={type}\n id={id}\n data-size={size}\n {...rest}\n ref={buttonRefs}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={joinIds(ariaDescribedBy, infoId)}\n aria-expanded={open}\n aria-controls={open ? noteId : undefined}\n className={cn(\n infoButtonClassNames.root,\n 'inline-flex size-6 shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-muted-foreground',\n GLYPH[size].margin,\n 'not-disabled:not-aria-disabled:hover:text-foreground',\n focusRing,\n className,\n )}\n onClick={composeEventHandlers(onClick, handleClick)}\n onFocus={composeEventHandlers(onFocus, handleFocus)}\n onKeyDown={composeEventHandlers(onKeyDown, onButtonKeyDown)}\n onPointerEnter={composeEventHandlers(onPointerEnter, hoverTriggerHandlers.onPointerEnter)}\n onPointerMove={composeEventHandlers(onPointerMove, hoverTriggerHandlers.onPointerMove)}\n onPointerLeave={composeEventHandlers(onPointerLeave, hoverTriggerHandlers.onPointerLeave)}\n // Bookkeeping that follows the DOM (where focus went, whether a press is under way): it\n // runs even when the consumer prevents the default.\n onBlur={composeEventHandlers(onBlur, handleBlur, { checkDefaultPrevented: false })}\n onPointerDown={composeEventHandlers(onPointerDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n onMouseDown={composeEventHandlers(onMouseDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n >\n <InfoIcon size={GLYPH[size].size} />\n </button>\n {/* The same element follows the button in every phase, so a row styled by position never\n shifts: it holds `info` while the note is not mounted, and nothing while the note does. */}\n <span id={isMounted ? undefined : infoId} hidden>\n {isMounted ? null : info}\n </span>\n {isMounted && (\n <Portal layerId={layerId}>\n <div\n ref={noteRefs}\n id={noteId}\n role=\"note\"\n aria-labelledby={ariaLabelledBy || id}\n data-wave-infolabel-surface=\"\"\n data-state={open ? 'open' : 'closed'}\n {...presenceProps}\n {...floatingProps}\n onKeyDown={onNoteKeyDown}\n onPointerEnter={hoverSurfaceHandlers.onPointerEnter}\n onPointerLeave={hoverSurfaceHandlers.onPointerLeave}\n className={cn(\n infoButtonClassNames.info,\n noteClassName,\n 'w-max max-w-[min(20rem,calc(100vw-1rem))] rounded-md border border-border bg-background px-3 py-2 text-caption-1 text-foreground shadow-16',\n motionClassName,\n )}\n >\n <PopoverBeak\n ref={arrowRef}\n side={side}\n style={arrowStyles}\n data-wave-popover-arrow=\"\"\n className={infoButtonClassNames.beak}\n />\n <div id={infoId}>{info}</div>\n </div>\n </Portal>\n )}\n </>\n );\n};\nInfoButton.displayName = 'InfoButton';\n"],"mappings":"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"}
@@ -4,22 +4,25 @@ import { composeEventHandlers as t } from "../../lib/composeEventHandlers.mjs";
4
4
  import { infoButtonClassNames as n } from "../../lib/classNames.mjs";
5
5
  import { useEventCallback as r } from "../../hooks/useEventCallback.mjs";
6
6
  import { useId as i } from "../../hooks/useId.mjs";
7
- import { getFirstTabbable as a } from "../../lib/focus.mjs";
8
- import { useMergedRefs as o } from "../../hooks/useMergedRefs.mjs";
9
- import { joinIds as s } from "../../lib/aria.mjs";
10
- import { usePresence as ee } from "../../hooks/usePresence.mjs";
11
- import { usePopupPosition as te } from "../../hooks/usePopupPosition.mjs";
12
- import { useDismiss as ne } from "../../hooks/useDismiss.mjs";
13
- import { useRestoreFocus as re } from "../../hooks/useRestoreFocus.mjs";
14
- import { focusRing as ie } from "../../lib/styles.mjs";
15
- import { Portal as ae } from "../portal/Portal.mjs";
16
- import { InfoIcon as oe } from "../../lib/icons.mjs";
17
- import { useHoverIntent as se } from "../../hooks/useHoverIntent.mjs";
18
- import { PopoverBeak as ce, isFocusInSurfaceTree as c, usePopoverTabOrder as l } from "../overlays/Popover.shared.mjs";
19
- import * as u from "react";
20
- import { Fragment as d, jsx as f, jsxs as p } from "react/jsx-runtime";
7
+ import { getFirstTabbable as ee } from "../../lib/focus.mjs";
8
+ import { useMergedRefs as a } from "../../hooks/useMergedRefs.mjs";
9
+ import { joinIds as te } from "../../lib/aria.mjs";
10
+ import { usePresence as ne } from "../../hooks/usePresence.mjs";
11
+ import { usePopupPosition as re } from "../../hooks/usePopupPosition.mjs";
12
+ import { useDismiss as ie } from "../../hooks/useDismiss.mjs";
13
+ import { useRestoreFocus as ae } from "../../hooks/useRestoreFocus.mjs";
14
+ import { focusRing as oe } from "../../lib/styles.mjs";
15
+ import { Portal as se } from "../portal/Portal.mjs";
16
+ import { InfoIcon as ce } from "../../lib/icons.mjs";
17
+ import { popupMotion as le } from "../../lib/motion.mjs";
18
+ import { useFocusBeforeClose as ue } from "../../hooks/useFocusBeforeClose.mjs";
19
+ import { useHoverIntent as o } from "../../hooks/useHoverIntent.mjs";
20
+ import { useMotionClasses as s } from "../../hooks/useMotion.mjs";
21
+ import { PopoverBeak as c, isFocusInSurfaceTree as de, usePopoverTabOrder as fe } from "../overlays/Popover.shared.mjs";
22
+ import * as l from "react";
23
+ import { Fragment as pe, jsx as u, jsxs as d } from "react/jsx-runtime";
21
24
  //#region src/components/data-display/InfoButton.tsx
22
- var m = {
25
+ var f = {
23
26
  small: {
24
27
  size: 12,
25
28
  margin: "-my-1"
@@ -32,41 +35,46 @@ var m = {
32
35
  size: 20,
33
36
  margin: "-my-px"
34
37
  }
35
- }, h = 250, g = u.createContext(null);
36
- g.displayName = "InfoNoteClassContext";
37
- var _ = ({ info: _, size: v, openOnHover: y = !0, id: le, type: ue = "button", "aria-label": de, "aria-labelledby": b, "aria-describedby": fe, className: pe, disabled: x, onClick: me, onFocus: he, onBlur: ge, onKeyDown: _e, onPointerDown: ve, onMouseDown: ye, onPointerEnter: S, onPointerMove: C, onPointerLeave: w, ref: T, ...E }) => {
38
- let D = v === "small" || v === "large" ? v : "medium", O = i("info-button"), k = le || O, A = u.useContext(g), j = A?.buttonId === k ? A.className : void 0, M = de || "Information", N = i("info-note"), P = i("info"), [F, I] = u.useState(null), L = F !== null, R = u.useRef(null), z = u.useRef(null), B = u.useRef(null), [be, xe] = u.useState(null), [V, Se] = u.useState(null), H = u.useRef(F);
39
- u.useLayoutEffect(() => {
38
+ }, p = 250, m = l.createContext(null);
39
+ m.displayName = "InfoNoteClassContext";
40
+ var h = ({ info: h, size: g, openOnHover: _ = !0, id: me, type: he = "button", "aria-label": ge, motion: _e, "aria-labelledby": v, "aria-describedby": ve, className: ye, disabled: y, onClick: be, onFocus: xe, onBlur: b, onKeyDown: x, onPointerDown: S, onMouseDown: C, onPointerEnter: w, onPointerMove: T, onPointerLeave: E, ref: D, ...O }) => {
41
+ let k = g === "small" || g === "large" ? g : "medium", A = i("info-button"), j = me || A, M = l.useContext(m), Se = M?.buttonId === j ? M.className : void 0, Ce = ge || "Information", N = i("info-note"), P = i("info"), [F, I] = l.useState(null), L = F !== null, R = l.useRef(null), z = l.useRef(null), B = l.useRef(null), [we, Te] = l.useState(null), [V, Ee] = l.useState(null), H = l.useRef(F);
42
+ l.useLayoutEffect(() => {
40
43
  H.current = F;
41
44
  });
42
- let U = u.useRef(!1), W = u.useRef(!1), G = u.useRef(void 0), K = u.useRef(!1), { isMounted: q, ref: Ce, presenceProps: we } = ee(L), { setReference: Te, setFloating: Ee, floatingProps: De, arrowStyles: Oe, side: ke } = te({
45
+ let U = l.useRef(!1), W = l.useRef(!1), G = l.useRef(void 0), K = l.useRef(!1), { isMounted: q, ref: De, presenceProps: Oe } = ne(L, { appear: !0 }), { setReference: ke, setFloating: Ae, floatingProps: je, arrowStyles: Me, side: J } = re({
43
46
  open: L,
44
47
  side: "top",
45
48
  align: "center",
46
49
  offset: 8,
47
50
  arrowRef: B
48
- }), { layerId: J } = ne({
51
+ }), { layerId: Y } = ie({
49
52
  open: L,
50
53
  onDismiss: (e) => {
51
- e !== "focus-outside" && (U.current = !0), K.current = !1, Z(), I(null);
54
+ e !== "focus-outside" && (U.current = !0), K.current = !1, Q(), I(null);
52
55
  },
53
56
  refs: [R, z],
54
57
  anchorRef: R,
55
58
  focusOutside: !0
56
59
  });
57
- re({
60
+ ae({
58
61
  enabled: L,
59
62
  container: V,
60
63
  triggerRef: R,
61
64
  onlyIfFocusInside: !0,
62
65
  isHoverClose: () => K.current
66
+ }), ue({
67
+ open: L,
68
+ surfaceRef: z,
69
+ captureOpener: () => R.current,
70
+ candidates: () => [R.current]
63
71
  });
64
- let { triggerHandlers: Y, surfaceHandlers: X, cancel: Z } = se({
65
- enabled: y && !x,
72
+ let Ne = s(_e, le), { triggerHandlers: X, surfaceHandlers: Z, cancel: Q } = o({
73
+ enabled: _ && !y,
66
74
  open: L,
67
- openDelay: h,
68
- closeDelay: h,
69
- trigger: be,
75
+ openDelay: p,
76
+ closeDelay: p,
77
+ trigger: we,
70
78
  surface: V,
71
79
  onOpen: () => {
72
80
  K.current = !1, I((e) => e ?? "hover");
@@ -74,9 +82,9 @@ var _ = ({ info: _, size: v, openOnHover: y = !0, id: le, type: ue = "button", "
74
82
  onClose: () => {
75
83
  K.current = !0, I((e) => e === "hover" ? null : e);
76
84
  },
77
- canClose: () => H.current === "hover" && !c(V, J)
85
+ canClose: () => H.current === "hover" && !de(V, Y)
78
86
  });
79
- u.useEffect(() => () => clearTimeout(G.current), []), u.useEffect(() => {
87
+ l.useEffect(() => () => clearTimeout(G.current), []), l.useEffect(() => {
80
88
  if (!V) return;
81
89
  let e = (e) => {
82
90
  let t = e.relatedTarget;
@@ -84,90 +92,90 @@ var _ = ({ info: _, size: v, openOnHover: y = !0, id: le, type: ue = "button", "
84
92
  };
85
93
  return V.addEventListener("focusout", e), () => V.removeEventListener("focusout", e);
86
94
  }, [V]);
87
- let Ae = r(() => {
88
- Z(), K.current = !1;
95
+ let Pe = r(() => {
96
+ Q(), K.current = !1;
89
97
  let e = F === "click";
90
98
  e && (U.current = !0), I(e ? null : "click");
91
- }), je = r(() => {
92
- !y || W.current || U.current && !L || (K.current = !1, Z(), I((e) => e === "click" ? e : "focus"));
93
- }), Q = r((e) => {
99
+ }), Fe = r(() => {
100
+ !_ || W.current || U.current && !L || (K.current = !1, Q(), I((e) => e === "click" ? e : "focus"));
101
+ }), Ie = r((e) => {
94
102
  let t = e.currentTarget, n = e.relatedTarget;
95
103
  n && V?.contains(n) || (n || t.ownerDocument.activeElement !== t) && (U.current = !1);
96
104
  }), $ = r(() => {
97
105
  W.current = !0, clearTimeout(G.current), G.current = setTimeout(() => {
98
106
  G.current = void 0, W.current = !1;
99
107
  }, 0);
100
- }), Me = (e) => {
108
+ }), Le = (e) => {
101
109
  if (e.key !== "Tab" || e.shiftKey || !L || !V) return;
102
- let t = a(V);
110
+ let t = ee(V);
103
111
  t && (e.preventDefault(), t.focus());
104
- }, Ne = l({
112
+ }, Re = fe({
105
113
  enabled: L,
106
114
  surface: V,
107
115
  anchorRef: R,
108
116
  getPreviousStop: () => R.current
109
- }), Pe = o(T, R, xe, Te), Fe = o(z, Se, Ee, Ce);
110
- return /* @__PURE__ */ p(d, { children: [
111
- /* @__PURE__ */ f("button", {
112
- type: ue,
113
- id: k,
114
- "data-size": D,
115
- ...E,
116
- ref: Pe,
117
- disabled: x,
118
- "aria-label": M,
119
- "aria-labelledby": b,
120
- "aria-describedby": s(fe, P),
117
+ }), ze = a(D, R, Te, ke), Be = a(z, Ee, Ae, De);
118
+ return /* @__PURE__ */ d(pe, { children: [
119
+ /* @__PURE__ */ u("button", {
120
+ type: he,
121
+ id: j,
122
+ "data-size": k,
123
+ ...O,
124
+ ref: ze,
125
+ disabled: y,
126
+ "aria-label": Ce,
127
+ "aria-labelledby": v,
128
+ "aria-describedby": te(ve, P),
121
129
  "aria-expanded": L,
122
130
  "aria-controls": L ? N : void 0,
123
- className: e(n.root, "inline-flex size-6 shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-muted-foreground", m[D].margin, "not-disabled:not-aria-disabled:hover:text-foreground", ie, pe),
124
- onClick: t(me, Ae),
125
- onFocus: t(he, je),
126
- onKeyDown: t(_e, Me),
127
- onPointerEnter: t(S, Y.onPointerEnter),
128
- onPointerMove: t(C, Y.onPointerMove),
129
- onPointerLeave: t(w, Y.onPointerLeave),
130
- onBlur: t(ge, Q, { checkDefaultPrevented: !1 }),
131
- onPointerDown: t(ve, $, { checkDefaultPrevented: !1 }),
132
- onMouseDown: t(ye, $, { checkDefaultPrevented: !1 }),
133
- children: /* @__PURE__ */ f(oe, { size: m[D].size })
131
+ className: e(n.root, "inline-flex size-6 shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-muted-foreground", f[k].margin, "not-disabled:not-aria-disabled:hover:text-foreground", oe, ye),
132
+ onClick: t(be, Pe),
133
+ onFocus: t(xe, Fe),
134
+ onKeyDown: t(x, Le),
135
+ onPointerEnter: t(w, X.onPointerEnter),
136
+ onPointerMove: t(T, X.onPointerMove),
137
+ onPointerLeave: t(E, X.onPointerLeave),
138
+ onBlur: t(b, Ie, { checkDefaultPrevented: !1 }),
139
+ onPointerDown: t(S, $, { checkDefaultPrevented: !1 }),
140
+ onMouseDown: t(C, $, { checkDefaultPrevented: !1 }),
141
+ children: /* @__PURE__ */ u(ce, { size: f[k].size })
134
142
  }),
135
- /* @__PURE__ */ f("span", {
143
+ /* @__PURE__ */ u("span", {
136
144
  id: q ? void 0 : P,
137
145
  hidden: !0,
138
- children: q ? null : _
146
+ children: q ? null : h
139
147
  }),
140
- q && /* @__PURE__ */ f(ae, {
141
- layerId: J,
142
- children: /* @__PURE__ */ p("div", {
143
- ref: Fe,
148
+ q && /* @__PURE__ */ u(se, {
149
+ layerId: Y,
150
+ children: /* @__PURE__ */ d("div", {
151
+ ref: Be,
144
152
  id: N,
145
153
  role: "note",
146
- "aria-labelledby": b || k,
154
+ "aria-labelledby": v || j,
147
155
  "data-wave-infolabel-surface": "",
148
156
  "data-state": L ? "open" : "closed",
149
- ...we,
150
- ...De,
151
- onKeyDown: Ne,
152
- onPointerEnter: X.onPointerEnter,
153
- onPointerLeave: X.onPointerLeave,
154
- className: e(n.info, j, "w-max max-w-[min(20rem,calc(100vw-1rem))] rounded-md border border-border bg-background px-3 py-2 text-caption-1 text-foreground shadow-16"),
155
- children: [/* @__PURE__ */ f(ce, {
157
+ ...Oe,
158
+ ...je,
159
+ onKeyDown: Re,
160
+ onPointerEnter: Z.onPointerEnter,
161
+ onPointerLeave: Z.onPointerLeave,
162
+ className: e(n.info, Se, "w-max max-w-[min(20rem,calc(100vw-1rem))] rounded-md border border-border bg-background px-3 py-2 text-caption-1 text-foreground shadow-16", Ne),
163
+ children: [/* @__PURE__ */ u(c, {
156
164
  ref: B,
157
- side: ke,
158
- style: Oe,
165
+ side: J,
166
+ style: Me,
159
167
  "data-wave-popover-arrow": "",
160
168
  className: n.beak
161
- }), /* @__PURE__ */ f("div", {
169
+ }), /* @__PURE__ */ u("div", {
162
170
  id: P,
163
- children: _
171
+ children: h
164
172
  })]
165
173
  })
166
174
  })
167
175
  ] });
168
176
  };
169
- _.displayName = "InfoButton";
177
+ h.displayName = "InfoButton";
170
178
  //#endregion
171
- export { _ as InfoButton, g as InfoNoteClassContext };
179
+ export { h as InfoButton, m as InfoNoteClassContext };
172
180
 
173
181
  //# sourceMappingURL=InfoButton.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"InfoButton.mjs","names":["useId","_Fragment","useId$1","React","GLYPH","HOVER_DELAY","InfoNoteClassContext","InfoButton","sizeProp","idProp","ariaLabelProp","ariaLabelledBy","ariaDescribedBy","rest","size","generatedId","id","noteClass","noteClassName","ariaLabel","noteId","infoId","reason","setReason","open","buttonRef","noteRef","arrowRef","buttonElement","setButtonElement","note","setNote","reasonRef","dismissedRef","pointerPressRef","pressTimerRef","hoverCloseRef","presenceRef","usePresence","usePopupPosition","useDismiss","dismissReason","cancelHover","hoverTriggerHandlers","hoverSurfaceHandlers","useHoverIntent","openOnHover","disabled","current","isFocusInSurfaceTree","layerId","onFocusOut","next","event","handleClick","useEventCallback","closing","handleFocus","handleBlur","button","handlePointerPress","onButtonKeyDown","first","getFirstTabbable","onNoteKeyDown","usePopoverTabOrder","buttonRefs","useMergedRefs","ref","setReference","noteRefs","setFloating","Fragment","joinIds","cn","infoButtonClassNames","focusRing","className","composeEventHandlers","onClick","onFocus","onKeyDown","onPointerEnter","onPointerMove","onPointerLeave","onBlur","onPointerDown","onMouseDown","InfoIcon","isMounted","info","Portal","presenceProps","floatingProps","PopoverBeak","arrowStyles"],"sources":["../../../src/components/data-display/InfoButton.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { joinIds } from '../../lib/aria';\nimport { infoButtonClassNames } from '../../lib/classNames';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { getFirstTabbable } from '../../lib/focus';\nimport { InfoIcon } from '../../lib/icons';\nimport { focusRing } from '../../lib/styles';\nimport type { CoreSize } from '../../lib/types';\nimport { useDismiss } from '../../hooks/useDismiss';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { useHoverIntent } from '../../hooks/useHoverIntent';\nimport { useId } from '../../hooks/useId';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { usePopupPosition } from '../../hooks/usePopupPosition';\nimport { usePresence } from '../../hooks/usePresence';\nimport { useRestoreFocus } from '../../hooks/useRestoreFocus';\nimport { isFocusInSurfaceTree, PopoverBeak, usePopoverTabOrder } from '../overlays/Popover.shared';\nimport { Portal } from '../portal/Portal';\n\n/** Properties for the InfoButton component. */\nexport interface InfoButtonProps extends Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n 'children'\n> {\n /**\n * The information the note shows: text, links or other content. It also describes the button\n * (`aria-describedby`), from the server HTML on. It is rendered once: next to the button, not\n * displayed, while the note is closed, and inside the note while it is shown.\n */\n info: React.ReactNode;\n /**\n * Size of the glyph: 12, 16 or 20px. The button box stays 24×24px at every size, with negative\n * margins above and below, so it never makes a row that centres its items (as InfoLabel does)\n * taller.\n * @default 'medium'\n */\n size?: CoreSize;\n /**\n * Open the note when a mouse pointer rests on the button (after 250 ms; it closes 250 ms after\n * the pointer has left the button and the note, touch and pen never hover), and when the button\n * receives keyboard focus. `false` opens it only on a click, and so on Enter and Space (Fluent's\n * InfoButton opens on click only). A click always toggles the note: it pins a note that hover or\n * keyboard focus opened, and closes a pinned one.\n * @default true\n */\n openOnHover?: boolean;\n /**\n * Accessible name of the button, and of the note, which is labelled like the button. Localize it\n * here. With `aria-labelledby` listing the id of your text and the button's own `id`, the name is\n * your text followed by this one (\"Billing Information\").\n * @default 'Information'\n */\n 'aria-label'?: string;\n /** Ref to the button. */\n ref?: React.Ref<HTMLButtonElement>;\n}\n\n/** Why the note is open: a click pins it; keyboard focus owns it; hover opens it transiently. */\ntype OpenReason = 'hover' | 'focus' | 'click';\n\n/**\n * Glyph size and negative margins per size: the 24px box never makes a row that centres its items\n * taller.\n */\nconst GLYPH: Readonly<Record<CoreSize, { size: number; margin: string }>> = {\n small: { size: 12, margin: '-my-1' },\n medium: { size: 16, margin: '-my-0.5' },\n large: { size: 20, margin: '-my-px' },\n};\n\n/**\n * Milliseconds from a mouse pointer resting on the button to the opening, and from the pointer\n * leaving the button and the note to the hover close (the 0.7 InfoLabel delays).\n */\nconst HOVER_DELAY = 250;\n\n/** What InfoLabel tells the InfoButton it renders: see {@link InfoNoteClassContext}. */\ninterface InfoNoteClass {\n /** The `id` of the InfoButton the class is for. */\n buttonId: string;\n /** The class added to that InfoButton's note. */\n className: string;\n}\n\n/**\n * Internal (P3-00, D5): the class InfoLabel adds to the note of the InfoButton it renders, its\n * 0.8 `info` part. Only the InfoButton whose `id` matches takes it, so an InfoButton inside the\n * label's `info` content takes none. Not exported from the barrels.\n */\nexport const InfoNoteClassContext = React.createContext<InfoNoteClass | null>(null);\nInfoNoteClassContext.displayName = 'InfoNoteClassContext';\n\n/**\n * A button with the info glyph that shows `info` in a note next to it: text, links or other rich\n * content, placed above the button with a beak (a toggletip).\n *\n * - **Naming.** The button is named \"Information\" (`aria-label`, which localizes it). Next to a\n * heading or other text, pass `aria-labelledby` with that text's id and the button's own `id`:\n * the button is then named \"‹text› Information\", so several info buttons on a page are told\n * apart. The note (`role=\"note\"`) is labelled like the button: by the same `aria-labelledby`\n * ids, else by the button. The button has `aria-expanded`, and `aria-controls` while the note is\n * open, but no `aria-haspopup`: a note is none of the popup kinds it names.\n * - **Opening.** A click pins the note open, and a click on a pinned note closes it. With\n * `openOnHover` (the default) a mouse pointer resting on the button opens it after 250 ms, and\n * it closes 250 ms after the pointer has left the button and the note (a pointer moving\n * diagonally from the button into the note keeps it open; touch and pen never hover); keyboard\n * focus on the button opens it too, while focus that comes from a pointer press waits for the\n * click. A hover close moves no focus, and a note that keyboard focus opened or keeps never\n * closes because of pointer movement. Opening never moves focus. Escape, a press outside and\n * focus moving outside the button and the note close it; Escape returns focus to the button when\n * it was in the note, and a dismissed note opens again on keyboard focus only once focus has\n * left both.\n * - **Tab path.** The note is portaled, but it keeps its place right after the button in the\n * keyboard order: Tab from the open button enters the note's first element (a link in it), Tab\n * past its last element continues after the button, and Shift+Tab from its first element\n * returns to the button.\n * - **One copy of `info`.** While the note is closed, `info` renders next to the button in an\n * element that is not displayed and describes the button (`aria-describedby`), so it is in the\n * server HTML and is read when the button takes focus. While the note is shown, `info` renders\n * only inside it, in an unnamed element that describes the button. `info` is never in the\n * document twice, so content with ids or effects is safe; it remounts each time the note opens\n * and closes, so state inside it starts over and its effects run again.\n * - **Structure.** The button is the root element: `ref`, `className`, `style` and every other\n * attribute go to it, the consumer's handlers first. The description and the note render next\n * to it, never inside it. Its box is 24×24px at every `size` and renders `data-size`. The note\n * mounts through the presence core: it carries `data-presence`, `data-state` (`open`, and\n * `closed` while it exits, `inert`) and the `data-wave-infolabel-surface` marker; it ships no\n * motion of its own.\n *\n * Fluent's InfoButton takes the same `info` and `size`. It opens on click only\n * (`openOnHover={false}` here), renders its note in place, and moves focus into the note; this one\n * keeps focus on the button and renders the note in a portal. Fluent's `popover` props and a\n * controlled open state are not offered.\n *\n * @example\n * <h2 id=\"billing\">Billing</h2>\n * <InfoButton\n * id=\"billing-info\"\n * aria-labelledby=\"billing billing-info\"\n * info={<>Invoices are sent monthly. <a href=\"/help/billing\">Learn more</a></>}\n * />\n */\nexport const InfoButton = ({\n info,\n size: sizeProp,\n openOnHover = true,\n id: idProp,\n type = 'button',\n 'aria-label': ariaLabelProp,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n className,\n disabled,\n onClick,\n onFocus,\n onBlur,\n onKeyDown,\n onPointerDown,\n onMouseDown,\n onPointerEnter,\n onPointerMove,\n onPointerLeave,\n ref,\n ...rest\n}: InfoButtonProps) => {\n // A size from untyped code renders, and reports, the default.\n const size: CoreSize = sizeProp === 'small' || sizeProp === 'large' ? sizeProp : 'medium';\n const generatedId = useId('info-button');\n const id = idProp || generatedId;\n const noteClass = React.useContext(InfoNoteClassContext);\n const noteClassName = noteClass?.buttonId === id ? noteClass.className : undefined;\n // An empty name would leave the button and the note unnamed: the glyph is decorative.\n const ariaLabel = ariaLabelProp || 'Information';\n const noteId = useId('info-note');\n // The one copy of `info`: not displayed while the note is closed, inside the note while shown.\n const infoId = useId('info');\n const [reason, setReason] = React.useState<OpenReason | null>(null);\n const open = reason !== null;\n\n const buttonRef = React.useRef<HTMLButtonElement | null>(null);\n const noteRef = React.useRef<HTMLDivElement | null>(null);\n const arrowRef = React.useRef<HTMLDivElement | null>(null);\n // The button and the note held in state too (C-POPUPS): for the hover intent (the safe zone\n // measures both) and the focus restore.\n const [buttonElement, setButtonElement] = React.useState<HTMLButtonElement | null>(null);\n const [note, setNote] = React.useState<HTMLDivElement | null>(null);\n\n // The latest reason, for the hover close (read in the hover intent's pointer events and timers).\n const reasonRef = React.useRef(reason);\n React.useLayoutEffect(() => {\n reasonRef.current = reason;\n });\n // Set by a dismissal (Escape, outside press): keyboard focus reopens only after focus has left\n // both the button and the note (a focus restore must not reopen it). A click that closes the\n // note sets it too: in Safari a click leaves focus on <body>, and the focus restore follows. It\n // blocks only a reopening: keyboard focus still takes over a note that a click or hover opened.\n const dismissedRef = React.useRef(false);\n // A pointer press focuses the button; that focus waits for the click (0.7).\n const pointerPressRef = React.useRef(false);\n const pressTimerRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // A hover close in flight, set before its request: that close moves no focus. Every other close\n // request and every opening clears it.\n const hoverCloseRef = React.useRef(false);\n\n // The note mounts and unmounts through the presence core; the dismiss layer, the focus restore\n // and the positioning below key on `open`, so they go on close, not after an exit motion.\n // Destructured: react-hooks/refs treats an object whose member is passed to `ref` as a ref.\n const { isMounted, ref: presenceRef, presenceProps } = usePresence(open);\n const { setReference, setFloating, floatingProps, arrowStyles, side } = usePopupPosition({\n open,\n side: 'top',\n align: 'center',\n offset: 8,\n arrowRef,\n });\n const { layerId } = useDismiss({\n open,\n onDismiss: (dismissReason) => {\n // Only a focusin elsewhere dismisses with 'focus-outside' (focus lost to <body> never does):\n // focus has left both then, so keyboard focus may reopen the note at once.\n if (dismissReason !== 'focus-outside') dismissedRef.current = true;\n hoverCloseRef.current = false;\n // Declared below; the layer calls this only from its document listeners, after render.\n cancelHover();\n setReason(null);\n },\n refs: [buttonRef, noteRef],\n anchorRef: buttonRef,\n focusOutside: true,\n });\n // Focus returns to the button when the note closes with focus inside it (or lost to <body>); a\n // hover close returns only focus that is inside it, so it never moves focus.\n useRestoreFocus({\n enabled: open,\n container: note,\n triggerRef: buttonRef,\n onlyIfFocusInside: true,\n isHoverClose: () => hoverCloseRef.current,\n });\n\n const {\n triggerHandlers: hoverTriggerHandlers,\n surfaceHandlers: hoverSurfaceHandlers,\n cancel: cancelHover,\n } = useHoverIntent({\n // Browsers send pointer events to a disabled button too, and the hook skips only an\n // `aria-disabled` trigger.\n enabled: openOnHover && !disabled,\n open,\n openDelay: HOVER_DELAY,\n closeDelay: HOVER_DELAY,\n trigger: buttonElement,\n surface: note,\n onOpen: () => {\n hoverCloseRef.current = false;\n setReason((current) => current ?? 'hover');\n },\n onClose: () => {\n hoverCloseRef.current = true;\n setReason((current) => (current === 'hover' ? null : current));\n },\n // Only a note opened by hover, not pinned and not keyboard-owned, closes by hover; focus\n // inside the note (or a layer opened from it) blocks it too (Phase 2 D18).\n canClose: () => reasonRef.current === 'hover' && !isFocusInSurfaceTree(note, layerId),\n });\n\n React.useEffect(() => () => clearTimeout(pressTimerRef.current), []);\n\n // A dismissal ends once focus has left both the button and the note: at the button's blur\n // (below) and at a focusout of the note. Focus the note loses to nothing is the note closing\n // (removed, or inert while it exits): that ends nothing.\n React.useEffect(() => {\n if (!note) return undefined;\n const onFocusOut = (event: FocusEvent) => {\n const next = event.relatedTarget as Node | null;\n if (!next || note.contains(next) || buttonRef.current?.contains(next)) return;\n dismissedRef.current = false;\n };\n note.addEventListener('focusout', onFocusOut);\n return () => note.removeEventListener('focusout', onFocusOut);\n }, [note]);\n\n // The handlers below read and write refs: useEventCallback keeps them out of render (C-HOOKS).\n const handleClick = useEventCallback(() => {\n cancelHover();\n hoverCloseRef.current = false;\n // Closed, or opened by hover or keyboard focus: pinned. Pinned: closed.\n const closing = reason === 'click';\n if (closing) dismissedRef.current = true;\n setReason(closing ? null : 'click');\n });\n\n const handleFocus = useEventCallback(() => {\n // Keyboard focus opens, as hovering's keyboard counterpart.\n if (!openOnHover || pointerPressRef.current || (dismissedRef.current && !open)) return;\n hoverCloseRef.current = false;\n cancelHover();\n // A note that hover opened becomes focus-owned; a pinned one stays pinned.\n setReason((current) => (current === 'click' ? current : 'focus'));\n });\n\n const handleBlur = useEventCallback((event: React.FocusEvent<HTMLButtonElement>) => {\n const button = event.currentTarget;\n const next = event.relatedTarget as Node | null;\n // Tab from the button into the note keeps a dismissal.\n if (next && note?.contains(next)) return;\n // So does the window losing focus (a switch of window or tab): the button stays the document's\n // focused element, and the browser focuses it again, from nothing, when the window comes back.\n if (!next && button.ownerDocument.activeElement === button) return;\n // Focus lost to nothing in the page (a press on a spot that takes no focus) has left both.\n dismissedRef.current = false;\n });\n\n const handlePointerPress = useEventCallback(() => {\n pointerPressRef.current = true;\n // A press focuses the button (where it does) as the default action of `mousedown`, in the same\n // event turn. Forget the press after that turn: a press that does not focus the button (Safari\n // and Firefox on macOS, iOS) and ends without a click (dragged off, or a touch that becomes a\n // scroll) must not turn a later keyboard focus into pointer focus. A touch tap dispatches its\n // compatibility `mousedown` (and the focus) after the touch ends; that `mousedown` sets the\n // flag again.\n clearTimeout(pressTimerRef.current);\n pressTimerRef.current = setTimeout(() => {\n pressTimerRef.current = undefined;\n pointerPressRef.current = false;\n }, 0);\n });\n\n // Tab from the open button enters the note. The button's own handler runs before a modal focus\n // trap's document listener, which would wrap the Tab when the button is the trap's last element.\n const onButtonKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== 'Tab' || event.shiftKey || !open || !note) return;\n const first = getFirstTabbable(note);\n if (!first) return;\n event.preventDefault();\n first.focus();\n };\n // Tab past the note's last element continues after the button, Shift+Tab from its first element\n // returns to it, and the page order skips the note where its portal is.\n const onNoteKeyDown = usePopoverTabOrder({\n enabled: open,\n surface: note,\n anchorRef: buttonRef,\n getPreviousStop: () => buttonRef.current,\n });\n\n const buttonRefs = useMergedRefs<HTMLButtonElement>(\n ref,\n buttonRef,\n setButtonElement,\n setReference,\n );\n const noteRefs = useMergedRefs<HTMLDivElement>(noteRef, setNote, setFloating, presenceRef);\n\n return (\n <>\n <button\n type={type}\n id={id}\n data-size={size}\n {...rest}\n ref={buttonRefs}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={joinIds(ariaDescribedBy, infoId)}\n aria-expanded={open}\n aria-controls={open ? noteId : undefined}\n className={cn(\n infoButtonClassNames.root,\n 'inline-flex size-6 shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-muted-foreground',\n GLYPH[size].margin,\n 'not-disabled:not-aria-disabled:hover:text-foreground',\n focusRing,\n className,\n )}\n onClick={composeEventHandlers(onClick, handleClick)}\n onFocus={composeEventHandlers(onFocus, handleFocus)}\n onKeyDown={composeEventHandlers(onKeyDown, onButtonKeyDown)}\n onPointerEnter={composeEventHandlers(onPointerEnter, hoverTriggerHandlers.onPointerEnter)}\n onPointerMove={composeEventHandlers(onPointerMove, hoverTriggerHandlers.onPointerMove)}\n onPointerLeave={composeEventHandlers(onPointerLeave, hoverTriggerHandlers.onPointerLeave)}\n // Bookkeeping that follows the DOM (where focus went, whether a press is under way): it\n // runs even when the consumer prevents the default.\n onBlur={composeEventHandlers(onBlur, handleBlur, { checkDefaultPrevented: false })}\n onPointerDown={composeEventHandlers(onPointerDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n onMouseDown={composeEventHandlers(onMouseDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n >\n <InfoIcon size={GLYPH[size].size} />\n </button>\n {/* The same element follows the button in every phase, so a row styled by position never\n shifts: it holds `info` while the note is not mounted, and nothing while the note does. */}\n <span id={isMounted ? undefined : infoId} hidden>\n {isMounted ? null : info}\n </span>\n {isMounted && (\n <Portal layerId={layerId}>\n <div\n ref={noteRefs}\n id={noteId}\n role=\"note\"\n aria-labelledby={ariaLabelledBy || id}\n data-wave-infolabel-surface=\"\"\n data-state={open ? 'open' : 'closed'}\n {...presenceProps}\n {...floatingProps}\n onKeyDown={onNoteKeyDown}\n onPointerEnter={hoverSurfaceHandlers.onPointerEnter}\n onPointerLeave={hoverSurfaceHandlers.onPointerLeave}\n className={cn(\n infoButtonClassNames.info,\n noteClassName,\n 'w-max max-w-[min(20rem,calc(100vw-1rem))] rounded-md border border-border bg-background px-3 py-2 text-caption-1 text-foreground shadow-16',\n )}\n >\n <PopoverBeak\n ref={arrowRef}\n side={side}\n style={arrowStyles}\n data-wave-popover-arrow=\"\"\n className={infoButtonClassNames.beak}\n />\n <div id={infoId}>{info}</div>\n </div>\n </Portal>\n )}\n </>\n );\n};\nInfoButton.displayName = 'InfoButton';\n"],"mappings":"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"}
1
+ {"version":3,"file":"InfoButton.mjs","names":["useId","_Fragment","useId$1","React","GLYPH","HOVER_DELAY","InfoNoteClassContext","InfoButton","sizeProp","idProp","ariaLabelProp","ariaLabelledBy","ariaDescribedBy","rest","size","generatedId","id","noteClass","noteClassName","ariaLabel","noteId","infoId","reason","setReason","open","buttonRef","noteRef","arrowRef","buttonElement","setButtonElement","note","setNote","reasonRef","dismissedRef","pointerPressRef","pressTimerRef","hoverCloseRef","presenceRef","usePresence","usePopupPosition","useDismiss","dismissReason","cancelHover","useRestoreFocus","useFocusBeforeClose","motionClassName","useMotionClasses","motion","popupMotion","hoverTriggerHandlers","hoverSurfaceHandlers","useHoverIntent","openOnHover","disabled","current","isFocusInSurfaceTree","layerId","onFocusOut","next","event","handleClick","useEventCallback","closing","handleFocus","handleBlur","button","handlePointerPress","onButtonKeyDown","first","getFirstTabbable","onNoteKeyDown","usePopoverTabOrder","buttonRefs","useMergedRefs","ref","setReference","noteRefs","setFloating","Fragment","joinIds","cn","infoButtonClassNames","focusRing","className","composeEventHandlers","onClick","onFocus","onKeyDown","onPointerEnter","onPointerMove","onPointerLeave","onBlur","onPointerDown","onMouseDown","InfoIcon","isMounted","info","Portal","presenceProps","floatingProps","PopoverBeak","arrowStyles"],"sources":["../../../src/components/data-display/InfoButton.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { joinIds } from '../../lib/aria';\nimport { infoButtonClassNames } from '../../lib/classNames';\nimport { composeEventHandlers } from '../../lib/composeEventHandlers';\nimport { getFirstTabbable } from '../../lib/focus';\nimport { InfoIcon } from '../../lib/icons';\nimport { focusRing } from '../../lib/styles';\nimport { popupMotion } from '../../lib/motion';\nimport type { CoreSize, MotionSlot } from '../../lib/types';\nimport { useDismiss } from '../../hooks/useDismiss';\nimport { useEventCallback } from '../../hooks/useEventCallback';\nimport { useFocusBeforeClose } from '../../hooks/useFocusBeforeClose';\nimport { useHoverIntent } from '../../hooks/useHoverIntent';\nimport { useId } from '../../hooks/useId';\nimport { useMergedRefs } from '../../hooks/useMergedRefs';\nimport { useMotionClasses } from '../../hooks/useMotion';\nimport { usePopupPosition } from '../../hooks/usePopupPosition';\nimport { usePresence } from '../../hooks/usePresence';\nimport { useRestoreFocus } from '../../hooks/useRestoreFocus';\nimport { isFocusInSurfaceTree, PopoverBeak, usePopoverTabOrder } from '../overlays/Popover.shared';\nimport { Portal } from '../portal/Portal';\n\n/** Properties for the InfoButton component. */\nexport interface InfoButtonProps extends Omit<\n React.ButtonHTMLAttributes<HTMLButtonElement>,\n 'children'\n> {\n /**\n * The information the note shows: text, links or other content. It also describes the button\n * (`aria-describedby`), from the server HTML on. It is rendered once: next to the button, not\n * displayed, while the note is closed, and inside the note while it is shown.\n */\n info: React.ReactNode;\n /**\n * Size of the glyph: 12, 16 or 20px. The button box stays 24×24px at every size, with negative\n * margins above and below, so it never makes a row that centres its items (as InfoLabel does)\n * taller.\n * @default 'medium'\n */\n size?: CoreSize;\n /**\n * Open the note when a mouse pointer rests on the button (after 250 ms; it closes 250 ms after\n * the pointer has left the button and the note, touch and pen never hover), and when the button\n * receives keyboard focus. `false` opens it only on a click, and so on Enter and Space (Fluent's\n * InfoButton opens on click only). A click always toggles the note: it pins a note that hover or\n * keyboard focus opened, and closes a pinned one.\n * @default true\n */\n openOnHover?: boolean;\n /**\n * Accessible name of the button, and of the note, which is labelled like the button. Localize it\n * here. With `aria-labelledby` listing the id of your text and the button's own `id`, the name is\n * your text followed by this one (\"Billing Information\").\n * @default 'Information'\n */\n 'aria-label'?: string;\n /**\n * The note's motion. By default it fades in and slides 4px away from the button, on the side it\n * opened on, over 200 ms with a decelerating curve, and fades out and slides back over 100 ms\n * with an accelerating curve; under reduced motion it appears and disappears at once. `null`\n * turns the motion off (the note still renders its `data-presence` phases, which then end at\n * once); a class string or `{ className }` replaces the default classes with yours (see\n * `MotionSlot`), also under a `WaveProvider` with `motion=\"none\"`, which otherwise turns the\n * default off. Fluent's `Popover` `surfaceMotion` (its InfoButton is a Popover).\n * @default the fade and slide above\n */\n motion?: MotionSlot | null;\n /** Ref to the button. */\n ref?: React.Ref<HTMLButtonElement>;\n}\n\n/** Why the note is open: a click pins it; keyboard focus owns it; hover opens it transiently. */\ntype OpenReason = 'hover' | 'focus' | 'click';\n\n/**\n * Glyph size and negative margins per size: the 24px box never makes a row that centres its items\n * taller.\n */\nconst GLYPH: Readonly<Record<CoreSize, { size: number; margin: string }>> = {\n small: { size: 12, margin: '-my-1' },\n medium: { size: 16, margin: '-my-0.5' },\n large: { size: 20, margin: '-my-px' },\n};\n\n/**\n * Milliseconds from a mouse pointer resting on the button to the opening, and from the pointer\n * leaving the button and the note to the hover close (the 0.7 InfoLabel delays).\n */\nconst HOVER_DELAY = 250;\n\n/** What InfoLabel tells the InfoButton it renders: see {@link InfoNoteClassContext}. */\ninterface InfoNoteClass {\n /** The `id` of the InfoButton the class is for. */\n buttonId: string;\n /** The class added to that InfoButton's note. */\n className: string;\n}\n\n/**\n * Internal (P3-00, D5): the class InfoLabel adds to the note of the InfoButton it renders, its\n * 0.8 `info` part. Only the InfoButton whose `id` matches takes it, so an InfoButton inside the\n * label's `info` content takes none. Not exported from the barrels.\n */\nexport const InfoNoteClassContext = React.createContext<InfoNoteClass | null>(null);\nInfoNoteClassContext.displayName = 'InfoNoteClassContext';\n\n/**\n * A button with the info glyph that shows `info` in a note next to it: text, links or other rich\n * content, placed above the button with a beak (a toggletip).\n *\n * - **Naming.** The button is named \"Information\" (`aria-label`, which localizes it). Next to a\n * heading or other text, pass `aria-labelledby` with that text's id and the button's own `id`:\n * the button is then named \"‹text› Information\", so several info buttons on a page are told\n * apart. The note (`role=\"note\"`) is labelled like the button: by the same `aria-labelledby`\n * ids, else by the button. The button has `aria-expanded`, and `aria-controls` while the note is\n * open, but no `aria-haspopup`: a note is none of the popup kinds it names.\n * - **Opening.** A click pins the note open, and a click on a pinned note closes it. With\n * `openOnHover` (the default) a mouse pointer resting on the button opens it after 250 ms, and\n * it closes 250 ms after the pointer has left the button and the note (a pointer moving\n * diagonally from the button into the note keeps it open; touch and pen never hover); keyboard\n * focus on the button opens it too, while focus that comes from a pointer press waits for the\n * click. A hover close moves no focus, and a note that keyboard focus opened or keeps never\n * closes because of pointer movement. Opening never moves focus. Escape, a press outside and\n * focus moving outside the button and the note close it; Escape returns focus to the button when\n * it was in the note, and a dismissed note opens again on keyboard focus only once focus has\n * left both.\n * - **Tab path.** The note is portaled, but it keeps its place right after the button in the\n * keyboard order: Tab from the open button enters the note's first element (a link in it), Tab\n * past its last element continues after the button, and Shift+Tab from its first element\n * returns to the button.\n * - **One copy of `info`.** While the note is closed, `info` renders next to the button in an\n * element that is not displayed and describes the button (`aria-describedby`), so it is in the\n * server HTML and is read when the button takes focus. While the note is shown, `info` renders\n * only inside it, in an unnamed element that describes the button. `info` is never in the\n * document twice, so content with ids or effects is safe; it remounts each time the note opens\n * and closes, so state inside it starts over and its effects run again.\n * - **Structure.** The button is the root element: `ref`, `className`, `style` and every other\n * attribute go to it, the consumer's handlers first. The description and the note render next\n * to it, never inside it. Its box is 24×24px at every `size` and renders `data-size`. The note\n * mounts through the presence core: it carries `data-presence`, `data-state` (`open`, and\n * `closed` while it exits, `inert`) and the `data-wave-infolabel-surface` marker.\n * - **Motion.** The note fades in and slides toward the button from the side it opened on, and\n * fades out and slides back as it closes (see `motion`); it stays in the document, `inert`,\n * until the exit ends, with `info` still inside it (never twice in the document), while the\n * dismiss layer, the focus restore and the button's `aria-expanded` follow the open state at\n * once. Focus inside a closing note moves to the button in the closing commit. Without motion\n * (reduced motion, `motion={null}`, or a provider with `motion=\"none\"`) it unmounts as soon as\n * it closes.\n *\n * Fluent's InfoButton takes the same `info` and `size`. It opens on click only\n * (`openOnHover={false}` here), renders its note in place, and moves focus into the note; this one\n * keeps focus on the button and renders the note in a portal. Fluent's `popover` props and a\n * controlled open state are not offered.\n *\n * @example\n * <h2 id=\"billing\">Billing</h2>\n * <InfoButton\n * id=\"billing-info\"\n * aria-labelledby=\"billing billing-info\"\n * info={<>Invoices are sent monthly. <a href=\"/help/billing\">Learn more</a></>}\n * />\n */\nexport const InfoButton = ({\n info,\n size: sizeProp,\n openOnHover = true,\n id: idProp,\n type = 'button',\n 'aria-label': ariaLabelProp,\n motion,\n 'aria-labelledby': ariaLabelledBy,\n 'aria-describedby': ariaDescribedBy,\n className,\n disabled,\n onClick,\n onFocus,\n onBlur,\n onKeyDown,\n onPointerDown,\n onMouseDown,\n onPointerEnter,\n onPointerMove,\n onPointerLeave,\n ref,\n ...rest\n}: InfoButtonProps) => {\n // A size from untyped code renders, and reports, the default.\n const size: CoreSize = sizeProp === 'small' || sizeProp === 'large' ? sizeProp : 'medium';\n const generatedId = useId('info-button');\n const id = idProp || generatedId;\n const noteClass = React.useContext(InfoNoteClassContext);\n const noteClassName = noteClass?.buttonId === id ? noteClass.className : undefined;\n // An empty name would leave the button and the note unnamed: the glyph is decorative.\n const ariaLabel = ariaLabelProp || 'Information';\n const noteId = useId('info-note');\n // The one copy of `info`: not displayed while the note is closed, inside the note while shown.\n const infoId = useId('info');\n const [reason, setReason] = React.useState<OpenReason | null>(null);\n const open = reason !== null;\n\n const buttonRef = React.useRef<HTMLButtonElement | null>(null);\n const noteRef = React.useRef<HTMLDivElement | null>(null);\n const arrowRef = React.useRef<HTMLDivElement | null>(null);\n // The button and the note held in state too (C-POPUPS): for the hover intent (the safe zone\n // measures both) and the focus restore.\n const [buttonElement, setButtonElement] = React.useState<HTMLButtonElement | null>(null);\n const [note, setNote] = React.useState<HTMLDivElement | null>(null);\n\n // The latest reason, for the hover close (read in the hover intent's pointer events and timers).\n const reasonRef = React.useRef(reason);\n React.useLayoutEffect(() => {\n reasonRef.current = reason;\n });\n // Set by a dismissal (Escape, outside press): keyboard focus reopens only after focus has left\n // both the button and the note (a focus restore must not reopen it). A click that closes the\n // note sets it too: in Safari a click leaves focus on <body>, and the focus restore follows. It\n // blocks only a reopening: keyboard focus still takes over a note that a click or hover opened.\n const dismissedRef = React.useRef(false);\n // A pointer press focuses the button; that focus waits for the click (0.7).\n const pointerPressRef = React.useRef(false);\n const pressTimerRef = React.useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // A hover close in flight, set before its request: that close moves no focus. Every other close\n // request and every opening clears it.\n const hoverCloseRef = React.useRef(false);\n\n // The note mounts and unmounts through the presence core; the dismiss layer, the focus restore\n // and the positioning below key on `open`, so they go on close, not after an exit motion.\n // Destructured: react-hooks/refs treats an object whose member is passed to `ref` as a ref.\n const {\n isMounted,\n ref: presenceRef,\n presenceProps,\n } = usePresence(open, {\n appear: true,\n });\n const { setReference, setFloating, floatingProps, arrowStyles, side } = usePopupPosition({\n open,\n side: 'top',\n align: 'center',\n offset: 8,\n arrowRef,\n });\n const { layerId } = useDismiss({\n open,\n onDismiss: (dismissReason) => {\n // Only a focusin elsewhere dismisses with 'focus-outside' (focus lost to <body> never does):\n // focus has left both then, so keyboard focus may reopen the note at once.\n if (dismissReason !== 'focus-outside') dismissedRef.current = true;\n hoverCloseRef.current = false;\n // Declared below; the layer calls this only from its document listeners, after render.\n cancelHover();\n setReason(null);\n },\n refs: [buttonRef, noteRef],\n anchorRef: buttonRef,\n focusOutside: true,\n });\n // Focus returns to the button when the note closes with focus inside it (or lost to <body>); a\n // hover close returns only focus that is inside it, so it never moves focus.\n useRestoreFocus({\n enabled: open,\n container: note,\n triggerRef: buttonRef,\n onlyIfFocusInside: true,\n isHoverClose: () => hoverCloseRef.current,\n });\n\n // Focus before close: the note stays mounted while it exits, so focus inside a closing note moves\n // to the button in the closing commit, where useRestoreFocus would put it. Unmounting is left to\n // useRestoreFocus.\n useFocusBeforeClose({\n open,\n surfaceRef: noteRef,\n captureOpener: () => buttonRef.current,\n candidates: () => [buttonRef.current],\n });\n const motionClassName = useMotionClasses(motion, popupMotion);\n\n const {\n triggerHandlers: hoverTriggerHandlers,\n surfaceHandlers: hoverSurfaceHandlers,\n cancel: cancelHover,\n } = useHoverIntent({\n // Browsers send pointer events to a disabled button too, and the hook skips only an\n // `aria-disabled` trigger.\n enabled: openOnHover && !disabled,\n open,\n openDelay: HOVER_DELAY,\n closeDelay: HOVER_DELAY,\n trigger: buttonElement,\n surface: note,\n onOpen: () => {\n hoverCloseRef.current = false;\n setReason((current) => current ?? 'hover');\n },\n onClose: () => {\n hoverCloseRef.current = true;\n setReason((current) => (current === 'hover' ? null : current));\n },\n // Only a note opened by hover, not pinned and not keyboard-owned, closes by hover; focus\n // inside the note (or a layer opened from it) blocks it too (Phase 2 D18).\n canClose: () => reasonRef.current === 'hover' && !isFocusInSurfaceTree(note, layerId),\n });\n\n React.useEffect(() => () => clearTimeout(pressTimerRef.current), []);\n\n // A dismissal ends once focus has left both the button and the note: at the button's blur\n // (below) and at a focusout of the note. Focus the note loses to nothing is the note closing\n // (removed, or inert while it exits): that ends nothing.\n React.useEffect(() => {\n if (!note) return undefined;\n const onFocusOut = (event: FocusEvent) => {\n const next = event.relatedTarget as Node | null;\n if (!next || note.contains(next) || buttonRef.current?.contains(next)) return;\n dismissedRef.current = false;\n };\n note.addEventListener('focusout', onFocusOut);\n return () => note.removeEventListener('focusout', onFocusOut);\n }, [note]);\n\n // The handlers below read and write refs: useEventCallback keeps them out of render (C-HOOKS).\n const handleClick = useEventCallback(() => {\n cancelHover();\n hoverCloseRef.current = false;\n // Closed, or opened by hover or keyboard focus: pinned. Pinned: closed.\n const closing = reason === 'click';\n if (closing) dismissedRef.current = true;\n setReason(closing ? null : 'click');\n });\n\n const handleFocus = useEventCallback(() => {\n // Keyboard focus opens, as hovering's keyboard counterpart.\n if (!openOnHover || pointerPressRef.current || (dismissedRef.current && !open)) return;\n hoverCloseRef.current = false;\n cancelHover();\n // A note that hover opened becomes focus-owned; a pinned one stays pinned.\n setReason((current) => (current === 'click' ? current : 'focus'));\n });\n\n const handleBlur = useEventCallback((event: React.FocusEvent<HTMLButtonElement>) => {\n const button = event.currentTarget;\n const next = event.relatedTarget as Node | null;\n // Tab from the button into the note keeps a dismissal.\n if (next && note?.contains(next)) return;\n // So does the window losing focus (a switch of window or tab): the button stays the document's\n // focused element, and the browser focuses it again, from nothing, when the window comes back.\n if (!next && button.ownerDocument.activeElement === button) return;\n // Focus lost to nothing in the page (a press on a spot that takes no focus) has left both.\n dismissedRef.current = false;\n });\n\n const handlePointerPress = useEventCallback(() => {\n pointerPressRef.current = true;\n // A press focuses the button (where it does) as the default action of `mousedown`, in the same\n // event turn. Forget the press after that turn: a press that does not focus the button (Safari\n // and Firefox on macOS, iOS) and ends without a click (dragged off, or a touch that becomes a\n // scroll) must not turn a later keyboard focus into pointer focus. A touch tap dispatches its\n // compatibility `mousedown` (and the focus) after the touch ends; that `mousedown` sets the\n // flag again.\n clearTimeout(pressTimerRef.current);\n pressTimerRef.current = setTimeout(() => {\n pressTimerRef.current = undefined;\n pointerPressRef.current = false;\n }, 0);\n });\n\n // Tab from the open button enters the note. The button's own handler runs before a modal focus\n // trap's document listener, which would wrap the Tab when the button is the trap's last element.\n const onButtonKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== 'Tab' || event.shiftKey || !open || !note) return;\n const first = getFirstTabbable(note);\n if (!first) return;\n event.preventDefault();\n first.focus();\n };\n // Tab past the note's last element continues after the button, Shift+Tab from its first element\n // returns to it, and the page order skips the note where its portal is.\n const onNoteKeyDown = usePopoverTabOrder({\n enabled: open,\n surface: note,\n anchorRef: buttonRef,\n getPreviousStop: () => buttonRef.current,\n });\n\n const buttonRefs = useMergedRefs<HTMLButtonElement>(\n ref,\n buttonRef,\n setButtonElement,\n setReference,\n );\n const noteRefs = useMergedRefs<HTMLDivElement>(noteRef, setNote, setFloating, presenceRef);\n\n return (\n <>\n <button\n type={type}\n id={id}\n data-size={size}\n {...rest}\n ref={buttonRefs}\n disabled={disabled}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n aria-describedby={joinIds(ariaDescribedBy, infoId)}\n aria-expanded={open}\n aria-controls={open ? noteId : undefined}\n className={cn(\n infoButtonClassNames.root,\n 'inline-flex size-6 shrink-0 items-center justify-center rounded-full border-0 bg-transparent p-0 text-muted-foreground',\n GLYPH[size].margin,\n 'not-disabled:not-aria-disabled:hover:text-foreground',\n focusRing,\n className,\n )}\n onClick={composeEventHandlers(onClick, handleClick)}\n onFocus={composeEventHandlers(onFocus, handleFocus)}\n onKeyDown={composeEventHandlers(onKeyDown, onButtonKeyDown)}\n onPointerEnter={composeEventHandlers(onPointerEnter, hoverTriggerHandlers.onPointerEnter)}\n onPointerMove={composeEventHandlers(onPointerMove, hoverTriggerHandlers.onPointerMove)}\n onPointerLeave={composeEventHandlers(onPointerLeave, hoverTriggerHandlers.onPointerLeave)}\n // Bookkeeping that follows the DOM (where focus went, whether a press is under way): it\n // runs even when the consumer prevents the default.\n onBlur={composeEventHandlers(onBlur, handleBlur, { checkDefaultPrevented: false })}\n onPointerDown={composeEventHandlers(onPointerDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n onMouseDown={composeEventHandlers(onMouseDown, handlePointerPress, {\n checkDefaultPrevented: false,\n })}\n >\n <InfoIcon size={GLYPH[size].size} />\n </button>\n {/* The same element follows the button in every phase, so a row styled by position never\n shifts: it holds `info` while the note is not mounted, and nothing while the note does. */}\n <span id={isMounted ? undefined : infoId} hidden>\n {isMounted ? null : info}\n </span>\n {isMounted && (\n <Portal layerId={layerId}>\n <div\n ref={noteRefs}\n id={noteId}\n role=\"note\"\n aria-labelledby={ariaLabelledBy || id}\n data-wave-infolabel-surface=\"\"\n data-state={open ? 'open' : 'closed'}\n {...presenceProps}\n {...floatingProps}\n onKeyDown={onNoteKeyDown}\n onPointerEnter={hoverSurfaceHandlers.onPointerEnter}\n onPointerLeave={hoverSurfaceHandlers.onPointerLeave}\n className={cn(\n infoButtonClassNames.info,\n noteClassName,\n 'w-max max-w-[min(20rem,calc(100vw-1rem))] rounded-md border border-border bg-background px-3 py-2 text-caption-1 text-foreground shadow-16',\n motionClassName,\n )}\n >\n <PopoverBeak\n ref={arrowRef}\n side={side}\n style={arrowStyles}\n data-wave-popover-arrow=\"\"\n className={infoButtonClassNames.beak}\n />\n <div id={infoId}>{info}</div>\n </div>\n </Portal>\n )}\n </>\n );\n};\nInfoButton.displayName = 'InfoButton';\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/dev.cjs"),r=require("../../lib/classNames.cjs"),i=require("../../hooks/useId.cjs"),a=require("../input/fieldLabel.cjs"),o=require("../input/Label.cjs"),s=require("./InfoButton.cjs");let c=require("react");c=e.__toESM(c,1);let l=require("react/jsx-runtime");var u=({children:e,label:a,info:u,infoButtonLabel:d=`Information`,openOnHover:f,id:p,htmlFor:m,required:h,size:g=`medium`,weight:_,disabled:v,className:y,ref:b,...x})=>{let S=n.resolveDeprecatedProp(`InfoLabel`,e,a,`label`,`children`),C=i.useId(`info-label`),w=p||C,T=i.useId(`info-label-button`),E=c.useMemo(()=>({buttonId:T,className:r.infoLabelClassNames.info}),[T]);return(0,l.jsxs)(`span`,{ref:b,className:t.cn(r.infoLabelClassNames.root,`inline-flex items-center gap-0`,y),...x,children:[(0,l.jsx)(o.Label,{className:r.infoLabelClassNames.label,id:w,htmlFor:m,required:h,size:g,weight:_,disabled:v,children:S}),(0,l.jsx)(s.InfoNoteClassContext.Provider,{value:E,children:(0,l.jsx)(s.InfoButton,{id:T,className:r.infoLabelClassNames.infoButton,info:u,size:g,openOnHover:f,"aria-label":d,"aria-labelledby":`${w} ${T}`})})]})};u.displayName=`InfoLabel`,a.markFieldLabel(u),exports.InfoLabel=u;
1
+ "use client";const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../lib/cn.cjs"),n=require("../../lib/dev.cjs"),r=require("../../lib/classNames.cjs"),i=require("../../hooks/useId.cjs"),a=require("../input/fieldLabel.cjs"),o=require("../input/Label.cjs"),s=require("./InfoButton.cjs");let c=require("react");c=e.__toESM(c,1);let l=require("react/jsx-runtime");var u=({children:e,label:a,info:u,infoButtonLabel:d=`Information`,openOnHover:f,motion:p,id:m,htmlFor:h,required:g,size:_=`medium`,weight:v,disabled:y,className:b,ref:x,...S})=>{let C=n.resolveDeprecatedProp(`InfoLabel`,e,a,`label`,`children`),w=i.useId(`info-label`),T=m||w,E=i.useId(`info-label-button`),D=c.useMemo(()=>({buttonId:E,className:r.infoLabelClassNames.info}),[E]);return(0,l.jsxs)(`span`,{ref:x,className:t.cn(r.infoLabelClassNames.root,`inline-flex items-center gap-0`,b),...S,children:[(0,l.jsx)(o.Label,{className:r.infoLabelClassNames.label,id:T,htmlFor:h,required:g,size:_,weight:v,disabled:y,children:C}),(0,l.jsx)(s.InfoNoteClassContext.Provider,{value:D,children:(0,l.jsx)(s.InfoButton,{id:E,className:r.infoLabelClassNames.infoButton,info:u,size:_,openOnHover:f,motion:p,"aria-label":d,"aria-labelledby":`${T} ${E}`})})]})};u.displayName=`InfoLabel`,a.markFieldLabel(u),exports.InfoLabel=u;
2
2
  //# sourceMappingURL=InfoLabel.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"InfoLabel.cjs","names":["resolveDeprecatedProp","useId","React","infoLabelClassNames","cn","Label","InfoNoteClassContext","InfoButton","markFieldLabel","require_runtime","require_cn","require_dev","require_classNames","require_useId","require_fieldLabel","require_Label","require_InfoButton","react","react_jsx_runtime","InfoLabel","rest","content","children","label","generatedLabelId","labelId","id","buttonId","noteClass","className","infoButtonLabel"],"sources":["../../../src/components/data-display/InfoLabel.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { infoLabelClassNames } from '../../lib/classNames';\nimport { resolveDeprecatedProp } from '../../lib/dev';\nimport { useId } from '../../hooks/useId';\nimport { markFieldLabel } from '../input/fieldLabel';\nimport { Label, type LabelProps } from '../input/Label';\nimport { InfoButton, InfoNoteClassContext } from './InfoButton';\n\n/** Properties for the InfoLabel component. */\nexport interface InfoLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'>,\n Pick<LabelProps, 'htmlFor' | 'required' | 'size' | 'weight' | 'disabled'> {\n /** The label content (Fluent's `children`). */\n children?: React.ReactNode;\n /**\n * The label content.\n * @deprecated Use `children`.\n */\n label?: React.ReactNode;\n /** The information the info button's note shows: text, links or other rich content. */\n info: React.ReactNode;\n /**\n * Accessible name of the info button: the second half of its name, after the label text\n * (\"Password Information\"). Localize it here.\n * @default 'Information'\n */\n infoButtonLabel?: string;\n /**\n * Opens the note on a mouse hover, after a short delay, and on keyboard focus, next to a click\n * (see `InfoButton`'s `openOnHover`, which this passes through unchanged).\n * @default true\n */\n openOnHover?: boolean;\n /** Ref to the root `<span>`. */\n ref?: React.Ref<HTMLSpanElement>;\n}\n\n/**\n * A label followed by a button that reveals more information about it (Fluent's InfoLabel): the\n * label renders in a real `<label htmlFor>`, and the button shows `info` — text, links or other\n * rich content — in a note next to it, rendered once whether the note is open or closed (see\n * `InfoButton`, which owns that note and its opening, closing and Tab order).\n *\n * - `children` is the label content; `label` is a deprecated alias (a warning fires once, and\n * `children` wins when both are given).\n * - `htmlFor`, `required`, `size`, `weight` and `disabled` route to the `<label>`; `disabled` dims\n * only the label text, and the info button stays usable.\n * - The button is named \"‹label text› ‹infoButtonLabel›\" through `aria-labelledby`, which points\n * at the `<label>` and at the button's own id (whose `aria-label` supplies the second half), so\n * several InfoLabels on a page are told apart; `infoButtonLabel` localizes that second half.\n * `openOnHover` and `size` reach the button too.\n * - No `controlRef`: InfoLabel is a label, not a form control, so C-ROUTING's composite-control\n * routing does not apply to it; a layout that needs the button's own element renders `InfoButton`\n * next to a `Label` instead.\n *\n * @example\n * <InfoLabel info=\"Use at least 8 characters.\">Password</InfoLabel>\n */\nexport const InfoLabel = ({\n children,\n label,\n info,\n infoButtonLabel = 'Information',\n openOnHover,\n id,\n htmlFor,\n required,\n size = 'medium',\n weight,\n disabled,\n className,\n ref,\n ...rest\n}: InfoLabelProps) => {\n const content = resolveDeprecatedProp('InfoLabel', children, label, 'label', 'children');\n const generatedLabelId = useId('info-label');\n // An empty `id` counts as unset, so the button's name never points at an empty id.\n const labelId = id || generatedLabelId;\n const buttonId = useId('info-label-button');\n // The note of this InfoButton carries the label's `info` class, its 0.8 popup class (P3-00 D8).\n const noteClass = React.useMemo(\n () => ({ buttonId, className: infoLabelClassNames.info }),\n [buttonId],\n );\n return (\n <span\n ref={ref}\n className={cn(infoLabelClassNames.root, 'inline-flex items-center gap-0', className)}\n {...rest}\n >\n <Label\n className={infoLabelClassNames.label}\n id={labelId}\n htmlFor={htmlFor}\n required={required}\n size={size}\n weight={weight}\n disabled={disabled}\n >\n {content}\n </Label>\n <InfoNoteClassContext.Provider value={noteClass}>\n <InfoButton\n id={buttonId}\n className={infoLabelClassNames.infoButton}\n info={info}\n size={size}\n openOnHover={openOnHover}\n aria-label={infoButtonLabel}\n aria-labelledby={`${labelId} ${buttonId}`}\n />\n </InfoNoteClassContext.Provider>\n </span>\n );\n};\n\nInfoLabel.displayName = 'InfoLabel';\nmarkFieldLabel(InfoLabel);\n"],"mappings":"yXA4DA,IAAamB,GAAa,CACxB,WACA,QACA,OACA,kBAAkB,cAClB,cACA,KACA,UACA,WACA,OAAO,SACP,SACA,WACA,YACA,MACA,GAAGC,KACiB,CACpB,IAAMC,EAAUrB,EAAAA,sBAAsB,YAAasB,EAAUC,EAAO,QAAS,UAAU,EACjFC,EAAmBvB,EAAAA,MAAM,YAAY,EAErCwB,EAAUC,GAAMF,EAChBG,EAAW1B,EAAAA,MAAM,mBAAmB,EAEpC2B,EAAY1B,EAAM,aACf,CAAE,WAAU,UAAWC,EAAAA,oBAAoB,IAAK,GACvD,CAACwB,CAAQ,CACX,EACA,OACE,EAAAT,EAAA,KAAA,CAAC,OAAD,CACO,MACL,UAAWd,EAAAA,GAAGD,EAAAA,oBAAoB,KAAM,iCAAkC0B,CAAS,EACnF,GAAIT,EAHN,SAAA,EAKE,EAAAF,EAAA,IAAA,CAACb,EAAAA,MAAD,CACE,UAAWF,EAAAA,oBAAoB,MAC/B,GAAIsB,EACK,UACC,WACJ,OACE,SACE,WAET,SAAAJ,CACI,CAAA,GACP,EAAAH,EAAA,IAAA,CAACZ,EAAAA,qBAAqB,SAAtB,CAA+B,MAAOsB,EACpC,UAAA,EAAAV,EAAA,IAAA,CAACX,EAAAA,WAAD,CACE,GAAIoB,EACJ,UAAWxB,EAAAA,oBAAoB,WACzB,OACA,OACO,cACb,aAAY2B,EACZ,kBAAiB,GAAGL,EAAQ,GAAGE,GAChC,CAAA,CAC4B,CAAA,CAC3B,GAEV,EAEA,EAAU,YAAc,YACxBnB,EAAAA,eAAeW,CAAS"}
1
+ {"version":3,"file":"InfoLabel.cjs","names":["resolveDeprecatedProp","useId","React","infoLabelClassNames","cn","Label","InfoNoteClassContext","InfoButton","markFieldLabel","require_runtime","require_cn","require_dev","require_classNames","require_useId","require_fieldLabel","require_Label","require_InfoButton","react","react_jsx_runtime","InfoLabel","rest","content","children","label","generatedLabelId","labelId","id","buttonId","noteClass","className","infoButtonLabel"],"sources":["../../../src/components/data-display/InfoLabel.tsx"],"sourcesContent":["import * as React from 'react';\nimport { cn } from '../../lib/cn';\nimport { infoLabelClassNames } from '../../lib/classNames';\nimport { resolveDeprecatedProp } from '../../lib/dev';\nimport type { MotionSlot } from '../../lib/types';\nimport { useId } from '../../hooks/useId';\nimport { markFieldLabel } from '../input/fieldLabel';\nimport { Label, type LabelProps } from '../input/Label';\nimport { InfoButton, InfoNoteClassContext } from './InfoButton';\n\n/** Properties for the InfoLabel component. */\nexport interface InfoLabelProps\n extends\n Omit<React.HTMLAttributes<HTMLSpanElement>, 'children'>,\n Pick<LabelProps, 'htmlFor' | 'required' | 'size' | 'weight' | 'disabled'> {\n /** The label content (Fluent's `children`). */\n children?: React.ReactNode;\n /**\n * The label content.\n * @deprecated Use `children`.\n */\n label?: React.ReactNode;\n /** The information the info button's note shows: text, links or other rich content. */\n info: React.ReactNode;\n /**\n * Accessible name of the info button: the second half of its name, after the label text\n * (\"Password Information\"). Localize it here.\n * @default 'Information'\n */\n infoButtonLabel?: string;\n /**\n * Opens the note on a mouse hover, after a short delay, and on keyboard focus, next to a click\n * (see `InfoButton`'s `openOnHover`, which this passes through unchanged).\n * @default true\n */\n openOnHover?: boolean;\n /**\n * The motion of the info button's note, passed through unchanged (see `InfoButton`'s `motion`:\n * `undefined` is the default fade and slide, `null` none, a value replaces it). Fluent's\n * `Popover` `surfaceMotion`.\n * @default the fade and slide of InfoButton's note\n */\n motion?: MotionSlot | null;\n /** Ref to the root `<span>`. */\n ref?: React.Ref<HTMLSpanElement>;\n}\n\n/**\n * A label followed by a button that reveals more information about it (Fluent's InfoLabel): the\n * label renders in a real `<label htmlFor>`, and the button shows `info` — text, links or other\n * rich content — in a note next to it, rendered once whether the note is open or closed (see\n * `InfoButton`, which owns that note and its opening, closing and Tab order).\n *\n * - `children` is the label content; `label` is a deprecated alias (a warning fires once, and\n * `children` wins when both are given).\n * - `htmlFor`, `required`, `size`, `weight` and `disabled` route to the `<label>`; `disabled` dims\n * only the label text, and the info button stays usable.\n * - The button is named \"‹label text› ‹infoButtonLabel›\" through `aria-labelledby`, which points\n * at the `<label>` and at the button's own id (whose `aria-label` supplies the second half), so\n * several InfoLabels on a page are told apart; `infoButtonLabel` localizes that second half.\n * `openOnHover`, `motion` and `size` reach the button too.\n * - No `controlRef`: InfoLabel is a label, not a form control, so C-ROUTING's composite-control\n * routing does not apply to it; a layout that needs the button's own element renders `InfoButton`\n * next to a `Label` instead.\n *\n * @example\n * <InfoLabel info=\"Use at least 8 characters.\">Password</InfoLabel>\n */\nexport const InfoLabel = ({\n children,\n label,\n info,\n infoButtonLabel = 'Information',\n openOnHover,\n motion,\n id,\n htmlFor,\n required,\n size = 'medium',\n weight,\n disabled,\n className,\n ref,\n ...rest\n}: InfoLabelProps) => {\n const content = resolveDeprecatedProp('InfoLabel', children, label, 'label', 'children');\n const generatedLabelId = useId('info-label');\n // An empty `id` counts as unset, so the button's name never points at an empty id.\n const labelId = id || generatedLabelId;\n const buttonId = useId('info-label-button');\n // The note of this InfoButton carries the label's `info` class, its 0.8 popup class (P3-00 D8).\n const noteClass = React.useMemo(\n () => ({ buttonId, className: infoLabelClassNames.info }),\n [buttonId],\n );\n return (\n <span\n ref={ref}\n className={cn(infoLabelClassNames.root, 'inline-flex items-center gap-0', className)}\n {...rest}\n >\n <Label\n className={infoLabelClassNames.label}\n id={labelId}\n htmlFor={htmlFor}\n required={required}\n size={size}\n weight={weight}\n disabled={disabled}\n >\n {content}\n </Label>\n <InfoNoteClassContext.Provider value={noteClass}>\n <InfoButton\n id={buttonId}\n className={infoLabelClassNames.infoButton}\n info={info}\n size={size}\n openOnHover={openOnHover}\n motion={motion}\n aria-label={infoButtonLabel}\n aria-labelledby={`${labelId} ${buttonId}`}\n />\n </InfoNoteClassContext.Provider>\n </span>\n );\n};\n\nInfoLabel.displayName = 'InfoLabel';\nmarkFieldLabel(InfoLabel);\n"],"mappings":"yXAoEA,IAAamB,GAAa,CACxB,WACA,QACA,OACA,kBAAkB,cAClB,cACA,SACA,KACA,UACA,WACA,OAAO,SACP,SACA,WACA,YACA,MACA,GAAGC,KACiB,CACpB,IAAMC,EAAUrB,EAAAA,sBAAsB,YAAasB,EAAUC,EAAO,QAAS,UAAU,EACjFC,EAAmBvB,EAAAA,MAAM,YAAY,EAErCwB,EAAUC,GAAMF,EAChBG,EAAW1B,EAAAA,MAAM,mBAAmB,EAEpC2B,EAAY1B,EAAM,aACf,CAAE,WAAU,UAAWC,EAAAA,oBAAoB,IAAK,GACvD,CAACwB,CAAQ,CACX,EACA,OACE,EAAAT,EAAA,KAAA,CAAC,OAAD,CACO,MACL,UAAWd,EAAAA,GAAGD,EAAAA,oBAAoB,KAAM,iCAAkC0B,CAAS,EACnF,GAAIT,EAHN,SAAA,EAKE,EAAAF,EAAA,IAAA,CAACb,EAAAA,MAAD,CACE,UAAWF,EAAAA,oBAAoB,MAC/B,GAAIsB,EACK,UACC,WACJ,OACE,SACE,WAET,SAAAJ,CACI,CAAA,GACP,EAAAH,EAAA,IAAA,CAACZ,EAAAA,qBAAqB,SAAtB,CAA+B,MAAOsB,EACpC,UAAA,EAAAV,EAAA,IAAA,CAACX,EAAAA,WAAD,CACE,GAAIoB,EACJ,UAAWxB,EAAAA,oBAAoB,WACzB,OACA,OACO,cACL,SACR,aAAY2B,EACZ,kBAAiB,GAAGL,EAAQ,GAAGE,GAChC,CAAA,CAC4B,CAAA,CAC3B,GAEV,EAEA,EAAU,YAAc,YACxBnB,EAAAA,eAAeW,CAAS"}