@allxsmith/bestax-bulma 5.65.1 → 5.66.1

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 (113) hide show
  1. package/dist/bestax.css +1 -1
  2. package/dist/bestax.css.map +1 -1
  3. package/dist/esm/components/Card.js +6 -6
  4. package/dist/esm/components/Card.js.map +1 -1
  5. package/dist/esm/components/Carousel.js +19 -7
  6. package/dist/esm/components/Carousel.js.map +1 -1
  7. package/dist/esm/components/Message.js +1 -1
  8. package/dist/esm/components/Message.js.map +1 -1
  9. package/dist/esm/components/Modal.js +1 -1
  10. package/dist/esm/components/Modal.js.map +1 -1
  11. package/dist/esm/components/Navbar.js +1 -1
  12. package/dist/esm/components/Navbar.js.map +1 -1
  13. package/dist/esm/components/Popover.js +4 -1
  14. package/dist/esm/components/Popover.js.map +1 -1
  15. package/dist/esm/components/SegmentedControl.js +4 -4
  16. package/dist/esm/components/SegmentedControl.js.map +1 -1
  17. package/dist/esm/components/Sidebar.js +1 -1
  18. package/dist/esm/components/Sidebar.js.map +1 -1
  19. package/dist/esm/elements/Figure.js +1 -1
  20. package/dist/esm/elements/Figure.js.map +1 -1
  21. package/dist/esm/form/ColorInputBase.js +1 -1
  22. package/dist/esm/form/ColorInputBase.js.map +1 -1
  23. package/dist/esm/form/DateInput.js +4 -1
  24. package/dist/esm/form/DateInput.js.map +1 -1
  25. package/dist/esm/form/DateInputBase.js +1 -1
  26. package/dist/esm/form/DateInputBase.js.map +1 -1
  27. package/dist/esm/form/DateRangeInput.js +4 -1
  28. package/dist/esm/form/DateRangeInput.js.map +1 -1
  29. package/dist/esm/form/DateRangeInputBase.js +1 -1
  30. package/dist/esm/form/DateRangeInputBase.js.map +1 -1
  31. package/dist/esm/form/DateTimeInput.js +4 -1
  32. package/dist/esm/form/DateTimeInput.js.map +1 -1
  33. package/dist/esm/form/DateTimeInputBase.js +1 -1
  34. package/dist/esm/form/DateTimeInputBase.js.map +1 -1
  35. package/dist/esm/form/Slider.js +19 -9
  36. package/dist/esm/form/Slider.js.map +1 -1
  37. package/dist/esm/form/TimeInput.js +4 -1
  38. package/dist/esm/form/TimeInput.js.map +1 -1
  39. package/dist/esm/form/TimeInputBase.js +1 -1
  40. package/dist/esm/form/TimeInputBase.js.map +1 -1
  41. package/dist/esm/form/TreeSelect.js.map +1 -1
  42. package/dist/esm/form/_pickerInternals/PickerPopover.js +4 -1
  43. package/dist/esm/form/_pickerInternals/PickerPopover.js.map +1 -1
  44. package/dist/esm/form/_pickerInternals/TimeWheels.js +1 -1
  45. package/dist/esm/form/_pickerInternals/TimeWheels.js.map +1 -1
  46. package/dist/esm/form/_pickerInternals/useLocaleDefaults.js +15 -1
  47. package/dist/esm/form/_pickerInternals/useLocaleDefaults.js.map +1 -1
  48. package/dist/esm/form/_selectInternals/DialogSelect.js +1 -1
  49. package/dist/esm/form/_selectInternals/DialogSelect.js.map +1 -1
  50. package/dist/esm/helpers/useAnchoredPosition.js +11 -3
  51. package/dist/esm/helpers/useAnchoredPosition.js.map +1 -1
  52. package/dist/extras.css +1 -1
  53. package/dist/extras.css.map +1 -1
  54. package/dist/index.cjs +123 -41
  55. package/dist/index.cjs.map +1 -1
  56. package/dist/index.esm.js +6 -6
  57. package/dist/types/components/Card.d.ts +52 -0
  58. package/dist/types/components/Carousel.d.ts +12 -3
  59. package/dist/types/components/Message.d.ts +16 -0
  60. package/dist/types/components/Modal.d.ts +71 -0
  61. package/dist/types/components/Navbar.d.ts +11 -1
  62. package/dist/types/components/Popover.d.ts +5 -1
  63. package/dist/types/components/SegmentedControl.d.ts +9 -0
  64. package/dist/types/components/Sidebar.d.ts +45 -5
  65. package/dist/types/elements/Figure.d.ts +11 -0
  66. package/dist/types/form/ColorInputBase.d.ts +7 -1
  67. package/dist/types/form/DateInputBase.d.ts +7 -1
  68. package/dist/types/form/DateRangeInputBase.d.ts +7 -1
  69. package/dist/types/form/DateTimeInputBase.d.ts +7 -1
  70. package/dist/types/form/TimeInputBase.d.ts +7 -1
  71. package/dist/types/form/TreeSelect.d.ts +4 -1
  72. package/dist/types/form/_pickerInternals/PickerPopover.d.ts +5 -0
  73. package/dist/types/form/_pickerInternals/useLocaleDefaults.d.ts +12 -0
  74. package/dist/types/form/_selectInternals/DialogSelect.d.ts +1 -0
  75. package/dist/types/helpers/useAnchoredPosition.d.ts +9 -2
  76. package/dist/types-cjs/components/Card.d.ts +52 -0
  77. package/dist/types-cjs/components/Carousel.d.ts +12 -3
  78. package/dist/types-cjs/components/Message.d.ts +16 -0
  79. package/dist/types-cjs/components/Modal.d.ts +71 -0
  80. package/dist/types-cjs/components/Navbar.d.ts +11 -1
  81. package/dist/types-cjs/components/Popover.d.ts +5 -1
  82. package/dist/types-cjs/components/SegmentedControl.d.ts +9 -0
  83. package/dist/types-cjs/components/Sidebar.d.ts +45 -5
  84. package/dist/types-cjs/elements/Figure.d.ts +11 -0
  85. package/dist/types-cjs/form/ColorInputBase.d.ts +7 -1
  86. package/dist/types-cjs/form/DateInputBase.d.ts +7 -1
  87. package/dist/types-cjs/form/DateRangeInputBase.d.ts +7 -1
  88. package/dist/types-cjs/form/DateTimeInputBase.d.ts +7 -1
  89. package/dist/types-cjs/form/TimeInputBase.d.ts +7 -1
  90. package/dist/types-cjs/form/TreeSelect.d.ts +4 -1
  91. package/dist/types-cjs/form/_pickerInternals/PickerPopover.d.ts +5 -0
  92. package/dist/types-cjs/form/_pickerInternals/useLocaleDefaults.d.ts +12 -0
  93. package/dist/types-cjs/form/_selectInternals/DialogSelect.d.ts +1 -0
  94. package/dist/types-cjs/helpers/useAnchoredPosition.d.ts +9 -2
  95. package/dist/versions/bestax-no-dark-mode.css +1 -1
  96. package/dist/versions/bestax-no-dark-mode.css.map +1 -1
  97. package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
  98. package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
  99. package/dist/versions/bestax-no-helpers.css +1 -1
  100. package/dist/versions/bestax-no-helpers.css.map +1 -1
  101. package/dist/versions/bestax-prefixed.css +1 -1
  102. package/dist/versions/bestax-prefixed.css.map +1 -1
  103. package/package.json +1 -1
  104. package/src/scss/_input-trigger.scss +24 -5
  105. package/src/scss/components/_carousel.scss +22 -4
  106. package/src/scss/components/_pagination.scss +31 -2
  107. package/src/scss/components/_popover.scss +11 -0
  108. package/src/scss/components/_tooltip.scss +19 -0
  109. package/src/scss/elements/_button.scss +27 -3
  110. package/src/scss/form/_dateinput.scss +6 -0
  111. package/src/scss/form/_passwordinput.scss +6 -5
  112. package/src/scss/form/_picker-popover.scss +45 -5
  113. package/src/scss/form/_slider.scss +39 -0
