@umamichi-ui/common-components 0.3.2 → 0.3.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"FloatingMenu.d.ts","sourceRoot":"","sources":["../../src/menu/FloatingMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM3E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,WAAW,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,qBAAqB,CAAC;AAEzE,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAaF,wBAAgB,YAAY,CAAC,EAC3B,aAAa,EACb,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAA0B,EAC1B,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,aAA+B,GAChC,EAAE,iBAAiB,+BAqHnB"}
1
+ {"version":3,"file":"FloatingMenu.d.ts","sourceRoot":"","sources":["../../src/menu/FloatingMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAsC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAM3E,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,IAAI,EAAE,WAAW,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG,gBAAgB,GAAG,qBAAqB,CAAC;AAEzE,MAAM,MAAM,0BAA0B,GAAG,SAAS,GAAG,MAAM,CAAC;AAE5D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,cAAc,CAAC,EAAE,0BAA0B,CAAC;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAaF,wBAAgB,YAAY,CAAC,EAC3B,aAAa,EACb,KAAK,EACL,YAAY,EACZ,WAAW,EACX,cAA0B,EAC1B,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,aAA+B,GAChC,EAAE,iBAAiB,+BAuHnB"}
@@ -69,7 +69,9 @@ function FloatingMenu({
69
69
  top: menuGeometry?.top ?? -9999,
70
70
  left: menuGeometry?.left ?? -9999,
71
71
  maxHeight: menuGeometry?.maxHeight,
72
- overflowY: menuGeometry?.maxHeight ? "auto" : void 0
72
+ overflowY: menuGeometry?.maxHeight ? "auto" : void 0,
73
+ // Prevent wheel/touch overscroll from chaining to the page (avoids jitter while scrolling items).
74
+ overscrollBehavior: menuGeometry?.maxHeight ? "contain" : void 0
73
75
  },
74
76
  children: items.map((entry) => {
75
77
  if (entry.kind === "separator") {
@@ -1 +1 @@
1
- {"version":3,"file":"FloatingMenu.js","sources":["../../src/menu/FloatingMenu.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { mergeOverlayRefs, useOverlayPresence, withOverlayOpen } from '../presence/useOverlayPresence';\r\nimport { DropdownMenuChevron } from '../icons/DropdownMenuChevron';\r\nimport { useFloatingMenuGeometry } from './useFloatingMenuGeometry';\r\n\r\nexport type FloatingMenuItem = {\r\n kind: 'item';\r\n id: string;\r\n label: ReactNode;\r\n disabled?: boolean;\r\n title?: string;\r\n onSelect: () => void;\r\n};\r\n\r\nexport type FloatingMenuSeparator = {\r\n kind: 'separator';\r\n id: string;\r\n};\r\n\r\nexport type FloatingMenuEntry = FloatingMenuItem | FloatingMenuSeparator;\r\n\r\nexport type FloatingMenuTriggerVariant = 'labeled' | 'icon';\r\n\r\nexport type FloatingMenuProps = {\r\n menuAriaLabel: string;\r\n items: FloatingMenuEntry[];\r\n triggerLabel?: ReactNode;\r\n triggerIcon?: ReactNode;\r\n triggerVariant?: FloatingMenuTriggerVariant;\r\n triggerClassName?: string;\r\n triggerAriaLabel?: string;\r\n scrollRootSelector?: string;\r\n wrapClassName?: string;\r\n};\r\n\r\nfunction IconDropdownTriggerContent({ icon }: { icon: ReactNode }) {\r\n return (\r\n <>\r\n {icon}\r\n <span className=\"dropdown-menu-trigger-caret\" aria-hidden=\"true\">\r\n <DropdownMenuChevron />\r\n </span>\r\n </>\r\n );\r\n}\r\n\r\nexport function FloatingMenu({\r\n menuAriaLabel,\r\n items,\r\n triggerLabel,\r\n triggerIcon,\r\n triggerVariant = 'labeled',\r\n triggerClassName,\r\n triggerAriaLabel,\r\n scrollRootSelector,\r\n wrapClassName = 'dropdown-menu',\r\n}: FloatingMenuProps) {\r\n const wrapRef = useRef<HTMLDivElement | null>(null);\r\n const menuId = useId();\r\n const [menuOpen, setMenuOpen] = useState(false);\r\n const { mounted: menuMounted, isOpen: menuShown, overlayRef: menuOverlayRef } =\r\n useOverlayPresence<HTMLUListElement>(menuOpen);\r\n const { menuPanelRef, triggerRef, menuGeometry } = useFloatingMenuGeometry({\r\n menuOpen,\r\n menuMounted,\r\n scrollRootSelector,\r\n });\r\n\r\n useEffect(() => {\r\n if (!menuOpen) {\r\n return;\r\n }\r\n const onDocumentMouseDown = (event: MouseEvent) => {\r\n const target = event.target as Node;\r\n if (wrapRef.current?.contains(target) || menuPanelRef.current?.contains(target)) {\r\n return;\r\n }\r\n setMenuOpen(false);\r\n };\r\n document.addEventListener('mousedown', onDocumentMouseDown);\r\n return () => document.removeEventListener('mousedown', onDocumentMouseDown);\r\n }, [menuOpen, menuPanelRef]);\r\n\r\n useEffect(() => {\r\n if (!menuOpen) {\r\n return;\r\n }\r\n const onKeyDown = (event: KeyboardEvent) => {\r\n if (event.key === 'Escape') {\r\n event.stopPropagation();\r\n setMenuOpen(false);\r\n }\r\n };\r\n window.addEventListener('keydown', onKeyDown, true);\r\n return () => window.removeEventListener('keydown', onKeyDown, true);\r\n }, [menuOpen]);\r\n\r\n const menuPanel = menuMounted ? (\r\n <ul\r\n ref={mergeOverlayRefs(menuOverlayRef, menuPanelRef)}\r\n id={menuId}\r\n className={withOverlayOpen('dropdown-menu-panel', menuShown && menuGeometry !== null)}\r\n role=\"menu\"\r\n aria-label={menuAriaLabel}\r\n style={{\r\n top: menuGeometry?.top ?? -9999,\r\n left: menuGeometry?.left ?? -9999,\r\n maxHeight: menuGeometry?.maxHeight,\r\n overflowY: menuGeometry?.maxHeight ? 'auto' : undefined,\r\n }}\r\n >\r\n {items.map((entry) => {\r\n if (entry.kind === 'separator') {\r\n return (\r\n <li key={entry.id} className=\"dropdown-menu-separator\" role=\"separator\" aria-orientation=\"horizontal\" />\r\n );\r\n }\r\n\r\n return (\r\n <li key={entry.id} role=\"none\">\r\n <button\r\n type=\"button\"\r\n className=\"dropdown-menu-item\"\r\n role=\"menuitem\"\r\n disabled={entry.disabled}\r\n title={entry.title}\r\n onClick={() => {\r\n if (entry.disabled) {\r\n return;\r\n }\r\n entry.onSelect();\r\n setMenuOpen(false);\r\n }}\r\n >\r\n {entry.label}\r\n </button>\r\n </li>\r\n );\r\n })}\r\n </ul>\r\n ) : null;\r\n\r\n const resolvedTriggerClassName =\r\n triggerClassName ??\r\n (triggerVariant === 'icon'\r\n ? 'icon-button dropdown-menu-trigger dropdown-menu-trigger--icon'\r\n : 'secondary-button dropdown-menu-trigger');\r\n\r\n return (\r\n <div className={wrapClassName} ref={wrapRef}>\r\n <button\r\n ref={triggerRef}\r\n type=\"button\"\r\n className={resolvedTriggerClassName}\r\n aria-label={triggerAriaLabel ?? (triggerVariant === 'icon' ? menuAriaLabel : undefined)}\r\n aria-expanded={menuOpen}\r\n aria-haspopup=\"menu\"\r\n aria-controls={menuId}\r\n onClick={() => setMenuOpen((open) => !open)}\r\n >\r\n {triggerVariant === 'icon' ? (\r\n <IconDropdownTriggerContent icon={triggerIcon} />\r\n ) : (\r\n <>\r\n {triggerLabel}\r\n {' '}\r\n <DropdownMenuChevron />\r\n </>\r\n )}\r\n </button>\r\n {menuPanel ? createPortal(menuPanel, document.body) : null}\r\n </div>\r\n );\r\n}\r\n"],"names":[],"mappings":";;;;;;AAoCA,SAAS,2BAA2B,EAAE,QAA6B;AACjE,SACE,qBAAA,UAAA,EACG,UAAA;AAAA,IAAA;AAAA,IACD,oBAAC,UAAK,WAAU,+BAA8B,eAAY,QACxD,UAAA,oBAAC,uBAAoB,EAAA,CACvB;AAAA,EAAA,GACF;AAEJ;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AAAA,EACA,gBAAgB;AAClB,GAAsB;AACpB,QAAM,UAAU,OAA8B,IAAI;AAClD,QAAM,SAAS,MAAA;AACf,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,EAAE,SAAS,aAAa,QAAQ,WAAW,YAAY,eAAA,IAC3D,mBAAqC,QAAQ;AAC/C,QAAM,EAAE,cAAc,YAAY,aAAA,IAAiB,wBAAwB;AAAA,IACzE;AAAA,IACA;AAAA,IACA;AAAA,EAAA,CACD;AAED,YAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AACA,UAAM,sBAAsB,CAAC,UAAsB;AACjD,YAAM,SAAS,MAAM;AACrB,UAAI,QAAQ,SAAS,SAAS,MAAM,KAAK,aAAa,SAAS,SAAS,MAAM,GAAG;AAC/E;AAAA,MACF;AACA,kBAAY,KAAK;AAAA,IACnB;AACA,aAAS,iBAAiB,aAAa,mBAAmB;AAC1D,WAAO,MAAM,SAAS,oBAAoB,aAAa,mBAAmB;AAAA,EAC5E,GAAG,CAAC,UAAU,YAAY,CAAC;AAE3B,YAAU,MAAM;AACd,QAAI,CAAC,UAAU;AACb;AAAA,IACF;AACA,UAAM,YAAY,CAAC,UAAyB;AAC1C,UAAI,MAAM,QAAQ,UAAU;AAC1B,cAAM,gBAAA;AACN,oBAAY,KAAK;AAAA,MACnB;AAAA,IACF;AACA,WAAO,iBAAiB,WAAW,WAAW,IAAI;AAClD,WAAO,MAAM,OAAO,oBAAoB,WAAW,WAAW,IAAI;AAAA,EACpE,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,YAAY,cAChB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAK,iBAAiB,gBAAgB,YAAY;AAAA,MAClD,IAAI;AAAA,MACJ,WAAW,gBAAgB,uBAAuB,aAAa,iBAAiB,IAAI;AAAA,MACpF,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,OAAO;AAAA,QACL,KAAK,cAAc,OAAO;AAAA,QAC1B,MAAM,cAAc,QAAQ;AAAA,QAC5B,WAAW,cAAc;AAAA,QACzB,WAAW,cAAc,YAAY,SAAS;AAAA,MAAA;AAAA,MAG/C,UAAA,MAAM,IAAI,CAAC,UAAU;AACpB,YAAI,MAAM,SAAS,aAAa;AAC9B,iBACE,oBAAC,QAAkB,WAAU,2BAA0B,MAAK,aAAY,oBAAiB,gBAAhF,MAAM,EAAuF;AAAA,QAE1G;AAEA,eACE,oBAAC,MAAA,EAAkB,MAAK,QACtB,UAAA;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,WAAU;AAAA,YACV,MAAK;AAAA,YACL,UAAU,MAAM;AAAA,YAChB,OAAO,MAAM;AAAA,YACb,SAAS,MAAM;AACb,kBAAI,MAAM,UAAU;AAClB;AAAA,cACF;AACA,oBAAM,SAAA;AACN,0BAAY,KAAK;AAAA,YACnB;AAAA,YAEC,UAAA,MAAM;AAAA,UAAA;AAAA,QAAA,EACT,GAhBO,MAAM,EAiBf;AAAA,MAEJ,CAAC;AAAA,IAAA;AAAA,EAAA,IAED;AAEJ,QAAM,2BACJ,qBACC,mBAAmB,SAChB,kEACA;AAEN,SACE,qBAAC,OAAA,EAAI,WAAW,eAAe,KAAK,SAClC,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,KAAK;AAAA,QACL,MAAK;AAAA,QACL,WAAW;AAAA,QACX,cAAY,qBAAqB,mBAAmB,SAAS,gBAAgB;AAAA,QAC7E,iBAAe;AAAA,QACf,iBAAc;AAAA,QACd,iBAAe;AAAA,QACf,SAAS,MAAM,YAAY,CAAC,SAAS,CAAC,IAAI;AAAA,QAEzC,6BAAmB,SAClB,oBAAC,8BAA2B,MAAM,YAAA,CAAa,IAE/C,qBAAA,UAAA,EACG,UAAA;AAAA,UAAA;AAAA,UACA;AAAA,8BACA,qBAAA,CAAA,CAAoB;AAAA,QAAA,EAAA,CACvB;AAAA,MAAA;AAAA,IAAA;AAAA,IAGH,YAAY,aAAa,WAAW,SAAS,IAAI,IAAI;AAAA,EAAA,GACxD;AAEJ;"}
1
+ {"version":3,"file":"FloatingMenu.js","sources":["../../src/menu/FloatingMenu.tsx"],"sourcesContent":["import { useEffect, useId, useRef, useState, type ReactNode } from 'react';\r\nimport { createPortal } from 'react-dom';\r\nimport { mergeOverlayRefs, useOverlayPresence, withOverlayOpen } from '../presence/useOverlayPresence';\r\nimport { DropdownMenuChevron } from '../icons/DropdownMenuChevron';\r\nimport { useFloatingMenuGeometry } from './useFloatingMenuGeometry';\r\n\r\nexport type FloatingMenuItem = {\r\n kind: 'item';\r\n id: string;\r\n label: ReactNode;\r\n disabled?: boolean;\r\n title?: string;\r\n onSelect: () => void;\r\n};\r\n\r\nexport type FloatingMenuSeparator = {\r\n kind: 'separator';\r\n id: string;\r\n};\r\n\r\nexport type FloatingMenuEntry = FloatingMenuItem | FloatingMenuSeparator;\r\n\r\nexport type FloatingMenuTriggerVariant = 'labeled' | 'icon';\r\n\r\nexport type FloatingMenuProps = {\r\n menuAriaLabel: string;\r\n items: FloatingMenuEntry[];\r\n triggerLabel?: ReactNode;\r\n triggerIcon?: ReactNode;\r\n triggerVariant?: FloatingMenuTriggerVariant;\r\n triggerClassName?: string;\r\n triggerAriaLabel?: string;\r\n scrollRootSelector?: string;\r\n wrapClassName?: string;\r\n};\r\n\r\nfunction IconDropdownTriggerContent({ icon }: { icon: ReactNode }) {\r\n return (\r\n <>\r\n {icon}\r\n <span className=\"dropdown-menu-trigger-caret\" aria-hidden=\"true\">\r\n <DropdownMenuChevron />\r\n </span>\r\n </>\r\n );\r\n}\r\n\r\nexport function FloatingMenu({\r\n menuAriaLabel,\r\n items,\r\n triggerLabel,\r\n triggerIcon,\r\n triggerVariant = 'labeled',\r\n triggerClassName,\r\n triggerAriaLabel,\r\n scrollRootSelector,\r\n wrapClassName = 'dropdown-menu',\r\n}: FloatingMenuProps) {\r\n const wrapRef = useRef<HTMLDivElement | null>(null);\r\n const menuId = useId();\r\n const [menuOpen, setMenuOpen] = useState(false);\r\n const { mounted: menuMounted, isOpen: menuShown, overlayRef: menuOverlayRef } =\r\n useOverlayPresence<HTMLUListElement>(menuOpen);\r\n const { menuPanelRef, triggerRef, menuGeometry } = useFloatingMenuGeometry({\r\n menuOpen,\r\n menuMounted,\r\n scrollRootSelector,\r\n });\r\n\r\n useEffect(() => {\r\n if (!menuOpen) {\r\n return;\r\n }\r\n const onDocumentMouseDown = (event: MouseEvent) => {\r\n const target = event.target as Node;\r\n if (wrapRef.current?.contains(target) || menuPanelRef.current?.contains(target)) {\r\n return;\r\n }\r\n setMenuOpen(false);\r\n };\r\n document.addEventListener('mousedown', onDocumentMouseDown);\r\n return () => document.removeEventListener('mousedown', onDocumentMouseDown);\r\n }, [menuOpen, menuPanelRef]);\r\n\r\n useEffect(() => {\r\n if (!menuOpen) {\r\n return;\r\n }\r\n const onKeyDown = (event: KeyboardEvent) => {\r\n if (event.key === 'Escape') {\r\n event.stopPropagation();\r\n setMenuOpen(false);\r\n }\r\n };\r\n window.addEventListener('keydown', onKeyDown, true);\r\n return () => window.removeEventListener('keydown', onKeyDown, true);\r\n }, [menuOpen]);\r\n\r\n const menuPanel = menuMounted ? (\r\n <ul\r\n ref={mergeOverlayRefs(menuOverlayRef, menuPanelRef)}\r\n id={menuId}\r\n className={withOverlayOpen('dropdown-menu-panel', menuShown && menuGeometry !== null)}\r\n role=\"menu\"\r\n aria-label={menuAriaLabel}\r\n style={{\r\n top: menuGeometry?.top ?? -9999,\r\n left: menuGeometry?.left ?? -9999,\r\n maxHeight: menuGeometry?.maxHeight,\r\n overflowY: menuGeometry?.maxHeight ? 'auto' : undefined,\r\n // Prevent wheel/touch overscroll from chaining to the page (avoids jitter while scrolling items).\r\n overscrollBehavior: menuGeometry?.maxHeight ? 'contain' : undefined,\r\n }}\r\n >\r\n {items.map((entry) => {\r\n if (entry.kind === 'separator') {\r\n return (\r\n <li key={entry.id} className=\"dropdown-menu-separator\" role=\"separator\" aria-orientation=\"horizontal\" />\r\n );\r\n }\r\n\r\n return (\r\n <li key={entry.id} role=\"none\">\r\n <button\r\n type=\"button\"\r\n className=\"dropdown-menu-item\"\r\n role=\"menuitem\"\r\n disabled={entry.disabled}\r\n title={entry.title}\r\n onClick={() => {\r\n if (entry.disabled) {\r\n return;\r\n }\r\n entry.onSelect();\r\n setMenuOpen(false);\r\n }}\r\n >\r\n {entry.label}\r\n </button>\r\n </li>\r\n );\r\n })}\r\n </ul>\r\n ) : null;\r\n\r\n const resolvedTriggerClassName =\r\n triggerClassName ??\r\n (triggerVariant === 'icon'\r\n ? 'icon-button dropdown-menu-trigger dropdown-menu-trigger--icon'\r\n : 'secondary-button dropdown-menu-trigger');\r\n\r\n return (\r\n <div className={wrapClassName} ref={wrapRef}>\r\n <button\r\n ref={triggerRef}\r\n type=\"button\"\r\n className={resolvedTriggerClassName}\r\n aria-label={triggerAriaLabel ?? (triggerVariant === 'icon' ? menuAriaLabel : undefined)}\r\n aria-expanded={menuOpen}\r\n aria-haspopup=\"menu\"\r\n aria-controls={menuId}\r\n onClick={() => setMenuOpen((open) => !open)}\r\n >\r\n {triggerVariant === 'icon' ? (\r\n <IconDropdownTriggerContent icon={triggerIcon} />\r\n ) : (\r\n <>\r\n {triggerLabel}\r\n {' '}\r\n <DropdownMenuChevron />\r\n </>\r\n )}\r\n </button>\r\n {menuPanel ? createPortal(menuPanel, document.body) : null}\r\n </div>\r\n );\r\n}\r\n"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"computeFloatingMenuGeometry.d.ts","sourceRoot":"","sources":["../../src/menu/computeFloatingMenuGeometry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,kCAAuC,GAC/C,oBAAoB,CA2BtB"}
1
+ {"version":3,"file":"computeFloatingMenuGeometry.d.ts","sourceRoot":"","sources":["../../src/menu/computeFloatingMenuGeometry.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AA2BF,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,WAAW,EACpB,IAAI,EAAE,WAAW,EACjB,OAAO,GAAE,kCAAuC,GAC/C,oBAAoB,CA2CtB"}
@@ -1,3 +1,21 @@
1
+ function readUnconstrainedMenuHeight(menu) {
2
+ const previousMaxHeight = menu.style.maxHeight;
3
+ const previousOverflowY = menu.style.overflowY;
4
+ menu.style.maxHeight = "none";
5
+ menu.style.overflowY = "visible";
6
+ const height = menu.offsetHeight;
7
+ menu.style.maxHeight = previousMaxHeight;
8
+ menu.style.overflowY = previousOverflowY;
9
+ return height;
10
+ }
11
+ function resolveMaxHeight(available, minScrollHeight, vh, pad) {
12
+ const viewportCap = Math.max(0, vh - 2 * pad);
13
+ const usable = Math.max(0, Math.min(available, viewportCap));
14
+ if (usable > 0) {
15
+ return usable;
16
+ }
17
+ return Math.min(minScrollHeight, viewportCap);
18
+ }
1
19
  function computeFloatingMenuGeometry(trigger, menu, options = {}) {
2
20
  const pad = options.pad ?? 8;
3
21
  const gap = options.gap ?? 4;
@@ -6,23 +24,34 @@ function computeFloatingMenuGeometry(trigger, menu, options = {}) {
6
24
  const vw = window.innerWidth;
7
25
  const vh = window.innerHeight;
8
26
  const menuWidth = menu.offsetWidth;
9
- const menuHeight = menu.offsetHeight;
27
+ const contentHeight = readUnconstrainedMenuHeight(menu);
10
28
  let left = rect.right - menuWidth;
11
29
  if (left < pad) {
12
30
  left = rect.left;
13
31
  }
14
32
  left = Math.max(pad, Math.min(left, vw - menuWidth - pad));
15
- let top = rect.bottom + gap;
16
- let maxHeight;
17
- if (top + menuHeight > vh - pad) {
18
- const topIfAbove = rect.top - gap - menuHeight;
19
- if (topIfAbove >= pad) {
20
- top = topIfAbove;
21
- } else {
22
- maxHeight = Math.max(minScrollHeight, vh - pad - top);
23
- }
33
+ const topBelow = rect.bottom + gap;
34
+ const spaceBelow = vh - pad - topBelow;
35
+ const spaceAbove = rect.top - gap - pad;
36
+ if (contentHeight <= spaceBelow) {
37
+ return { top: topBelow, left };
38
+ }
39
+ if (contentHeight <= spaceAbove) {
40
+ return { top: rect.top - gap - contentHeight, left };
41
+ }
42
+ if (spaceBelow >= spaceAbove) {
43
+ return {
44
+ top: topBelow,
45
+ left,
46
+ maxHeight: resolveMaxHeight(spaceBelow, minScrollHeight, vh, pad)
47
+ };
24
48
  }
25
- return { top, left, maxHeight };
49
+ const maxHeight = resolveMaxHeight(spaceAbove, minScrollHeight, vh, pad);
50
+ return {
51
+ top: Math.max(pad, rect.top - gap - maxHeight),
52
+ left,
53
+ maxHeight
54
+ };
26
55
  }
27
56
  export {
28
57
  computeFloatingMenuGeometry
@@ -1 +1 @@
1
- {"version":3,"file":"computeFloatingMenuGeometry.js","sources":["../../src/menu/computeFloatingMenuGeometry.ts"],"sourcesContent":["export type FloatingMenuGeometry = {\r\n top: number;\r\n left: number;\r\n maxHeight?: number;\r\n};\r\n\r\nexport type ComputeFloatingMenuGeometryOptions = {\r\n pad?: number;\r\n gap?: number;\r\n minScrollHeight?: number;\r\n};\r\n\r\nexport function computeFloatingMenuGeometry(\r\n trigger: HTMLElement,\r\n menu: HTMLElement,\r\n options: ComputeFloatingMenuGeometryOptions = {},\r\n): FloatingMenuGeometry {\r\n const pad = options.pad ?? 8;\r\n const gap = options.gap ?? 4;\r\n const minScrollHeight = options.minScrollHeight ?? 120;\r\n const rect = trigger.getBoundingClientRect();\r\n const vw = window.innerWidth;\r\n const vh = window.innerHeight;\r\n const menuWidth = menu.offsetWidth;\r\n const menuHeight = menu.offsetHeight;\r\n\r\n let left = rect.right - menuWidth;\r\n if (left < pad) {\r\n left = rect.left;\r\n }\r\n left = Math.max(pad, Math.min(left, vw - menuWidth - pad));\r\n\r\n let top = rect.bottom + gap;\r\n let maxHeight: number | undefined;\r\n if (top + menuHeight > vh - pad) {\r\n const topIfAbove = rect.top - gap - menuHeight;\r\n if (topIfAbove >= pad) {\r\n top = topIfAbove;\r\n } else {\r\n maxHeight = Math.max(minScrollHeight, vh - pad - top);\r\n }\r\n }\r\n return { top, left, maxHeight };\r\n}\r\n"],"names":[],"mappings":"AAYO,SAAS,4BACd,SACA,MACA,UAA8C,CAAA,GACxB;AACtB,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,OAAO,QAAQ,sBAAA;AACrB,QAAM,KAAK,OAAO;AAClB,QAAM,KAAK,OAAO;AAClB,QAAM,YAAY,KAAK;AACvB,QAAM,aAAa,KAAK;AAExB,MAAI,OAAO,KAAK,QAAQ;AACxB,MAAI,OAAO,KAAK;AACd,WAAO,KAAK;AAAA,EACd;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,MAAM,KAAK,YAAY,GAAG,CAAC;AAEzD,MAAI,MAAM,KAAK,SAAS;AACxB,MAAI;AACJ,MAAI,MAAM,aAAa,KAAK,KAAK;AAC/B,UAAM,aAAa,KAAK,MAAM,MAAM;AACpC,QAAI,cAAc,KAAK;AACrB,YAAM;AAAA,IACR,OAAO;AACL,kBAAY,KAAK,IAAI,iBAAiB,KAAK,MAAM,GAAG;AAAA,IACtD;AAAA,EACF;AACA,SAAO,EAAE,KAAK,MAAM,UAAA;AACtB;"}
1
+ {"version":3,"file":"computeFloatingMenuGeometry.js","sources":["../../src/menu/computeFloatingMenuGeometry.ts"],"sourcesContent":["export type FloatingMenuGeometry = {\r\n top: number;\r\n left: number;\r\n maxHeight?: number;\r\n};\r\n\r\nexport type ComputeFloatingMenuGeometryOptions = {\r\n pad?: number;\r\n gap?: number;\r\n minScrollHeight?: number;\r\n};\r\n\r\n/** Read full content height even if the panel is currently height-constrained. */\r\nfunction readUnconstrainedMenuHeight(menu: HTMLElement): number {\r\n const previousMaxHeight = menu.style.maxHeight;\r\n const previousOverflowY = menu.style.overflowY;\r\n menu.style.maxHeight = 'none';\r\n menu.style.overflowY = 'visible';\r\n const height = menu.offsetHeight;\r\n menu.style.maxHeight = previousMaxHeight;\r\n menu.style.overflowY = previousOverflowY;\r\n return height;\r\n}\r\n\r\n/**\r\n * Cap scrollable panel height to the chosen side's available space (never past the viewport pad).\r\n * `minScrollHeight` is only used when available space collapses to ~0.\r\n */\r\nfunction resolveMaxHeight(available: number, minScrollHeight: number, vh: number, pad: number): number {\r\n const viewportCap = Math.max(0, vh - 2 * pad);\r\n const usable = Math.max(0, Math.min(available, viewportCap));\r\n if (usable > 0) {\r\n return usable;\r\n }\r\n return Math.min(minScrollHeight, viewportCap);\r\n}\r\n\r\nexport function computeFloatingMenuGeometry(\r\n trigger: HTMLElement,\r\n menu: HTMLElement,\r\n options: ComputeFloatingMenuGeometryOptions = {},\r\n): FloatingMenuGeometry {\r\n const pad = options.pad ?? 8;\r\n const gap = options.gap ?? 4;\r\n const minScrollHeight = options.minScrollHeight ?? 120;\r\n const rect = trigger.getBoundingClientRect();\r\n const vw = window.innerWidth;\r\n const vh = window.innerHeight;\r\n const menuWidth = menu.offsetWidth;\r\n const contentHeight = readUnconstrainedMenuHeight(menu);\r\n\r\n let left = rect.right - menuWidth;\r\n if (left < pad) {\r\n left = rect.left;\r\n }\r\n left = Math.max(pad, Math.min(left, vw - menuWidth - pad));\r\n\r\n const topBelow = rect.bottom + gap;\r\n const spaceBelow = vh - pad - topBelow;\r\n const spaceAbove = rect.top - gap - pad;\r\n\r\n if (contentHeight <= spaceBelow) {\r\n return { top: topBelow, left };\r\n }\r\n\r\n if (contentHeight <= spaceAbove) {\r\n return { top: rect.top - gap - contentHeight, left };\r\n }\r\n\r\n // Content taller than both sides — open toward the larger side and scroll inside.\r\n if (spaceBelow >= spaceAbove) {\r\n return {\r\n top: topBelow,\r\n left,\r\n maxHeight: resolveMaxHeight(spaceBelow, minScrollHeight, vh, pad),\r\n };\r\n }\r\n\r\n const maxHeight = resolveMaxHeight(spaceAbove, minScrollHeight, vh, pad);\r\n return {\r\n top: Math.max(pad, rect.top - gap - maxHeight),\r\n left,\r\n maxHeight,\r\n };\r\n}\r\n"],"names":[],"mappings":"AAaA,SAAS,4BAA4B,MAA2B;AAC9D,QAAM,oBAAoB,KAAK,MAAM;AACrC,QAAM,oBAAoB,KAAK,MAAM;AACrC,OAAK,MAAM,YAAY;AACvB,OAAK,MAAM,YAAY;AACvB,QAAM,SAAS,KAAK;AACpB,OAAK,MAAM,YAAY;AACvB,OAAK,MAAM,YAAY;AACvB,SAAO;AACT;AAMA,SAAS,iBAAiB,WAAmB,iBAAyB,IAAY,KAAqB;AACrG,QAAM,cAAc,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG;AAC5C,QAAM,SAAS,KAAK,IAAI,GAAG,KAAK,IAAI,WAAW,WAAW,CAAC;AAC3D,MAAI,SAAS,GAAG;AACd,WAAO;AAAA,EACT;AACA,SAAO,KAAK,IAAI,iBAAiB,WAAW;AAC9C;AAEO,SAAS,4BACd,SACA,MACA,UAA8C,CAAA,GACxB;AACtB,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,kBAAkB,QAAQ,mBAAmB;AACnD,QAAM,OAAO,QAAQ,sBAAA;AACrB,QAAM,KAAK,OAAO;AAClB,QAAM,KAAK,OAAO;AAClB,QAAM,YAAY,KAAK;AACvB,QAAM,gBAAgB,4BAA4B,IAAI;AAEtD,MAAI,OAAO,KAAK,QAAQ;AACxB,MAAI,OAAO,KAAK;AACd,WAAO,KAAK;AAAA,EACd;AACA,SAAO,KAAK,IAAI,KAAK,KAAK,IAAI,MAAM,KAAK,YAAY,GAAG,CAAC;AAEzD,QAAM,WAAW,KAAK,SAAS;AAC/B,QAAM,aAAa,KAAK,MAAM;AAC9B,QAAM,aAAa,KAAK,MAAM,MAAM;AAEpC,MAAI,iBAAiB,YAAY;AAC/B,WAAO,EAAE,KAAK,UAAU,KAAA;AAAA,EAC1B;AAEA,MAAI,iBAAiB,YAAY;AAC/B,WAAO,EAAE,KAAK,KAAK,MAAM,MAAM,eAAe,KAAA;AAAA,EAChD;AAGA,MAAI,cAAc,YAAY;AAC5B,WAAO;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA,WAAW,iBAAiB,YAAY,iBAAiB,IAAI,GAAG;AAAA,IAAA;AAAA,EAEpE;AAEA,QAAM,YAAY,iBAAiB,YAAY,iBAAiB,IAAI,GAAG;AACvE,SAAO;AAAA,IACL,KAAK,KAAK,IAAI,KAAK,KAAK,MAAM,MAAM,SAAS;AAAA,IAC7C;AAAA,IACA;AAAA,EAAA;AAEJ;"}
@@ -1 +1 @@
1
- {"version":3,"file":"useFloatingMenuGeometry.d.ts","sourceRoot":"","sources":["../../src/menu/useFloatingMenuGeometry.ts"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAEvG,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,WAAW,EACX,kBAAkB,GACnB,EAAE,8BAA8B;;;;EA8ChC"}
1
+ {"version":3,"file":"useFloatingMenuGeometry.d.ts","sourceRoot":"","sources":["../../src/menu/useFloatingMenuGeometry.ts"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAEvG,MAAM,MAAM,8BAA8B,GAAG;IAC3C,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,wCAAwC;IACxC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAEF,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,WAAW,EACX,kBAAkB,GACnB,EAAE,8BAA8B;;;;EAwDhC"}
@@ -21,6 +21,14 @@ function useFloatingMenuGeometry({
21
21
  }
22
22
  setMenuGeometry(computeFloatingMenuGeometry(trigger, menu));
23
23
  };
24
+ const onScroll = (event) => {
25
+ const menu = menuPanelRef.current;
26
+ const target = event.target;
27
+ if (menu && target instanceof Node && menu.contains(target)) {
28
+ return;
29
+ }
30
+ update();
31
+ };
24
32
  update();
25
33
  const scrollRoots = [];
26
34
  if (scrollRootSelector) {
@@ -30,15 +38,15 @@ function useFloatingMenuGeometry({
30
38
  }
31
39
  }
32
40
  window.addEventListener("resize", update);
33
- window.addEventListener("scroll", update, true);
41
+ window.addEventListener("scroll", onScroll, true);
34
42
  for (const root of scrollRoots) {
35
- root.addEventListener("scroll", update, { passive: true });
43
+ root.addEventListener("scroll", onScroll, { passive: true });
36
44
  }
37
45
  return () => {
38
46
  window.removeEventListener("resize", update);
39
- window.removeEventListener("scroll", update, true);
47
+ window.removeEventListener("scroll", onScroll, true);
40
48
  for (const root of scrollRoots) {
41
- root.removeEventListener("scroll", update);
49
+ root.removeEventListener("scroll", onScroll);
42
50
  }
43
51
  };
44
52
  }, [menuOpen, menuMounted, scrollRootSelector]);
@@ -1 +1 @@
1
- {"version":3,"file":"useFloatingMenuGeometry.js","sources":["../../src/menu/useFloatingMenuGeometry.ts"],"sourcesContent":["import { useLayoutEffect, useRef, useState } from 'react';\r\nimport { computeFloatingMenuGeometry, type FloatingMenuGeometry } from './computeFloatingMenuGeometry';\r\n\r\nexport type UseFloatingMenuGeometryOptions = {\r\n menuOpen: boolean;\r\n menuMounted: boolean;\r\n /** 额外监听 scroll 的容器选择器(如 `.app-main`) */\r\n scrollRootSelector?: string;\r\n};\r\n\r\nexport function useFloatingMenuGeometry({\r\n menuOpen,\r\n menuMounted,\r\n scrollRootSelector,\r\n}: UseFloatingMenuGeometryOptions) {\r\n const menuPanelRef = useRef<HTMLUListElement | null>(null);\r\n const triggerRef = useRef<HTMLButtonElement | null>(null);\r\n const [menuGeometry, setMenuGeometry] = useState<FloatingMenuGeometry | null>(null);\r\n\r\n useLayoutEffect(() => {\r\n if (!menuOpen || !menuMounted) {\r\n setMenuGeometry(null);\r\n return;\r\n }\r\n\r\n const update = () => {\r\n const trigger = triggerRef.current;\r\n const menu = menuPanelRef.current;\r\n if (!trigger || !menu) {\r\n return;\r\n }\r\n setMenuGeometry(computeFloatingMenuGeometry(trigger, menu));\r\n };\r\n\r\n update();\r\n\r\n const scrollRoots: HTMLElement[] = [];\r\n if (scrollRootSelector) {\r\n const root = document.querySelector<HTMLElement>(scrollRootSelector);\r\n if (root) {\r\n scrollRoots.push(root);\r\n }\r\n }\r\n\r\n window.addEventListener('resize', update);\r\n window.addEventListener('scroll', update, true);\r\n for (const root of scrollRoots) {\r\n root.addEventListener('scroll', update, { passive: true });\r\n }\r\n\r\n return () => {\r\n window.removeEventListener('resize', update);\r\n window.removeEventListener('scroll', update, true);\r\n for (const root of scrollRoots) {\r\n root.removeEventListener('scroll', update);\r\n }\r\n };\r\n }, [menuOpen, menuMounted, scrollRootSelector]);\r\n\r\n return { menuPanelRef, triggerRef, menuGeometry };\r\n}\r\n"],"names":[],"mappings":";;AAUO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAAmC;AACjC,QAAM,eAAe,OAAgC,IAAI;AACzD,QAAM,aAAa,OAAiC,IAAI;AACxD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAsC,IAAI;AAElF,kBAAgB,MAAM;AACpB,QAAI,CAAC,YAAY,CAAC,aAAa;AAC7B,sBAAgB,IAAI;AACpB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM;AACnB,YAAM,UAAU,WAAW;AAC3B,YAAM,OAAO,aAAa;AAC1B,UAAI,CAAC,WAAW,CAAC,MAAM;AACrB;AAAA,MACF;AACA,sBAAgB,4BAA4B,SAAS,IAAI,CAAC;AAAA,IAC5D;AAEA,WAAA;AAEA,UAAM,cAA6B,CAAA;AACnC,QAAI,oBAAoB;AACtB,YAAM,OAAO,SAAS,cAA2B,kBAAkB;AACnE,UAAI,MAAM;AACR,oBAAY,KAAK,IAAI;AAAA,MACvB;AAAA,IACF;AAEA,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,iBAAiB,UAAU,QAAQ,IAAI;AAC9C,eAAW,QAAQ,aAAa;AAC9B,WAAK,iBAAiB,UAAU,QAAQ,EAAE,SAAS,MAAM;AAAA,IAC3D;AAEA,WAAO,MAAM;AACX,aAAO,oBAAoB,UAAU,MAAM;AAC3C,aAAO,oBAAoB,UAAU,QAAQ,IAAI;AACjD,iBAAW,QAAQ,aAAa;AAC9B,aAAK,oBAAoB,UAAU,MAAM;AAAA,MAC3C;AAAA,IACF;AAAA,EACF,GAAG,CAAC,UAAU,aAAa,kBAAkB,CAAC;AAE9C,SAAO,EAAE,cAAc,YAAY,aAAA;AACrC;"}
1
+ {"version":3,"file":"useFloatingMenuGeometry.js","sources":["../../src/menu/useFloatingMenuGeometry.ts"],"sourcesContent":["import { useLayoutEffect, useRef, useState } from 'react';\r\nimport { computeFloatingMenuGeometry, type FloatingMenuGeometry } from './computeFloatingMenuGeometry';\r\n\r\nexport type UseFloatingMenuGeometryOptions = {\r\n menuOpen: boolean;\r\n menuMounted: boolean;\r\n /** 额外监听 scroll 的容器选择器(如 `.app-main`) */\r\n scrollRootSelector?: string;\r\n};\r\n\r\nexport function useFloatingMenuGeometry({\r\n menuOpen,\r\n menuMounted,\r\n scrollRootSelector,\r\n}: UseFloatingMenuGeometryOptions) {\r\n const menuPanelRef = useRef<HTMLUListElement | null>(null);\r\n const triggerRef = useRef<HTMLButtonElement | null>(null);\r\n const [menuGeometry, setMenuGeometry] = useState<FloatingMenuGeometry | null>(null);\r\n\r\n useLayoutEffect(() => {\r\n if (!menuOpen || !menuMounted) {\r\n setMenuGeometry(null);\r\n return;\r\n }\r\n\r\n const update = () => {\r\n const trigger = triggerRef.current;\r\n const menu = menuPanelRef.current;\r\n if (!trigger || !menu) {\r\n return;\r\n }\r\n setMenuGeometry(computeFloatingMenuGeometry(trigger, menu));\r\n };\r\n\r\n const onScroll = (event: Event) => {\r\n const menu = menuPanelRef.current;\r\n const target = event.target;\r\n // Ignore the panel's own scrolling so geometry does not thrash / reset maxHeight.\r\n if (menu && target instanceof Node && menu.contains(target)) {\r\n return;\r\n }\r\n update();\r\n };\r\n\r\n update();\r\n\r\n const scrollRoots: HTMLElement[] = [];\r\n if (scrollRootSelector) {\r\n const root = document.querySelector<HTMLElement>(scrollRootSelector);\r\n if (root) {\r\n scrollRoots.push(root);\r\n }\r\n }\r\n\r\n window.addEventListener('resize', update);\r\n window.addEventListener('scroll', onScroll, true);\r\n for (const root of scrollRoots) {\r\n root.addEventListener('scroll', onScroll, { passive: true });\r\n }\r\n\r\n return () => {\r\n window.removeEventListener('resize', update);\r\n window.removeEventListener('scroll', onScroll, true);\r\n for (const root of scrollRoots) {\r\n root.removeEventListener('scroll', onScroll);\r\n }\r\n };\r\n }, [menuOpen, menuMounted, scrollRootSelector]);\r\n\r\n return { menuPanelRef, triggerRef, menuGeometry };\r\n}\r\n"],"names":[],"mappings":";;AAUO,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,GAAmC;AACjC,QAAM,eAAe,OAAgC,IAAI;AACzD,QAAM,aAAa,OAAiC,IAAI;AACxD,QAAM,CAAC,cAAc,eAAe,IAAI,SAAsC,IAAI;AAElF,kBAAgB,MAAM;AACpB,QAAI,CAAC,YAAY,CAAC,aAAa;AAC7B,sBAAgB,IAAI;AACpB;AAAA,IACF;AAEA,UAAM,SAAS,MAAM;AACnB,YAAM,UAAU,WAAW;AAC3B,YAAM,OAAO,aAAa;AAC1B,UAAI,CAAC,WAAW,CAAC,MAAM;AACrB;AAAA,MACF;AACA,sBAAgB,4BAA4B,SAAS,IAAI,CAAC;AAAA,IAC5D;AAEA,UAAM,WAAW,CAAC,UAAiB;AACjC,YAAM,OAAO,aAAa;AAC1B,YAAM,SAAS,MAAM;AAErB,UAAI,QAAQ,kBAAkB,QAAQ,KAAK,SAAS,MAAM,GAAG;AAC3D;AAAA,MACF;AACA,aAAA;AAAA,IACF;AAEA,WAAA;AAEA,UAAM,cAA6B,CAAA;AACnC,QAAI,oBAAoB;AACtB,YAAM,OAAO,SAAS,cAA2B,kBAAkB;AACnE,UAAI,MAAM;AACR,oBAAY,KAAK,IAAI;AAAA,MACvB;AAAA,IACF;AAEA,WAAO,iBAAiB,UAAU,MAAM;AACxC,WAAO,iBAAiB,UAAU,UAAU,IAAI;AAChD,eAAW,QAAQ,aAAa;AAC9B,WAAK,iBAAiB,UAAU,UAAU,EAAE,SAAS,MAAM;AAAA,IAC7D;AAEA,WAAO,MAAM;AACX,aAAO,oBAAoB,UAAU,MAAM;AAC3C,aAAO,oBAAoB,UAAU,UAAU,IAAI;AACnD,iBAAW,QAAQ,aAAa;AAC9B,aAAK,oBAAoB,UAAU,QAAQ;AAAA,MAC7C;AAAA,IACF;AAAA,EACF,GAAG,CAAC,UAAU,aAAa,kBAAkB,CAAC;AAE9C,SAAO,EAAE,cAAc,YAAY,aAAA;AACrC;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umamichi-ui/common-components",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Reusable React UI components for Umamichi sites (overlay stack, dialogs, menus)",
5
5
  "type": "module",
6
6
  "license": "MIT",