@ahrowe/ui 0.39.0 → 0.40.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 (138) hide show
  1. package/README.md +2 -1
  2. package/dist/cjs-types/common/calendar/calendar.d.ts +4 -0
  3. package/dist/cjs-types/common/calendar/calendar.types.d.ts +141 -0
  4. package/dist/cjs-types/common/calendar/components/agendaList/agendaList.d.ts +5 -0
  5. package/dist/cjs-types/common/calendar/components/agendaList/agendaList.types.d.ts +23 -0
  6. package/dist/cjs-types/common/calendar/components/agendaList/index.d.ts +1 -0
  7. package/dist/cjs-types/common/calendar/components/calendarHeader/calendarHeader.d.ts +4 -0
  8. package/dist/cjs-types/common/calendar/components/calendarHeader/calendarHeader.types.d.ts +17 -0
  9. package/dist/cjs-types/common/calendar/components/calendarHeader/index.d.ts +1 -0
  10. package/dist/cjs-types/common/calendar/components/compactMonth/compactMonth.d.ts +9 -0
  11. package/dist/cjs-types/common/calendar/components/compactMonth/compactMonth.types.d.ts +1 -0
  12. package/dist/cjs-types/common/calendar/components/compactMonth/index.d.ts +1 -0
  13. package/dist/cjs-types/common/calendar/components/monthView/index.d.ts +1 -0
  14. package/dist/cjs-types/common/calendar/components/monthView/monthView.d.ts +5 -0
  15. package/dist/cjs-types/common/calendar/components/monthView/monthView.types.d.ts +10 -0
  16. package/dist/cjs-types/common/calendar/components/timeGrid/index.d.ts +1 -0
  17. package/dist/cjs-types/common/calendar/components/timeGrid/timeGrid.d.ts +5 -0
  18. package/dist/cjs-types/common/calendar/components/timeGrid/timeGrid.types.d.ts +40 -0
  19. package/dist/cjs-types/common/calendar/dates.d.ts +50 -0
  20. package/dist/cjs-types/common/calendar/format.d.ts +32 -0
  21. package/dist/cjs-types/common/calendar/gesture.d.ts +32 -0
  22. package/dist/cjs-types/common/calendar/index.d.ts +2 -0
  23. package/dist/cjs-types/common/calendar/layout.d.ts +74 -0
  24. package/dist/cjs-types/common/calendar/useGestures.d.ts +37 -0
  25. package/dist/cjs-types/common/calendar/usePager.d.ts +20 -0
  26. package/dist/cjs-types/common/calendar/view.types.d.ts +26 -0
  27. package/dist/cjs-types/common/configProvider/configProvider.types.d.ts +3 -0
  28. package/dist/cjs-types/common/confirmModal/confirmModal.types.d.ts +1 -1
  29. package/dist/cjs-types/common/datePicker/index.d.ts +1 -1
  30. package/dist/cjs-types/common/modal/modal.types.d.ts +4 -3
  31. package/dist/cjs-types/common/themeProvider/theme.types.d.ts +8 -0
  32. package/dist/cjs-types/common/utils/day.d.ts +8 -0
  33. package/dist/cjs-types/common/virtualList/useColumns.d.ts +3 -1
  34. package/dist/cjs-types/index.d.ts +2 -0
  35. package/dist/esm/common/calendar/calendar.mjs +2 -0
  36. package/dist/esm/common/calendar/calendar.mjs.map +1 -0
  37. package/dist/esm/common/calendar/calendar.module.mjs +2 -0
  38. package/dist/esm/common/calendar/calendar.module.mjs.map +1 -0
  39. package/dist/esm/common/calendar/calendar.types.mjs +2 -0
  40. package/dist/esm/common/calendar/calendar.types.mjs.map +1 -0
  41. package/dist/esm/common/calendar/components/agendaList/agendaList.mjs +2 -0
  42. package/dist/esm/common/calendar/components/agendaList/agendaList.mjs.map +1 -0
  43. package/dist/esm/common/calendar/components/agendaList/agendaList.module.mjs +2 -0
  44. package/dist/esm/common/calendar/components/agendaList/agendaList.module.mjs.map +1 -0
  45. package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.mjs +2 -0
  46. package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.mjs.map +1 -0
  47. package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.module.mjs +2 -0
  48. package/dist/esm/common/calendar/components/calendarHeader/calendarHeader.module.mjs.map +1 -0
  49. package/dist/esm/common/calendar/components/compactMonth/compactMonth.mjs +2 -0
  50. package/dist/esm/common/calendar/components/compactMonth/compactMonth.mjs.map +1 -0
  51. package/dist/esm/common/calendar/components/compactMonth/compactMonth.module.mjs +2 -0
  52. package/dist/esm/common/calendar/components/compactMonth/compactMonth.module.mjs.map +1 -0
  53. package/dist/esm/common/calendar/components/monthView/monthView.mjs +2 -0
  54. package/dist/esm/common/calendar/components/monthView/monthView.mjs.map +1 -0
  55. package/dist/esm/common/calendar/components/monthView/monthView.module.mjs +2 -0
  56. package/dist/esm/common/calendar/components/monthView/monthView.module.mjs.map +1 -0
  57. package/dist/esm/common/calendar/components/timeGrid/timeGrid.mjs +2 -0
  58. package/dist/esm/common/calendar/components/timeGrid/timeGrid.mjs.map +1 -0
  59. package/dist/esm/common/calendar/components/timeGrid/timeGrid.module.mjs +2 -0
  60. package/dist/esm/common/calendar/components/timeGrid/timeGrid.module.mjs.map +1 -0
  61. package/dist/esm/common/calendar/dates.mjs +2 -0
  62. package/dist/esm/common/calendar/dates.mjs.map +1 -0
  63. package/dist/esm/common/calendar/format.mjs +2 -0
  64. package/dist/esm/common/calendar/format.mjs.map +1 -0
  65. package/dist/esm/common/calendar/gesture.mjs +2 -0
  66. package/dist/esm/common/calendar/gesture.mjs.map +1 -0
  67. package/dist/esm/common/calendar/layout.mjs +2 -0
  68. package/dist/esm/common/calendar/layout.mjs.map +1 -0
  69. package/dist/esm/common/calendar/pager.module.mjs +2 -0
  70. package/dist/esm/common/calendar/pager.module.mjs.map +1 -0
  71. package/dist/esm/common/calendar/useGestures.mjs +2 -0
  72. package/dist/esm/common/calendar/useGestures.mjs.map +1 -0
  73. package/dist/esm/common/calendar/usePager.mjs +2 -0
  74. package/dist/esm/common/calendar/usePager.mjs.map +1 -0
  75. package/dist/esm/common/confirmModal/confirmModal.mjs +1 -1
  76. package/dist/esm/common/confirmModal/confirmModal.mjs.map +1 -1
  77. package/dist/esm/common/datePicker/components/datePickerMonth/datePickerMonth.mjs +1 -1
  78. package/dist/esm/common/datePicker/dateFunctions.mjs +1 -1
  79. package/dist/esm/common/datePicker/datePicker.mjs +1 -1
  80. package/dist/esm/common/input/input.mjs +1 -1
  81. package/dist/esm/common/input/input.mjs.map +1 -1
  82. package/dist/esm/common/modal/modal.mjs +1 -1
  83. package/dist/esm/common/modal/modal.mjs.map +1 -1
  84. package/dist/esm/common/popover/popover.mjs +1 -1
  85. package/dist/esm/common/popover/popover.mjs.map +1 -1
  86. package/dist/esm/common/scrollFade/scrollFade.mjs +1 -1
  87. package/dist/esm/common/tabHeader/tabHeader.module.mjs.map +1 -1
  88. package/dist/esm/common/utils/day.mjs +1 -1
  89. package/dist/esm/common/utils/day.mjs.map +1 -1
  90. package/dist/esm/common/virtualList/useColumns.mjs +1 -1
  91. package/dist/esm/common/virtualList/useColumns.mjs.map +1 -1
  92. package/dist/esm/common/virtualList/virtualList.mjs +1 -1
  93. package/dist/esm/common/virtualList/virtualList.mjs.map +1 -1
  94. package/dist/esm/index.mjs +1 -1
  95. package/dist/index.cjs +3 -3
  96. package/dist/index.cjs.map +1 -1
  97. package/dist/style.css +1 -1
  98. package/dist/types/common/calendar/calendar.d.ts +4 -0
  99. package/dist/types/common/calendar/calendar.types.d.ts +141 -0
  100. package/dist/types/common/calendar/components/agendaList/agendaList.d.ts +5 -0
  101. package/dist/types/common/calendar/components/agendaList/agendaList.types.d.ts +23 -0
  102. package/dist/types/common/calendar/components/agendaList/index.d.ts +1 -0
  103. package/dist/types/common/calendar/components/calendarHeader/calendarHeader.d.ts +4 -0
  104. package/dist/types/common/calendar/components/calendarHeader/calendarHeader.types.d.ts +17 -0
  105. package/dist/types/common/calendar/components/calendarHeader/index.d.ts +1 -0
  106. package/dist/types/common/calendar/components/compactMonth/compactMonth.d.ts +9 -0
  107. package/dist/types/common/calendar/components/compactMonth/compactMonth.types.d.ts +1 -0
  108. package/dist/types/common/calendar/components/compactMonth/index.d.ts +1 -0
  109. package/dist/types/common/calendar/components/monthView/index.d.ts +1 -0
  110. package/dist/types/common/calendar/components/monthView/monthView.d.ts +5 -0
  111. package/dist/types/common/calendar/components/monthView/monthView.types.d.ts +10 -0
  112. package/dist/types/common/calendar/components/timeGrid/index.d.ts +1 -0
  113. package/dist/types/common/calendar/components/timeGrid/timeGrid.d.ts +5 -0
  114. package/dist/types/common/calendar/components/timeGrid/timeGrid.types.d.ts +40 -0
  115. package/dist/types/common/calendar/dates.d.ts +50 -0
  116. package/dist/types/common/calendar/format.d.ts +32 -0
  117. package/dist/types/common/calendar/gesture.d.ts +32 -0
  118. package/dist/types/common/calendar/index.d.ts +2 -0
  119. package/dist/types/common/calendar/layout.d.ts +74 -0
  120. package/dist/types/common/calendar/useGestures.d.ts +37 -0
  121. package/dist/types/common/calendar/usePager.d.ts +20 -0
  122. package/dist/types/common/calendar/view.types.d.ts +26 -0
  123. package/dist/types/common/configProvider/configProvider.types.d.ts +3 -0
  124. package/dist/types/common/confirmModal/confirmModal.types.d.ts +1 -1
  125. package/dist/types/common/datePicker/index.d.ts +1 -1
  126. package/dist/types/common/modal/modal.types.d.ts +4 -3
  127. package/dist/types/common/themeProvider/theme.types.d.ts +8 -0
  128. package/dist/types/common/utils/day.d.ts +8 -0
  129. package/dist/types/common/virtualList/useColumns.d.ts +3 -1
  130. package/dist/types/index.d.ts +2 -0
  131. package/docs/CLAUDE.md +7 -5
  132. package/docs/Calendar.md +184 -0
  133. package/docs/ConfigProvider.md +2 -1
  134. package/docs/ConfirmModal.md +1 -1
  135. package/docs/DatePicker.md +9 -2
  136. package/docs/Modal.md +2 -2
  137. package/docs/TimeInput.md +2 -0
  138. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"modal.mjs","names":[],"sources":["../../../../package/common/modal/modal.tsx"],"sourcesContent":["import cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './modal.module.pcss';\nimport sheetStyles from 'components/common/styles/sheet.module.pcss';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faXmark as closeIcon } from '@fortawesome/free-solid-svg-icons';\nimport { ModalProps } from './modal.types';\nimport type { ModalLabels } from './modal.types';\nimport { useEffect, useId, useRef, useState } from 'react';\nimport { useVisualViewportSize } from 'components/common/hooks/useVisualViewportSize';\nimport { useOverlay } from 'components/common/hooks/useOverlay';\nimport { useSheet } from 'components/common/hooks/useSheet';\nimport { useScrollEdges } from 'components/common/hooks/useScrollEdges';\nimport fadeStyles from 'components/common/styles/scrollFade.module.pcss';\nimport { useComponentDefaults, useLabels, usePresentationDefault } from 'components/common/configProvider';\n\nconst DEFAULT_LABELS: Required<ModalLabels> = {\n close: 'Close',\n};\n\nfunction Modal(props: ModalProps) {\n const {\n children = null,\n title = '',\n isOpen = false,\n onClose = () => null,\n footer = null,\n className = '',\n style,\n hideHeader = false,\n closeOnEscape = true,\n presentation: presentationProp,\n classNames,\n styles: slotStyles,\n labels,\n ...rest\n } = useComponentDefaults('Modal', props);\n const text = useLabels('Modal', labels, DEFAULT_LABELS);\n const [isRendered, setIsRendered] = useState(isOpen);\n const [animateIn, setAnimateIn] = useState(isOpen);\n const modalRef = useRef<HTMLDivElement>(null);\n const bodyRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // Centering/sizing against the actually-visible viewport (not the full layout viewport) keeps\n // the modal — and whatever's focused inside it — from ending up under a mobile keyboard.\n const { height: viewportHeight, offsetTop: viewportOffsetTop } = useVisualViewportSize();\n // Explicit prop → this component's global default → the app-wide one. There's no built-in\n // fallback to add: `Default` and no presentation at all both mean the ordinary centred dialog.\n const appPresentation = usePresentationDefault();\n const presentation = presentationProp ?? appPresentation;\n const { isSheet, sheetStyle } = useSheet(presentation);\n\n useEffect(() => {\n if (isOpen) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setIsRendered(isOpen);\n setTimeout(() => setAnimateIn(true), 1);\n } else {\n setAnimateIn(false);\n setTimeout(() => setIsRendered(isOpen), 300);\n }\n }, [isOpen]);\n\n // Keyed on `animateIn` rather than `isOpen`, because the panel this needs to focus and trap\n // focus inside of only mounts once that flips.\n useOverlay({\n isActive: isRendered && animateIn,\n containerRef: modalRef,\n onEscape: closeOnEscape ? onClose : undefined,\n });\n\n const { hasStart, hasEnd } = useScrollEdges(bodyRef, { isEnabled: isRendered && animateIn });\n\n if (!isRendered) return null;\n\n const hasHeaderTitle = !hideHeader && !!title;\n\n return (\n <BodyEnd>\n <div\n className={cx(styles.modalContainer, classNames?.container)}\n style={{ height: viewportHeight, top: viewportOffsetTop, ...slotStyles?.container }}\n >\n <div\n className={cx(styles.modalWrapper, animateIn && styles.modalWrapperShown, classNames?.backdrop)}\n style={slotStyles?.backdrop}\n onClick={onClose}\n />\n {animateIn && (\n <div\n {...rest}\n ref={modalRef}\n role='dialog'\n aria-modal='true'\n aria-labelledby={hasHeaderTitle ? titleId : undefined}\n // Focusable so the trap has somewhere to put focus when the content has no tabbables.\n tabIndex={-1}\n className={cx(\n styles.modal,\n animateIn && styles.modalShown,\n // The panel is its own sheet surface here: there's no inner panel to fill, since\n // Modal already splits itself into a fixed header and a scrolling body.\n isSheet && [sheetStyles.sheet, sheetStyles.sheetSurface, styles.modalSheet],\n className,\n classNames?.modal,\n )}\n style={{ ...(isSheet ? sheetStyle : undefined), ...style, ...slotStyles?.modal }}\n onClick={(e) => e.stopPropagation()}\n >\n {!hideHeader && (\n <div className={cx(styles.modalHeader, classNames?.header)} style={slotStyles?.header}>\n <div id={titleId} className={classNames?.title} style={slotStyles?.title}>\n {title}\n </div>\n <button\n type='button'\n onClick={onClose}\n aria-label={text.close}\n className={cx(styles.modalCloseButton, classNames?.closeButton)}\n style={slotStyles?.closeButton}\n >\n <FontAwesomeIcon icon={closeIcon} />\n </button>\n </div>\n )}\n <div\n ref={bodyRef}\n className={cx(\n styles.modalBody,\n fadeStyles.scrollFade,\n fadeStyles.scrollFadeY,\n hasStart && fadeStyles.scrollFadeFadedStart,\n hasEnd && fadeStyles.scrollFadeFadedEnd,\n classNames?.body,\n )}\n style={slotStyles?.body}\n >\n {children}\n </div>\n {footer && (\n <div className={cx(styles.modalFooter, classNames?.footer)} style={slotStyles?.footer}>\n {footer}\n </div>\n )}\n </div>\n )}\n </div>\n </BodyEnd>\n );\n}\n\nexport default Modal;\n"],"mappings":"o4BAiBA,IAAM,EAAwC,CAC5C,MAAO,OACT,EAEA,SAAS,EAAM,EAAmB,CAChC,GAAM,CACJ,WAAW,KACX,QAAQ,GACR,SAAS,GACT,cAAgB,KAChB,SAAS,KACT,YAAY,GACZ,QACA,aAAa,GACb,gBAAgB,GAChB,aAAc,EACd,aACA,OAAQ,EACR,SACA,GAAG,GACD,EAAqB,QAAS,CAAK,EACjC,EAAO,EAAU,QAAS,EAAQ,CAAc,EAChD,CAAC,EAAY,GAAiB,EAAS,CAAM,EAC7C,CAAC,EAAW,GAAgB,EAAS,CAAM,EAC3C,EAAW,EAAuB,IAAI,EACtC,EAAU,EAAuB,IAAI,EACrC,EAAU,EAAM,EAGhB,CAAE,OAAQ,EAAgB,UAAW,GAAsB,EAAsB,EAGjF,EAAkB,EAAuB,EAEzC,CAAE,UAAS,cAAe,EADX,GAAoB,CACY,EAErD,MAAgB,CACV,GAEF,EAAc,CAAM,EACpB,eAAiB,EAAa,EAAI,EAAG,CAAC,IAEtC,EAAa,EAAK,EAClB,eAAiB,EAAc,CAAM,EAAG,GAAG,EAE/C,EAAG,CAAC,CAAM,CAAC,EAIX,EAAW,CACT,SAAU,GAAc,EACxB,aAAc,EACd,SAAU,EAAgB,EAAU,IAAA,EACtC,CAAC,EAED,GAAM,CAAE,WAAU,UAAW,EAAe,EAAS,CAAE,UAAW,GAAc,CAAU,CAAC,EAE3F,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAiB,CAAC,GAAc,CAAC,CAAC,EAExC,OACE,EAAC,EAAD,CAAA,SACE,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,eAAgB,GAAY,SAAS,EAC1D,MAAO,CAAE,OAAQ,EAAgB,IAAK,EAAmB,GAAG,GAAY,SAAU,EAFpF,SAAA,CAIE,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,aAAc,GAAa,EAAO,kBAAmB,GAAY,QAAQ,EAC9F,MAAO,GAAY,SACnB,QAAS,CACV,CAAA,EACA,GACC,EAAC,MAAD,CACE,GAAI,EACJ,IAAK,EACL,KAAK,SACL,aAAW,OACX,kBAAiB,EAAiB,EAAU,IAAA,GAE5C,SAAU,GACV,UAAW,EACT,EAAO,MACP,GAAa,EAAO,WAGpB,GAAW,CAAC,EAAY,MAAO,EAAY,aAAc,EAAO,UAAU,EAC1E,EACA,GAAY,KACd,EACA,MAAO,CAAE,GAAI,EAAU,EAAa,IAAA,GAAY,GAAG,EAAO,GAAG,GAAY,KAAM,EAC/E,QAAU,GAAM,EAAE,gBAAgB,EAlBpC,SAAA,CAoBG,CAAC,GACA,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,YAAa,GAAY,MAAM,EAAG,MAAO,GAAY,OAA/E,SAAA,CACE,EAAC,MAAD,CAAK,GAAI,EAAS,UAAW,GAAY,MAAO,MAAO,GAAY,MAChE,SAAA,CACE,CAAA,EACL,EAAC,SAAD,CACE,KAAK,SACL,QAAS,EACT,aAAY,EAAK,MACjB,UAAW,EAAG,EAAO,iBAAkB,GAAY,WAAW,EAC9D,MAAO,GAAY,YAEnB,SAAA,EAAC,EAAD,CAAiB,KAAM,CAAY,CAAA,CAC7B,CAAA,CACL,IAEP,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EACT,EAAO,UACP,EAAW,WACX,EAAW,YACX,GAAY,EAAW,qBACvB,GAAU,EAAW,mBACrB,GAAY,IACd,EACA,MAAO,GAAY,KAElB,UACE,CAAA,EACJ,GACC,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,YAAa,GAAY,MAAM,EAAG,MAAO,GAAY,OAC5E,SAAA,CACE,CAAA,CAEJ,CAEJ,CAAA,CAAA,CACE,CAAA,CAAA,CAAA,CAEb"}