@@ -48,7 +48,7 @@ const samePosition = (a, b) => a.position === b.position &&
48
48
  */
49
49
  function useAnchoredPosition(anchorRef, panelRef, { active, position, fixed, offset = 4, styledGap, matchWidth = false, followContent = false, }) {
50
50
  const [resolved, setResolved] = useState({
51
- position: position === 'auto' ? 'bottom-left' : position,
51
+ position: position === undefined || position === 'auto' ? 'bottom-left' : position,
52
52
  });
53
53
  // The measurement while the panel is active, for `followContent` to run
54
54
  // again after a render.
@@ -72,14 +72,22 @@ function useAnchoredPosition(anchorRef, panelRef, { active, position, fixed, off
72
72
  const { height } = measured;
73
73
  const corner = position === 'auto'
74
74
  ? resolveAuto(view, rect, width, height, offset + (styledGap?.(panel) ?? 0))
75
- : position;
75
+ : (position ?? 'bottom-left');
76
+ // The default lines up with the anchor's inline start, so right to
77
+ // left a fixed panel lines up the right edges. A named corner doesn't,
78
+ // and a panel in place takes the default's side from its stylesheet.
79
+ const startsRight = position === undefined &&
80
+ fixed &&
81
+ getComputedStyle(anchor).direction === 'rtl';
76
82
  const next = fixed
77
83
  ? {
78
84
  position: corner,
79
85
  top: corner.startsWith('bottom')
80
86
  ? rect.bottom + offset
81
87
  : rect.top - height - offset,
82
- left: corner.endsWith('left') ? rect.left : rect.right - width,
88
+ left: corner.endsWith('left') && !startsRight
89
+ ? rect.left
90
+ : rect.right - width,
83
91
  ...(matchWidth ? { minWidth: rect.width } : {}),
84
92
  }
85
93
  : { position: corner };
@@ -1 +1 @@
1
- {"version":3,"file":"useAnchoredPosition.js","sources":["../../../src/helpers/useAnchoredPosition.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useRef, useState } from 'react';\nimport type { RefObject } from 'react';\nimport type { PickerPosition } from '../form/_pickerInternals/pickerTypes';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\n\n/** A corner of the anchor the panel opens from, once `auto` is resolved. */\nexport type AnchoredCorner = Exclude<PickerPosition, 'auto'>;\n\n/**\n * Where the panel goes: the corner it opens from and, when it is fixed to\n * the viewport, its coordinates there.\n */\nexport interface AnchoredPosition {\n position: AnchoredCorner;\n top?: number;\n left?: number;\n /**\n * With `matchWidth`, the anchor's width, which a fixed panel takes as its\n * `min-width`.\n */\n minWidth?: number;\n}\n\n/**\n * Options for `useAnchoredPosition`.\n */\nexport interface UseAnchoredPositionOptions {\n /** Whether the panel is on the page. Nothing is measured while it is not. */\n active: boolean;\n /** The corner asked for, or `auto` to pick one that keeps the panel in the viewport. */\n position: PickerPosition;\n /**\n * Whether the panel is fixed to the viewport (it was portaled away from the\n * anchor), which needs coordinates. Otherwise the panel's CSS places it\n * against the anchor from the corner alone.\n */\n fixed: boolean;\n /**\n * The gap between the anchor and the panel, in pixels, that the hook puts\n * there itself: added to the fixed coordinates and to the room `auto` asks\n * for below the anchor. Popover and the pickers' popover pass `0` and take\n * their gap from a margin in their stylesheet, read through `styledGap`, so\n * a theme's offset moves the panel and `auto` alike. A new panel should do\n * the same rather than rely on this default, which no stylesheet sees.\n * @defaultValue 4\n */\n offset?: number;\n /**\n * Reads the gap the panel's own stylesheet puts between it and the anchor,\n * in pixels, each time the panel is measured. `auto` leaves room for it as\n * well; the coordinates don't include it, because the stylesheet applies it.\n */\n styledGap?: (panel: HTMLElement) => number;\n /**\n * Whether the panel is at least as wide as the anchor, as a list under its\n * field is. A panel in place gets that from its stylesheet. A fixed one\n * can't, so its placement carries the anchor's width as `minWidth` for the\n * panel to set, and its coordinates and `auto` already allow for it.\n * @defaultValue false\n */\n matchWidth?: boolean;\n /**\n * Measures again after each render of the component holding the hook, for\n * a panel whose content changes while it's open, as a filtered list's\n * does. A fixed panel above its anchor is placed from its height, and\n * `auto` reads the height too, so a measurement taken when it opened goes\n * stale as the content changes.\n * @defaultValue false\n */\n followContent?: boolean;\n}\n\n/**\n * A `styledGap` for a panel whose stylesheet puts the gap in a margin on the\n * side facing the anchor, negative on a portaled panel above it, so the\n * larger magnitude is the gap. In pixels.\n */\nexport function marginGap(panel: HTMLElement): number {\n const { marginTop, marginBottom } = getComputedStyle(panel);\n return Math.max(\n Math.abs(parseFloat(marginTop)) || 0,\n Math.abs(parseFloat(marginBottom)) || 0\n );\n}\n\n/**\n * The corner that keeps a panel of this size in the viewport, preferring\n * below the anchor and lined up with its left edge.\n */\nfunction resolveAuto(\n view: Window,\n rect: DOMRect,\n panelWidth: number,\n panelHeight: number,\n gap: number\n): AnchoredCorner {\n const fitsBelow = rect.bottom + panelHeight + gap <= view.innerHeight;\n const fitsRight = rect.left + panelWidth <= view.innerWidth;\n if (fitsBelow) return fitsRight ? 'bottom-left' : 'bottom-right';\n return fitsRight ? 'top-left' : 'top-right';\n}\n\nconst samePosition = (a: AnchoredPosition, b: AnchoredPosition): boolean =>\n a.position === b.position &&\n a.top === b.top &&\n a.left === b.left &&\n a.minWidth === b.minWidth;\n\n/**\n * Places a floating panel against its anchor: resolves `auto` to a corner\n * and, for a panel fixed to the viewport, works out its coordinates. It\n * measures again on resize and on any scroll of the anchor's window (an\n * iframe's, when the anchor renders into one), so a fixed panel follows its\n * anchor, and with `followContent` after each render too. A measurement that\n * changes nothing doesn't render again.\n *\n * Measuring happens in a layout effect, so the panel is placed before it\n * paints. It needs both elements on the page while `active` is on, and\n * keeps the last placement when either is missing.\n *\n * @function useAnchoredPosition\n * @param anchorRef - The element the panel opens from.\n * @param panelRef - The floating panel.\n * @param options - Whether to measure, the corner asked for, and how the panel is placed.\n * @returns The corner the panel opens from, with coordinates when it is fixed.\n */\nexport function useAnchoredPosition(\n anchorRef: RefObject<HTMLElement | null>,\n panelRef: RefObject<HTMLElement | null>,\n {\n active,\n position,\n fixed,\n offset = 4,\n styledGap,\n matchWidth = false,\n followContent = false,\n }: UseAnchoredPositionOptions\n): AnchoredPosition {\n const [resolved, setResolved] = useState<AnchoredPosition>({\n position: position === 'auto' ? 'bottom-left' : position,\n });\n // The measurement while the panel is active, for `followContent` to run\n // again after a render.\n const updateRef = useRef<(() => void) | null>(null);\n\n useClientLayoutEffect(() => {\n if (!active) return undefined;\n const view =\n (anchorRef.current ?? panelRef.current)?.ownerDocument.defaultView ??\n window;\n const update = () => {\n const anchor = anchorRef.current;\n const panel = panelRef.current;\n if (!anchor || !panel) return;\n const rect = anchor.getBoundingClientRect();\n const measured = panel.getBoundingClientRect();\n // The width the panel has once it's at least as wide as the anchor.\n const width = matchWidth\n ? Math.max(measured.width, rect.width)\n : measured.width;\n const { height } = measured;\n const corner =\n position === 'auto'\n ? resolveAuto(\n view,\n rect,\n width,\n height,\n offset + (styledGap?.(panel) ?? 0)\n )\n : position;\n const next: AnchoredPosition = fixed\n ? {\n position: corner,\n top: corner.startsWith('bottom')\n ? rect.bottom + offset\n : rect.top - height - offset,\n left: corner.endsWith('left') ? rect.left : rect.right - width,\n ...(matchWidth ? { minWidth: rect.width } : {}),\n }\n : { position: corner };\n setResolved(prev => (samePosition(prev, next) ? prev : next));\n };\n updateRef.current = update;\n update();\n view.addEventListener('resize', update, { passive: true });\n view.addEventListener('scroll', update, { passive: true, capture: true });\n return () => {\n updateRef.current = null;\n view.removeEventListener('resize', update);\n view.removeEventListener('scroll', update, { capture: true });\n };\n }, [\n active,\n anchorRef,\n panelRef,\n position,\n fixed,\n offset,\n styledGap,\n matchWidth,\n ]);\n\n // After every render, on purpose: the panel's content is what changed.\n useClientLayoutEffect(() => {\n if (followContent) updateRef.current?.();\n });\n\n return resolved;\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useAnchoredPosition.js","sources":["../../../src/helpers/useAnchoredPosition.ts"],"sourcesContent":["// INTERNAL: deliberately not exported from src/index.ts.\nimport { useRef, useState } from 'react';\nimport type { RefObject } from 'react';\nimport type { PickerPosition } from '../form/_pickerInternals/pickerTypes';\nimport { useClientLayoutEffect } from './useClientLayoutEffect';\n\n/** A corner of the anchor the panel opens from, once `auto` is resolved. */\nexport type AnchoredCorner = Exclude<PickerPosition, 'auto'>;\n\n/**\n * Where the panel goes: the corner it opens from and, when it is fixed to\n * the viewport, its coordinates there.\n */\nexport interface AnchoredPosition {\n position: AnchoredCorner;\n top?: number;\n left?: number;\n /**\n * With `matchWidth`, the anchor's width, which a fixed panel takes as its\n * `min-width`.\n */\n minWidth?: number;\n}\n\n/**\n * Options for `useAnchoredPosition`.\n */\nexport interface UseAnchoredPositionOptions {\n /** Whether the panel is on the page. Nothing is measured while it is not. */\n active: boolean;\n /**\n * The corner asked for, or `auto` to pick one that keeps the panel in the\n * viewport. A named corner is physical, whatever the page's direction.\n * Left out, the corner is `bottom-left` lined up with the anchor's inline\n * start, which is its right edge right to left. A panel in place gets that\n * from its stylesheet, as Popover's and the pickers' do; a fixed panel's\n * coordinates read the anchor's direction.\n */\n position?: PickerPosition;\n /**\n * Whether the panel is fixed to the viewport (it was portaled away from the\n * anchor), which needs coordinates. Otherwise the panel's CSS places it\n * against the anchor from the corner alone.\n */\n fixed: boolean;\n /**\n * The gap between the anchor and the panel, in pixels, that the hook puts\n * there itself: added to the fixed coordinates and to the room `auto` asks\n * for below the anchor. Popover and the pickers' popover pass `0` and take\n * their gap from a margin in their stylesheet, read through `styledGap`, so\n * a theme's offset moves the panel and `auto` alike. A new panel should do\n * the same rather than rely on this default, which no stylesheet sees.\n * @defaultValue 4\n */\n offset?: number;\n /**\n * Reads the gap the panel's own stylesheet puts between it and the anchor,\n * in pixels, each time the panel is measured. `auto` leaves room for it as\n * well; the coordinates don't include it, because the stylesheet applies it.\n */\n styledGap?: (panel: HTMLElement) => number;\n /**\n * Whether the panel is at least as wide as the anchor, as a list under its\n * field is. A panel in place gets that from its stylesheet. A fixed one\n * can't, so its placement carries the anchor's width as `minWidth` for the\n * panel to set, and its coordinates and `auto` already allow for it.\n * @defaultValue false\n */\n matchWidth?: boolean;\n /**\n * Measures again after each render of the component holding the hook, for\n * a panel whose content changes while it's open, as a filtered list's\n * does. A fixed panel above its anchor is placed from its height, and\n * `auto` reads the height too, so a measurement taken when it opened goes\n * stale as the content changes.\n * @defaultValue false\n */\n followContent?: boolean;\n}\n\n/**\n * A `styledGap` for a panel whose stylesheet puts the gap in a margin on the\n * side facing the anchor, negative on a portaled panel above it, so the\n * larger magnitude is the gap. In pixels.\n */\nexport function marginGap(panel: HTMLElement): number {\n const { marginTop, marginBottom } = getComputedStyle(panel);\n return Math.max(\n Math.abs(parseFloat(marginTop)) || 0,\n Math.abs(parseFloat(marginBottom)) || 0\n );\n}\n\n/**\n * The corner that keeps a panel of this size in the viewport, preferring\n * below the anchor and lined up with its left edge.\n */\nfunction resolveAuto(\n view: Window,\n rect: DOMRect,\n panelWidth: number,\n panelHeight: number,\n gap: number\n): AnchoredCorner {\n const fitsBelow = rect.bottom + panelHeight + gap <= view.innerHeight;\n const fitsRight = rect.left + panelWidth <= view.innerWidth;\n if (fitsBelow) return fitsRight ? 'bottom-left' : 'bottom-right';\n return fitsRight ? 'top-left' : 'top-right';\n}\n\nconst samePosition = (a: AnchoredPosition, b: AnchoredPosition): boolean =>\n a.position === b.position &&\n a.top === b.top &&\n a.left === b.left &&\n a.minWidth === b.minWidth;\n\n/**\n * Places a floating panel against its anchor: resolves `auto` to a corner\n * and, for a panel fixed to the viewport, works out its coordinates. It\n * measures again on resize and on any scroll of the anchor's window (an\n * iframe's, when the anchor renders into one), so a fixed panel follows its\n * anchor, and with `followContent` after each render too. A measurement that\n * changes nothing doesn't render again.\n *\n * Measuring happens in a layout effect, so the panel is placed before it\n * paints. It needs both elements on the page while `active` is on, and\n * keeps the last placement when either is missing.\n *\n * @function useAnchoredPosition\n * @param anchorRef - The element the panel opens from.\n * @param panelRef - The floating panel.\n * @param options - Whether to measure, the corner asked for, and how the panel is placed.\n * @returns The corner the panel opens from, with coordinates when it is fixed.\n */\nexport function useAnchoredPosition(\n anchorRef: RefObject<HTMLElement | null>,\n panelRef: RefObject<HTMLElement | null>,\n {\n active,\n position,\n fixed,\n offset = 4,\n styledGap,\n matchWidth = false,\n followContent = false,\n }: UseAnchoredPositionOptions\n): AnchoredPosition {\n const [resolved, setResolved] = useState<AnchoredPosition>({\n position:\n position === undefined || position === 'auto' ? 'bottom-left' : position,\n });\n // The measurement while the panel is active, for `followContent` to run\n // again after a render.\n const updateRef = useRef<(() => void) | null>(null);\n\n useClientLayoutEffect(() => {\n if (!active) return undefined;\n const view =\n (anchorRef.current ?? panelRef.current)?.ownerDocument.defaultView ??\n window;\n const update = () => {\n const anchor = anchorRef.current;\n const panel = panelRef.current;\n if (!anchor || !panel) return;\n const rect = anchor.getBoundingClientRect();\n const measured = panel.getBoundingClientRect();\n // The width the panel has once it's at least as wide as the anchor.\n const width = matchWidth\n ? Math.max(measured.width, rect.width)\n : measured.width;\n const { height } = measured;\n const corner =\n position === 'auto'\n ? resolveAuto(\n view,\n rect,\n width,\n height,\n offset + (styledGap?.(panel) ?? 0)\n )\n : (position ?? 'bottom-left');\n // The default lines up with the anchor's inline start, so right to\n // left a fixed panel lines up the right edges. A named corner doesn't,\n // and a panel in place takes the default's side from its stylesheet.\n const startsRight =\n position === undefined &&\n fixed &&\n getComputedStyle(anchor).direction === 'rtl';\n const next: AnchoredPosition = fixed\n ? {\n position: corner,\n top: corner.startsWith('bottom')\n ? rect.bottom + offset\n : rect.top - height - offset,\n left:\n corner.endsWith('left') && !startsRight\n ? rect.left\n : rect.right - width,\n ...(matchWidth ? { minWidth: rect.width } : {}),\n }\n : { position: corner };\n setResolved(prev => (samePosition(prev, next) ? prev : next));\n };\n updateRef.current = update;\n update();\n view.addEventListener('resize', update, { passive: true });\n view.addEventListener('scroll', update, { passive: true, capture: true });\n return () => {\n updateRef.current = null;\n view.removeEventListener('resize', update);\n view.removeEventListener('scroll', update, { capture: true });\n };\n }, [\n active,\n anchorRef,\n panelRef,\n position,\n fixed,\n offset,\n styledGap,\n matchWidth,\n ]);\n\n // After every render, on purpose: the panel's content is what changed.\n useClientLayoutEffect(() => {\n if (followContent) updateRef.current?.();\n });\n\n return resolved;\n}\n"],"names":[],"mappings":"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"}