1
+ {"version":3,"file":"modal.mjs","names":[],"sources":["../../../../package/common/modal/modal.tsx"],"sourcesContent":["import cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './modal.module.pcss';\nimport sheetStyles from 'components/common/styles/sheet.module.pcss';\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\nimport { faXmark as closeIcon } from '@fortawesome/free-solid-svg-icons';\nimport { ModalProps } from './modal.types';\nimport type { ModalLabels } from './modal.types';\nimport { useEffect, useId, useRef, useState } from 'react';\nimport { useVisualViewportSize } from 'components/common/hooks/useVisualViewportSize';\nimport { useOverlay } from 'components/common/hooks/useOverlay';\nimport { useSheet } from 'components/common/hooks/useSheet';\nimport { useScrollEdges } from 'components/common/hooks/useScrollEdges';\nimport fadeStyles from 'components/common/styles/scrollFade.module.pcss';\nimport { useComponentDefaults, useLabels, usePresentationDefault } from 'components/common/configProvider';\n\nconst DEFAULT_LABELS: Required<ModalLabels> = {\n close: 'Close',\n};\n\nfunction Modal(props: ModalProps) {\n const {\n children = null,\n title,\n isOpen = false,\n onClose = () => null,\n footer = null,\n className = '',\n style,\n hideHeader = false,\n closeOnEscape = true,\n presentation: presentationProp,\n classNames,\n styles: slotStyles,\n labels,\n ...rest\n } = useComponentDefaults('Modal', props);\n const text = useLabels('Modal', labels, DEFAULT_LABELS);\n const [isRendered, setIsRendered] = useState(isOpen);\n const [animateIn, setAnimateIn] = useState(isOpen);\n const modalRef = useRef<HTMLDivElement>(null);\n const bodyRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // Centering/sizing against the actually-visible viewport (not the full layout viewport) keeps\n // the modal — and whatever's focused inside it — from ending up under a mobile keyboard.\n const { height: viewportHeight, offsetTop: viewportOffsetTop } = useVisualViewportSize();\n // Explicit prop → this component's global default → the app-wide one. There's no built-in\n // fallback to add: `Default` and no presentation at all both mean the ordinary centred dialog.\n const appPresentation = usePresentationDefault();\n const presentation = presentationProp ?? appPresentation;\n const { isSheet, sheetStyle } = useSheet(presentation);\n\n useEffect(() => {\n if (isOpen) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setIsRendered(isOpen);\n setTimeout(() => setAnimateIn(true), 1);\n } else {\n setAnimateIn(false);\n setTimeout(() => setIsRendered(isOpen), 300);\n }\n }, [isOpen]);\n\n // Keyed on `animateIn` rather than `isOpen`, because the panel this needs to focus and trap\n // focus inside of only mounts once that flips.\n useOverlay({\n isActive: isRendered && animateIn,\n containerRef: modalRef,\n onEscape: closeOnEscape ? onClose : undefined,\n });\n\n const { hasStart, hasEnd } = useScrollEdges(bodyRef, { isEnabled: isRendered && animateIn });\n\n if (!isRendered) return null;\n\n const hasHeaderTitle = !hideHeader && !!title;\n\n return (\n <BodyEnd>\n <div\n className={cx(styles.modalContainer, classNames?.container)}\n style={{ height: viewportHeight, top: viewportOffsetTop, ...slotStyles?.container }}\n >\n <div\n className={cx(styles.modalWrapper, animateIn && styles.modalWrapperShown, classNames?.backdrop)}\n style={slotStyles?.backdrop}\n onClick={onClose}\n />\n {animateIn && (\n <div\n {...rest}\n ref={modalRef}\n role='dialog'\n aria-modal='true'\n // aria-labelledby would outrank aria-label, which is the only way to name a title with no text.\n aria-labelledby={hasHeaderTitle && !rest['aria-label'] ? titleId : undefined}\n // Focusable so the trap has somewhere to put focus when the content has no tabbables.\n tabIndex={-1}\n className={cx(\n styles.modal,\n animateIn && styles.modalShown,\n // The panel is its own sheet surface here: there's no inner panel to fill, since\n // Modal already splits itself into a fixed header and a scrolling body.\n isSheet && [sheetStyles.sheet, sheetStyles.sheetSurface, styles.modalSheet],\n className,\n classNames?.modal,\n )}\n style={{ ...(isSheet ? sheetStyle : undefined), ...style, ...slotStyles?.modal }}\n onClick={(e) => e.stopPropagation()}\n >\n {!hideHeader && (\n <div className={cx(styles.modalHeader, classNames?.header)} style={slotStyles?.header}>\n <div id={titleId} className={classNames?.title} style={slotStyles?.title}>\n {title}\n </div>\n <button\n type='button'\n onClick={onClose}\n aria-label={text.close}\n className={cx(styles.modalCloseButton, classNames?.closeButton)}\n style={slotStyles?.closeButton}\n >\n <FontAwesomeIcon icon={closeIcon} />\n </button>\n </div>\n )}\n <div\n ref={bodyRef}\n className={cx(\n styles.modalBody,\n fadeStyles.scrollFade,\n fadeStyles.scrollFadeY,\n hasStart && fadeStyles.scrollFadeFadedStart,\n hasEnd && fadeStyles.scrollFadeFadedEnd,\n classNames?.body,\n )}\n style={slotStyles?.body}\n >\n {children}\n </div>\n {footer && (\n <div className={cx(styles.modalFooter, classNames?.footer)} style={slotStyles?.footer}>\n {footer}\n </div>\n )}\n </div>\n )}\n </div>\n </BodyEnd>\n );\n}\n\nexport default Modal;\n"],"mappings":"o4BAiBA,IAAM,EAAwC,CAC5C,MAAO,OACT,EAEA,SAAS,EAAM,EAAmB,CAChC,GAAM,CACJ,WAAW,KACX,QACA,SAAS,GACT,cAAgB,KAChB,SAAS,KACT,YAAY,GACZ,QACA,aAAa,GACb,gBAAgB,GAChB,aAAc,EACd,aACA,OAAQ,EACR,SACA,GAAG,GACD,EAAqB,QAAS,CAAK,EACjC,EAAO,EAAU,QAAS,EAAQ,CAAc,EAChD,CAAC,EAAY,GAAiB,EAAS,CAAM,EAC7C,CAAC,EAAW,GAAgB,EAAS,CAAM,EAC3C,EAAW,EAAuB,IAAI,EACtC,EAAU,EAAuB,IAAI,EACrC,EAAU,EAAM,EAGhB,CAAE,OAAQ,EAAgB,UAAW,GAAsB,EAAsB,EAGjF,EAAkB,EAAuB,EAEzC,CAAE,UAAS,cAAe,EADX,GAAoB,CACY,EAErD,MAAgB,CACV,GAEF,EAAc,CAAM,EACpB,eAAiB,EAAa,EAAI,EAAG,CAAC,IAEtC,EAAa,EAAK,EAClB,eAAiB,EAAc,CAAM,EAAG,GAAG,EAE/C,EAAG,CAAC,CAAM,CAAC,EAIX,EAAW,CACT,SAAU,GAAc,EACxB,aAAc,EACd,SAAU,EAAgB,EAAU,IAAA,EACtC,CAAC,EAED,GAAM,CAAE,WAAU,UAAW,EAAe,EAAS,CAAE,UAAW,GAAc,CAAU,CAAC,EAE3F,GAAI,CAAC,EAAY,OAAO,KAExB,IAAM,EAAiB,CAAC,GAAc,CAAC,CAAC,EAExC,OACE,EAAC,EAAD,CAAA,SACE,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,eAAgB,GAAY,SAAS,EAC1D,MAAO,CAAE,OAAQ,EAAgB,IAAK,EAAmB,GAAG,GAAY,SAAU,EAFpF,SAAA,CAIE,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,aAAc,GAAa,EAAO,kBAAmB,GAAY,QAAQ,EAC9F,MAAO,GAAY,SACnB,QAAS,CACV,CAAA,EACA,GACC,EAAC,MAAD,CACE,GAAI,EACJ,IAAK,EACL,KAAK,SACL,aAAW,OAEX,kBAAiB,GAAkB,CAAC,EAAK,cAAgB,EAAU,IAAA,GAEnE,SAAU,GACV,UAAW,EACT,EAAO,MACP,GAAa,EAAO,WAGpB,GAAW,CAAC,EAAY,MAAO,EAAY,aAAc,EAAO,UAAU,EAC1E,EACA,GAAY,KACd,EACA,MAAO,CAAE,GAAI,EAAU,EAAa,IAAA,GAAY,GAAG,EAAO,GAAG,GAAY,KAAM,EAC/E,QAAU,GAAM,EAAE,gBAAgB,EAnBpC,SAAA,CAqBG,CAAC,GACA,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,YAAa,GAAY,MAAM,EAAG,MAAO,GAAY,OAA/E,SAAA,CACE,EAAC,MAAD,CAAK,GAAI,EAAS,UAAW,GAAY,MAAO,MAAO,GAAY,MAChE,SAAA,CACE,CAAA,EACL,EAAC,SAAD,CACE,KAAK,SACL,QAAS,EACT,aAAY,EAAK,MACjB,UAAW,EAAG,EAAO,iBAAkB,GAAY,WAAW,EAC9D,MAAO,GAAY,YAEnB,SAAA,EAAC,EAAD,CAAiB,KAAM,CAAY,CAAA,CAC7B,CAAA,CACL,IAEP,EAAC,MAAD,CACE,IAAK,EACL,UAAW,EACT,EAAO,UACP,EAAW,WACX,EAAW,YACX,GAAY,EAAW,qBACvB,GAAU,EAAW,mBACrB,GAAY,IACd,EACA,MAAO,GAAY,KAElB,UACE,CAAA,EACJ,GACC,EAAC,MAAD,CAAK,UAAW,EAAG,EAAO,YAAa,GAAY,MAAM,EAAG,MAAO,GAAY,OAC5E,SAAA,CACE,CAAA,CAEJ,CAEJ,CAAA,CAAA,CACE,CAAA,CAAA,CAAA,CAEb"}
@@ -1,2 +1,2 @@
1
- "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{usePresentationDefault as t}from"../configProvider/usePresentationDefault.mjs";import{Presentation as n}from"../configProvider/presentation.types.mjs";import r from"../bodyEnd/bodyEnd.mjs";import i from"./popover.module.mjs";import a from"../styles/sheet.module.mjs";import o from"../sheetBackdrop/sheetBackdrop.mjs";import{PopoverAlign as ee,PopoverPlacement as s,PopoverTrigger as c}from"./popover.types.mjs";import{usePopoverPosition as l}from"./usePopoverPosition.mjs";import{tabbablesIn as u,useFocusBoundary as te}from"../hooks/useFocusBoundary.mjs";import ne from"../hooks/useSheet.mjs";import{useOverlay as re}from"../hooks/useOverlay.mjs";import{useCallback as ie,useEffect as d,useRef as f,useState as p}from"react";import m from"classnames";import{jsx as h,jsxs as g}from"react/jsx-runtime";var ae=1e3;function _(e,t){return e?{current:e}:t}function v(v){let{children:y=null,anchor:b,content:x=null,placement:S=s.Bottom,align:C=ee.Center,trigger:w=c.Click,isOpen:T,defaultOpen:oe=!1,onOpenChange:E=()=>{},offset:se=10,withArrow:D=!0,closeOnOutsideClick:O=!0,closeOnEscape:ce=!0,openDelay:le=100,closeDelay:ue=120,disabled:k=!1,focusOnOpen:A=`keyboard`,closeOnTriggerClick:de=!0,width:fe=`auto`,constrainWidth:pe=!0,presentation:me,className:he,style:ge,classNames:j,styles:M,...N}=e(`Popover`,v),P=f(null),F=f(null),I=f(null),L=f(null),R=f(void 0),z=f(!1),B=f(!1);function V(){z.current=!0}let[H,_e]=p(()=>({anchor:b,ref:_(b,P)}));H.anchor!==b&&_e({anchor:b,ref:_(b,P)});let U=H.ref,W=f(null),G=b!==void 0;d(()=>{if(!G)return;function e(e){W.current={target:e.target,keyboard:e.detail===0,time:performance.now()}}return document.addEventListener(`click`,e,!0),()=>document.removeEventListener(`click`,e,!0)},[G]);let K=T!==void 0,[ve,ye]=p(oe),q=K?T:ve,J=f(E);d(()=>{J.current=E});let Y=ie((e,t=!1)=>{k&&e||(e&&(B.current=t),K||ye(e),J.current(e,{fromKeyboard:t}))},[k,K]),be=t(),xe=me??be??n.Default,{isSheet:Se,sheetStyle:Ce}=ne(xe),X=Se&&w===c.Click;l({isOpen:!!q&&!X,placement:S,align:C,offset:se,triggerRef:U,floatingRef:F,panelRef:I,arrowRef:D?L:{current:null},matchTriggerWidth:fe===`trigger`});let{restoreFocusToTrigger:Z,focusAfterTrigger:we}=te({triggerRef:U,contentRef:F});re({isActive:!!q,containerRef:I,onEscape:ce?()=>{F.current?.contains(document.activeElement)&&Z(),Y(!1,!0)}:void 0,trapFocus:!1,lockScroll:X}),d(()=>{if(!q){B.current=!1;return}let e=W.current;W.current=null;let t=!!b&&!!e?.keyboard&&b.contains(e.target)&&performance.now()-e.time<ae;if(!(A===!0||A===`keyboard`&&(B.current||t))||w!==c.Click)return;let n=u(I.current);n.length!==0&&(F.current?.setAttribute(`role`,`dialog`),n[0].focus())},[q,w,A,b]);function Q(e){if(e.key!==`Tab`||w!==c.Click)return;let t=u(I.current);if(t.length===0)return;let n=document.activeElement;!e.shiftKey&&n===t[t.length-1]?(e.preventDefault(),we(),Y(!1,!0)):e.shiftKey&&n===t[0]&&(e.preventDefault(),Z(),Y(!1,!0))}d(()=>{if(!q)return;z.current=!1;function e(e){let t=z.current;z.current=!1;let n=e.target,r=F.current?.contains(n),i=P.current?.contains(n)||U.current?.contains(n);!t&&!r&&!i&&Y(!1)}function t(e){let t=z.current;z.current=!1;let n=e.target,r=F.current?.contains(n),i=P.current?.contains(n)||U.current?.contains(n);!t&&!r&&!i&&Y(!1,!0)}return O&&w!==c.Hover&&(window.addEventListener(`mouseup`,e),document.addEventListener(`focusin`,t)),()=>{window.removeEventListener(`mouseup`,e),document.removeEventListener(`focusin`,t)}},[q,O,w,Y,U]),d(()=>()=>clearTimeout(R.current),[]);function $(e){clearTimeout(R.current),R.current=setTimeout(()=>Y(e),e?le:ue)}function Te(e){q||e.key!==`ArrowDown`&&e.key!==`ArrowUp`||(e.preventDefault(),Y(!0,!0))}let Ee=w===c.Click?{onClick:e=>{(!q||de)&&Y(!q,e.detail===0)},onKeyDown:Te}:w===c.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{onFocus:()=>Y(!0),onBlur:()=>Y(!1)},De=w===c.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{};return g(`div`,{...N,className:m(i.popover,he,j?.root),style:{...ge,...M?.root},children:[h(`div`,{ref:P,className:m(i.popoverTrigger,j?.trigger),style:M?.trigger,...Ee,children:y}),q&&g(r,{children:[X&&h(o,{onClose:()=>{O&&Y(!1)},className:j?.backdrop,style:M?.backdrop}),g(`div`,{ref:F,className:m(X?a.sheet:i.popoverFloating,j?.floating),style:{...X?Ce:void 0,...M?.floating},"data-placement":S,onKeyDown:Q,onMouseUpCapture:V,onFocusCapture:V,...De,children:[h(`div`,{ref:I,className:m(i.popoverPanel,pe&&i.popoverPanelConstrained,X&&a.sheetPanel,j?.panel),style:M?.panel,children:x}),D&&!X&&h(`div`,{ref:L,className:m(i.popoverArrow,j?.arrow),style:M?.arrow})]})]})]})}export{v as default};
1
+ "use client";import{useComponentDefaults as e}from"../configProvider/useComponentDefaults.mjs";import{usePresentationDefault as t}from"../configProvider/usePresentationDefault.mjs";import{Presentation as n}from"../configProvider/presentation.types.mjs";import r from"../bodyEnd/bodyEnd.mjs";import i from"./popover.module.mjs";import a from"../styles/sheet.module.mjs";import o from"../sheetBackdrop/sheetBackdrop.mjs";import{PopoverAlign as ee,PopoverPlacement as s,PopoverTrigger as c}from"./popover.types.mjs";import{usePopoverPosition as l}from"./usePopoverPosition.mjs";import{tabbablesIn as u,useFocusBoundary as te}from"../hooks/useFocusBoundary.mjs";import ne from"../hooks/useSheet.mjs";import{useOverlay as re}from"../hooks/useOverlay.mjs";import{useCallback as ie,useEffect as d,useRef as f,useState as p}from"react";import m from"classnames";import{jsx as h,jsxs as g}from"react/jsx-runtime";var ae=1e3;function _(e,t){return e?{current:e}:t}function v(v){let{children:oe=null,anchor:y,content:b=null,placement:x=s.Bottom,align:S=ee.Center,trigger:C=c.Click,isOpen:w,defaultOpen:se=!1,onOpenChange:T=()=>{},offset:ce=10,withArrow:E=!0,closeOnOutsideClick:D=!0,closeOnEscape:le=!0,openDelay:ue=100,closeDelay:de=120,disabled:O=!1,focusOnOpen:k=`keyboard`,closeOnTriggerClick:fe=!0,width:pe=`auto`,constrainWidth:me=!0,presentation:he,className:ge,style:_e,classNames:A,styles:j,...M}=e(`Popover`,v),N=f(null),P=f(null),F=f(null),I=f(null),L=f(void 0),R=f(!1),z=f(!1),B=f(!1);function V(){R.current=!0}let[H,ve]=p(()=>({anchor:y,ref:_(y,N)}));H.anchor!==y&&ve({anchor:y,ref:_(y,N)});let U=H.ref,W=f(null),G=y!==void 0;d(()=>{if(!G)return;function e(e){W.current={target:e.target,keyboard:e.detail===0,time:performance.now()}}return document.addEventListener(`click`,e,!0),()=>document.removeEventListener(`click`,e,!0)},[G]);let K=w!==void 0,[ye,be]=p(se),q=K?w:ye,J=f(T);d(()=>{J.current=T});let Y=ie((e,t=!1)=>{O&&e||(e&&(B.current=t),K||be(e),J.current(e,{fromKeyboard:t}))},[O,K]),xe=t(),Se=he??xe??n.Default,{isSheet:Ce,sheetStyle:we}=ne(Se),X=Ce&&C===c.Click;l({isOpen:!!q&&!X,placement:x,align:S,offset:ce,triggerRef:U,floatingRef:P,panelRef:F,arrowRef:E?I:{current:null},matchTriggerWidth:pe===`trigger`});let{restoreFocusToTrigger:Z,focusAfterTrigger:Q}=te({triggerRef:U,contentRef:P});re({isActive:!!q,containerRef:F,onEscape:le?()=>{P.current?.contains(document.activeElement)&&Z(),Y(!1,!0)}:void 0,trapFocus:!1,lockScroll:X}),d(()=>{if(!q){B.current=!1;return}let e=W.current;W.current=null;let t=!!y&&!!e?.keyboard&&y.contains(e.target)&&performance.now()-e.time<ae;if(!(k===!0||k===`keyboard`&&(B.current||t))||C!==c.Click)return;let n=u(F.current);n.length!==0&&(P.current?.setAttribute(`role`,`dialog`),n[0].focus())},[q,C,k,y]);function Te(e){if(e.key!==`Tab`||C!==c.Click)return;let t=u(F.current);if(t.length===0)return;let n=document.activeElement;!e.shiftKey&&n===t[t.length-1]?(e.preventDefault(),Q(),Y(!1,!0)):e.shiftKey&&n===t[0]&&(e.preventDefault(),Z(),Y(!1,!0))}d(()=>{if(!q)return;R.current=!1,z.current=!1;function e(e){let t=R.current;R.current=!1;let n=e.target,r=P.current?.contains(n),i=N.current?.contains(n)||U.current?.contains(n);return!!(t||r||i)}function t(t){z.current=!e(t)}function n(t){let n=z.current;z.current=!1,!e(t)&&n&&Y(!1)}function r(t){e(t)||Y(!1,!0)}return D&&C!==c.Hover&&(window.addEventListener(`mousedown`,t),window.addEventListener(`mouseup`,n),document.addEventListener(`focusin`,r)),()=>{window.removeEventListener(`mousedown`,t),window.removeEventListener(`mouseup`,n),document.removeEventListener(`focusin`,r)}},[q,D,C,Y,U]),d(()=>()=>clearTimeout(L.current),[]);function $(e){clearTimeout(L.current),L.current=setTimeout(()=>Y(e),e?ue:de)}function Ee(e){q||e.key!==`ArrowDown`&&e.key!==`ArrowUp`||(e.preventDefault(),Y(!0,!0))}let De=C===c.Click?{onClick:e=>{(!q||fe)&&Y(!q,e.detail===0)},onKeyDown:Ee}:C===c.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{onFocus:()=>Y(!0),onBlur:()=>Y(!1)},Oe=C===c.Hover?{onMouseEnter:()=>$(!0),onMouseLeave:()=>$(!1)}:{};return g(`div`,{...M,className:m(i.popover,ge,A?.root),style:{..._e,...j?.root},children:[h(`div`,{ref:N,className:m(i.popoverTrigger,A?.trigger),style:j?.trigger,...De,children:oe}),q&&g(r,{children:[X&&h(o,{onClose:()=>{D&&Y(!1)},className:A?.backdrop,style:j?.backdrop}),g(`div`,{ref:P,className:m(X?a.sheet:i.popoverFloating,A?.floating),style:{...X?we:void 0,...j?.floating},"data-placement":x,onKeyDown:Te,onMouseDownCapture:V,onMouseUpCapture:V,onFocusCapture:V,...Oe,children:[h(`div`,{ref:F,className:m(i.popoverPanel,me&&i.popoverPanelConstrained,X&&a.sheetPanel,A?.panel),style:j?.panel,children:b}),E&&!X&&h(`div`,{ref:I,className:m(i.popoverArrow,A?.arrow),style:j?.arrow})]})]})]})}export{v as default};
2
2
  //# sourceMappingURL=popover.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"popover.mjs","names":[],"sources":["../../../../package/common/popover/popover.tsx"],"sourcesContent":["import { useRef, useEffect, useState, useCallback, type RefObject } from 'react';\nimport cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './popover.module.pcss';\nimport sheetStyles from '../styles/sheet.module.pcss';\nimport SheetBackdrop from '../sheetBackdrop';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useFocusBoundary, tabbablesIn } from '../hooks/useFocusBoundary';\nimport { useSheet } from '../hooks/useSheet';\nimport { useOverlay } from '../hooks/useOverlay';\nimport { useComponentDefaults, usePresentationDefault } from '../configProvider';\nimport { Presentation } from '../configProvider/presentation.types';\nimport { PopoverPlacement, PopoverAlign, PopoverTrigger, type PopoverProps } from './popover.types';\n\n/**\n * How long after its click an anchored popover still counts as opened by it: long enough for the\n * render the click causes, short enough that an old keypress does not decide a later open.\n */\nconst ACTIVATION_WINDOW_MS = 1000;\n\nfunction toReference(\n anchor: HTMLElement | null | undefined,\n triggerRef: RefObject<HTMLElement | null>,\n): RefObject<HTMLElement | null> {\n return anchor ? { current: anchor } : triggerRef;\n}\n\nfunction Popover(props: PopoverProps) {\n const {\n children = null,\n anchor,\n content = null,\n placement = PopoverPlacement.Bottom,\n align = PopoverAlign.Center,\n trigger = PopoverTrigger.Click,\n isOpen,\n defaultOpen = false,\n onOpenChange = () => {},\n offset = 10,\n withArrow = true,\n closeOnOutsideClick = true,\n closeOnEscape = true,\n openDelay = 100,\n closeDelay = 120,\n disabled = false,\n focusOnOpen = 'keyboard',\n closeOnTriggerClick = true,\n width = 'auto',\n constrainWidth = true,\n presentation: presentationProp,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Popover', props);\n const triggerRef = useRef<HTMLDivElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const arrowRef = useRef<HTMLDivElement>(null);\n const hoverTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // Set from a capture handler on the panel, which React fires for its whole\n // React subtree — including children that portal elsewhere in the DOM, such as\n // a Dropdown's list inside a DatePicker's calendar. A DOM `contains` check\n // cannot see those, and without this the panel closes when one is used.\n const panelInteractedRef = useRef(false);\n // Whether the open that is running came from the keyboard, which is what the\n // default `focusOnOpen` follows and what `onOpenChange` reports. A click raised\n // by Enter or Space reports `detail === 0`, where a pointer press counts the\n // clicks; an open with no trigger event at all, such as a panel that appears\n // while the user types, leaves this false.\n const openedByKeyboardRef = useRef(false);\n function markPanelInteraction() {\n panelInteractedRef.current = true;\n }\n\n // What the panel is placed against, and where the focus goes back to. A new object exactly when\n // `anchor` changes, which is what restarts the position tracking for the new element: state, not\n // a memo, because a memo React chose to drop would restart it for no reason.\n const [reference, setReference] = useState(() => ({ anchor, ref: toReference(anchor, triggerRef) }));\n if (reference.anchor !== anchor) setReference({ anchor, ref: toReference(anchor, triggerRef) });\n const referenceRef = reference.ref;\n\n // An anchored popover is opened by its consumer through `isOpen`, so none of the key handling\n // below sees how. The click that caused it does: Enter and Space raise one with `detail === 0`,\n // the same test the trigger uses. Not `:focus-visible`, which a text field matches after a\n // mouse click too, and which stays on a keyboard-focused button that is then clicked.\n const activationRef = useRef<{ target: Node; keyboard: boolean; time: number } | null>(null);\n const anchored = anchor !== undefined;\n useEffect(() => {\n if (!anchored) return;\n function onClick(event: MouseEvent) {\n activationRef.current = { target: event.target as Node, keyboard: event.detail === 0, time: performance.now() };\n }\n // Capturing, so it is recorded before the consumer's own handler opens the panel.\n document.addEventListener('click', onClick, true);\n return () => document.removeEventListener('click', onClick, true);\n }, [anchored]);\n\n const isControlled = isOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = isControlled ? isOpen : internalOpen;\n\n // Keep the outside-click/Escape listeners pointed at the latest setter without re-subscribing.\n const onOpenChangeRef = useRef(onOpenChange);\n useEffect(() => {\n onOpenChangeRef.current = onOpenChange;\n });\n\n const setOpen = useCallback(\n (next: boolean, fromKeyboard = false) => {\n if (disabled && next) return;\n if (next) openedByKeyboardRef.current = fromKeyboard;\n if (!isControlled) setInternalOpen(next);\n onOpenChangeRef.current(next, { fromKeyboard });\n },\n [disabled, isControlled],\n );\n\n // Explicit prop → this component's global default → the app-wide one → anchored.\n const appPresentation = usePresentationDefault();\n const presentation = presentationProp ?? appPresentation ?? Presentation.Default;\n const { isSheet: sheetPresentation, sheetStyle } = useSheet(presentation);\n // Hover and focus are pointer affordances that open something small next to what caused them;\n // a sheet is a deliberate, screen-owning open. Only a click asks for one.\n const isSheet = sheetPresentation && trigger === PopoverTrigger.Click;\n\n // A sheet isn't anchored to anything, so there's nothing to track, flip or clamp.\n usePopoverPosition({\n isOpen: !!open && !isSheet,\n placement,\n align,\n offset,\n triggerRef: referenceRef,\n floatingRef,\n panelRef,\n arrowRef: withArrow ? arrowRef : { current: null },\n matchTriggerWidth: width === 'trigger',\n });\n\n // Registered for as long as it's open, not just as a sheet: this is what puts the popover on\n // the overlay stack, so an enclosing Modal knows something sits above it and neither drags\n // focus out of this (portaled, so outside the modal's panel) panel nor closes itself on the\n // Escape meant for this. Focus isn't trapped: a Popover is deliberately non-modal, and it\n // already moves focus into the panel on open by itself.\n const { restoreFocusToTrigger, focusAfterTrigger } = useFocusBoundary({\n triggerRef: referenceRef,\n contentRef: floatingRef,\n });\n\n useOverlay({\n isActive: !!open,\n containerRef: panelRef,\n onEscape: closeOnEscape\n ? () => {\n // Hand focus back to the trigger when it currently sits inside the panel.\n if (floatingRef.current?.contains(document.activeElement)) restoreFocusToTrigger();\n setOpen(false, true);\n }\n : undefined,\n trapFocus: false,\n lockScroll: isSheet,\n });\n\n // The panel is portaled to the end of the DOM, so Tab never flows into it naturally. When we\n // manage focus (click trigger + focusable content), move focus in on open and, on Tab out of\n // either end, close and continue focus as if the panel sat right after the trigger.\n useEffect(() => {\n if (!open) {\n openedByKeyboardRef.current = false;\n return;\n }\n const activation = activationRef.current;\n activationRef.current = null;\n const anchorKeyed =\n !!anchor &&\n !!activation?.keyboard &&\n anchor.contains(activation.target) &&\n performance.now() - activation.time < ACTIVATION_WINDOW_MS;\n const takeFocus =\n focusOnOpen === true || (focusOnOpen === 'keyboard' && (openedByKeyboardRef.current || anchorKeyed));\n if (!takeFocus || trigger !== PopoverTrigger.Click) return;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n // Only claim the dialog role when we're actually managing focus for real content.\n floatingRef.current?.setAttribute('role', 'dialog');\n focusables[0].focus();\n }, [open, trigger, focusOnOpen, anchor]);\n\n function handleFloatingKeyDown(event: React.KeyboardEvent) {\n if (event.key !== 'Tab' || trigger !== PopoverTrigger.Click) return;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n const active = document.activeElement;\n if (!event.shiftKey && active === focusables[focusables.length - 1]) {\n event.preventDefault();\n focusAfterTrigger();\n setOpen(false, true);\n } else if (event.shiftKey && active === focusables[0]) {\n event.preventDefault();\n restoreFocusToTrigger();\n setOpen(false, true);\n }\n }\n\n // Outside click. Registered only while open and only for the relevant trigger mode; Escape is\n // handled by `useOverlay` above, which scopes it to the innermost open layer.\n useEffect(() => {\n if (!open) return;\n // Focus that landed in the panel before these listeners existed (ours on open, or content\n // that focuses itself on mount) set the flag with nothing to clear it, and the first\n // click outside was swallowed.\n panelInteractedRef.current = false;\n\n function onMouseUp(event: MouseEvent) {\n // Read and clear: the flag describes this one event, and the panel's React\n // handler has already run by the time this does.\n const inPanelSubtree = panelInteractedRef.current;\n panelInteractedRef.current = false;\n const target = event.target as Node;\n const inPanel = floatingRef.current?.contains(target);\n // Both: with an anchor, `children` still toggle the panel, and a press on them is theirs.\n const inTrigger = triggerRef.current?.contains(target) || referenceRef.current?.contains(target);\n if (!inPanelSubtree && !inPanel && !inTrigger) setOpen(false);\n }\n // Keyboard counterpart to the outside click: once focus leaves both the trigger\n // and the (portaled) panel — e.g. tabbing on to the next element — close.\n function onFocusIn(event: FocusEvent) {\n const inPanelSubtree = panelInteractedRef.current;\n panelInteractedRef.current = false;\n const target = event.target as Node;\n const inPanel = floatingRef.current?.contains(target);\n const inTrigger = triggerRef.current?.contains(target) || referenceRef.current?.contains(target);\n if (!inPanelSubtree && !inPanel && !inTrigger) setOpen(false, true);\n }\n\n if (closeOnOutsideClick && trigger !== PopoverTrigger.Hover) {\n window.addEventListener('mouseup', onMouseUp);\n document.addEventListener('focusin', onFocusIn);\n }\n return () => {\n window.removeEventListener('mouseup', onMouseUp);\n document.removeEventListener('focusin', onFocusIn);\n };\n }, [open, closeOnOutsideClick, trigger, setOpen, referenceRef]);\n\n useEffect(() => () => clearTimeout(hoverTimer.current), []);\n\n function scheduleHover(next: boolean) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = setTimeout(() => setOpen(next), next ? openDelay : closeDelay);\n }\n\n // Opening from the keyboard without the trigger having to be a button that\n // happens to toggle on Enter: the arrow keys are what the menu-button pattern\n // asks for, and what a consumer reaches for first.\n function handleTriggerKeyDown(event: React.KeyboardEvent) {\n if (open || (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')) return;\n event.preventDefault();\n setOpen(true, true);\n }\n\n const triggerHandlers =\n trigger === PopoverTrigger.Click\n ? {\n onClick: (event: React.MouseEvent) => {\n // A field that opens a picker keeps it open when clicked again, so\n // that placing the caret in the field does not close the panel.\n if (open && !closeOnTriggerClick) return;\n setOpen(!open, event.detail === 0);\n },\n onKeyDown: handleTriggerKeyDown,\n }\n : trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : { onFocus: () => setOpen(true), onBlur: () => setOpen(false) };\n\n const floatingHoverHandlers =\n trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : {};\n\n return (\n <div\n {...rest}\n className={cx(styles.popover, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n >\n <div\n ref={triggerRef}\n className={cx(styles.popoverTrigger, classNames?.trigger)}\n style={slotStyles?.trigger}\n {...triggerHandlers}\n >\n {children}\n </div>\n {open && (\n <BodyEnd>\n {isSheet && (\n <SheetBackdrop\n onClose={() => {\n if (closeOnOutsideClick) setOpen(false);\n }}\n className={classNames?.backdrop}\n style={slotStyles?.backdrop}\n />\n )}\n <div\n ref={floatingRef}\n className={cx(isSheet ? sheetStyles.sheet : styles.popoverFloating, classNames?.floating)}\n style={{ ...(isSheet ? sheetStyle : undefined), ...slotStyles?.floating }}\n data-placement={placement}\n onKeyDown={handleFloatingKeyDown}\n onMouseUpCapture={markPanelInteraction}\n onFocusCapture={markPanelInteraction}\n {...floatingHoverHandlers}\n >\n <div\n ref={panelRef}\n className={cx(\n styles.popoverPanel,\n constrainWidth && styles.popoverPanelConstrained,\n isSheet && sheetStyles.sheetPanel,\n classNames?.panel,\n )}\n style={slotStyles?.panel}\n >\n {content}\n </div>\n {withArrow && !isSheet && (\n <div ref={arrowRef} className={cx(styles.popoverArrow, classNames?.arrow)} style={slotStyles?.arrow} />\n )}\n </div>\n </BodyEnd>\n )}\n </div>\n );\n}\n\nexport default Popover;\n"],"mappings":"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"}
1
+ {"version":3,"file":"popover.mjs","names":[],"sources":["../../../../package/common/popover/popover.tsx"],"sourcesContent":["import { useRef, useEffect, useState, useCallback, type RefObject } from 'react';\nimport cx from 'classnames';\nimport BodyEnd from 'components/common/bodyEnd';\n\nimport styles from './popover.module.pcss';\nimport sheetStyles from '../styles/sheet.module.pcss';\nimport SheetBackdrop from '../sheetBackdrop';\nimport { usePopoverPosition } from './usePopoverPosition';\nimport { useFocusBoundary, tabbablesIn } from '../hooks/useFocusBoundary';\nimport { useSheet } from '../hooks/useSheet';\nimport { useOverlay } from '../hooks/useOverlay';\nimport { useComponentDefaults, usePresentationDefault } from '../configProvider';\nimport { Presentation } from '../configProvider/presentation.types';\nimport { PopoverPlacement, PopoverAlign, PopoverTrigger, type PopoverProps } from './popover.types';\n\n/**\n * How long after its click an anchored popover still counts as opened by it: long enough for the\n * render the click causes, short enough that an old keypress does not decide a later open.\n */\nconst ACTIVATION_WINDOW_MS = 1000;\n\nfunction toReference(\n anchor: HTMLElement | null | undefined,\n triggerRef: RefObject<HTMLElement | null>,\n): RefObject<HTMLElement | null> {\n return anchor ? { current: anchor } : triggerRef;\n}\n\nfunction Popover(props: PopoverProps) {\n const {\n children = null,\n anchor,\n content = null,\n placement = PopoverPlacement.Bottom,\n align = PopoverAlign.Center,\n trigger = PopoverTrigger.Click,\n isOpen,\n defaultOpen = false,\n onOpenChange = () => {},\n offset = 10,\n withArrow = true,\n closeOnOutsideClick = true,\n closeOnEscape = true,\n openDelay = 100,\n closeDelay = 120,\n disabled = false,\n focusOnOpen = 'keyboard',\n closeOnTriggerClick = true,\n width = 'auto',\n constrainWidth = true,\n presentation: presentationProp,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = useComponentDefaults('Popover', props);\n const triggerRef = useRef<HTMLDivElement>(null);\n const floatingRef = useRef<HTMLDivElement>(null);\n const panelRef = useRef<HTMLDivElement>(null);\n const arrowRef = useRef<HTMLDivElement>(null);\n const hoverTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n // Set from a capture handler on the panel, which React fires for its whole\n // React subtree — including children that portal elsewhere in the DOM, such as\n // a Dropdown's list inside a DatePicker's calendar. A DOM `contains` check\n // cannot see those, and without this the panel closes when one is used.\n const panelInteractedRef = useRef(false);\n // An outside click is a press that starts and ends outside while the panel is open. One that\n // starts inside is a drag, such as selecting a field's text, and Chromium and WebKit send its\n // `mouseup` to wherever it ends. One that started before the panel opened is the gesture that\n // opened it, such as Calendar's drag to create, which opens an editor on `pointerup`.\n const pressStartedOutsideRef = useRef(false);\n // Whether the open that is running came from the keyboard, which is what the\n // default `focusOnOpen` follows and what `onOpenChange` reports. A click raised\n // by Enter or Space reports `detail === 0`, where a pointer press counts the\n // clicks; an open with no trigger event at all, such as a panel that appears\n // while the user types, leaves this false.\n const openedByKeyboardRef = useRef(false);\n function markPanelInteraction() {\n panelInteractedRef.current = true;\n }\n\n // What the panel is placed against, and where the focus goes back to. A new object exactly when\n // `anchor` changes, which is what restarts the position tracking for the new element: state, not\n // a memo, because a memo React chose to drop would restart it for no reason.\n const [reference, setReference] = useState(() => ({ anchor, ref: toReference(anchor, triggerRef) }));\n if (reference.anchor !== anchor) setReference({ anchor, ref: toReference(anchor, triggerRef) });\n const referenceRef = reference.ref;\n\n // An anchored popover is opened by its consumer through `isOpen`, so none of the key handling\n // below sees how. The click that caused it does: Enter and Space raise one with `detail === 0`,\n // the same test the trigger uses. Not `:focus-visible`, which a text field matches after a\n // mouse click too, and which stays on a keyboard-focused button that is then clicked.\n const activationRef = useRef<{ target: Node; keyboard: boolean; time: number } | null>(null);\n const anchored = anchor !== undefined;\n useEffect(() => {\n if (!anchored) return;\n function onClick(event: MouseEvent) {\n activationRef.current = { target: event.target as Node, keyboard: event.detail === 0, time: performance.now() };\n }\n // Capturing, so it is recorded before the consumer's own handler opens the panel.\n document.addEventListener('click', onClick, true);\n return () => document.removeEventListener('click', onClick, true);\n }, [anchored]);\n\n const isControlled = isOpen !== undefined;\n const [internalOpen, setInternalOpen] = useState(defaultOpen);\n const open = isControlled ? isOpen : internalOpen;\n\n // Keep the outside-click/Escape listeners pointed at the latest setter without re-subscribing.\n const onOpenChangeRef = useRef(onOpenChange);\n useEffect(() => {\n onOpenChangeRef.current = onOpenChange;\n });\n\n const setOpen = useCallback(\n (next: boolean, fromKeyboard = false) => {\n if (disabled && next) return;\n if (next) openedByKeyboardRef.current = fromKeyboard;\n if (!isControlled) setInternalOpen(next);\n onOpenChangeRef.current(next, { fromKeyboard });\n },\n [disabled, isControlled],\n );\n\n // Explicit prop → this component's global default → the app-wide one → anchored.\n const appPresentation = usePresentationDefault();\n const presentation = presentationProp ?? appPresentation ?? Presentation.Default;\n const { isSheet: sheetPresentation, sheetStyle } = useSheet(presentation);\n // Hover and focus are pointer affordances that open something small next to what caused them;\n // a sheet is a deliberate, screen-owning open. Only a click asks for one.\n const isSheet = sheetPresentation && trigger === PopoverTrigger.Click;\n\n // A sheet isn't anchored to anything, so there's nothing to track, flip or clamp.\n usePopoverPosition({\n isOpen: !!open && !isSheet,\n placement,\n align,\n offset,\n triggerRef: referenceRef,\n floatingRef,\n panelRef,\n arrowRef: withArrow ? arrowRef : { current: null },\n matchTriggerWidth: width === 'trigger',\n });\n\n // Registered for as long as it's open, not just as a sheet: this is what puts the popover on\n // the overlay stack, so an enclosing Modal knows something sits above it and neither drags\n // focus out of this (portaled, so outside the modal's panel) panel nor closes itself on the\n // Escape meant for this. Focus isn't trapped: a Popover is deliberately non-modal, and it\n // already moves focus into the panel on open by itself.\n const { restoreFocusToTrigger, focusAfterTrigger } = useFocusBoundary({\n triggerRef: referenceRef,\n contentRef: floatingRef,\n });\n\n useOverlay({\n isActive: !!open,\n containerRef: panelRef,\n onEscape: closeOnEscape\n ? () => {\n // Hand focus back to the trigger when it currently sits inside the panel.\n if (floatingRef.current?.contains(document.activeElement)) restoreFocusToTrigger();\n setOpen(false, true);\n }\n : undefined,\n trapFocus: false,\n lockScroll: isSheet,\n });\n\n // The panel is portaled to the end of the DOM, so Tab never flows into it naturally. When we\n // manage focus (click trigger + focusable content), move focus in on open and, on Tab out of\n // either end, close and continue focus as if the panel sat right after the trigger.\n useEffect(() => {\n if (!open) {\n openedByKeyboardRef.current = false;\n return;\n }\n const activation = activationRef.current;\n activationRef.current = null;\n const anchorKeyed =\n !!anchor &&\n !!activation?.keyboard &&\n anchor.contains(activation.target) &&\n performance.now() - activation.time < ACTIVATION_WINDOW_MS;\n const takeFocus =\n focusOnOpen === true || (focusOnOpen === 'keyboard' && (openedByKeyboardRef.current || anchorKeyed));\n if (!takeFocus || trigger !== PopoverTrigger.Click) return;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n // Only claim the dialog role when we're actually managing focus for real content.\n floatingRef.current?.setAttribute('role', 'dialog');\n focusables[0].focus();\n }, [open, trigger, focusOnOpen, anchor]);\n\n function handleFloatingKeyDown(event: React.KeyboardEvent) {\n if (event.key !== 'Tab' || trigger !== PopoverTrigger.Click) return;\n const focusables = tabbablesIn(panelRef.current);\n if (focusables.length === 0) return;\n const active = document.activeElement;\n if (!event.shiftKey && active === focusables[focusables.length - 1]) {\n event.preventDefault();\n focusAfterTrigger();\n setOpen(false, true);\n } else if (event.shiftKey && active === focusables[0]) {\n event.preventDefault();\n restoreFocusToTrigger();\n setOpen(false, true);\n }\n }\n\n // Outside click. Registered only while open and only for the relevant trigger mode; Escape is\n // handled by `useOverlay` above, which scopes it to the innermost open layer.\n useEffect(() => {\n if (!open) return;\n // Focus that landed in the panel before these listeners existed (ours on open, or content\n // that focuses itself on mount) set the flag with nothing to clear it, and the first\n // click outside was swallowed.\n panelInteractedRef.current = false;\n pressStartedOutsideRef.current = false;\n\n function isInside(event: Event) {\n // Read and clear: the flag describes this one event, and the panel's React\n // handler has already run by the time this does.\n const inPanelSubtree = panelInteractedRef.current;\n panelInteractedRef.current = false;\n const target = event.target as Node;\n const inPanel = floatingRef.current?.contains(target);\n // Both: with an anchor, `children` still toggle the panel, and a press on them is theirs.\n const inTrigger = triggerRef.current?.contains(target) || referenceRef.current?.contains(target);\n return Boolean(inPanelSubtree || inPanel || inTrigger);\n }\n function onMouseDown(event: MouseEvent) {\n pressStartedOutsideRef.current = !isInside(event);\n }\n function onMouseUp(event: MouseEvent) {\n const pressStartedOutside = pressStartedOutsideRef.current;\n pressStartedOutsideRef.current = false;\n if (!isInside(event) && pressStartedOutside) setOpen(false);\n }\n // Keyboard counterpart to the outside click: once focus leaves both the trigger\n // and the (portaled) panel — e.g. tabbing on to the next element — close.\n function onFocusIn(event: FocusEvent) {\n if (!isInside(event)) setOpen(false, true);\n }\n\n if (closeOnOutsideClick && trigger !== PopoverTrigger.Hover) {\n window.addEventListener('mousedown', onMouseDown);\n window.addEventListener('mouseup', onMouseUp);\n document.addEventListener('focusin', onFocusIn);\n }\n return () => {\n window.removeEventListener('mousedown', onMouseDown);\n window.removeEventListener('mouseup', onMouseUp);\n document.removeEventListener('focusin', onFocusIn);\n };\n }, [open, closeOnOutsideClick, trigger, setOpen, referenceRef]);\n\n useEffect(() => () => clearTimeout(hoverTimer.current), []);\n\n function scheduleHover(next: boolean) {\n clearTimeout(hoverTimer.current);\n hoverTimer.current = setTimeout(() => setOpen(next), next ? openDelay : closeDelay);\n }\n\n // Opening from the keyboard without the trigger having to be a button that\n // happens to toggle on Enter: the arrow keys are what the menu-button pattern\n // asks for, and what a consumer reaches for first.\n function handleTriggerKeyDown(event: React.KeyboardEvent) {\n if (open || (event.key !== 'ArrowDown' && event.key !== 'ArrowUp')) return;\n event.preventDefault();\n setOpen(true, true);\n }\n\n const triggerHandlers =\n trigger === PopoverTrigger.Click\n ? {\n onClick: (event: React.MouseEvent) => {\n // A field that opens a picker keeps it open when clicked again, so\n // that placing the caret in the field does not close the panel.\n if (open && !closeOnTriggerClick) return;\n setOpen(!open, event.detail === 0);\n },\n onKeyDown: handleTriggerKeyDown,\n }\n : trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : { onFocus: () => setOpen(true), onBlur: () => setOpen(false) };\n\n const floatingHoverHandlers =\n trigger === PopoverTrigger.Hover\n ? { onMouseEnter: () => scheduleHover(true), onMouseLeave: () => scheduleHover(false) }\n : {};\n\n return (\n <div\n {...rest}\n className={cx(styles.popover, className, classNames?.root)}\n style={{ ...style, ...slotStyles?.root }}\n >\n <div\n ref={triggerRef}\n className={cx(styles.popoverTrigger, classNames?.trigger)}\n style={slotStyles?.trigger}\n {...triggerHandlers}\n >\n {children}\n </div>\n {open && (\n <BodyEnd>\n {isSheet && (\n <SheetBackdrop\n onClose={() => {\n if (closeOnOutsideClick) setOpen(false);\n }}\n className={classNames?.backdrop}\n style={slotStyles?.backdrop}\n />\n )}\n <div\n ref={floatingRef}\n className={cx(isSheet ? sheetStyles.sheet : styles.popoverFloating, classNames?.floating)}\n style={{ ...(isSheet ? sheetStyle : undefined), ...slotStyles?.floating }}\n data-placement={placement}\n onKeyDown={handleFloatingKeyDown}\n onMouseDownCapture={markPanelInteraction}\n onMouseUpCapture={markPanelInteraction}\n onFocusCapture={markPanelInteraction}\n {...floatingHoverHandlers}\n >\n <div\n ref={panelRef}\n className={cx(\n styles.popoverPanel,\n constrainWidth && styles.popoverPanelConstrained,\n isSheet && sheetStyles.sheetPanel,\n classNames?.panel,\n )}\n style={slotStyles?.panel}\n >\n {content}\n </div>\n {withArrow && !isSheet && (\n <div ref={arrowRef} className={cx(styles.popoverArrow, classNames?.arrow)} style={slotStyles?.arrow} />\n )}\n </div>\n </BodyEnd>\n )}\n </div>\n );\n}\n\nexport default Popover;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use client";import e from"../styles/scrollFade.module.mjs";import{mergeRefs as t}from"../utils/mergeRefs.mjs";import{useScrollEdges as n}from"../hooks/useScrollEdges.mjs";import{ScrollFadeAxis as r}from"./scrollFade.types.mjs";import i from"./scrollFade.module.mjs";import{useRef as a}from"react";import o from"classnames";import{jsx as s}from"react/jsx-runtime";function c({children:c=null,axis:l=r.Vertical,fadeLength:u,isAlwaysFaded:d=!1,className:f,style:p,ref:m,...h}){let g=a(null),_=l===r.Horizontal,{hasStart:v,hasEnd:y}=n(g,{axis:l,isEnabled:!d});return s(`div`,{...h,ref:t(g,m),className:o(_?i.scrollFadeHorizontal:i.scrollFadeVertical,e.scrollFade,_?e.scrollFadeX:e.scrollFadeY,(d||v)&&e.scrollFadeFadedStart,(d||y)&&e.scrollFadeFadedEnd,f),style:{...u?{"--custom-scroll-fade-length":u}:void 0,...p},children:c})}export{c as default};
1
+ "use client";import{mergeRefs as e}from"../utils/mergeRefs.mjs";import t from"../styles/scrollFade.module.mjs";import{useScrollEdges as n}from"../hooks/useScrollEdges.mjs";import{ScrollFadeAxis as r}from"./scrollFade.types.mjs";import i from"./scrollFade.module.mjs";import{useRef as a}from"react";import o from"classnames";import{jsx as s}from"react/jsx-runtime";function c({children:c=null,axis:l=r.Vertical,fadeLength:u,isAlwaysFaded:d=!1,className:f,style:p,ref:m,...h}){let g=a(null),_=l===r.Horizontal,{hasStart:v,hasEnd:y}=n(g,{axis:l,isEnabled:!d});return s(`div`,{...h,ref:e(g,m),className:o(_?i.scrollFadeHorizontal:i.scrollFadeVertical,t.scrollFade,_?t.scrollFadeX:t.scrollFadeY,(d||v)&&t.scrollFadeFadedStart,(d||y)&&t.scrollFadeFadedEnd,f),style:{...u?{"--custom-scroll-fade-length":u}:void 0,...p},children:c})}export{c as default};
2
2
  //# sourceMappingURL=scrollFade.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tabHeader.module.mjs","names":[],"sources":["../../../../package/common/tabHeader/tabHeader.module.pcss"],"sourcesContent":[".tabHeader {\n display: flex;\n align-items: stretch;\n gap: 4px;\n border-bottom: 1px solid var(--border-color);\n user-select: none;\n overflow-x: auto;\n\n &-tab {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n color: var(--text-dark);\n border-bottom: 2px solid transparent;\n border-radius: var(--default-border-radius) var(--default-border-radius) 0 0;\n transition:\n color 0.2s,\n background 0.2s,\n border-color 0.2s;\n white-space: nowrap;\n\n &:hover {\n /* Longhand, not the shorthand: hover beats -active on specificity, and the shorthand\n would reset the active tab's gradient indicator away while the pointer is over it. */\n background-color: var(--background-accent-light);\n color: var(--text-color);\n }\n\n &-active {\n color: var(--primary-color);\n border-bottom-color: var(--primary-color);\n\n /* Paints the gradient exactly over border-bottom, which a background layer only reaches\n because its origin is the border box. The border stays as the no-gradient fallback. */\n background-image: var(--primary-gradient, none);\n background-repeat: no-repeat;\n background-size: 100% 2px;\n background-position: bottom;\n background-origin: border-box;\n\n &:hover {\n color: var(--primary-color);\n }\n }\n\n &-disabled {\n opacity: 0.5;\n pointer-events: none;\n cursor: default;\n }\n }\n\n &-icon {\n font-size: 18px;\n flex-shrink: 0;\n }\n\n &-labels {\n display: flex;\n flex-direction: column;\n text-align: left;\n line-height: 1.25;\n }\n\n &-title {\n font-weight: var(--header-font-weight, 600);\n font-size: var(--default-font-size);\n }\n\n &-subtitle {\n font-size: var(--text-small-font-size);\n color: var(--text-dark);\n }\n}\n\n@media (width <= 640px) {\n .tabHeader-tab:not(.tabHeader-tab-active) .tabHeader-labels {\n display: none;\n }\n}\n"],"mappings":""}
1
+ {"version":3,"file":"tabHeader.module.mjs","names":[],"sources":["../../../../package/common/tabHeader/tabHeader.module.pcss"],"sourcesContent":[".tabHeader {\n display: flex;\n align-items: stretch;\n gap: 4px;\n border-bottom: 1px solid var(--border-color);\n user-select: none;\n\n /* The y value is not redundant: overflow-x: auto alone computes overflow-y to auto, so any\n vertical overflow, such as a flex column squeezing the strip, let the tabs scroll up and down. */\n overflow: auto hidden;\n\n &-tab {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 12px 16px;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n color: var(--text-dark);\n border-bottom: 2px solid transparent;\n border-radius: var(--default-border-radius) var(--default-border-radius) 0 0;\n transition:\n color 0.2s,\n background 0.2s,\n border-color 0.2s;\n white-space: nowrap;\n\n &:hover {\n /* Longhand, not the shorthand: hover beats -active on specificity, and the shorthand\n would reset the active tab's gradient indicator away while the pointer is over it. */\n background-color: var(--background-accent-light);\n color: var(--text-color);\n }\n\n &-active {\n color: var(--primary-color);\n border-bottom-color: var(--primary-color);\n\n /* Paints the gradient exactly over border-bottom, which a background layer only reaches\n because its origin is the border box. The border stays as the no-gradient fallback. */\n background-image: var(--primary-gradient, none);\n background-repeat: no-repeat;\n background-size: 100% 2px;\n background-position: bottom;\n background-origin: border-box;\n\n &:hover {\n color: var(--primary-color);\n }\n }\n\n &-disabled {\n opacity: 0.5;\n pointer-events: none;\n cursor: default;\n }\n }\n\n &-icon {\n font-size: 18px;\n flex-shrink: 0;\n }\n\n &-labels {\n display: flex;\n flex-direction: column;\n text-align: left;\n line-height: 1.25;\n }\n\n &-title {\n font-weight: var(--header-font-weight, 600);\n font-size: var(--default-font-size);\n }\n\n &-subtitle {\n font-size: var(--text-small-font-size);\n color: var(--text-dark);\n }\n}\n\n@media (width <= 640px) {\n .tabHeader-tab:not(.tabHeader-tab-active) .tabHeader-labels {\n display: none;\n }\n}\n"],"mappings":""}
@@ -1,2 +1,2 @@
1
- "use client";var e=/^(\d{4})-(\d{2})-(\d{2})$/;function t({year:e,month:t}){let n=new Date(0);return n.setUTCFullYear(e,t,0),n.getUTCDate()}function n(n){if(typeof n!=`string`)return null;let r=e.exec(n);if(!r)return null;let i=Number(r[1]),a=Number(r[2]),o=Number(r[3]);return a<1||a>12||o<1||o>t({year:i,month:a})?null:{year:i,month:a,day:o}}function r(e){return n(e)?e:null}function i({year:e,month:t,day:n}){return`${String(e).padStart(4,`0`)}-${String(t).padStart(2,`0`)}-${String(n).padStart(2,`0`)}`}function a(e){return Number.isNaN(e.getTime())?``:i({year:e.getFullYear(),month:e.getMonth()+1,day:e.getDate()})}function o(e){let t=n(e);if(!t)return new Date(NaN);let r=new Date(2e3,0,1);return r.setFullYear(t.year,t.month-1,t.day),r}export{r as asDay,a as dateToDay,o as dayToDate,t as daysInMonth,i as formatDay,n as parseDay};
1
+ "use client";var e=/^(\d{4})-(\d{2})-(\d{2})$/;function t({year:e,month:t}){let n=new Date(0);return n.setUTCFullYear(e,t,0),n.getUTCDate()}function n(n){if(typeof n!=`string`)return null;let r=e.exec(n);if(!r)return null;let i=Number(r[1]),a=Number(r[2]),o=Number(r[3]);return a<1||a>12||o<1||o>t({year:i,month:a})?null:{year:i,month:a,day:o}}function r(e){return n(e)?e:null}function i({year:e,month:t,day:n}){return`${String(e).padStart(4,`0`)}-${String(t).padStart(2,`0`)}-${String(n).padStart(2,`0`)}`}function a(e){return Number.isNaN(e.getTime())?``:i({year:e.getFullYear(),month:e.getMonth()+1,day:e.getDate()})}function o(e){let t=n(e);if(!t)return new Date(NaN);let r=new Date(2e3,0,1);return r.setFullYear(t.year,t.month-1,t.day),r}var s=/^(\d{2}):(\d{2})$/;function c(e){return Number.isNaN(e.getTime())?``:`${String(e.getHours()).padStart(2,`0`)}:${String(e.getMinutes()).padStart(2,`0`)}`}function l(e,t){let n=o(e),r=s.exec(t),i=r?Number(r[1]):NaN,a=r?Number(r[2]):NaN;return Number.isNaN(n.getTime())||!(i<=23&&a<=59)?new Date(NaN):(n.setHours(i,a,0,0),n)}export{r as asDay,a as dateToDay,c as dateToTime,l as dayTimeToDate,o as dayToDate,t as daysInMonth,i as formatDay,n as parseDay};
2
2
  //# sourceMappingURL=day.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"day.mjs","names":[],"sources":["../../../../package/common/utils/day.ts"],"sourcesContent":["// A day is a `'YYYY-MM-DD'` string, the value of `<input type=\"date\">`: no time and no time zone,\n// so it is the same day for everyone. A `Date` is a moment and cannot be one.\n//\n// Dates are built with `setFullYear` rather than the `Date` constructor or `Date.UTC`, which read\n// the years 0 to 99 as 1900 to 1999.\n\nexport interface DayParts {\n year: number;\n /** 1 to 12. */\n month: number;\n day: number;\n}\n\nconst DAY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\nexport function daysInMonth({ year, month }: Pick<DayParts, 'year' | 'month'>): number {\n const date = new Date(0);\n date.setUTCFullYear(year, month, 0);\n return date.getUTCDate();\n}\n\n/** The parts of a `'YYYY-MM-DD'` string, or `null` for anything else, 31 February included. */\nexport function parseDay(value: unknown): DayParts | null {\n if (typeof value !== 'string') return null;\n const match = DAY.exec(value);\n if (!match) return null;\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n if (month < 1 || month > 12 || day < 1 || day > daysInMonth({ year, month })) return null;\n return { year, month, day };\n}\n\n/** `value` itself when it is a `'YYYY-MM-DD'` day that exists, otherwise `null`. */\nexport function asDay(value: unknown): string | null {\n return parseDay(value) ? (value as string) : null;\n}\n\nexport function formatDay({ year, month, day }: DayParts): string {\n return `${String(year).padStart(4, '0')}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;\n}\n\n/** The day `date` falls on in the browser's time zone, as `'YYYY-MM-DD'`; `''` for an Invalid Date. */\nexport function dateToDay(date: Date): string {\n if (Number.isNaN(date.getTime())) return '';\n return formatDay({ year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate() });\n}\n\n/** The first moment of a `'YYYY-MM-DD'` day in the browser's time zone. An Invalid Date for anything else. */\nexport function dayToDate(day: string): Date {\n const parts = parseDay(day);\n if (!parts) return new Date(NaN);\n const date = new Date(2000, 0, 1);\n date.setFullYear(parts.year, parts.month - 1, parts.day);\n return date;\n}\n"],"mappings":"aAaA,IAAM,EAAM,4BAEZ,SAAgB,EAAY,CAAE,OAAM,SAAmD,CACrF,IAAM,EAAO,IAAI,KAAK,CAAC,EAEvB,OADA,EAAK,eAAe,EAAM,EAAO,CAAC,EAC3B,EAAK,WAAW,CACzB,CAGA,SAAgB,EAAS,EAAiC,CACxD,GAAI,OAAO,GAAU,SAAU,OAAO,KACtC,IAAM,EAAQ,EAAI,KAAK,CAAK,EAC5B,GAAI,CAAC,EAAO,OAAO,KACnB,IAAM,EAAO,OAAO,EAAM,EAAE,EACtB,EAAQ,OAAO,EAAM,EAAE,EACvB,EAAM,OAAO,EAAM,EAAE,EAE3B,OADI,EAAQ,GAAK,EAAQ,IAAM,EAAM,GAAK,EAAM,EAAY,CAAE,OAAM,OAAM,CAAC,EAAU,KAC9E,CAAE,OAAM,QAAO,KAAI,CAC5B,CAGA,SAAgB,EAAM,EAA+B,CACnD,OAAO,EAAS,CAAK,EAAK,EAAmB,IAC/C,CAEA,SAAgB,EAAU,CAAE,OAAM,QAAO,OAAyB,CAChE,MAAO,GAAG,OAAO,CAAI,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAAG,OAAO,CAAK,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAAG,OAAO,CAAG,CAAC,CAAC,SAAS,EAAG,GAAG,GAC1G,CAGA,SAAgB,EAAU,EAAoB,CAE5C,OADI,OAAO,MAAM,EAAK,QAAQ,CAAC,EAAU,GAClC,EAAU,CAAE,KAAM,EAAK,YAAY,EAAG,MAAO,EAAK,SAAS,EAAI,EAAG,IAAK,EAAK,QAAQ,CAAE,CAAC,CAChG,CAGA,SAAgB,EAAU,EAAmB,CAC3C,IAAM,EAAQ,EAAS,CAAG,EAC1B,GAAI,CAAC,EAAO,OAAO,IAAI,KAAK,GAAG,EAC/B,IAAM,EAAO,IAAI,KAAK,IAAM,EAAG,CAAC,EAEhC,OADA,EAAK,YAAY,EAAM,KAAM,EAAM,MAAQ,EAAG,EAAM,GAAG,EAChD,CACT"}
1
+ {"version":3,"file":"day.mjs","names":[],"sources":["../../../../package/common/utils/day.ts"],"sourcesContent":["// A day is a `'YYYY-MM-DD'` string, the value of `<input type=\"date\">`: no time and no time zone,\n// so it is the same day for everyone. A `Date` is a moment and cannot be one.\n//\n// Dates are built with `setFullYear` rather than the `Date` constructor or `Date.UTC`, which read\n// the years 0 to 99 as 1900 to 1999.\n\nexport interface DayParts {\n year: number;\n /** 1 to 12. */\n month: number;\n day: number;\n}\n\nconst DAY = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\n\nexport function daysInMonth({ year, month }: Pick<DayParts, 'year' | 'month'>): number {\n const date = new Date(0);\n date.setUTCFullYear(year, month, 0);\n return date.getUTCDate();\n}\n\n/** The parts of a `'YYYY-MM-DD'` string, or `null` for anything else, 31 February included. */\nexport function parseDay(value: unknown): DayParts | null {\n if (typeof value !== 'string') return null;\n const match = DAY.exec(value);\n if (!match) return null;\n const year = Number(match[1]);\n const month = Number(match[2]);\n const day = Number(match[3]);\n if (month < 1 || month > 12 || day < 1 || day > daysInMonth({ year, month })) return null;\n return { year, month, day };\n}\n\n/** `value` itself when it is a `'YYYY-MM-DD'` day that exists, otherwise `null`. */\nexport function asDay(value: unknown): string | null {\n return parseDay(value) ? (value as string) : null;\n}\n\nexport function formatDay({ year, month, day }: DayParts): string {\n return `${String(year).padStart(4, '0')}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;\n}\n\n/** The day `date` falls on in the browser's time zone, as `'YYYY-MM-DD'`; `''` for an Invalid Date. */\nexport function dateToDay(date: Date): string {\n if (Number.isNaN(date.getTime())) return '';\n return formatDay({ year: date.getFullYear(), month: date.getMonth() + 1, day: date.getDate() });\n}\n\n/** The first moment of a `'YYYY-MM-DD'` day in the browser's time zone. An Invalid Date for anything else. */\nexport function dayToDate(day: string): Date {\n const parts = parseDay(day);\n if (!parts) return new Date(NaN);\n const date = new Date(2000, 0, 1);\n date.setFullYear(parts.year, parts.month - 1, parts.day);\n return date;\n}\n\nconst TIME = /^(\\d{2}):(\\d{2})$/;\n\n/** The `'HH:MM'` of `date` in the browser's time zone, the value a TimeInput holds; `''` for an Invalid Date. */\nexport function dateToTime(date: Date): string {\n if (Number.isNaN(date.getTime())) return '';\n return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;\n}\n\n/**\n * The moment a `'YYYY-MM-DD'` day and an `'HH:MM'` time stand for in the browser's time zone,\n * DatePicker's and TimeInput's values together. A time the clock skips that day resolves forwards\n * (02:30 becomes 03:30), a repeated one to its first occurrence. An Invalid Date for anything else.\n */\nexport function dayTimeToDate(day: string, time: string): Date {\n const date = dayToDate(day);\n const match = TIME.exec(time);\n const hours = match ? Number(match[1]) : NaN;\n const minutes = match ? Number(match[2]) : NaN;\n if (Number.isNaN(date.getTime()) || !(hours <= 23 && minutes <= 59)) return new Date(NaN);\n date.setHours(hours, minutes, 0, 0);\n return date;\n}\n"],"mappings":"aAaA,IAAM,EAAM,4BAEZ,SAAgB,EAAY,CAAE,OAAM,SAAmD,CACrF,IAAM,EAAO,IAAI,KAAK,CAAC,EAEvB,OADA,EAAK,eAAe,EAAM,EAAO,CAAC,EAC3B,EAAK,WAAW,CACzB,CAGA,SAAgB,EAAS,EAAiC,CACxD,GAAI,OAAO,GAAU,SAAU,OAAO,KACtC,IAAM,EAAQ,EAAI,KAAK,CAAK,EAC5B,GAAI,CAAC,EAAO,OAAO,KACnB,IAAM,EAAO,OAAO,EAAM,EAAE,EACtB,EAAQ,OAAO,EAAM,EAAE,EACvB,EAAM,OAAO,EAAM,EAAE,EAE3B,OADI,EAAQ,GAAK,EAAQ,IAAM,EAAM,GAAK,EAAM,EAAY,CAAE,OAAM,OAAM,CAAC,EAAU,KAC9E,CAAE,OAAM,QAAO,KAAI,CAC5B,CAGA,SAAgB,EAAM,EAA+B,CACnD,OAAO,EAAS,CAAK,EAAK,EAAmB,IAC/C,CAEA,SAAgB,EAAU,CAAE,OAAM,QAAO,OAAyB,CAChE,MAAO,GAAG,OAAO,CAAI,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAAG,OAAO,CAAK,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAAG,OAAO,CAAG,CAAC,CAAC,SAAS,EAAG,GAAG,GAC1G,CAGA,SAAgB,EAAU,EAAoB,CAE5C,OADI,OAAO,MAAM,EAAK,QAAQ,CAAC,EAAU,GAClC,EAAU,CAAE,KAAM,EAAK,YAAY,EAAG,MAAO,EAAK,SAAS,EAAI,EAAG,IAAK,EAAK,QAAQ,CAAE,CAAC,CAChG,CAGA,SAAgB,EAAU,EAAmB,CAC3C,IAAM,EAAQ,EAAS,CAAG,EAC1B,GAAI,CAAC,EAAO,OAAO,IAAI,KAAK,GAAG,EAC/B,IAAM,EAAO,IAAI,KAAK,IAAM,EAAG,CAAC,EAEhC,OADA,EAAK,YAAY,EAAM,KAAM,EAAM,MAAQ,EAAG,EAAM,GAAG,EAChD,CACT,CAEA,IAAM,EAAO,oBAGb,SAAgB,EAAW,EAAoB,CAE7C,OADI,OAAO,MAAM,EAAK,QAAQ,CAAC,EAAU,GAClC,GAAG,OAAO,EAAK,SAAS,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,EAAE,GAAG,OAAO,EAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAG,GAAG,GACjG,CAOA,SAAgB,EAAc,EAAa,EAAoB,CAC7D,IAAM,EAAO,EAAU,CAAG,EACpB,EAAQ,EAAK,KAAK,CAAI,EACtB,EAAQ,EAAQ,OAAO,EAAM,EAAE,EAAI,IACnC,EAAU,EAAQ,OAAO,EAAM,EAAE,EAAI,IAG3C,OAFI,OAAO,MAAM,EAAK,QAAQ,CAAC,GAAK,EAAE,GAAS,IAAM,GAAW,IAAY,IAAI,KAAK,GAAG,GACxF,EAAK,SAAS,EAAO,EAAS,EAAG,CAAC,EAC3B,EACT"}
@@ -1,2 +1,2 @@
1
- "use client";import{COLUMN_STORAGE_PREFIX as e,buildColumnTemplate as t,buildFrozenTemplate as n,columnWidthVar as r,isColumnInitiallyVisible as i,loadPersistedColumns as a,measureCellWidth as o}from"./virtualList.utils.mjs";import{useCallback as s,useEffect as c,useLayoutEffect as l,useRef as u,useState as d}from"react";function f({columns:f,items:p,multiSelect:m,visibleColumnKeys:h,onVisibleColumnsChange:g,persistColumnsKey:_,showColumnToggle:v,userWidths:y,isFrozen:b,rootRef:x,startIndex:S,endIndex:C,containerHeight:w,containerWidth:T}){let E=h!==void 0,[D,O]=d(!1),[k,A]=d({}),[j,M]=d(()=>{let e=a(_);return new Set((f??[]).filter(t=>i(t,e)).map(e=>e.key))}),N=u(new Set((f??[]).map(e=>e.key)));c(()=>{if(E)return;let e=f??[],t=new Set(e.map(e=>e.key)),n=N.current,r=t.size===n.size;if(r){for(let e of t)if(!n.has(e)){r=!1;break}}if(r)return;N.current=t;let o=a(_);M(t=>{let r=new Set;for(let a of e)n.has(a.key)?t.has(a.key)&&r.add(a.key):i(a,o)&&r.add(a.key);return r})},[f,E,_]),c(()=>{if(E||!_||typeof window>`u`)return;let t=f??[];if(t.length!==0&&j.size!==0)try{window.localStorage.setItem(e+_,JSON.stringify({visible:[...j],known:t.map(e=>e.key),widths:y}))}catch{}},[j,f,E,_,y]);let P=E?new Set(h):j,F=(f??[]).filter(e=>e.lockVisible).map(e=>e.key),I=F.some(e=>!P.has(e))?new Set([...P,...F]):P,L=!!f?.length,R=L?f.filter(e=>I.has(e.key)):[],z=L?f.filter(e=>!e.defaultHidden&&!I.has(e.key)).length:0,B=L?`${m&&L?`40px `:``}${b?n(R):t(R,k)}`:void 0,V=v!==!1&&L&&f.some(e=>!e.hideFromToggle),H={};if(b&&L){let e=R.map(e=>`var(${r(e.key)}, 160px)`);m&&e.unshift(`40px`),V&&e.push(`32px`),H[`--vl-total`]=`calc(${e.join(` + `)})`}let U=R.filter(e=>e.width.type===`fit`),W=!b&&U.length>0,G=U.map(e=>e.key).join(`|`),[K,q]=d({key:G,items:p});(K.key!==G||K.items!==p)&&(q({key:G,items:p}),Object.keys(k).length>0&&A({}));let J=s(()=>{let e=x.current;if(!e)return;let t=new Map;e.querySelectorAll(`[data-fit-key]`).forEach(e=>{let n=e,r=n.getAttribute(`data-fit-key`);if(!r)return;let i=o(n);i>(t.get(r)??0)&&t.set(r,i)}),t.size!==0&&A(n=>{let r=!1,i={...n};for(let[a,o]of t){if(o<=0)continue;let t=Math.min(Math.ceil(o),e.clientWidth);t>(n[a]??0)&&(i[a]=t,r=!0)}return r?i:n})},[x]),[Y]=d(()=>typeof ResizeObserver>`u`?null:new ResizeObserver(()=>J())),X=u(new Set);c(()=>()=>{Y?.disconnect(),X.current.clear()},[Y]),l(()=>{if(!W)return;let e=x.current;if(!e||(J(),!Y))return;let t=new Set(e.querySelectorAll(`[data-fit-key]`)),n=X.current;for(let e of n)t.has(e)||(Y.unobserve(e),n.delete(e));for(let e of t)n.has(e)||(Y.observe(e),n.add(e))},[x,W,G,k,S,C,w,T,p,J,Y]),c(()=>{if(!W)return;let e=0,t=requestAnimationFrame(()=>{J(),e=requestAnimationFrame(()=>J())});return()=>{cancelAnimationFrame(t),cancelAnimationFrame(e)}},[W,G,p,J]);let Z=s(()=>A({}),[]);function Q(e){if(f?.find(t=>t.key===e)?.lockVisible)return;let t=new Set(I);if(t.has(e)){if(t.size<=1)return;t.delete(e)}else t.add(e);let n=[...t];E||M(t),g?.(n)}return{hasColumns:L,visibleColumns:R,columnTemplate:B,widthVars:H,shouldShowToggle:V,effectiveVisible:I,manuallyHiddenCount:z,toggleOpen:D,setToggleOpen:O,handleToggleColumn:Q,recalculateColumns:Z}}export{f as useColumns};
1
+ "use client";import{COLUMN_STORAGE_PREFIX as e,buildColumnTemplate as t,buildFrozenTemplate as n,columnWidthVar as r,isColumnInitiallyVisible as i,loadPersistedColumns as a,measureCellWidth as o}from"./virtualList.utils.mjs";import{useCallback as s,useEffect as c,useLayoutEffect as l,useRef as u,useState as d}from"react";function f({columns:f,items:p,multiSelect:m,visibleColumnKeys:h,onVisibleColumnsChange:g,persistColumnsKey:_,showColumnToggle:v,userWidths:y,isFrozen:b,rootRef:x,startIndex:S,endIndex:C,containerHeight:w,containerWidth:T,isLoading:E}){let D=h!==void 0,[O,k]=d(!1),[A,j]=d({}),[M,N]=d(()=>{let e=a(_);return new Set((f??[]).filter(t=>i(t,e)).map(e=>e.key))}),P=u(new Set((f??[]).map(e=>e.key)));c(()=>{if(D)return;let e=f??[],t=new Set(e.map(e=>e.key)),n=P.current,r=t.size===n.size;if(r){for(let e of t)if(!n.has(e)){r=!1;break}}if(r)return;P.current=t;let o=a(_);N(t=>{let r=new Set;for(let a of e)n.has(a.key)?t.has(a.key)&&r.add(a.key):i(a,o)&&r.add(a.key);return r})},[f,D,_]),c(()=>{if(D||!_||typeof window>`u`)return;let t=f??[];if(t.length!==0&&M.size!==0)try{window.localStorage.setItem(e+_,JSON.stringify({visible:[...M],known:t.map(e=>e.key),widths:y}))}catch{}},[M,f,D,_,y]);let F=D?new Set(h):M,I=(f??[]).filter(e=>e.lockVisible).map(e=>e.key),L=I.some(e=>!F.has(e))?new Set([...F,...I]):F,R=!!f?.length,z=R?f.filter(e=>L.has(e.key)):[],B=R?f.filter(e=>!e.defaultHidden&&!L.has(e.key)).length:0,V=R?`${m&&R?`40px `:``}${b?n(z):t(z,A)}`:void 0,H=v!==!1&&R&&f.some(e=>!e.hideFromToggle),U={};if(b&&R){let e=z.map(e=>`var(${r(e.key)}, 160px)`);m&&e.unshift(`40px`),H&&e.push(`32px`),U[`--vl-total`]=`calc(${e.join(` + `)})`}let W=z.filter(e=>e.width.type===`fit`),G=!b&&W.length>0,K=W.map(e=>e.key).join(`|`),[q,J]=d({key:K,items:p});(q.key!==K||q.items!==p)&&(J({key:K,items:p}),Object.keys(A).length>0&&j({}));let Y=s(()=>{let e=x.current;if(!e)return;let t=new Map;e.querySelectorAll(`[data-fit-key]`).forEach(e=>{let n=e,r=n.getAttribute(`data-fit-key`);if(!r)return;let i=o(n);i>(t.get(r)??0)&&t.set(r,i)}),t.size!==0&&j(n=>{let r=!1,i={...n};for(let[a,o]of t){if(o<=0)continue;let t=Math.min(Math.ceil(o),e.clientWidth);t>(n[a]??0)&&(i[a]=t,r=!0)}return r?i:n})},[x]),[X]=d(()=>typeof ResizeObserver>`u`?null:new ResizeObserver(()=>Y())),Z=u(new Set);c(()=>()=>{X?.disconnect(),Z.current.clear()},[X]),l(()=>{if(!G)return;let e=x.current;if(!e||(Y(),!X))return;let t=new Set(e.querySelectorAll(`[data-fit-key]`)),n=Z.current;for(let e of n)t.has(e)||(X.unobserve(e),n.delete(e));for(let e of t)n.has(e)||(X.observe(e),n.add(e))},[x,G,K,A,S,C,w,T,p,E,Y,X]),c(()=>{if(!G)return;let e=0,t=requestAnimationFrame(()=>{Y(),e=requestAnimationFrame(()=>Y())});return()=>{cancelAnimationFrame(t),cancelAnimationFrame(e)}},[G,K,p,E,Y]);let Q=s(()=>j({}),[]);function $(e){if(f?.find(t=>t.key===e)?.lockVisible)return;let t=new Set(L);if(t.has(e)){if(t.size<=1)return;t.delete(e)}else t.add(e);let n=[...t];D||N(t),g?.(n)}return{hasColumns:R,visibleColumns:z,columnTemplate:V,widthVars:U,shouldShowToggle:H,effectiveVisible:L,manuallyHiddenCount:B,toggleOpen:O,setToggleOpen:k,handleToggleColumn:$,recalculateColumns:Q}}export{f as useColumns};
2
2
  //# sourceMappingURL=useColumns.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"useColumns.mjs","names":[],"sources":["../../../../package/common/virtualList/useColumns.ts"],"sourcesContent":["import React, { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';\n\nimport {\n buildColumnTemplate,\n buildFrozenTemplate,\n columnWidthVar,\n DEFAULT_FROZEN_WIDTH,\n measureCellWidth,\n loadPersistedColumns,\n isColumnInitiallyVisible,\n COLUMN_STORAGE_PREFIX,\n type PersistedColumns,\n} from './virtualList.utils';\nimport type { VirtualListColumn } from './virtualList.types';\n\ninterface UseColumnsArgs<T> {\n columns?: VirtualListColumn<T>[];\n items: T[];\n multiSelect: boolean;\n visibleColumnKeys?: string[];\n onVisibleColumnsChange?: (keys: string[]) => void;\n persistColumnsKey?: string;\n showColumnToggle?: boolean;\n /** User-set column widths, from `useColumnResize`. */\n userWidths: Record<string, number>;\n /** Whether those widths have taken over the layout. */\n isFrozen: boolean;\n rootRef: React.RefObject<HTMLDivElement | null>;\n /** Visible-window bounds — re-measure `fit` columns as new cells scroll in. */\n startIndex: number;\n endIndex: number;\n containerHeight: number;\n /** Re-measure `fit` columns when the container width settles (e.g. after a route transition). */\n containerWidth: number;\n}\n\nexport interface UseColumnsResult<T> {\n hasColumns: boolean;\n visibleColumns: VirtualListColumn<T>[];\n columnTemplate: string | undefined;\n /**\n * Custom properties for the list element: the total width of a frozen layout,\n * as a `calc()` over the per-column properties so that rewriting one width\n * during a drag updates the total without any bookkeeping.\n */\n widthVars: React.CSSProperties;\n shouldShowToggle: boolean;\n effectiveVisible: Set<string>;\n /** How many default-visible columns the user has hidden (excludes defaultHidden). */\n manuallyHiddenCount: number;\n toggleOpen: boolean;\n setToggleOpen: React.Dispatch<React.SetStateAction<boolean>>;\n handleToggleColumn: (key: string) => void;\n /** Imperative escape hatch: drop fit-column pins so they re-measure on the\n * next layout. For out-of-band content changes that don't flow through a new\n * `items` array or `columns` set (which reset automatically). */\n recalculateColumns: () => void;\n}\n\n/**\n * Table-mode column logic: visibility (controlled or uncontrolled, with\n * localStorage persistence and runtime reconciliation), the toggle popover, and\n * `fit`-column width measurement that pins one shared px track across the header\n * and every row grid so they stay aligned.\n */\nexport function useColumns<T>({\n columns,\n items,\n multiSelect,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n showColumnToggle,\n userWidths,\n isFrozen,\n rootRef,\n startIndex,\n endIndex,\n containerHeight,\n containerWidth,\n}: UseColumnsArgs<T>): UseColumnsResult<T> {\n const isControlled = visibleColumnKeys !== undefined;\n const [toggleOpen, setToggleOpen] = useState(false);\n // Measured px widths for `fit` columns, keyed by column key. Pinning these\n // into the shared column template keeps the header and rows aligned (a raw\n // `fit-content` track resolves per-grid and drifts). See the effects below.\n const [fitWidths, setFitWidths] = useState<Record<string, number>>({});\n\n const [internalVisibleKeys, setInternalVisibleKeys] = useState<Set<string>>(() => {\n const persisted = loadPersistedColumns(persistColumnsKey);\n return new Set((columns ?? []).filter((c) => isColumnInitiallyVisible(c, persisted)).map((c) => c.key));\n });\n // The set of column keys the internal visibility state was last reconciled\n // against. The lazy initializer above only runs at mount, so without this a\n // list that mounts in list mode (no columns) and later switches to table mode\n // would keep an empty visible set and render no columns.\n const knownColumnKeysRef = useRef<Set<string>>(new Set((columns ?? []).map((c) => c.key)));\n\n // Re-derive internal visibility when the column set changes at runtime: new\n // columns get their saved/default visibility, removed columns drop out, and\n // existing columns keep whatever the user toggled them to. This is also what\n // applies persisted settings when a list switches from list mode to table\n // mode (columns absent at mount, supplied later).\n useEffect(() => {\n if (isControlled) return;\n const cols = columns ?? [];\n const currentKeys = new Set(cols.map((c) => c.key));\n const known = knownColumnKeysRef.current;\n let identical = currentKeys.size === known.size;\n if (identical)\n for (const k of currentKeys)\n if (!known.has(k)) {\n identical = false;\n break;\n }\n if (identical) return;\n knownColumnKeysRef.current = currentKeys;\n const persisted = loadPersistedColumns(persistColumnsKey);\n setInternalVisibleKeys((prev) => {\n const next = new Set<string>();\n for (const c of cols) {\n if (known.has(c.key)) {\n if (prev.has(c.key)) next.add(c.key); // preserve the current-session choice\n } else if (isColumnInitiallyVisible(c, persisted)) {\n next.add(c.key); // newly-added column: saved setting, else defaultHidden\n }\n }\n return next;\n });\n }, [columns, isControlled, persistColumnsKey]);\n\n // Persist visibility whenever it changes (uncontrolled mode only). Skip while\n // there are no columns, or the transient empty state during a list→table\n // switch before the reconcile effect above has filled in the visible set —\n // writing then would clobber the saved config with an empty set.\n useEffect(() => {\n if (isControlled || !persistColumnsKey || typeof window === 'undefined') return;\n const cols = columns ?? [];\n if (cols.length === 0 || internalVisibleKeys.size === 0) return;\n try {\n window.localStorage.setItem(\n COLUMN_STORAGE_PREFIX + persistColumnsKey,\n JSON.stringify({\n visible: [...internalVisibleKeys],\n known: cols.map((c) => c.key),\n widths: userWidths,\n } satisfies PersistedColumns),\n );\n } catch {\n // Storage full or unavailable — non-fatal, just don't persist.\n }\n }, [internalVisibleKeys, columns, isControlled, persistColumnsKey, userWidths]);\n\n // Locked columns can never be hidden — force them into the visible set even if\n // a controlled `visibleColumnKeys` or persisted state omits them. Only clone\n // (rather than mutate the state Set) when a locked key is actually missing;\n // in uncontrolled mode they're already present via isColumnInitiallyVisible.\n const baseVisible: Set<string> = isControlled ? new Set(visibleColumnKeys) : internalVisibleKeys;\n const lockedKeys = (columns ?? []).filter((c) => c.lockVisible).map((c) => c.key);\n const effectiveVisible: Set<string> = lockedKeys.some((k) => !baseVisible.has(k))\n ? new Set([...baseVisible, ...lockedKeys])\n : baseVisible;\n\n const hasColumns = !!columns?.length;\n const visibleColumns = hasColumns ? columns!.filter((c) => effectiveVisible.has(c.key)) : [];\n // A column counts as \"manually hidden\" only if it's default-visible yet\n // currently hidden — a defaultHidden column being hidden is its default\n // state, not a user choice, so it never contributes to the chip.\n const manuallyHiddenCount = hasColumns\n ? columns!.filter((c) => !c.defaultHidden && !effectiveVisible.has(c.key)).length\n : 0;\n const selectPrefix = multiSelect && hasColumns ? '40px ' : '';\n const columnTemplate = hasColumns\n ? `${selectPrefix}${isFrozen ? buildFrozenTemplate(visibleColumns) : buildColumnTemplate(visibleColumns, fitWidths)}`\n : undefined;\n // Hide the gear entirely when nothing is left to toggle (every column opts out).\n const shouldShowToggle = showColumnToggle !== false && hasColumns && columns!.some((c) => !c.hideFromToggle);\n\n // Sum of the tracks, so the header and body can stretch past the viewport and\n // the list scrolls horizontally instead of clipping. Kept as a `calc()` over\n // the same custom properties the tracks read, so a drag rewriting one of them\n // resizes the row canvas in the same layout pass.\n const widthVars: React.CSSProperties = {};\n if (isFrozen && hasColumns) {\n const parts = visibleColumns.map((c) => `var(${columnWidthVar(c.key)}, ${DEFAULT_FROZEN_WIDTH}px)`);\n if (multiSelect) parts.unshift('40px');\n if (shouldShowToggle) parts.push('32px');\n (widthVars as Record<string, string>)['--vl-total'] = `calc(${parts.join(' + ')})`;\n }\n\n // ── `fit` column width measurement ──────────────────────────────────────────\n const fitColumns = visibleColumns.filter((c) => c.width.type === 'fit');\n // A frozen layout is exact px and ignores the pins, so measuring would only\n // fight the user: `measureFitWidths` never shrinks a column, and every drag\n // narrower than the content would be undone on the next scroll frame.\n const hasFit = !isFrozen && fitColumns.length > 0;\n const fitColKey = fitColumns.map((c) => c.key).join('|');\n\n // Drop the measured pins when the data or the fit-column set changes, so the\n // measure effect below re-reads the true intrinsic width on the next pass —\n // this is what lets a column shrink (during scroll the effect only grows it,\n // to avoid jitter). Resetting state during render is React's recommended\n // pattern for \"reset on prop change\" and avoids a cascading effect render.\n const [fitReset, setFitReset] = useState<{ key: string; items: T[] }>({ key: fitColKey, items });\n if (fitReset.key !== fitColKey || fitReset.items !== items) {\n setFitReset({ key: fitColKey, items });\n if (Object.keys(fitWidths).length > 0) setFitWidths({});\n }\n\n // Measure every fit cell currently in the DOM (header + visible rows), keyed\n // by its `data-fit-key`, and grow each column's pin to the widest. Reading the\n // key off the DOM (rather than closing over `fitColumns`) keeps this a stable\n // callback the ResizeObserver below can reuse. `measureCellWidth` reports the\n // true content width even when the cell is pinned narrower, so a later pass\n // always recovers a column first measured while it was still empty.\n const measureFitWidths = useCallback(() => {\n const root = rootRef.current;\n if (!root) return;\n const maxByKey = new Map<string, number>();\n root.querySelectorAll('[data-fit-key]').forEach((node) => {\n const el = node as HTMLElement;\n const key = el.getAttribute('data-fit-key');\n if (!key) return;\n const w = measureCellWidth(el);\n if (w > (maxByKey.get(key) ?? 0)) maxByKey.set(key, w);\n });\n if (maxByKey.size === 0) return;\n setFitWidths((prev) => {\n let changed = false;\n const next = { ...prev };\n for (const [key, max] of maxByKey) {\n if (max <= 0) continue;\n // Cap at the viewport width so one oversized cell can't blow out layout.\n const pinned = Math.min(Math.ceil(max), root.clientWidth);\n if (pinned > (prev[key] ?? 0)) {\n next[key] = pinned;\n changed = true;\n }\n }\n return changed ? next : prev;\n });\n }, [rootRef]);\n\n // One ResizeObserver shared across all fit cells. It re-measures whenever a\n // cell's box changes — crucially when content first appears after mount (which\n // changes the cell's height even while its width is pinned), so a too-early or\n // empty-cell measure self-corrects instead of sticking. Its initial callback\n // also fires once after layout settles, covering route transitions and other\n // post-mount layout shifts without depending on font/rAF timing.\n const [fitObserver] = useState<ResizeObserver | null>(() =>\n typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => measureFitWidths()),\n );\n const observedRef = useRef<Set<Element>>(new Set());\n useEffect(\n () => () => {\n fitObserver?.disconnect();\n observedRef.current.clear();\n },\n [fitObserver],\n );\n\n // Measure synchronously (before paint, so a wider row scrolling in never shows\n // a clipped frame), then *diff* the observed set rather than re-observing every\n // cell. Re-observing fires a redundant ResizeObserver callback per node, and\n // `startIndex`/`endIndex` change every scroll frame — so observing only the\n // cells that entered (and dropping the ones that left) keeps stable, already\n // pinned cells from generating any observer traffic while scrolling. A newly\n // entered cell still fires exactly once, which is when we want to measure it.\n useLayoutEffect(() => {\n if (!hasFit) return;\n const root = rootRef.current;\n if (!root) return;\n measureFitWidths();\n if (!fitObserver) return;\n const current = new Set<Element>(root.querySelectorAll('[data-fit-key]'));\n const observed = observedRef.current;\n for (const el of observed)\n if (!current.has(el)) {\n fitObserver.unobserve(el);\n observed.delete(el);\n }\n for (const el of current)\n if (!observed.has(el)) {\n fitObserver.observe(el);\n observed.add(el);\n }\n }, [\n rootRef,\n hasFit,\n fitColKey,\n fitWidths,\n startIndex,\n endIndex,\n containerHeight,\n containerWidth,\n items,\n measureFitWidths,\n fitObserver,\n ]);\n\n // Cheap insurance: re-measure on the next two frames after mount / data /\n // fit-column changes, in case the icons (or the injected FontAwesome sizing\n // CSS) resolve a frame after the first synchronous pass. Safe and scoped —\n // measureFitWidths only ever grows a pin, and this runs on mount / data change\n // (not every scroll frame), so it keeps the diff-observe perf win.\n useEffect(() => {\n if (!hasFit) return;\n let raf2 = 0;\n const raf1 = requestAnimationFrame(() => {\n measureFitWidths();\n raf2 = requestAnimationFrame(() => measureFitWidths());\n });\n return () => {\n cancelAnimationFrame(raf1);\n cancelAnimationFrame(raf2);\n };\n }, [hasFit, fitColKey, items, measureFitWidths]);\n\n // Force a fresh fit-column measure regardless of current pins. Always installs\n // a new empty object so the measure layout effect re-runs even when nothing\n // was pinned (e.g. content changed while the column was still fit-content).\n const recalculateColumns = useCallback(() => setFitWidths({}), []);\n\n function handleToggleColumn(key: string) {\n // Locked columns can't be toggled off.\n if (columns?.find((c) => c.key === key)?.lockVisible) return;\n const next = new Set(effectiveVisible);\n if (next.has(key)) {\n if (next.size <= 1) return;\n next.delete(key);\n } else {\n next.add(key);\n }\n const nextArray = [...next];\n if (!isControlled) setInternalVisibleKeys(next);\n onVisibleColumnsChange?.(nextArray);\n }\n\n return {\n hasColumns,\n visibleColumns,\n columnTemplate,\n widthVars,\n shouldShowToggle,\n effectiveVisible,\n manuallyHiddenCount,\n toggleOpen,\n setToggleOpen,\n handleToggleColumn,\n recalculateColumns,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useColumns.mjs","names":[],"sources":["../../../../package/common/virtualList/useColumns.ts"],"sourcesContent":["import React, { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';\n\nimport {\n buildColumnTemplate,\n buildFrozenTemplate,\n columnWidthVar,\n DEFAULT_FROZEN_WIDTH,\n measureCellWidth,\n loadPersistedColumns,\n isColumnInitiallyVisible,\n COLUMN_STORAGE_PREFIX,\n type PersistedColumns,\n} from './virtualList.utils';\nimport type { VirtualListColumn } from './virtualList.types';\n\ninterface UseColumnsArgs<T> {\n columns?: VirtualListColumn<T>[];\n items: T[];\n multiSelect: boolean;\n visibleColumnKeys?: string[];\n onVisibleColumnsChange?: (keys: string[]) => void;\n persistColumnsKey?: string;\n showColumnToggle?: boolean;\n /** User-set column widths, from `useColumnResize`. */\n userWidths: Record<string, number>;\n /** Whether those widths have taken over the layout. */\n isFrozen: boolean;\n rootRef: React.RefObject<HTMLDivElement | null>;\n /** Visible-window bounds — re-measure `fit` columns as new cells scroll in. */\n startIndex: number;\n endIndex: number;\n containerHeight: number;\n /** Re-measure `fit` columns when the container width settles (e.g. after a route transition). */\n containerWidth: number;\n /** Re-measure `fit` columns when rows replace the spinner under the same `items`. */\n isLoading: boolean;\n}\n\nexport interface UseColumnsResult<T> {\n hasColumns: boolean;\n visibleColumns: VirtualListColumn<T>[];\n columnTemplate: string | undefined;\n /**\n * Custom properties for the list element: the total width of a frozen layout,\n * as a `calc()` over the per-column properties so that rewriting one width\n * during a drag updates the total without any bookkeeping.\n */\n widthVars: React.CSSProperties;\n shouldShowToggle: boolean;\n effectiveVisible: Set<string>;\n /** How many default-visible columns the user has hidden (excludes defaultHidden). */\n manuallyHiddenCount: number;\n toggleOpen: boolean;\n setToggleOpen: React.Dispatch<React.SetStateAction<boolean>>;\n handleToggleColumn: (key: string) => void;\n /** Imperative escape hatch: drop fit-column pins so they re-measure on the\n * next layout. For out-of-band content changes that don't flow through a new\n * `items` array or `columns` set (which reset automatically). */\n recalculateColumns: () => void;\n}\n\n/**\n * Table-mode column logic: visibility (controlled or uncontrolled, with\n * localStorage persistence and runtime reconciliation), the toggle popover, and\n * `fit`-column width measurement that pins one shared px track across the header\n * and every row grid so they stay aligned.\n */\nexport function useColumns<T>({\n columns,\n items,\n multiSelect,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n showColumnToggle,\n userWidths,\n isFrozen,\n rootRef,\n startIndex,\n endIndex,\n containerHeight,\n containerWidth,\n isLoading,\n}: UseColumnsArgs<T>): UseColumnsResult<T> {\n const isControlled = visibleColumnKeys !== undefined;\n const [toggleOpen, setToggleOpen] = useState(false);\n // Measured px widths for `fit` columns, keyed by column key. Pinning these\n // into the shared column template keeps the header and rows aligned (a raw\n // `fit-content` track resolves per-grid and drifts). See the effects below.\n const [fitWidths, setFitWidths] = useState<Record<string, number>>({});\n\n const [internalVisibleKeys, setInternalVisibleKeys] = useState<Set<string>>(() => {\n const persisted = loadPersistedColumns(persistColumnsKey);\n return new Set((columns ?? []).filter((c) => isColumnInitiallyVisible(c, persisted)).map((c) => c.key));\n });\n // The set of column keys the internal visibility state was last reconciled\n // against. The lazy initializer above only runs at mount, so without this a\n // list that mounts in list mode (no columns) and later switches to table mode\n // would keep an empty visible set and render no columns.\n const knownColumnKeysRef = useRef<Set<string>>(new Set((columns ?? []).map((c) => c.key)));\n\n // Re-derive internal visibility when the column set changes at runtime: new\n // columns get their saved/default visibility, removed columns drop out, and\n // existing columns keep whatever the user toggled them to. This is also what\n // applies persisted settings when a list switches from list mode to table\n // mode (columns absent at mount, supplied later).\n useEffect(() => {\n if (isControlled) return;\n const cols = columns ?? [];\n const currentKeys = new Set(cols.map((c) => c.key));\n const known = knownColumnKeysRef.current;\n let identical = currentKeys.size === known.size;\n if (identical)\n for (const k of currentKeys)\n if (!known.has(k)) {\n identical = false;\n break;\n }\n if (identical) return;\n knownColumnKeysRef.current = currentKeys;\n const persisted = loadPersistedColumns(persistColumnsKey);\n setInternalVisibleKeys((prev) => {\n const next = new Set<string>();\n for (const c of cols) {\n if (known.has(c.key)) {\n if (prev.has(c.key)) next.add(c.key); // preserve the current-session choice\n } else if (isColumnInitiallyVisible(c, persisted)) {\n next.add(c.key); // newly-added column: saved setting, else defaultHidden\n }\n }\n return next;\n });\n }, [columns, isControlled, persistColumnsKey]);\n\n // Persist visibility whenever it changes (uncontrolled mode only). Skip while\n // there are no columns, or the transient empty state during a list→table\n // switch before the reconcile effect above has filled in the visible set —\n // writing then would clobber the saved config with an empty set.\n useEffect(() => {\n if (isControlled || !persistColumnsKey || typeof window === 'undefined') return;\n const cols = columns ?? [];\n if (cols.length === 0 || internalVisibleKeys.size === 0) return;\n try {\n window.localStorage.setItem(\n COLUMN_STORAGE_PREFIX + persistColumnsKey,\n JSON.stringify({\n visible: [...internalVisibleKeys],\n known: cols.map((c) => c.key),\n widths: userWidths,\n } satisfies PersistedColumns),\n );\n } catch {\n // Storage full or unavailable — non-fatal, just don't persist.\n }\n }, [internalVisibleKeys, columns, isControlled, persistColumnsKey, userWidths]);\n\n // Locked columns can never be hidden — force them into the visible set even if\n // a controlled `visibleColumnKeys` or persisted state omits them. Only clone\n // (rather than mutate the state Set) when a locked key is actually missing;\n // in uncontrolled mode they're already present via isColumnInitiallyVisible.\n const baseVisible: Set<string> = isControlled ? new Set(visibleColumnKeys) : internalVisibleKeys;\n const lockedKeys = (columns ?? []).filter((c) => c.lockVisible).map((c) => c.key);\n const effectiveVisible: Set<string> = lockedKeys.some((k) => !baseVisible.has(k))\n ? new Set([...baseVisible, ...lockedKeys])\n : baseVisible;\n\n const hasColumns = !!columns?.length;\n const visibleColumns = hasColumns ? columns!.filter((c) => effectiveVisible.has(c.key)) : [];\n // A column counts as \"manually hidden\" only if it's default-visible yet\n // currently hidden — a defaultHidden column being hidden is its default\n // state, not a user choice, so it never contributes to the chip.\n const manuallyHiddenCount = hasColumns\n ? columns!.filter((c) => !c.defaultHidden && !effectiveVisible.has(c.key)).length\n : 0;\n const selectPrefix = multiSelect && hasColumns ? '40px ' : '';\n const columnTemplate = hasColumns\n ? `${selectPrefix}${isFrozen ? buildFrozenTemplate(visibleColumns) : buildColumnTemplate(visibleColumns, fitWidths)}`\n : undefined;\n // Hide the gear entirely when nothing is left to toggle (every column opts out).\n const shouldShowToggle = showColumnToggle !== false && hasColumns && columns!.some((c) => !c.hideFromToggle);\n\n // Sum of the tracks, so the header and body can stretch past the viewport and\n // the list scrolls horizontally instead of clipping. Kept as a `calc()` over\n // the same custom properties the tracks read, so a drag rewriting one of them\n // resizes the row canvas in the same layout pass.\n const widthVars: React.CSSProperties = {};\n if (isFrozen && hasColumns) {\n const parts = visibleColumns.map((c) => `var(${columnWidthVar(c.key)}, ${DEFAULT_FROZEN_WIDTH}px)`);\n if (multiSelect) parts.unshift('40px');\n if (shouldShowToggle) parts.push('32px');\n (widthVars as Record<string, string>)['--vl-total'] = `calc(${parts.join(' + ')})`;\n }\n\n // ── `fit` column width measurement ──────────────────────────────────────────\n const fitColumns = visibleColumns.filter((c) => c.width.type === 'fit');\n // A frozen layout is exact px and ignores the pins, so measuring would only\n // fight the user: `measureFitWidths` never shrinks a column, and every drag\n // narrower than the content would be undone on the next scroll frame.\n const hasFit = !isFrozen && fitColumns.length > 0;\n const fitColKey = fitColumns.map((c) => c.key).join('|');\n\n // Drop the measured pins when the data or the fit-column set changes, so the\n // measure effect below re-reads the true intrinsic width on the next pass —\n // this is what lets a column shrink (during scroll the effect only grows it,\n // to avoid jitter). Resetting state during render is React's recommended\n // pattern for \"reset on prop change\" and avoids a cascading effect render.\n const [fitReset, setFitReset] = useState<{ key: string; items: T[] }>({ key: fitColKey, items });\n if (fitReset.key !== fitColKey || fitReset.items !== items) {\n setFitReset({ key: fitColKey, items });\n if (Object.keys(fitWidths).length > 0) setFitWidths({});\n }\n\n // Measure every fit cell currently in the DOM (header + visible rows), keyed\n // by its `data-fit-key`, and grow each column's pin to the widest. Reading the\n // key off the DOM (rather than closing over `fitColumns`) keeps this a stable\n // callback the ResizeObserver below can reuse. `measureCellWidth` reports the\n // true content width even when the cell is pinned narrower, so a later pass\n // always recovers a column first measured while it was still empty.\n const measureFitWidths = useCallback(() => {\n const root = rootRef.current;\n if (!root) return;\n const maxByKey = new Map<string, number>();\n root.querySelectorAll('[data-fit-key]').forEach((node) => {\n const el = node as HTMLElement;\n const key = el.getAttribute('data-fit-key');\n if (!key) return;\n const w = measureCellWidth(el);\n if (w > (maxByKey.get(key) ?? 0)) maxByKey.set(key, w);\n });\n if (maxByKey.size === 0) return;\n setFitWidths((prev) => {\n let changed = false;\n const next = { ...prev };\n for (const [key, max] of maxByKey) {\n if (max <= 0) continue;\n // Cap at the viewport width so one oversized cell can't blow out layout.\n const pinned = Math.min(Math.ceil(max), root.clientWidth);\n if (pinned > (prev[key] ?? 0)) {\n next[key] = pinned;\n changed = true;\n }\n }\n return changed ? next : prev;\n });\n }, [rootRef]);\n\n // One ResizeObserver shared across all fit cells. It re-measures whenever a\n // cell's box changes — crucially when content first appears after mount (which\n // changes the cell's height even while its width is pinned), so a too-early or\n // empty-cell measure self-corrects instead of sticking. Its initial callback\n // also fires once after layout settles, covering route transitions and other\n // post-mount layout shifts without depending on font/rAF timing.\n const [fitObserver] = useState<ResizeObserver | null>(() =>\n typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(() => measureFitWidths()),\n );\n const observedRef = useRef<Set<Element>>(new Set());\n useEffect(\n () => () => {\n fitObserver?.disconnect();\n observedRef.current.clear();\n },\n [fitObserver],\n );\n\n // Measure synchronously (before paint, so a wider row scrolling in never shows\n // a clipped frame), then *diff* the observed set rather than re-observing every\n // cell. Re-observing fires a redundant ResizeObserver callback per node, and\n // `startIndex`/`endIndex` change every scroll frame — so observing only the\n // cells that entered (and dropping the ones that left) keeps stable, already\n // pinned cells from generating any observer traffic while scrolling. A newly\n // entered cell still fires exactly once, which is when we want to measure it.\n useLayoutEffect(() => {\n if (!hasFit) return;\n const root = rootRef.current;\n if (!root) return;\n measureFitWidths();\n if (!fitObserver) return;\n const current = new Set<Element>(root.querySelectorAll('[data-fit-key]'));\n const observed = observedRef.current;\n for (const el of observed)\n if (!current.has(el)) {\n fitObserver.unobserve(el);\n observed.delete(el);\n }\n for (const el of current)\n if (!observed.has(el)) {\n fitObserver.observe(el);\n observed.add(el);\n }\n }, [\n rootRef,\n hasFit,\n fitColKey,\n fitWidths,\n startIndex,\n endIndex,\n containerHeight,\n containerWidth,\n items,\n isLoading,\n measureFitWidths,\n fitObserver,\n ]);\n\n // Cheap insurance: re-measure on the next two frames after mount / data /\n // fit-column changes, in case the icons (or the injected FontAwesome sizing\n // CSS) resolve a frame after the first synchronous pass. Safe and scoped —\n // measureFitWidths only ever grows a pin, and this runs on mount / data change\n // (not every scroll frame), so it keeps the diff-observe perf win.\n useEffect(() => {\n if (!hasFit) return;\n let raf2 = 0;\n const raf1 = requestAnimationFrame(() => {\n measureFitWidths();\n raf2 = requestAnimationFrame(() => measureFitWidths());\n });\n return () => {\n cancelAnimationFrame(raf1);\n cancelAnimationFrame(raf2);\n };\n }, [hasFit, fitColKey, items, isLoading, measureFitWidths]);\n\n // Force a fresh fit-column measure regardless of current pins. Always installs\n // a new empty object so the measure layout effect re-runs even when nothing\n // was pinned (e.g. content changed while the column was still fit-content).\n const recalculateColumns = useCallback(() => setFitWidths({}), []);\n\n function handleToggleColumn(key: string) {\n // Locked columns can't be toggled off.\n if (columns?.find((c) => c.key === key)?.lockVisible) return;\n const next = new Set(effectiveVisible);\n if (next.has(key)) {\n if (next.size <= 1) return;\n next.delete(key);\n } else {\n next.add(key);\n }\n const nextArray = [...next];\n if (!isControlled) setInternalVisibleKeys(next);\n onVisibleColumnsChange?.(nextArray);\n }\n\n return {\n hasColumns,\n visibleColumns,\n columnTemplate,\n widthVars,\n shouldShowToggle,\n effectiveVisible,\n manuallyHiddenCount,\n toggleOpen,\n setToggleOpen,\n handleToggleColumn,\n recalculateColumns,\n };\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- "use client";import{useLabels as e}from"../configProvider/useLabels.mjs";import t from"../loading/spinnerLoading/index.mjs";import n from"../checkbox/checkbox.mjs";import r from"./virtualList.module.mjs";import"./virtualList.utils.mjs";import ee from"./virtualRow.mjs";import i from"./virtualListHeader.mjs";import{useVirtualWindow as a}from"./useVirtualWindow.mjs";import{useSelection as o}from"./useSelection.mjs";import{useColumns as te}from"./useColumns.mjs";import{useRowDrag as ne}from"./useRowDrag.mjs";import{useSorting as re}from"./useSorting.mjs";import{useColumnResize as ie}from"./useColumnResize.mjs";import{useRowNavigation as ae}from"./useRowNavigation.mjs";import{useCallback as oe,useEffect as s,useId as se,useImperativeHandle as ce,useMemo as c,useRef as le}from"react";import l from"classnames";import{Fragment as ue,jsx as u,jsxs as d}from"react/jsx-runtime";var de={selectRow:`Select row`,selectAllRows:`Select all rows`,toggleColumns:`Toggle column visibility`,columnsHidden:e=>`${e} columns hidden`,resetColumnWidths:`Reset column widths`};function f(e,t){return!e||!t?e??t:n=>{e(n),t(n)}}function p(p){let{ref:fe,items:pe,renderRow:me,ariaRoles:m,columns:h,height:he=`100%`,estimatedRowHeight:ge=40,overscan:_e=3,rowGap:ve=0,rowPadding:g,selectedKey:_,getItemKey:v,onRowClick:y,keyboardNavigation:b=!0,tabAcrossRows:ye=!1,scrollParent:be,visibleColumnKeys:xe,onVisibleColumnsChange:Se,persistColumnsKey:x,resizableColumns:Ce=!1,columnWidths:we,onColumnWidthsChange:Te,labels:Ee,sort:De,defaultSort:Oe,onSortChange:ke,showColumnToggle:Ae,onLoadMore:je,loadMoreThreshold:Me=100,isLoading:S=!1,multiSelect:C=!1,selectedKeys:Ne,onSelectionChange:Pe,reorderable:w=!1,onReorder:Fe,longPressDelay:Ie=400,treeReorder:T=!1,getItemDepth:E,isGroup:D,onTreeDrop:Le,treeIndentPx:Re=24,dragHandle:ze=!1,showDivider:Be=!0,rowHover:Ve=!0,className:He,style:Ue,classNames:O,styles:k,...A}=p,j=p.scrollParent!=null&&p.height!==void 0;s(()=>{j&&console.warn("VirtualList: `height` is ignored alongside `scrollParent`, which leaves the list no viewport to size.")},[j]);let M=e(`VirtualList`,Ee,de),{rows:N,effectiveSort:We,isSorted:P,handleHeaderSort:Ge}=re({items:pe,columns:h,sort:De,defaultSort:Oe,onSortChange:ke}),F=oe((e,t)=>v?v(e,t):t,[v]),I=c(()=>N.map((e,t)=>F(e,t)),[N,F]),Ke=c(()=>E?N.map((e,t)=>E(e,t)):[],[N,E]),qe=oe(e=>D?D(N[e],e):!1,[D,N]),L=a({items:N,keys:I,getItemKey:v,estimatedRowHeight:ge,rowGap:ve,overscan:_e,onLoadMore:je,loadMoreThreshold:Me,isLoading:S,scrollParent:be}),{rootRef:R,headerRef:z,rowObserver:Je,keysRef:Ye,offsets:B,totalHeight:Xe,startIndex:V,endIndex:H}=L,U=le([]),W=ie({columns:h,visibleColumnsRef:U,resizableColumns:Ce,columnWidths:we,onColumnWidthsChange:Te,persistColumnsKey:x,rootRef:R}),G=o({items:N,getKey:F,multiSelect:C,selectedKeys:Ne,onSelectionChange:Pe}),{effectiveSelectedKeys:K}=G,q=te({columns:h,items:N,multiSelect:C,visibleColumnKeys:xe,onVisibleColumnsChange:Se,persistColumnsKey:x,showColumnToggle:Ae,userWidths:W.widths,isFrozen:W.isFrozen,rootRef:R,startIndex:V,endIndex:H,containerHeight:L.containerHeight,containerWidth:L.containerWidth}),{hasColumns:J,visibleColumns:Ze,columnTemplate:Qe,widthVars:$e,shouldShowToggle:et,recalculateColumns:tt}=q;U.current=Ze,ce(fe,()=>({...L.scrollApi,recalculateColumns:tt,resetColumnWidths:W.reset}),[L.scrollApi,tt,W.reset]);let nt=P&&(w||T);s(()=>{nt&&console.warn(`VirtualList: row reordering is disabled while the list is sorted. Clear the sort to drag rows.`)},[nt]);let Y=ne({reorderable:w&&!P,treeReorder:T&&!P,onReorder:Fe,onTreeDrop:Le,longPressDelay:Ie,dragHandle:ze,treeIndentPx:Re,keys:I,depths:Ke,isGroupAt:qe,rootRef:R,keysRef:Ye,getVisibleRect:L.getVisibleRect,scrollBy:L.scrollBy}),rt=se(),it=e=>`${rt}-row-${e}`,X=ae({enabled:b,tabAcrossRows:ye,rowCount:N.length,offsets:B,rootRef:R,headerRef:z,getVisibleSlice:L.getVisibleSlice,viewportHeight:L.viewportHeight,scrollToIndex:L.scrollApi.scrollToIndex,initialIndex:_==null?0:Math.max(0,I.indexOf(_)),multiSelect:C,onActivate:y&&(e=>y(N[e],e)),onToggleSelect:e=>G.handleRowSelect(I[e])}),at=typeof g==`number`?`${g}px`:g,Z=e=>{let t=N[e],i=F(t,e),a=_!=null&&i===_||K.has(i),o=Y.getRowDragProps(e,i);return u(ee,{id:it(e),index:e,top:B[e],padding:at,columnTemplate:Qe,isSelected:a,isActive:e===X.activeIndex,manageTabStops:b,isEntered:e===X.enteredIndex,activeClassName:O?.activeRow,activeStyle:k?.activeRow,showDivider:Be,hover:Ve,onClick:()=>{Y.consumeSuppressedClick()||(X.setActiveIndex(e),y?.(t,e))},observer:Je,ariaRowIndex:J?e+2:void 0,ariaPosInSet:J?void 0:e+1,ariaSetSize:J?void 0:N.length,rowRole:m?.row??(J?`row`:`listitem`),reserveToggleGutter:et,...o,dropIndicatorClassName:O?.dropIndicator,dropIndicatorStyle:k?.dropIndicator,rowClassName:O?.row,rowStyle:k?.row,children:J?d(ue,{children:[C&&u(`div`,{className:l(r.virtualListSelectCell,O?.selectCell),style:k?.selectCell,role:`gridcell`,onClick:e=>e.stopPropagation(),children:u(n,{selected:K.has(i),onToggle:()=>G.handleRowSelect(i),"aria-label":M.selectRow,tabIndex:-1})}),Ze.map(n=>u(`div`,{className:l(r.virtualListCell,O?.cell),style:k?.cell,role:`gridcell`,"data-col-key":n.key,"data-fit-key":n.width.type===`fit`?n.key:void 0,children:n.renderCell(t,e)},n.key))]}):me?.(t,e)},i)},Q=[];for(let e=V;e<=H;e++)Q.push(Z(e));let $=Y.draggingKey==null?-1:I.indexOf(Y.draggingKey);$>=0&&($<V||$>H)&&Q.push(Z($)),X.enteredIndex>=0&&(X.enteredIndex<V||X.enteredIndex>H)&&Q.push(Z(X.enteredIndex));let ot={...X.containerProps,onKeyDown:f(A.onKeyDown,X.containerProps.onKeyDown),onFocus:f(A.onFocus,X.containerProps.onFocus),onBlur:f(A.onBlur,X.containerProps.onBlur)};return d(`div`,{...A,...ot,ref:R,className:l(r.virtualList,L.isWindowed&&r.virtualListWindowed,Y.draggingKey!=null&&r.virtualListReordering,W.isResizing&&r.virtualListResizing,He,O?.root),style:{...L.isWindowed?null:{height:he},...$e,...Ue,...k?.root},role:m?.container??(J?`grid`:`list`),"aria-rowcount":J?N.length+1:void 0,"aria-activedescendant":X.activeIndex>=0&&(m?.container??(J?`grid`:`list`))!==`list`?it(X.activeIndex):void 0,children:[J&&u(i,{headerRef:z,columns:h,cols:q,resize:W,sort:We,onSort:Ge,multiSelect:C,allSelected:G.allSelected,someSelected:G.someSelected,onSelectAll:G.handleSelectAll,labels:M,classNames:O,styles:k}),S?u(`div`,{className:r.virtualListLoadingFull,children:u(t,{className:r.virtualListSpinner})}):u(`div`,{className:l(r.virtualListBody,O?.body),style:{height:Xe,...k?.body},children:Q}),L.showLoadMoreSpinner&&u(`div`,{className:l(r.virtualListLoadingIndicator,O?.loadingIndicator),style:k?.loadingIndicator,children:u(t,{className:r.virtualListSpinner})})]})}export{p as default};
1
+ "use client";import{useLabels as e}from"../configProvider/useLabels.mjs";import t from"../loading/spinnerLoading/index.mjs";import n from"../checkbox/checkbox.mjs";import r from"./virtualList.module.mjs";import"./virtualList.utils.mjs";import ee from"./virtualRow.mjs";import i from"./virtualListHeader.mjs";import{useVirtualWindow as a}from"./useVirtualWindow.mjs";import{useSelection as o}from"./useSelection.mjs";import{useColumns as te}from"./useColumns.mjs";import{useRowDrag as ne}from"./useRowDrag.mjs";import{useSorting as re}from"./useSorting.mjs";import{useColumnResize as ie}from"./useColumnResize.mjs";import{useRowNavigation as ae}from"./useRowNavigation.mjs";import{useCallback as oe,useEffect as s,useId as se,useImperativeHandle as ce,useMemo as c,useRef as le}from"react";import l from"classnames";import{Fragment as ue,jsx as u,jsxs as d}from"react/jsx-runtime";var de={selectRow:`Select row`,selectAllRows:`Select all rows`,toggleColumns:`Toggle column visibility`,columnsHidden:e=>`${e} columns hidden`,resetColumnWidths:`Reset column widths`};function f(e,t){return!e||!t?e??t:n=>{e(n),t(n)}}function p(p){let{ref:fe,items:pe,renderRow:me,ariaRoles:m,columns:h,height:he=`100%`,estimatedRowHeight:ge=40,overscan:_e=3,rowGap:ve=0,rowPadding:g,selectedKey:_,getItemKey:v,onRowClick:y,keyboardNavigation:b=!0,tabAcrossRows:ye=!1,scrollParent:be,visibleColumnKeys:xe,onVisibleColumnsChange:Se,persistColumnsKey:x,resizableColumns:Ce=!1,columnWidths:we,onColumnWidthsChange:Te,labels:Ee,sort:De,defaultSort:Oe,onSortChange:ke,showColumnToggle:Ae,onLoadMore:je,loadMoreThreshold:Me=100,isLoading:S=!1,multiSelect:C=!1,selectedKeys:Ne,onSelectionChange:Pe,reorderable:w=!1,onReorder:Fe,longPressDelay:Ie=400,treeReorder:T=!1,getItemDepth:E,isGroup:D,onTreeDrop:Le,treeIndentPx:Re=24,dragHandle:ze=!1,showDivider:Be=!0,rowHover:Ve=!0,className:He,style:Ue,classNames:O,styles:k,...A}=p,j=p.scrollParent!=null&&p.height!==void 0;s(()=>{j&&console.warn("VirtualList: `height` is ignored alongside `scrollParent`, which leaves the list no viewport to size.")},[j]);let M=e(`VirtualList`,Ee,de),{rows:N,effectiveSort:We,isSorted:P,handleHeaderSort:Ge}=re({items:pe,columns:h,sort:De,defaultSort:Oe,onSortChange:ke}),F=oe((e,t)=>v?v(e,t):t,[v]),I=c(()=>N.map((e,t)=>F(e,t)),[N,F]),Ke=c(()=>E?N.map((e,t)=>E(e,t)):[],[N,E]),qe=oe(e=>D?D(N[e],e):!1,[D,N]),L=a({items:N,keys:I,getItemKey:v,estimatedRowHeight:ge,rowGap:ve,overscan:_e,onLoadMore:je,loadMoreThreshold:Me,isLoading:S,scrollParent:be}),{rootRef:R,headerRef:z,rowObserver:Je,keysRef:Ye,offsets:B,totalHeight:Xe,startIndex:V,endIndex:H}=L,U=le([]),W=ie({columns:h,visibleColumnsRef:U,resizableColumns:Ce,columnWidths:we,onColumnWidthsChange:Te,persistColumnsKey:x,rootRef:R}),G=o({items:N,getKey:F,multiSelect:C,selectedKeys:Ne,onSelectionChange:Pe}),{effectiveSelectedKeys:K}=G,q=te({columns:h,items:N,multiSelect:C,visibleColumnKeys:xe,onVisibleColumnsChange:Se,persistColumnsKey:x,showColumnToggle:Ae,userWidths:W.widths,isFrozen:W.isFrozen,rootRef:R,startIndex:V,endIndex:H,containerHeight:L.containerHeight,containerWidth:L.containerWidth,isLoading:S}),{hasColumns:J,visibleColumns:Ze,columnTemplate:Qe,widthVars:$e,shouldShowToggle:et,recalculateColumns:tt}=q;U.current=Ze,ce(fe,()=>({...L.scrollApi,recalculateColumns:tt,resetColumnWidths:W.reset}),[L.scrollApi,tt,W.reset]);let nt=P&&(w||T);s(()=>{nt&&console.warn(`VirtualList: row reordering is disabled while the list is sorted. Clear the sort to drag rows.`)},[nt]);let Y=ne({reorderable:w&&!P,treeReorder:T&&!P,onReorder:Fe,onTreeDrop:Le,longPressDelay:Ie,dragHandle:ze,treeIndentPx:Re,keys:I,depths:Ke,isGroupAt:qe,rootRef:R,keysRef:Ye,getVisibleRect:L.getVisibleRect,scrollBy:L.scrollBy}),rt=se(),it=e=>`${rt}-row-${e}`,X=ae({enabled:b,tabAcrossRows:ye,rowCount:N.length,offsets:B,rootRef:R,headerRef:z,getVisibleSlice:L.getVisibleSlice,viewportHeight:L.viewportHeight,scrollToIndex:L.scrollApi.scrollToIndex,initialIndex:_==null?0:Math.max(0,I.indexOf(_)),multiSelect:C,onActivate:y&&(e=>y(N[e],e)),onToggleSelect:e=>G.handleRowSelect(I[e])}),at=typeof g==`number`?`${g}px`:g,Z=e=>{let t=N[e],i=F(t,e),a=_!=null&&i===_||K.has(i),o=Y.getRowDragProps(e,i);return u(ee,{id:it(e),index:e,top:B[e],padding:at,columnTemplate:Qe,isSelected:a,isActive:e===X.activeIndex,manageTabStops:b,isEntered:e===X.enteredIndex,activeClassName:O?.activeRow,activeStyle:k?.activeRow,showDivider:Be,hover:Ve,onClick:()=>{Y.consumeSuppressedClick()||(X.setActiveIndex(e),y?.(t,e))},observer:Je,ariaRowIndex:J?e+2:void 0,ariaPosInSet:J?void 0:e+1,ariaSetSize:J?void 0:N.length,rowRole:m?.row??(J?`row`:`listitem`),reserveToggleGutter:et,...o,dropIndicatorClassName:O?.dropIndicator,dropIndicatorStyle:k?.dropIndicator,rowClassName:O?.row,rowStyle:k?.row,children:J?d(ue,{children:[C&&u(`div`,{className:l(r.virtualListSelectCell,O?.selectCell),style:k?.selectCell,role:`gridcell`,onClick:e=>e.stopPropagation(),children:u(n,{selected:K.has(i),onToggle:()=>G.handleRowSelect(i),"aria-label":M.selectRow,tabIndex:-1})}),Ze.map(n=>u(`div`,{className:l(r.virtualListCell,O?.cell),style:k?.cell,role:`gridcell`,"data-col-key":n.key,"data-fit-key":n.width.type===`fit`?n.key:void 0,children:n.renderCell(t,e)},n.key))]}):me?.(t,e)},i)},Q=[];for(let e=V;e<=H;e++)Q.push(Z(e));let $=Y.draggingKey==null?-1:I.indexOf(Y.draggingKey);$>=0&&($<V||$>H)&&Q.push(Z($)),X.enteredIndex>=0&&(X.enteredIndex<V||X.enteredIndex>H)&&Q.push(Z(X.enteredIndex));let ot={...X.containerProps,onKeyDown:f(A.onKeyDown,X.containerProps.onKeyDown),onFocus:f(A.onFocus,X.containerProps.onFocus),onBlur:f(A.onBlur,X.containerProps.onBlur)};return d(`div`,{...A,...ot,ref:R,className:l(r.virtualList,L.isWindowed&&r.virtualListWindowed,Y.draggingKey!=null&&r.virtualListReordering,W.isResizing&&r.virtualListResizing,He,O?.root),style:{...L.isWindowed?null:{height:he},...$e,...Ue,...k?.root},role:m?.container??(J?`grid`:`list`),"aria-rowcount":J?N.length+1:void 0,"aria-activedescendant":X.activeIndex>=0&&(m?.container??(J?`grid`:`list`))!==`list`?it(X.activeIndex):void 0,children:[J&&u(i,{headerRef:z,columns:h,cols:q,resize:W,sort:We,onSort:Ge,multiSelect:C,allSelected:G.allSelected,someSelected:G.someSelected,onSelectAll:G.handleSelectAll,labels:M,classNames:O,styles:k}),S?u(`div`,{className:r.virtualListLoadingFull,children:u(t,{className:r.virtualListSpinner})}):u(`div`,{className:l(r.virtualListBody,O?.body),style:{height:Xe,...k?.body},children:Q}),L.showLoadMoreSpinner&&u(`div`,{className:l(r.virtualListLoadingIndicator,O?.loadingIndicator),style:k?.loadingIndicator,children:u(t,{className:r.virtualListSpinner})})]})}export{p as default};
2
2
  //# sourceMappingURL=virtualList.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"virtualList.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.tsx"],"sourcesContent":["import React, { useMemo, useCallback, useEffect, useId, useImperativeHandle, useRef } from 'react';\nimport cx from 'classnames';\nimport styles from './virtualList.module.pcss';\nimport SpinnerLoading from '../loading/spinnerLoading';\nimport Checkbox from '../checkbox';\nimport VirtualRow from './virtualRow';\nimport VirtualListHeader from './virtualListHeader';\nimport { useVirtualWindow } from './useVirtualWindow';\nimport { useSelection } from './useSelection';\nimport { useColumns } from './useColumns';\nimport { useRowDrag } from './useRowDrag';\nimport { useSorting } from './useSorting';\nimport { useColumnResize } from './useColumnResize';\nimport { useRowNavigation } from './useRowNavigation';\nimport { useLabels } from '../configProvider';\nimport type { VirtualListColumn, VirtualListLabels, VirtualListProps } from './virtualList.types';\n\nconst DEFAULT_LABELS: Required<VirtualListLabels> = {\n selectRow: 'Select row',\n selectAllRows: 'Select all rows',\n toggleColumns: 'Toggle column visibility',\n columnsHidden: (count) => `${count} columns hidden`,\n resetColumnWidths: 'Reset column widths',\n};\n\n// Pure helpers live in ./virtualList.utils; re-exported here so existing\n// imports (and unit tests) keep working against the component entry point.\nexport {\n buildOffsets,\n findStartIndex,\n getVisibleRange,\n resolveReorderTarget,\n resolveTreeDropTarget,\n lastDescendantIndex,\n buildColumnTemplate,\n compareSortValues,\n isColumnSortable,\n isEmptySortValue,\n nextSort,\n sortItems,\n} from './virtualList.utils';\n\n// ─── Orchestrator ─────────────────────────────────────────────────────────────\n// VirtualListInner wires the hooks together and renders the two pieces of\n// markup. Each concern lives in its own module:\n// useSorting — table mode: header sort state, and the sorted row order\n// useVirtualWindow — generic scroll / measurement / windowing (all modes)\n// useColumnResize — table mode: user column widths, and the drag that sets them\n// useSelection — multi-row selection\n// useColumns — table mode: columns, visibility, persistence, fit widths\n// useRowDrag — reorder + tree drag-and-drop (mouse + touch)\n// useRowNavigation — arrow-key navigation over the rows\n// virtualListHeader — the header row: sorting, resizing, the column menu\n// virtualRow — one positioned row, with its drag wiring\n//\n// The hook order is not free: useColumnResize decides whether the layout is\n// frozen, which useColumns needs to build the grid template.\n\n/** One handler after the other, or whichever of the two exists. */\nfunction chain<E>(theirs: ((event: E) => void) | undefined, ours: ((event: E) => void) | undefined) {\n if (!theirs || !ours) return theirs ?? ours;\n return (event: E) => {\n theirs(event);\n ours(event);\n };\n}\n\nfunction VirtualList<T>(props: VirtualListProps<T>) {\n const {\n ref,\n items,\n renderRow,\n ariaRoles,\n columns,\n height = '100%',\n estimatedRowHeight = 40,\n overscan = 3,\n rowGap = 0,\n rowPadding,\n selectedKey,\n getItemKey,\n onRowClick,\n keyboardNavigation = true,\n tabAcrossRows = false,\n scrollParent,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n resizableColumns = false,\n columnWidths,\n onColumnWidthsChange,\n labels,\n sort,\n defaultSort,\n onSortChange,\n showColumnToggle,\n onLoadMore,\n loadMoreThreshold = 100,\n isLoading = false,\n multiSelect = false,\n selectedKeys,\n onSelectionChange,\n reorderable = false,\n onReorder,\n longPressDelay = 400,\n treeReorder = false,\n getItemDepth,\n isGroup,\n onTreeDrop,\n treeIndentPx = 24,\n dragHandle = false,\n showDivider = true,\n rowHover = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = props;\n // Read off the raw props: the destructured value carries a default and so\n // cannot tell a passed '100%' from an omitted one.\n const heightIgnored = props.scrollParent != null && props.height !== undefined;\n useEffect(() => {\n if (heightIgnored)\n console.warn(\n 'VirtualList: `height` is ignored alongside `scrollParent`, which leaves the list no viewport to size.',\n );\n }, [heightIgnored]);\n\n const text = useLabels('VirtualList', labels, DEFAULT_LABELS);\n const { rows, effectiveSort, isSorted, handleHeaderSort } = useSorting<T>({\n items,\n columns,\n sort,\n defaultSort,\n onSortChange,\n });\n\n const getKey = useCallback(\n (item: T, index: number): string | number => (getItemKey ? getItemKey(item, index) : index),\n [getItemKey],\n );\n const keys = useMemo(() => rows.map((item, i) => getKey(item, i)), [rows, getKey]);\n const depths = useMemo(\n () => (getItemDepth ? rows.map((item, i) => getItemDepth(item, i)) : []),\n [rows, getItemDepth],\n );\n const isGroupAt = useCallback((i: number) => (isGroup ? isGroup(rows[i], i) : false), [isGroup, rows]);\n\n const win = useVirtualWindow<T>({\n items: rows,\n keys,\n getItemKey,\n estimatedRowHeight,\n rowGap,\n overscan,\n onLoadMore,\n loadMoreThreshold,\n isLoading,\n scrollParent,\n });\n const { rootRef, headerRef, rowObserver, keysRef, offsets, totalHeight, startIndex, endIndex } = win;\n\n // Filled from `cols` below: the resize hook only reads the visible set when a\n // handle is actually used, which is what lets it run before the hook that\n // derives it.\n const visibleColumnsRef = useRef<VirtualListColumn<T>[]>([]);\n const columnResize = useColumnResize<T>({\n columns,\n visibleColumnsRef,\n resizableColumns,\n columnWidths,\n onColumnWidthsChange,\n persistColumnsKey,\n rootRef,\n });\n\n const selection = useSelection<T>({ items: rows, getKey, multiSelect, selectedKeys, onSelectionChange });\n const { effectiveSelectedKeys } = selection;\n\n const cols = useColumns<T>({\n columns,\n items: rows,\n multiSelect,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n showColumnToggle,\n userWidths: columnResize.widths,\n isFrozen: columnResize.isFrozen,\n rootRef,\n startIndex,\n endIndex,\n containerHeight: win.containerHeight,\n containerWidth: win.containerWidth,\n });\n // The rest of `cols` is the header's business and goes there whole.\n const { hasColumns, visibleColumns, columnTemplate, widthVars, shouldShowToggle, recalculateColumns } = cols;\n visibleColumnsRef.current = visibleColumns;\n\n // Compose the forwarded handle from the hooks that own each piece: scroll\n // methods from the windowing engine, column controls from the column hooks.\n useImperativeHandle(ref, () => ({ ...win.scrollApi, recalculateColumns, resetColumnWidths: columnResize.reset }), [\n win.scrollApi,\n recalculateColumns,\n columnResize.reset,\n ]);\n\n // Dragging a row to position N is meaningless while the list decides the\n // order: the drop would be undone by the next sort pass.\n const dragBlockedBySort = isSorted && (reorderable || treeReorder);\n useEffect(() => {\n if (dragBlockedBySort)\n console.warn('VirtualList: row reordering is disabled while the list is sorted. Clear the sort to drag rows.');\n }, [dragBlockedBySort]);\n\n const drag = useRowDrag({\n reorderable: reorderable && !isSorted,\n treeReorder: treeReorder && !isSorted,\n onReorder,\n onTreeDrop,\n longPressDelay,\n dragHandle,\n treeIndentPx,\n keys,\n depths,\n isGroupAt,\n rootRef,\n keysRef,\n getVisibleRect: win.getVisibleRect,\n scrollBy: win.scrollBy,\n });\n\n const listId = useId();\n const rowDomId = (index: number) => `${listId}-row-${index}`;\n const nav = useRowNavigation({\n enabled: keyboardNavigation,\n tabAcrossRows,\n rowCount: rows.length,\n offsets,\n rootRef,\n headerRef,\n getVisibleSlice: win.getVisibleSlice,\n viewportHeight: win.viewportHeight,\n scrollToIndex: win.scrollApi.scrollToIndex,\n initialIndex: selectedKey != null ? Math.max(0, keys.indexOf(selectedKey)) : 0,\n multiSelect,\n onActivate: onRowClick && ((index: number) => onRowClick(rows[index], index)),\n onToggleSelect: (index) => selection.handleRowSelect(keys[index]),\n });\n\n const rowPaddingValue = typeof rowPadding === 'number' ? `${rowPadding}px` : rowPadding;\n\n const renderRowAt = (i: number) => {\n const item = rows[i];\n const key = getKey(item, i);\n const isSelected = (selectedKey != null && key === selectedKey) || effectiveSelectedKeys.has(key);\n const dragProps = drag.getRowDragProps(i, key);\n\n return (\n <VirtualRow\n key={key}\n id={rowDomId(i)}\n index={i}\n top={offsets[i]}\n padding={rowPaddingValue}\n columnTemplate={columnTemplate}\n isSelected={isSelected}\n isActive={i === nav.activeIndex}\n manageTabStops={keyboardNavigation}\n isEntered={i === nav.enteredIndex}\n activeClassName={classNames?.activeRow}\n activeStyle={slotStyles?.activeRow}\n showDivider={showDivider}\n hover={rowHover}\n onClick={() => {\n if (drag.consumeSuppressedClick()) return;\n // Keep the arrows where the pointer last was.\n nav.setActiveIndex(i);\n onRowClick?.(item, i);\n }}\n observer={rowObserver}\n ariaRowIndex={hasColumns ? i + 2 : undefined}\n ariaPosInSet={hasColumns ? undefined : i + 1}\n ariaSetSize={hasColumns ? undefined : rows.length}\n rowRole={ariaRoles?.row ?? (hasColumns ? 'row' : 'listitem')}\n reserveToggleGutter={shouldShowToggle}\n {...dragProps}\n dropIndicatorClassName={classNames?.dropIndicator}\n dropIndicatorStyle={slotStyles?.dropIndicator}\n rowClassName={classNames?.row}\n rowStyle={slotStyles?.row}\n >\n {hasColumns ? (\n <>\n {multiSelect && (\n <div\n className={cx(styles.virtualListSelectCell, classNames?.selectCell)}\n style={slotStyles?.selectCell}\n role='gridcell'\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox\n selected={effectiveSelectedKeys.has(key)}\n onToggle={() => selection.handleRowSelect(key)}\n aria-label={text.selectRow}\n // One tab stop per rendered row, changing as the list scrolls,\n // would strand the focus the moment a row unmounts. Space on\n // the active row is the keyboard path instead.\n tabIndex={-1}\n />\n </div>\n )}\n {visibleColumns.map((col) => (\n <div\n key={col.key}\n className={cx(styles.virtualListCell, classNames?.cell)}\n style={slotStyles?.cell}\n role='gridcell'\n data-col-key={col.key}\n data-fit-key={col.width.type === 'fit' ? col.key : undefined}\n >\n {col.renderCell(item, i)}\n </div>\n ))}\n </>\n ) : (\n renderRow?.(item, i)\n )}\n </VirtualRow>\n );\n };\n\n const visibleRows: React.ReactNode[] = [];\n for (let i = startIndex; i <= endIndex; i++) visibleRows.push(renderRowAt(i));\n\n // Keep the row being touch-dragged mounted even if it scrolls out of the\n // window. Touch events are dispatched to the element under the finger at\n // touchstart; if that row unmounts, the browser stops delivering touchmove\n // and the drag freezes. Pinning its DOM node keeps the gesture alive.\n const draggedIndex = drag.draggingKey != null ? keys.indexOf(drag.draggingKey) : -1;\n if (draggedIndex >= 0 && (draggedIndex < startIndex || draggedIndex > endIndex)) {\n visibleRows.push(renderRowAt(draggedIndex));\n }\n\n // Same reason, one step further: the row being used holds the focus, and\n // unmounting it would drop that focus to the document body mid-scroll.\n if (nav.enteredIndex >= 0 && (nav.enteredIndex < startIndex || nav.enteredIndex > endIndex)) {\n visibleRows.push(renderRowAt(nav.enteredIndex));\n }\n\n // Navigation must not replace handlers the consumer passed. Theirs runs first,\n // and a `preventDefault` in it keeps the event: the navigation handlers all\n // check `defaultPrevented`.\n const containerProps = {\n ...nav.containerProps,\n onKeyDown: chain<React.KeyboardEvent<HTMLDivElement>>(rest.onKeyDown, nav.containerProps.onKeyDown),\n onFocus: chain<React.FocusEvent<HTMLDivElement>>(rest.onFocus, nav.containerProps.onFocus),\n onBlur: chain<React.FocusEvent<HTMLDivElement>>(rest.onBlur, nav.containerProps.onBlur),\n };\n\n return (\n <div\n {...rest}\n {...containerProps}\n ref={rootRef}\n className={cx(\n styles.virtualList,\n win.isWindowed && styles.virtualListWindowed,\n drag.draggingKey != null && styles.virtualListReordering,\n columnResize.isResizing && styles.virtualListResizing,\n className,\n classNames?.root,\n )}\n style={{ ...(win.isWindowed ? null : { height }), ...widthVars, ...style, ...slotStyles?.root }}\n role={ariaRoles?.container ?? (hasColumns ? 'grid' : 'list')}\n aria-rowcount={hasColumns ? rows.length + 1 : undefined}\n // Only a composite role may point at an active descendant, and the default\n // `list` is not one. In list mode, pass `ariaRoles` (e.g. listbox/option)\n // to have the active row announced.\n aria-activedescendant={\n nav.activeIndex >= 0 && (ariaRoles?.container ?? (hasColumns ? 'grid' : 'list')) !== 'list'\n ? rowDomId(nav.activeIndex)\n : undefined\n }\n >\n {hasColumns && (\n <VirtualListHeader<T>\n headerRef={headerRef}\n columns={columns!}\n cols={cols}\n resize={columnResize}\n sort={effectiveSort}\n onSort={handleHeaderSort}\n multiSelect={multiSelect}\n allSelected={selection.allSelected}\n someSelected={selection.someSelected}\n onSelectAll={selection.handleSelectAll}\n labels={text}\n classNames={classNames}\n styles={slotStyles}\n />\n )}\n {isLoading ? (\n <div className={styles.virtualListLoadingFull}>\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n ) : (\n <div\n className={cx(styles.virtualListBody, classNames?.body)}\n style={{ height: totalHeight, ...slotStyles?.body }}\n >\n {visibleRows}\n </div>\n )}\n {win.showLoadMoreSpinner && (\n <div\n className={cx(styles.virtualListLoadingIndicator, classNames?.loadingIndicator)}\n style={slotStyles?.loadingIndicator}\n >\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n )}\n </div>\n );\n}\n\nexport default VirtualList;\n"],"mappings":"g3BAiBA,IAAM,GAA8C,CAClD,UAAW,aACX,cAAe,kBACf,cAAe,2BACf,cAAgB,GAAU,GAAG,EAAM,iBACnC,kBAAmB,qBACrB,EAoCA,SAAS,EAAS,EAA0C,EAAwC,CAElG,MADI,CAAC,GAAU,CAAC,EAAa,GAAU,EAC/B,GAAa,CACnB,EAAO,CAAK,EACZ,EAAK,CAAK,CACZ,CACF,CAEA,SAAS,EAAe,EAA4B,CAClD,GAAM,CACJ,OACA,SACA,aACA,YACA,UACA,UAAS,OACT,sBAAqB,GACrB,YAAW,EACX,UAAS,EACT,aACA,cACA,aACA,aACA,qBAAqB,GACrB,iBAAgB,GAChB,gBACA,qBACA,0BACA,oBACA,oBAAmB,GACnB,gBACA,wBACA,UACA,QACA,eACA,gBACA,oBACA,cACA,qBAAoB,IACpB,YAAY,GACZ,cAAc,GACd,gBACA,qBACA,cAAc,GACd,aACA,kBAAiB,IACjB,cAAc,GACd,eACA,UACA,cACA,gBAAe,GACf,cAAa,GACb,eAAc,GACd,YAAW,GACX,aACA,SACA,aACA,OAAQ,EACR,GAAG,GACD,EAGE,EAAgB,EAAM,cAAgB,MAAQ,EAAM,SAAW,IAAA,GACrE,MAAgB,CACV,GACF,QAAQ,KACN,uGACF,CACJ,EAAG,CAAC,CAAa,CAAC,EAElB,IAAM,EAAO,EAAU,cAAe,GAAQ,EAAc,EACtD,CAAE,OAAM,iBAAe,WAAU,qBAAqB,GAAc,CACxE,SACA,UACA,QACA,eACA,eACF,CAAC,EAEK,EAAS,IACZ,EAAS,IAAoC,EAAa,EAAW,EAAM,CAAK,EAAI,EACrF,CAAC,CAAU,CACb,EACM,EAAO,MAAc,EAAK,KAAK,EAAM,IAAM,EAAO,EAAM,CAAC,CAAC,EAAG,CAAC,EAAM,CAAM,CAAC,EAC3E,GAAS,MACN,EAAe,EAAK,KAAK,EAAM,IAAM,EAAa,EAAM,CAAC,CAAC,EAAI,CAAC,EACtE,CAAC,EAAM,CAAY,CACrB,EACM,GAAY,GAAa,GAAe,EAAU,EAAQ,EAAK,GAAI,CAAC,EAAI,GAAQ,CAAC,EAAS,CAAI,CAAC,EAE/F,EAAM,EAAoB,CAC9B,MAAO,EACP,OACA,aACA,sBACA,UACA,YACA,cACA,qBACA,YACA,eACF,CAAC,EACK,CAAE,UAAS,YAAW,eAAa,WAAS,UAAS,eAAa,aAAY,YAAa,EAK3F,EAAoB,GAA+B,CAAC,CAAC,EACrD,EAAe,GAAmB,CACtC,UACA,oBACA,oBACA,gBACA,wBACA,oBACA,SACF,CAAC,EAEK,EAAY,EAAgB,CAAE,MAAO,EAAM,SAAQ,cAAa,gBAAc,oBAAkB,CAAC,EACjG,CAAE,yBAA0B,EAE5B,EAAO,GAAc,CACzB,UACA,MAAO,EACP,cACA,qBACA,0BACA,oBACA,oBACA,WAAY,EAAa,OACzB,SAAU,EAAa,SACvB,UACA,aACA,WACA,gBAAiB,EAAI,gBACrB,eAAgB,EAAI,cACtB,CAAC,EAEK,CAAE,aAAY,kBAAgB,kBAAgB,aAAW,oBAAkB,uBAAuB,EACxG,EAAkB,QAAU,GAI5B,GAAoB,QAAY,CAAE,GAAG,EAAI,UAAW,sBAAoB,kBAAmB,EAAa,KAAM,GAAI,CAChH,EAAI,UACJ,GACA,EAAa,KACf,CAAC,EAID,IAAM,GAAoB,IAAa,GAAe,GACtD,MAAgB,CACV,IACF,QAAQ,KAAK,gGAAgG,CACjH,EAAG,CAAC,EAAiB,CAAC,EAEtB,IAAM,EAAO,GAAW,CACtB,YAAa,GAAe,CAAC,EAC7B,YAAa,GAAe,CAAC,EAC7B,aACA,cACA,kBACA,cACA,gBACA,OACA,UACA,aACA,UACA,WACA,eAAgB,EAAI,eACpB,SAAU,EAAI,QAChB,CAAC,EAEK,GAAS,GAAM,EACf,GAAY,GAAkB,GAAG,GAAO,OAAO,IAC/C,EAAM,GAAiB,CAC3B,QAAS,EACT,iBACA,SAAU,EAAK,OACf,UACA,UACA,YACA,gBAAiB,EAAI,gBACrB,eAAgB,EAAI,eACpB,cAAe,EAAI,UAAU,cAC7B,aAAc,GAAe,KAAgD,EAAzC,KAAK,IAAI,EAAG,EAAK,QAAQ,CAAW,CAAC,EACzE,cACA,WAAY,IAAgB,GAAkB,EAAW,EAAK,GAAQ,CAAK,GAC3E,eAAiB,GAAU,EAAU,gBAAgB,EAAK,EAAM,CAClE,CAAC,EAEK,GAAkB,OAAO,GAAe,SAAW,GAAG,EAAW,IAAM,EAEvE,EAAe,GAAc,CACjC,IAAM,EAAO,EAAK,GACZ,EAAM,EAAO,EAAM,CAAC,EACpB,EAAc,GAAe,MAAQ,IAAQ,GAAgB,EAAsB,IAAI,CAAG,EAC1F,EAAY,EAAK,gBAAgB,EAAG,CAAG,EAE7C,OACE,EAAC,GAAD,CAEE,GAAI,GAAS,CAAC,EACd,MAAO,EACP,IAAK,EAAQ,GACb,QAAS,GACO,kBACJ,aACZ,SAAU,IAAM,EAAI,YACpB,eAAgB,EAChB,UAAW,IAAM,EAAI,aACrB,gBAAiB,GAAY,UAC7B,YAAa,GAAY,UACZ,eACb,MAAO,GACP,YAAe,CACT,EAAK,uBAAuB,IAEhC,EAAI,eAAe,CAAC,EACpB,IAAa,EAAM,CAAC,EACtB,EACA,SAAU,GACV,aAAc,EAAa,EAAI,EAAI,IAAA,GACnC,aAAc,EAAa,IAAA,GAAY,EAAI,EAC3C,YAAa,EAAa,IAAA,GAAY,EAAK,OAC3C,QAAS,GAAW,MAAQ,EAAa,MAAQ,YACjD,oBAAqB,GACrB,GAAI,EACJ,uBAAwB,GAAY,cACpC,mBAAoB,GAAY,cAChC,aAAc,GAAY,IAC1B,SAAU,GAAY,IAErB,SAAA,EACC,EAAA,GAAA,CAAA,SAAA,CACG,GACC,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,sBAAuB,GAAY,UAAU,EAClE,MAAO,GAAY,WACnB,KAAK,WACL,QAAU,GAAM,EAAE,gBAAgB,EAElC,SAAA,EAAC,EAAD,CACE,SAAU,EAAsB,IAAI,CAAG,EACvC,aAAgB,EAAU,gBAAgB,CAAG,EAC7C,aAAY,EAAK,UAIjB,SAAU,EACX,CAAA,CACE,CAAA,EAEN,GAAe,IAAK,GACnB,EAAC,MAAD,CAEE,UAAW,EAAG,EAAO,gBAAiB,GAAY,IAAI,EACtD,MAAO,GAAY,KACnB,KAAK,WACL,eAAc,EAAI,IAClB,eAAc,EAAI,MAAM,OAAS,MAAQ,EAAI,IAAM,IAAA,GAElD,SAAA,EAAI,WAAW,EAAM,CAAC,CACpB,EARE,EAAI,GAQN,CACN,CACD,CAAA,CAAA,EAEF,KAAY,EAAM,CAAC,CAEX,EApEL,CAoEK,CAEhB,EAEM,EAAiC,CAAC,EACxC,IAAK,IAAI,EAAI,EAAY,GAAK,EAAU,IAAK,EAAY,KAAK,EAAY,CAAC,CAAC,EAM5E,IAAM,EAAe,EAAK,aAAe,KAAwC,GAAjC,EAAK,QAAQ,EAAK,WAAW,EACzE,GAAgB,IAAM,EAAe,GAAc,EAAe,IACpE,EAAY,KAAK,EAAY,CAAY,CAAC,EAKxC,EAAI,cAAgB,IAAM,EAAI,aAAe,GAAc,EAAI,aAAe,IAChF,EAAY,KAAK,EAAY,EAAI,YAAY,CAAC,EAMhD,IAAM,GAAiB,CACrB,GAAG,EAAI,eACP,UAAW,EAA2C,EAAK,UAAW,EAAI,eAAe,SAAS,EAClG,QAAS,EAAwC,EAAK,QAAS,EAAI,eAAe,OAAO,EACzF,OAAQ,EAAwC,EAAK,OAAQ,EAAI,eAAe,MAAM,CACxF,EAEA,OACE,EAAC,MAAD,CACE,GAAI,EACJ,GAAI,GACJ,IAAK,EACL,UAAW,EACT,EAAO,YACP,EAAI,YAAc,EAAO,oBACzB,EAAK,aAAe,MAAQ,EAAO,sBACnC,EAAa,YAAc,EAAO,oBAClC,GACA,GAAY,IACd,EACA,MAAO,CAAE,GAAI,EAAI,WAAa,KAAO,CAAE,SAAO,EAAI,GAAG,GAAW,GAAG,GAAO,GAAG,GAAY,IAAK,EAC9F,KAAM,GAAW,YAAc,EAAa,OAAS,QACrD,gBAAe,EAAa,EAAK,OAAS,EAAI,IAAA,GAI9C,wBACE,EAAI,aAAe,IAAM,GAAW,YAAc,EAAa,OAAS,WAAa,OACjF,GAAS,EAAI,WAAW,EACxB,IAAA,GArBR,SAAA,CAwBG,GACC,EAAC,EAAD,CACa,YACF,UACH,OACN,OAAQ,EACR,KAAM,GACN,OAAQ,GACK,cACb,YAAa,EAAU,YACvB,aAAc,EAAU,aACxB,YAAa,EAAU,gBACvB,OAAQ,EACI,aACZ,OAAQ,CACT,CAAA,EAEF,EACC,EAAC,MAAD,CAAK,UAAW,EAAO,uBACrB,SAAA,EAAC,EAAD,CAAgB,UAAW,EAAO,kBAAqB,CAAA,CACpD,CAAA,EAEL,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,gBAAiB,GAAY,IAAI,EACtD,MAAO,CAAE,OAAQ,GAAa,GAAG,GAAY,IAAK,EAEjD,SAAA,CACE,CAAA,EAEN,EAAI,qBACH,EAAC,MAAD,CACE,UAAW,EAAG,EAAO,4BAA6B,GAAY,gBAAgB,EAC9E,MAAO,GAAY,iBAEnB,SAAA,EAAC,EAAD,CAAgB,UAAW,EAAO,kBAAqB,CAAA,CACpD,CAAA,CAEJ,GAET"}
1
+ {"version":3,"file":"virtualList.mjs","names":[],"sources":["../../../../package/common/virtualList/virtualList.tsx"],"sourcesContent":["import React, { useMemo, useCallback, useEffect, useId, useImperativeHandle, useRef } from 'react';\nimport cx from 'classnames';\nimport styles from './virtualList.module.pcss';\nimport SpinnerLoading from '../loading/spinnerLoading';\nimport Checkbox from '../checkbox';\nimport VirtualRow from './virtualRow';\nimport VirtualListHeader from './virtualListHeader';\nimport { useVirtualWindow } from './useVirtualWindow';\nimport { useSelection } from './useSelection';\nimport { useColumns } from './useColumns';\nimport { useRowDrag } from './useRowDrag';\nimport { useSorting } from './useSorting';\nimport { useColumnResize } from './useColumnResize';\nimport { useRowNavigation } from './useRowNavigation';\nimport { useLabels } from '../configProvider';\nimport type { VirtualListColumn, VirtualListLabels, VirtualListProps } from './virtualList.types';\n\nconst DEFAULT_LABELS: Required<VirtualListLabels> = {\n selectRow: 'Select row',\n selectAllRows: 'Select all rows',\n toggleColumns: 'Toggle column visibility',\n columnsHidden: (count) => `${count} columns hidden`,\n resetColumnWidths: 'Reset column widths',\n};\n\n// Pure helpers live in ./virtualList.utils; re-exported here so existing\n// imports (and unit tests) keep working against the component entry point.\nexport {\n buildOffsets,\n findStartIndex,\n getVisibleRange,\n resolveReorderTarget,\n resolveTreeDropTarget,\n lastDescendantIndex,\n buildColumnTemplate,\n compareSortValues,\n isColumnSortable,\n isEmptySortValue,\n nextSort,\n sortItems,\n} from './virtualList.utils';\n\n// ─── Orchestrator ─────────────────────────────────────────────────────────────\n// VirtualListInner wires the hooks together and renders the two pieces of\n// markup. Each concern lives in its own module:\n// useSorting — table mode: header sort state, and the sorted row order\n// useVirtualWindow — generic scroll / measurement / windowing (all modes)\n// useColumnResize — table mode: user column widths, and the drag that sets them\n// useSelection — multi-row selection\n// useColumns — table mode: columns, visibility, persistence, fit widths\n// useRowDrag — reorder + tree drag-and-drop (mouse + touch)\n// useRowNavigation — arrow-key navigation over the rows\n// virtualListHeader — the header row: sorting, resizing, the column menu\n// virtualRow — one positioned row, with its drag wiring\n//\n// The hook order is not free: useColumnResize decides whether the layout is\n// frozen, which useColumns needs to build the grid template.\n\n/** One handler after the other, or whichever of the two exists. */\nfunction chain<E>(theirs: ((event: E) => void) | undefined, ours: ((event: E) => void) | undefined) {\n if (!theirs || !ours) return theirs ?? ours;\n return (event: E) => {\n theirs(event);\n ours(event);\n };\n}\n\nfunction VirtualList<T>(props: VirtualListProps<T>) {\n const {\n ref,\n items,\n renderRow,\n ariaRoles,\n columns,\n height = '100%',\n estimatedRowHeight = 40,\n overscan = 3,\n rowGap = 0,\n rowPadding,\n selectedKey,\n getItemKey,\n onRowClick,\n keyboardNavigation = true,\n tabAcrossRows = false,\n scrollParent,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n resizableColumns = false,\n columnWidths,\n onColumnWidthsChange,\n labels,\n sort,\n defaultSort,\n onSortChange,\n showColumnToggle,\n onLoadMore,\n loadMoreThreshold = 100,\n isLoading = false,\n multiSelect = false,\n selectedKeys,\n onSelectionChange,\n reorderable = false,\n onReorder,\n longPressDelay = 400,\n treeReorder = false,\n getItemDepth,\n isGroup,\n onTreeDrop,\n treeIndentPx = 24,\n dragHandle = false,\n showDivider = true,\n rowHover = true,\n className,\n style,\n classNames,\n styles: slotStyles,\n ...rest\n } = props;\n // Read off the raw props: the destructured value carries a default and so\n // cannot tell a passed '100%' from an omitted one.\n const heightIgnored = props.scrollParent != null && props.height !== undefined;\n useEffect(() => {\n if (heightIgnored)\n console.warn(\n 'VirtualList: `height` is ignored alongside `scrollParent`, which leaves the list no viewport to size.',\n );\n }, [heightIgnored]);\n\n const text = useLabels('VirtualList', labels, DEFAULT_LABELS);\n const { rows, effectiveSort, isSorted, handleHeaderSort } = useSorting<T>({\n items,\n columns,\n sort,\n defaultSort,\n onSortChange,\n });\n\n const getKey = useCallback(\n (item: T, index: number): string | number => (getItemKey ? getItemKey(item, index) : index),\n [getItemKey],\n );\n const keys = useMemo(() => rows.map((item, i) => getKey(item, i)), [rows, getKey]);\n const depths = useMemo(\n () => (getItemDepth ? rows.map((item, i) => getItemDepth(item, i)) : []),\n [rows, getItemDepth],\n );\n const isGroupAt = useCallback((i: number) => (isGroup ? isGroup(rows[i], i) : false), [isGroup, rows]);\n\n const win = useVirtualWindow<T>({\n items: rows,\n keys,\n getItemKey,\n estimatedRowHeight,\n rowGap,\n overscan,\n onLoadMore,\n loadMoreThreshold,\n isLoading,\n scrollParent,\n });\n const { rootRef, headerRef, rowObserver, keysRef, offsets, totalHeight, startIndex, endIndex } = win;\n\n // Filled from `cols` below: the resize hook only reads the visible set when a\n // handle is actually used, which is what lets it run before the hook that\n // derives it.\n const visibleColumnsRef = useRef<VirtualListColumn<T>[]>([]);\n const columnResize = useColumnResize<T>({\n columns,\n visibleColumnsRef,\n resizableColumns,\n columnWidths,\n onColumnWidthsChange,\n persistColumnsKey,\n rootRef,\n });\n\n const selection = useSelection<T>({ items: rows, getKey, multiSelect, selectedKeys, onSelectionChange });\n const { effectiveSelectedKeys } = selection;\n\n const cols = useColumns<T>({\n columns,\n items: rows,\n multiSelect,\n visibleColumnKeys,\n onVisibleColumnsChange,\n persistColumnsKey,\n showColumnToggle,\n userWidths: columnResize.widths,\n isFrozen: columnResize.isFrozen,\n rootRef,\n startIndex,\n endIndex,\n containerHeight: win.containerHeight,\n containerWidth: win.containerWidth,\n isLoading,\n });\n // The rest of `cols` is the header's business and goes there whole.\n const { hasColumns, visibleColumns, columnTemplate, widthVars, shouldShowToggle, recalculateColumns } = cols;\n visibleColumnsRef.current = visibleColumns;\n\n // Compose the forwarded handle from the hooks that own each piece: scroll\n // methods from the windowing engine, column controls from the column hooks.\n useImperativeHandle(ref, () => ({ ...win.scrollApi, recalculateColumns, resetColumnWidths: columnResize.reset }), [\n win.scrollApi,\n recalculateColumns,\n columnResize.reset,\n ]);\n\n // Dragging a row to position N is meaningless while the list decides the\n // order: the drop would be undone by the next sort pass.\n const dragBlockedBySort = isSorted && (reorderable || treeReorder);\n useEffect(() => {\n if (dragBlockedBySort)\n console.warn('VirtualList: row reordering is disabled while the list is sorted. Clear the sort to drag rows.');\n }, [dragBlockedBySort]);\n\n const drag = useRowDrag({\n reorderable: reorderable && !isSorted,\n treeReorder: treeReorder && !isSorted,\n onReorder,\n onTreeDrop,\n longPressDelay,\n dragHandle,\n treeIndentPx,\n keys,\n depths,\n isGroupAt,\n rootRef,\n keysRef,\n getVisibleRect: win.getVisibleRect,\n scrollBy: win.scrollBy,\n });\n\n const listId = useId();\n const rowDomId = (index: number) => `${listId}-row-${index}`;\n const nav = useRowNavigation({\n enabled: keyboardNavigation,\n tabAcrossRows,\n rowCount: rows.length,\n offsets,\n rootRef,\n headerRef,\n getVisibleSlice: win.getVisibleSlice,\n viewportHeight: win.viewportHeight,\n scrollToIndex: win.scrollApi.scrollToIndex,\n initialIndex: selectedKey != null ? Math.max(0, keys.indexOf(selectedKey)) : 0,\n multiSelect,\n onActivate: onRowClick && ((index: number) => onRowClick(rows[index], index)),\n onToggleSelect: (index) => selection.handleRowSelect(keys[index]),\n });\n\n const rowPaddingValue = typeof rowPadding === 'number' ? `${rowPadding}px` : rowPadding;\n\n const renderRowAt = (i: number) => {\n const item = rows[i];\n const key = getKey(item, i);\n const isSelected = (selectedKey != null && key === selectedKey) || effectiveSelectedKeys.has(key);\n const dragProps = drag.getRowDragProps(i, key);\n\n return (\n <VirtualRow\n key={key}\n id={rowDomId(i)}\n index={i}\n top={offsets[i]}\n padding={rowPaddingValue}\n columnTemplate={columnTemplate}\n isSelected={isSelected}\n isActive={i === nav.activeIndex}\n manageTabStops={keyboardNavigation}\n isEntered={i === nav.enteredIndex}\n activeClassName={classNames?.activeRow}\n activeStyle={slotStyles?.activeRow}\n showDivider={showDivider}\n hover={rowHover}\n onClick={() => {\n if (drag.consumeSuppressedClick()) return;\n // Keep the arrows where the pointer last was.\n nav.setActiveIndex(i);\n onRowClick?.(item, i);\n }}\n observer={rowObserver}\n ariaRowIndex={hasColumns ? i + 2 : undefined}\n ariaPosInSet={hasColumns ? undefined : i + 1}\n ariaSetSize={hasColumns ? undefined : rows.length}\n rowRole={ariaRoles?.row ?? (hasColumns ? 'row' : 'listitem')}\n reserveToggleGutter={shouldShowToggle}\n {...dragProps}\n dropIndicatorClassName={classNames?.dropIndicator}\n dropIndicatorStyle={slotStyles?.dropIndicator}\n rowClassName={classNames?.row}\n rowStyle={slotStyles?.row}\n >\n {hasColumns ? (\n <>\n {multiSelect && (\n <div\n className={cx(styles.virtualListSelectCell, classNames?.selectCell)}\n style={slotStyles?.selectCell}\n role='gridcell'\n onClick={(e) => e.stopPropagation()}\n >\n <Checkbox\n selected={effectiveSelectedKeys.has(key)}\n onToggle={() => selection.handleRowSelect(key)}\n aria-label={text.selectRow}\n // One tab stop per rendered row, changing as the list scrolls,\n // would strand the focus the moment a row unmounts. Space on\n // the active row is the keyboard path instead.\n tabIndex={-1}\n />\n </div>\n )}\n {visibleColumns.map((col) => (\n <div\n key={col.key}\n className={cx(styles.virtualListCell, classNames?.cell)}\n style={slotStyles?.cell}\n role='gridcell'\n data-col-key={col.key}\n data-fit-key={col.width.type === 'fit' ? col.key : undefined}\n >\n {col.renderCell(item, i)}\n </div>\n ))}\n </>\n ) : (\n renderRow?.(item, i)\n )}\n </VirtualRow>\n );\n };\n\n const visibleRows: React.ReactNode[] = [];\n for (let i = startIndex; i <= endIndex; i++) visibleRows.push(renderRowAt(i));\n\n // Keep the row being touch-dragged mounted even if it scrolls out of the\n // window. Touch events are dispatched to the element under the finger at\n // touchstart; if that row unmounts, the browser stops delivering touchmove\n // and the drag freezes. Pinning its DOM node keeps the gesture alive.\n const draggedIndex = drag.draggingKey != null ? keys.indexOf(drag.draggingKey) : -1;\n if (draggedIndex >= 0 && (draggedIndex < startIndex || draggedIndex > endIndex)) {\n visibleRows.push(renderRowAt(draggedIndex));\n }\n\n // Same reason, one step further: the row being used holds the focus, and\n // unmounting it would drop that focus to the document body mid-scroll.\n if (nav.enteredIndex >= 0 && (nav.enteredIndex < startIndex || nav.enteredIndex > endIndex)) {\n visibleRows.push(renderRowAt(nav.enteredIndex));\n }\n\n // Navigation must not replace handlers the consumer passed. Theirs runs first,\n // and a `preventDefault` in it keeps the event: the navigation handlers all\n // check `defaultPrevented`.\n const containerProps = {\n ...nav.containerProps,\n onKeyDown: chain<React.KeyboardEvent<HTMLDivElement>>(rest.onKeyDown, nav.containerProps.onKeyDown),\n onFocus: chain<React.FocusEvent<HTMLDivElement>>(rest.onFocus, nav.containerProps.onFocus),\n onBlur: chain<React.FocusEvent<HTMLDivElement>>(rest.onBlur, nav.containerProps.onBlur),\n };\n\n return (\n <div\n {...rest}\n {...containerProps}\n ref={rootRef}\n className={cx(\n styles.virtualList,\n win.isWindowed && styles.virtualListWindowed,\n drag.draggingKey != null && styles.virtualListReordering,\n columnResize.isResizing && styles.virtualListResizing,\n className,\n classNames?.root,\n )}\n style={{ ...(win.isWindowed ? null : { height }), ...widthVars, ...style, ...slotStyles?.root }}\n role={ariaRoles?.container ?? (hasColumns ? 'grid' : 'list')}\n aria-rowcount={hasColumns ? rows.length + 1 : undefined}\n // Only a composite role may point at an active descendant, and the default\n // `list` is not one. In list mode, pass `ariaRoles` (e.g. listbox/option)\n // to have the active row announced.\n aria-activedescendant={\n nav.activeIndex >= 0 && (ariaRoles?.container ?? (hasColumns ? 'grid' : 'list')) !== 'list'\n ? rowDomId(nav.activeIndex)\n : undefined\n }\n >\n {hasColumns && (\n <VirtualListHeader<T>\n headerRef={headerRef}\n columns={columns!}\n cols={cols}\n resize={columnResize}\n sort={effectiveSort}\n onSort={handleHeaderSort}\n multiSelect={multiSelect}\n allSelected={selection.allSelected}\n someSelected={selection.someSelected}\n onSelectAll={selection.handleSelectAll}\n labels={text}\n classNames={classNames}\n styles={slotStyles}\n />\n )}\n {isLoading ? (\n <div className={styles.virtualListLoadingFull}>\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n ) : (\n <div\n className={cx(styles.virtualListBody, classNames?.body)}\n style={{ height: totalHeight, ...slotStyles?.body }}\n >\n {visibleRows}\n </div>\n )}\n {win.showLoadMoreSpinner && (\n <div\n className={cx(styles.virtualListLoadingIndicator, classNames?.loadingIndicator)}\n style={slotStyles?.loadingIndicator}\n >\n <SpinnerLoading className={styles.virtualListSpinner} />\n </div>\n )}\n </div>\n );\n}\n\nexport default VirtualList;\n"],"mappings":"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"}