@12-apps/ui 6.39.9 → 6.39.11
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.
- package/dist/_internal/components/data-display/Badge/Badge.js +2 -2
- package/dist/_internal/components/data-display/CardKit/CardKebab.js +3 -3
- package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +4 -4
- package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +4 -4
- package/dist/_internal/components/data-display/Chart/Chart.js +4 -4
- package/dist/_internal/components/data-display/Chart/SpecChart.js +5 -5
- package/dist/_internal/components/data-display/Chart/chart-context.js +2 -2
- package/dist/_internal/components/data-display/Chart/chart-renderers.js +3 -3
- package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +30 -30
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +40 -40
- package/dist/_internal/components/data-display/DataViews/DeleteViewDialog.js +3 -3
- package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +5 -5
- package/dist/_internal/components/data-display/DataViews/SaveViewModal.js +4 -4
- package/dist/_internal/components/data-display/DataViews/base-list-card.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-category-pill.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-columns.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-empty.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-export.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-filter-panel.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-grid-bodies.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-grid-helpers.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +27 -27
- package/dist/_internal/components/data-display/DataViews/data-views-inline-bar.js +8 -8
- package/dist/_internal/components/data-display/DataViews/data-views-more-fields.js +1 -1
- package/dist/_internal/components/data-display/DataViews/data-views-more-filters.js +4 -4
- package/dist/_internal/components/data-display/DataViews/data-views-scope-tabs.js +2 -2
- package/dist/_internal/components/data-display/DataViews/data-views-select-all-strip.js +3 -3
- package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +10 -10
- package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +9 -9
- package/dist/_internal/components/data-display/DataViews/data-views-view-nav.js +3 -3
- package/dist/_internal/components/data-display/DataViews/use-data-views-state.js +5 -5
- package/dist/_internal/components/data-display/FleetMap/FleetBody.js +10 -10
- package/dist/_internal/components/data-display/FleetMap/FleetCanvas.js +7 -7
- package/dist/_internal/components/data-display/FleetMap/FleetMap.js +11 -11
- package/dist/_internal/components/data-display/FleetMap/FleetRoster.js +2 -2
- package/dist/_internal/components/data-display/HoverCard/HoverCard.hooks.js +1 -1
- package/dist/_internal/components/data-display/HoverCard/HoverCard.js +3 -3
- package/dist/_internal/components/data-display/Lightbox/Lightbox.js +2 -2
- package/dist/_internal/components/data-display/MapPreview/MapLayers.js +2 -2
- package/dist/_internal/components/data-display/MapPreview/MapPreview.js +6 -6
- package/dist/_internal/components/data-display/Sheet/Sheet.hooks.js +2 -2
- package/dist/_internal/components/data-display/Sheet/Sheet.js +3 -3
- package/dist/_internal/components/data-display/Table/Table.js +3 -3
- package/dist/_internal/components/data-display/index.js +29 -29
- package/dist/_internal/components/feedback/ConfirmAction/ConfirmButton.js +3 -3
- package/dist/_internal/components/feedback/Modal/Modal.js +2 -2
- package/dist/_internal/components/feedback/NotificationStatusNotice/NotificationStatusNotice.js +3 -3
- package/dist/_internal/components/feedback/index.js +9 -9
- package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.hooks.js +2 -2
- package/dist/_internal/components/form/AddressAutocomplete/AddressAutocomplete.js +6 -6
- package/dist/_internal/components/form/AddressAutocomplete/AddressAutocompleteField.js +3 -3
- package/dist/_internal/components/form/Button/Button.js +2 -2
- package/dist/_internal/components/form/Calendar/Calendar.hooks.js +2 -2
- package/dist/_internal/components/form/Calendar/Calendar.js +3 -3
- package/dist/_internal/components/form/CategorySelect/CategorySelect.js +2 -2
- package/dist/_internal/components/form/CepField/CepField.js +2 -2
- package/dist/_internal/components/form/CodeEditor/CodeEditor.js +2 -2
- package/dist/_internal/components/form/DateRangePicker/DateRangePicker.js +4 -4
- package/dist/_internal/components/form/Input/Input.js +1 -1
- package/dist/_internal/components/form/InputOTP/InputOTP.js +2 -2
- package/dist/_internal/components/form/Menubar/Menubar.js +2 -2
- package/dist/_internal/components/form/UploadButton/UploadButton.js +2 -2
- package/dist/_internal/components/form/index.js +22 -22
- package/dist/_internal/components/form/total-form/Fields/CategoryField.js +3 -3
- package/dist/_internal/components/form/total-form/Fields/CepField.js +3 -3
- package/dist/_internal/components/form/total-form/Fields/TextField.js +2 -2
- package/dist/_internal/components/form/total-form/Fields/index.js +11 -11
- package/dist/_internal/components/form/total-form/SubmitButton.js +3 -3
- package/dist/_internal/components/index.js +64 -64
- package/dist/_internal/components/layout/ScrollArea/ScrollArea.js +2 -2
- package/dist/_internal/components/layout/SettingsLayout/SettingsLayout.js +5 -4
- package/dist/_internal/components/layout/SettingsLayout/SettingsPanel.js +3 -2
- package/dist/_internal/components/layout/SettingsLayout/SettingsSectionChips.js +2 -1
- package/dist/_internal/components/layout/SettingsLayout/SettingsSectionChips.scroll.js +8 -0
- package/dist/_internal/components/layout/SettingsLayout/SettingsSectionChips.scroll.js.map +1 -0
- package/dist/_internal/components/layout/index.js +5 -5
- package/dist/_internal/components/navigation/AppHeader/AppHeader.details.js +6 -6
- package/dist/_internal/components/navigation/index.js +6 -6
- package/dist/_internal/social-login-button/social-login-button.js +3 -3
- package/dist/_internal/user-avatar/user-avatar.js +3 -3
- package/dist/charts.js +5 -5
- package/dist/{chunk-Y2HPRRVP.js → chunk-3333KCAW.js} +2 -2
- package/dist/{chunk-3RHZBO7L.js → chunk-36JKV7CA.js} +2 -2
- package/dist/{chunk-QGFBIUOK.js → chunk-3CTQZDSD.js} +2 -2
- package/dist/{chunk-R2WMSXQR.js → chunk-4BNV5AAI.js} +7 -7
- package/dist/{chunk-OHC3F7GJ.js → chunk-4NPPO63U.js} +6 -6
- package/dist/{chunk-ZXIUIIUE.js → chunk-4WF6ZLIW.js} +3 -3
- package/dist/{chunk-6WLHEZXI.js → chunk-4Y6W7BT4.js} +4 -4
- package/dist/{chunk-5KGABAQB.js → chunk-5752357K.js} +4 -4
- package/dist/{chunk-TYNK662L.js → chunk-5CQNBMLK.js} +2 -2
- package/dist/{chunk-3PQ5MZMJ.js → chunk-5FAI25JQ.js} +2 -2
- package/dist/{chunk-YMG4CXGI.js → chunk-5KWHINO5.js} +2 -2
- package/dist/{chunk-TT3HZRU6.js → chunk-5QD6YGZ2.js} +9 -9
- package/dist/{chunk-JQ3D5QP6.js → chunk-5WT4YWXC.js} +3 -3
- package/dist/{chunk-VIUDWOV6.js → chunk-7D2C4LK2.js} +5 -5
- package/dist/{chunk-6QXFBZ7Q.js → chunk-7ICK5MXV.js} +4 -4
- package/dist/{chunk-F6D6HXZU.js → chunk-7X7ZHJHM.js} +2 -2
- package/dist/{chunk-WYUBTRRV.js → chunk-AXKLCWFR.js} +15 -15
- package/dist/{chunk-34BZXJGT.js → chunk-BKASAYQL.js} +4 -4
- package/dist/{chunk-UAVSBSV5.js → chunk-BUPDBEBA.js} +2 -2
- package/dist/{chunk-DIM6SUGY.js → chunk-CMV6BRQ3.js} +18 -9
- package/dist/chunk-CMV6BRQ3.js.map +1 -0
- package/dist/{chunk-KBZFBHLO.js → chunk-DXZOSP6P.js} +3 -3
- package/dist/{chunk-4GWHYLEB.js → chunk-DYGWKZ7T.js} +2 -2
- package/dist/{chunk-PK6TRXV4.js → chunk-ECIHOZPK.js} +3 -3
- package/dist/{chunk-QU3JX53V.js → chunk-ENRBXB6L.js} +6 -6
- package/dist/{chunk-FWSMIUWA.js → chunk-ESCF5VMQ.js} +2 -2
- package/dist/{chunk-QI5CRSOW.js → chunk-F4KYBBTY.js} +4 -4
- package/dist/{chunk-NCEQWEXT.js → chunk-FNO5TWBJ.js} +4 -4
- package/dist/{chunk-S6IWKDWT.js → chunk-GC56IWIZ.js} +2 -2
- package/dist/{chunk-2EAAWOZ2.js → chunk-HM2JJNUE.js} +4 -4
- package/dist/{chunk-OOCREE37.js → chunk-HMAPQV4C.js} +2 -2
- package/dist/{chunk-2KWYNFVE.js → chunk-HUPR5JXY.js} +4 -4
- package/dist/{chunk-JSD5TFDK.js → chunk-I5ZVCY2N.js} +7 -7
- package/dist/{chunk-F4WFUQYR.js → chunk-IFS3BRRL.js} +5 -5
- package/dist/{chunk-QS2DCLG6.js → chunk-INMKA5SF.js} +3 -3
- package/dist/{chunk-3GTMGPNQ.js → chunk-IQJCLFF4.js} +5 -5
- package/dist/{chunk-2ZRZTGWP.js → chunk-ISW5K72V.js} +5 -5
- package/dist/{chunk-7XGHJD75.js → chunk-IYF246UA.js} +3 -3
- package/dist/{chunk-EDKLXA5W.js → chunk-J3XFUCCR.js} +3 -3
- package/dist/{chunk-TTZPO345.js → chunk-JGRBHNK6.js} +6 -6
- package/dist/{chunk-VURQZBFH.js → chunk-JRJCBDGN.js} +16 -3
- package/dist/chunk-JRJCBDGN.js.map +1 -0
- package/dist/{chunk-GS3RP2QE.js → chunk-KKN4LLVE.js} +2 -2
- package/dist/{chunk-QFTBXHOY.js → chunk-LH46NYGR.js} +2 -2
- package/dist/{chunk-56JF5PZK.js → chunk-LYVTJLCF.js} +4 -4
- package/dist/{chunk-PZ7PU4AP.js → chunk-M2Q5CLPU.js} +4 -4
- package/dist/{chunk-5L7N5HER.js → chunk-M4KVKQRN.js} +2 -2
- package/dist/{chunk-ERY6D2OV.js → chunk-MWPXKZ7O.js} +4 -4
- package/dist/{chunk-XWZJLXJT.js → chunk-N7LPVR7P.js} +36 -10
- package/dist/chunk-N7LPVR7P.js.map +1 -0
- package/dist/{chunk-3AOQBR7N.js → chunk-OAW6GCS3.js} +2 -2
- package/dist/{chunk-6N2EDWMG.js → chunk-OGB25P3N.js} +4 -4
- package/dist/{chunk-R53ZOW4B.js → chunk-PARTZ7XI.js} +2 -2
- package/dist/{chunk-7GHYCQCJ.js → chunk-PCLKFR27.js} +4 -4
- package/dist/{chunk-K6VKL62Z.js → chunk-PG5C2AA4.js} +5 -5
- package/dist/{chunk-R3JYC63P.js → chunk-QXSV7Z57.js} +2 -2
- package/dist/{chunk-SNYJSACF.js → chunk-S6JHTEV4.js} +2 -2
- package/dist/{chunk-5JCGBCFJ.js → chunk-SAOCDKNK.js} +4 -4
- package/dist/chunk-SLJAQA2X.js +82 -0
- package/dist/chunk-SLJAQA2X.js.map +1 -0
- package/dist/{chunk-XG7NTGUH.js → chunk-T3DNYPEA.js} +4 -4
- package/dist/{chunk-JU4MSYYH.js → chunk-T6EKPFKZ.js} +2 -2
- package/dist/{chunk-2XX45PC5.js → chunk-TGXCG62P.js} +2 -2
- package/dist/{chunk-Q2YNXJEL.js → chunk-TZBOWSAM.js} +5 -5
- package/dist/{chunk-D4FZ3ANG.js → chunk-UKX3MPB7.js} +2 -2
- package/dist/{chunk-TXGB2WSW.js → chunk-VJ2BNJ5M.js} +2 -2
- package/dist/{chunk-SXYIXCUB.js → chunk-VNWZWQF3.js} +41 -23
- package/dist/chunk-VNWZWQF3.js.map +1 -0
- package/dist/{chunk-BWHOCNGW.js → chunk-VPPIODKX.js} +2 -2
- package/dist/{chunk-4B5XUQQS.js → chunk-WB6ZU3B3.js} +3 -3
- package/dist/{chunk-W7F2QIAT.js → chunk-WCRGSMLV.js} +3 -3
- package/dist/{chunk-G62KCU3D.js → chunk-WL6N33AB.js} +2 -2
- package/dist/{chunk-TKKQ7RGT.js → chunk-WZNTZX5N.js} +2 -2
- package/dist/{chunk-KE473LK6.js → chunk-YMTZN54Q.js} +2 -2
- package/dist/{chunk-SSS64KYB.js → chunk-Z43N7ASP.js} +2 -2
- package/dist/{chunk-JGJFL3PF.js → chunk-ZNDDILNQ.js} +4 -4
- package/dist/{chunk-WC3W36AM.js → chunk-ZOUWHGV3.js} +4 -4
- package/dist/data-display/Badge.js +2 -2
- package/dist/data-display/CardKit.js +8 -8
- package/dist/data-display/Chart.js +5 -5
- package/dist/data-display/DataViews.js +52 -52
- package/dist/data-display/FleetMap.js +11 -11
- package/dist/data-display/HoverCard.js +3 -3
- package/dist/data-display/Lightbox.js +2 -2
- package/dist/data-display/MapPreview.js +6 -6
- package/dist/data-display/Sheet.js +3 -3
- package/dist/data-display/Table.js +3 -3
- package/dist/feedback/ConfirmAction.js +6 -6
- package/dist/feedback/Modal.js +2 -2
- package/dist/feedback/NotificationStatusNotice.js +3 -3
- package/dist/form/AddressAutocomplete.js +6 -6
- package/dist/form/Button.js +2 -2
- package/dist/form/Calendar.js +3 -3
- package/dist/form/CategorySelect.js +2 -2
- package/dist/form/CepField.js +2 -2
- package/dist/form/CodeEditor.js +2 -2
- package/dist/form/DateRangePicker.js +4 -4
- package/dist/form/Input.js +1 -1
- package/dist/form/InputOTP.js +2 -2
- package/dist/form/Menubar.js +2 -2
- package/dist/form/UploadButton.js +2 -2
- package/dist/form/total-form.js +12 -12
- package/dist/layout/ContentToolbar.js +3 -3
- package/dist/layout/ScrollArea.js +2 -2
- package/dist/layout/SettingsLayout.js +5 -4
- package/dist/navigation/AppHeader.js +6 -6
- package/dist/social-login-button.js +3 -3
- package/dist/types/components/data-display/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/types/components/data-display/HoverCard/HoverCard.hooks.d.ts +1 -0
- package/dist/types/components/data-display/HoverCard/HoverCard.hooks.d.ts.map +1 -1
- package/dist/types/components/form/Input/Input.d.ts.map +1 -1
- package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.d.ts.map +1 -1
- package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.scroll.d.ts +64 -0
- package/dist/types/components/layout/SettingsLayout/SettingsSectionChips.scroll.d.ts.map +1 -0
- package/dist/user-avatar.js +3 -3
- package/package.json +1 -1
- package/src/components/data-display/HoverCard/HoverCard.hooks.ts +80 -25
- package/src/components/data-display/HoverCard/HoverCard.md +1 -0
- package/src/components/data-display/HoverCard/HoverCard.tsx +11 -2
- package/src/components/form/Input/Input.tsx +40 -1
- package/src/components/layout/SettingsLayout/SettingsLayout.md +3 -1
- package/src/components/layout/SettingsLayout/SettingsSectionChips.scroll.ts +180 -0
- package/src/components/layout/SettingsLayout/SettingsSectionChips.tsx +73 -15
- package/dist/chunk-DIM6SUGY.js.map +0 -1
- package/dist/chunk-SXYIXCUB.js.map +0 -1
- package/dist/chunk-VURQZBFH.js.map +0 -1
- package/dist/chunk-XWZJLXJT.js.map +0 -1
- /package/dist/{chunk-Y2HPRRVP.js.map → chunk-3333KCAW.js.map} +0 -0
- /package/dist/{chunk-3RHZBO7L.js.map → chunk-36JKV7CA.js.map} +0 -0
- /package/dist/{chunk-QGFBIUOK.js.map → chunk-3CTQZDSD.js.map} +0 -0
- /package/dist/{chunk-R2WMSXQR.js.map → chunk-4BNV5AAI.js.map} +0 -0
- /package/dist/{chunk-OHC3F7GJ.js.map → chunk-4NPPO63U.js.map} +0 -0
- /package/dist/{chunk-ZXIUIIUE.js.map → chunk-4WF6ZLIW.js.map} +0 -0
- /package/dist/{chunk-6WLHEZXI.js.map → chunk-4Y6W7BT4.js.map} +0 -0
- /package/dist/{chunk-5KGABAQB.js.map → chunk-5752357K.js.map} +0 -0
- /package/dist/{chunk-TYNK662L.js.map → chunk-5CQNBMLK.js.map} +0 -0
- /package/dist/{chunk-3PQ5MZMJ.js.map → chunk-5FAI25JQ.js.map} +0 -0
- /package/dist/{chunk-YMG4CXGI.js.map → chunk-5KWHINO5.js.map} +0 -0
- /package/dist/{chunk-TT3HZRU6.js.map → chunk-5QD6YGZ2.js.map} +0 -0
- /package/dist/{chunk-JQ3D5QP6.js.map → chunk-5WT4YWXC.js.map} +0 -0
- /package/dist/{chunk-VIUDWOV6.js.map → chunk-7D2C4LK2.js.map} +0 -0
- /package/dist/{chunk-6QXFBZ7Q.js.map → chunk-7ICK5MXV.js.map} +0 -0
- /package/dist/{chunk-F6D6HXZU.js.map → chunk-7X7ZHJHM.js.map} +0 -0
- /package/dist/{chunk-WYUBTRRV.js.map → chunk-AXKLCWFR.js.map} +0 -0
- /package/dist/{chunk-34BZXJGT.js.map → chunk-BKASAYQL.js.map} +0 -0
- /package/dist/{chunk-UAVSBSV5.js.map → chunk-BUPDBEBA.js.map} +0 -0
- /package/dist/{chunk-KBZFBHLO.js.map → chunk-DXZOSP6P.js.map} +0 -0
- /package/dist/{chunk-4GWHYLEB.js.map → chunk-DYGWKZ7T.js.map} +0 -0
- /package/dist/{chunk-PK6TRXV4.js.map → chunk-ECIHOZPK.js.map} +0 -0
- /package/dist/{chunk-QU3JX53V.js.map → chunk-ENRBXB6L.js.map} +0 -0
- /package/dist/{chunk-FWSMIUWA.js.map → chunk-ESCF5VMQ.js.map} +0 -0
- /package/dist/{chunk-QI5CRSOW.js.map → chunk-F4KYBBTY.js.map} +0 -0
- /package/dist/{chunk-NCEQWEXT.js.map → chunk-FNO5TWBJ.js.map} +0 -0
- /package/dist/{chunk-S6IWKDWT.js.map → chunk-GC56IWIZ.js.map} +0 -0
- /package/dist/{chunk-2EAAWOZ2.js.map → chunk-HM2JJNUE.js.map} +0 -0
- /package/dist/{chunk-OOCREE37.js.map → chunk-HMAPQV4C.js.map} +0 -0
- /package/dist/{chunk-2KWYNFVE.js.map → chunk-HUPR5JXY.js.map} +0 -0
- /package/dist/{chunk-JSD5TFDK.js.map → chunk-I5ZVCY2N.js.map} +0 -0
- /package/dist/{chunk-F4WFUQYR.js.map → chunk-IFS3BRRL.js.map} +0 -0
- /package/dist/{chunk-QS2DCLG6.js.map → chunk-INMKA5SF.js.map} +0 -0
- /package/dist/{chunk-3GTMGPNQ.js.map → chunk-IQJCLFF4.js.map} +0 -0
- /package/dist/{chunk-2ZRZTGWP.js.map → chunk-ISW5K72V.js.map} +0 -0
- /package/dist/{chunk-7XGHJD75.js.map → chunk-IYF246UA.js.map} +0 -0
- /package/dist/{chunk-EDKLXA5W.js.map → chunk-J3XFUCCR.js.map} +0 -0
- /package/dist/{chunk-TTZPO345.js.map → chunk-JGRBHNK6.js.map} +0 -0
- /package/dist/{chunk-GS3RP2QE.js.map → chunk-KKN4LLVE.js.map} +0 -0
- /package/dist/{chunk-QFTBXHOY.js.map → chunk-LH46NYGR.js.map} +0 -0
- /package/dist/{chunk-56JF5PZK.js.map → chunk-LYVTJLCF.js.map} +0 -0
- /package/dist/{chunk-PZ7PU4AP.js.map → chunk-M2Q5CLPU.js.map} +0 -0
- /package/dist/{chunk-5L7N5HER.js.map → chunk-M4KVKQRN.js.map} +0 -0
- /package/dist/{chunk-ERY6D2OV.js.map → chunk-MWPXKZ7O.js.map} +0 -0
- /package/dist/{chunk-3AOQBR7N.js.map → chunk-OAW6GCS3.js.map} +0 -0
- /package/dist/{chunk-6N2EDWMG.js.map → chunk-OGB25P3N.js.map} +0 -0
- /package/dist/{chunk-R53ZOW4B.js.map → chunk-PARTZ7XI.js.map} +0 -0
- /package/dist/{chunk-7GHYCQCJ.js.map → chunk-PCLKFR27.js.map} +0 -0
- /package/dist/{chunk-K6VKL62Z.js.map → chunk-PG5C2AA4.js.map} +0 -0
- /package/dist/{chunk-R3JYC63P.js.map → chunk-QXSV7Z57.js.map} +0 -0
- /package/dist/{chunk-SNYJSACF.js.map → chunk-S6JHTEV4.js.map} +0 -0
- /package/dist/{chunk-5JCGBCFJ.js.map → chunk-SAOCDKNK.js.map} +0 -0
- /package/dist/{chunk-XG7NTGUH.js.map → chunk-T3DNYPEA.js.map} +0 -0
- /package/dist/{chunk-JU4MSYYH.js.map → chunk-T6EKPFKZ.js.map} +0 -0
- /package/dist/{chunk-2XX45PC5.js.map → chunk-TGXCG62P.js.map} +0 -0
- /package/dist/{chunk-Q2YNXJEL.js.map → chunk-TZBOWSAM.js.map} +0 -0
- /package/dist/{chunk-D4FZ3ANG.js.map → chunk-UKX3MPB7.js.map} +0 -0
- /package/dist/{chunk-TXGB2WSW.js.map → chunk-VJ2BNJ5M.js.map} +0 -0
- /package/dist/{chunk-BWHOCNGW.js.map → chunk-VPPIODKX.js.map} +0 -0
- /package/dist/{chunk-4B5XUQQS.js.map → chunk-WB6ZU3B3.js.map} +0 -0
- /package/dist/{chunk-W7F2QIAT.js.map → chunk-WCRGSMLV.js.map} +0 -0
- /package/dist/{chunk-G62KCU3D.js.map → chunk-WL6N33AB.js.map} +0 -0
- /package/dist/{chunk-TKKQ7RGT.js.map → chunk-WZNTZX5N.js.map} +0 -0
- /package/dist/{chunk-KE473LK6.js.map → chunk-YMTZN54Q.js.map} +0 -0
- /package/dist/{chunk-SSS64KYB.js.map → chunk-Z43N7ASP.js.map} +0 -0
- /package/dist/{chunk-JGJFL3PF.js.map → chunk-ZNDDILNQ.js.map} +0 -0
- /package/dist/{chunk-WC3W36AM.js.map → chunk-ZOUWHGV3.js.map} +0 -0
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
|
|
3
|
+
type TimerRef = React.MutableRefObject<number | undefined>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cancel the timer a ref holds, and forget it. Every place that schedules
|
|
7
|
+
* or dismisses goes through this, so no timer outlives the ref that could
|
|
8
|
+
* cancel it (FUT-2619: an overwritten enter timer reopened a dismissed card).
|
|
9
|
+
*/
|
|
10
|
+
const clearPending = (ref: TimerRef) => {
|
|
11
|
+
if (ref.current !== undefined) window.clearTimeout(ref.current);
|
|
12
|
+
ref.current = undefined;
|
|
13
|
+
};
|
|
14
|
+
|
|
3
15
|
interface HandlerInput {
|
|
4
16
|
isTouchDevice: boolean;
|
|
5
17
|
touchEnabled: boolean;
|
|
6
18
|
enterDelay: number;
|
|
7
|
-
openCard: (
|
|
19
|
+
openCard: (trigger: HTMLElement) => void;
|
|
8
20
|
closeCard: () => void;
|
|
9
|
-
exitTimeoutRef:
|
|
21
|
+
exitTimeoutRef: TimerRef;
|
|
10
22
|
}
|
|
11
23
|
|
|
12
24
|
/**
|
|
@@ -44,7 +56,7 @@ const useHoverHandlers = ({
|
|
|
44
56
|
const triggerHandlers = {
|
|
45
57
|
onMouseEnter: (event: React.MouseEvent<HTMLElement>) => {
|
|
46
58
|
if (hoverIgnored) return;
|
|
47
|
-
openCard(event);
|
|
59
|
+
openCard(event.currentTarget);
|
|
48
60
|
},
|
|
49
61
|
onMouseLeave: () => {
|
|
50
62
|
if (hoverIgnored) return;
|
|
@@ -53,7 +65,10 @@ const useHoverHandlers = ({
|
|
|
53
65
|
onTouchStart: (event: React.TouchEvent<HTMLElement>) => {
|
|
54
66
|
if (!touchEnabled) return;
|
|
55
67
|
if (touchTimeout) window.clearTimeout(touchTimeout);
|
|
56
|
-
|
|
68
|
+
// Read NOW: React nulls `currentTarget` once the handler returns, so
|
|
69
|
+
// the long-press timer used to open the card with no anchor at all.
|
|
70
|
+
const trigger = event.currentTarget;
|
|
71
|
+
setTouchTimeout(window.setTimeout(() => openCard(trigger), enterDelay));
|
|
57
72
|
},
|
|
58
73
|
// End and cancel do the same thing: the press did not become a long press.
|
|
59
74
|
onTouchEnd: cancelTouch,
|
|
@@ -62,9 +77,7 @@ const useHoverHandlers = ({
|
|
|
62
77
|
|
|
63
78
|
/** Moving onto the card keeps it open; leaving it starts the exit delay. */
|
|
64
79
|
const cardHandlers = {
|
|
65
|
-
onMouseEnter: () =>
|
|
66
|
-
if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);
|
|
67
|
-
},
|
|
80
|
+
onMouseEnter: () => clearPending(exitTimeoutRef),
|
|
68
81
|
onMouseLeave: closeCard,
|
|
69
82
|
};
|
|
70
83
|
|
|
@@ -85,6 +98,45 @@ const useEscapeKey = (active: boolean, onEscape: () => void) => {
|
|
|
85
98
|
}, [active, onEscape]);
|
|
86
99
|
};
|
|
87
100
|
|
|
101
|
+
/**
|
|
102
|
+
* A press anywhere outside the trigger and the card dismisses it.
|
|
103
|
+
*
|
|
104
|
+
* Touch has no mouse-leave, and MUI's own click-away was the popover's
|
|
105
|
+
* backdrop, which `pointer-events: none` on the popover root (so the page
|
|
106
|
+
* under an open card stays usable) takes out of play. So this listens on the
|
|
107
|
+
* document itself, in the CAPTURE phase, where a `stopPropagation` further in
|
|
108
|
+
* cannot swallow it. It only observes: no `preventDefault`, so the tap or
|
|
109
|
+
* click still reaches whatever it landed on. Attached while open, removed on
|
|
110
|
+
* close and on unmount.
|
|
111
|
+
*/
|
|
112
|
+
const useClickAway = (
|
|
113
|
+
active: boolean,
|
|
114
|
+
inside: () => ReadonlyArray<Element | null>,
|
|
115
|
+
onAway: () => void,
|
|
116
|
+
) => {
|
|
117
|
+
React.useEffect(() => {
|
|
118
|
+
if (!active) return;
|
|
119
|
+
|
|
120
|
+
const handlePointerDown = (event: globalThis.PointerEvent) => {
|
|
121
|
+
const target = event.target as Node | null;
|
|
122
|
+
if (target && inside().some((el) => el?.contains(target))) return;
|
|
123
|
+
onAway();
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
document.addEventListener('pointerdown', handlePointerDown, true);
|
|
127
|
+
return () => document.removeEventListener('pointerdown', handlePointerDown, true);
|
|
128
|
+
}, [active, inside, onAway]);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/** Whatever is still pending when the card unmounts is cancelled with it. */
|
|
132
|
+
const useClearOnUnmount = (...refs: TimerRef[]) => {
|
|
133
|
+
React.useEffect(
|
|
134
|
+
() => () => refs.forEach(clearPending),
|
|
135
|
+
// The refs are stable objects: a mount/unmount pair, as before.
|
|
136
|
+
[],
|
|
137
|
+
);
|
|
138
|
+
};
|
|
139
|
+
|
|
88
140
|
interface HoverCardTimingInput {
|
|
89
141
|
disabled: boolean;
|
|
90
142
|
touchEnabled: boolean;
|
|
@@ -115,6 +167,8 @@ export const useHoverCard = ({
|
|
|
115
167
|
const [isTouchDevice, setIsTouchDevice] = React.useState(false);
|
|
116
168
|
const enterTimeoutRef = React.useRef<number | undefined>(undefined);
|
|
117
169
|
const exitTimeoutRef = React.useRef<number | undefined>(undefined);
|
|
170
|
+
/** The card's own box, inside the popover paper: a press here is not "away". */
|
|
171
|
+
const contentRef = React.useRef<HTMLDivElement | null>(null);
|
|
118
172
|
|
|
119
173
|
// Probed in an effect, not during render, so SSR and hydration stay intact.
|
|
120
174
|
React.useEffect(() => {
|
|
@@ -122,16 +176,18 @@ export const useHoverCard = ({
|
|
|
122
176
|
}, []);
|
|
123
177
|
|
|
124
178
|
const openCard = React.useCallback(
|
|
125
|
-
(
|
|
179
|
+
(trigger: HTMLElement) => {
|
|
126
180
|
if (disabled) return;
|
|
127
181
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
182
|
+
clearPending(exitTimeoutRef);
|
|
183
|
+
// A second enter before the delay ran out (a click and then a hover, or
|
|
184
|
+
// the pointer jittering across the edge) must REPLACE the pending open,
|
|
185
|
+
// not add one: an overwritten timer can no longer be cancelled, and it
|
|
186
|
+
// reopened the card after Escape had closed it.
|
|
187
|
+
clearPending(enterTimeoutRef);
|
|
131
188
|
|
|
132
|
-
const target = event.currentTarget;
|
|
133
189
|
enterTimeoutRef.current = window.setTimeout(() => {
|
|
134
|
-
setAnchorEl(
|
|
190
|
+
setAnchorEl(trigger);
|
|
135
191
|
setIsOpen(true);
|
|
136
192
|
onOpen?.();
|
|
137
193
|
}, enterDelay);
|
|
@@ -140,9 +196,8 @@ export const useHoverCard = ({
|
|
|
140
196
|
);
|
|
141
197
|
|
|
142
198
|
const closeCard = React.useCallback(() => {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
199
|
+
clearPending(enterTimeoutRef);
|
|
200
|
+
clearPending(exitTimeoutRef);
|
|
146
201
|
|
|
147
202
|
exitTimeoutRef.current = window.setTimeout(() => {
|
|
148
203
|
setIsOpen(false);
|
|
@@ -151,7 +206,11 @@ export const useHoverCard = ({
|
|
|
151
206
|
}, exitDelay);
|
|
152
207
|
}, [exitDelay, onClose]);
|
|
153
208
|
|
|
209
|
+
// Escape and the popover's own close are FINAL: nothing still pending may
|
|
210
|
+
// reopen the card behind them. It opens again on the next enter.
|
|
154
211
|
const handleClose = React.useCallback(() => {
|
|
212
|
+
clearPending(enterTimeoutRef);
|
|
213
|
+
clearPending(exitTimeoutRef);
|
|
155
214
|
setIsOpen(false);
|
|
156
215
|
setAnchorEl(null);
|
|
157
216
|
onClose?.();
|
|
@@ -166,15 +225,11 @@ export const useHoverCard = ({
|
|
|
166
225
|
exitTimeoutRef,
|
|
167
226
|
});
|
|
168
227
|
|
|
169
|
-
|
|
228
|
+
const insideCard = React.useCallback(() => [anchorEl, contentRef.current], [anchorEl]);
|
|
170
229
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);
|
|
175
|
-
},
|
|
176
|
-
[],
|
|
177
|
-
);
|
|
230
|
+
useEscapeKey(isOpen, handleClose);
|
|
231
|
+
useClickAway(isOpen, insideCard, handleClose);
|
|
232
|
+
useClearOnUnmount(enterTimeoutRef, exitTimeoutRef);
|
|
178
233
|
|
|
179
|
-
return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers };
|
|
234
|
+
return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef };
|
|
180
235
|
};
|
|
@@ -107,6 +107,7 @@ The HoverCard component displays additional information in a card-style popup wh
|
|
|
107
107
|
## Accessibility Notes
|
|
108
108
|
|
|
109
109
|
- The component supports keyboard navigation (Escape key to close)
|
|
110
|
+
- The card is dismissed by moving the pointer off the trigger and the card (after `exitDelay`), by Escape, or by a tap or click anywhere outside them. The outside press still reaches what it landed on: the page under an open card stays usable
|
|
110
111
|
- Proper ARIA attributes are applied to the popover
|
|
111
112
|
- Focus management is handled automatically
|
|
112
113
|
- Touch support for mobile devices can be enabled/disabled
|
|
@@ -26,6 +26,15 @@ const StyledCard = styled(Card, {
|
|
|
26
26
|
const StyledPopover = styled(Popover, {
|
|
27
27
|
shouldForwardProp: (prop) => prop !== 'customAnimation',
|
|
28
28
|
})<{ customAnimation?: HoverCardAnimation }>(({ customAnimation }) => ({
|
|
29
|
+
// Only the card takes the pointer (its paper sets `pointer-events: auto`).
|
|
30
|
+
// MUI's popover root and its invisible backdrop cover the whole viewport, so
|
|
31
|
+
// while a card was open the pointer was "on the card" wherever it went: the
|
|
32
|
+
// backdrop's mouseover reached the card's onMouseEnter and cancelled the
|
|
33
|
+
// close the trigger's mouseleave had just scheduled. A card left by moving
|
|
34
|
+
// the mouse away stayed open, and the page's next click hit the backdrop
|
|
35
|
+
// instead of its target (FUT-2619). The backdrop was also the only
|
|
36
|
+
// click-away; `useClickAway` in the hooks replaces it.
|
|
37
|
+
pointerEvents: 'none',
|
|
29
38
|
// The popover only positions; StyledCard draws the surface.
|
|
30
39
|
'& .MuiPopover-paper': {
|
|
31
40
|
backgroundColor: 'transparent',
|
|
@@ -81,7 +90,7 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
|
|
|
81
90
|
...rest
|
|
82
91
|
} = withDefaults(props, DEFAULTS) as ResolvedProps;
|
|
83
92
|
|
|
84
|
-
const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers } = useHoverCard({
|
|
93
|
+
const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers, contentRef } = useHoverCard({
|
|
85
94
|
disabled,
|
|
86
95
|
touchEnabled,
|
|
87
96
|
enterDelay,
|
|
@@ -124,7 +133,7 @@ export const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props
|
|
|
124
133
|
slotProps={{ paper: { style: { pointerEvents: 'auto' } } }}
|
|
125
134
|
{...rest}
|
|
126
135
|
>
|
|
127
|
-
<Box sx={{ position: 'relative' }}>
|
|
136
|
+
<Box ref={contentRef} sx={{ position: 'relative' }}>
|
|
128
137
|
{showArrow && <ArrowContainer placement={placement} offset={offset} />}
|
|
129
138
|
<StyledCard
|
|
130
139
|
customVariant={variant}
|
|
@@ -89,6 +89,39 @@ const interactionProps = (
|
|
|
89
89
|
onClick: loading ? undefined : onClick,
|
|
90
90
|
});
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The ARIA that describes the field has to sit on the `<input>`: the text
|
|
94
|
+
* field puts every attribute it does not recognise on its root `FormControl`,
|
|
95
|
+
* a `<div>` with no role, where a screen reader never reads it.
|
|
96
|
+
*
|
|
97
|
+
* `aria-label` has ridden `inputProps` since FUT-755. `aria-describedby` and
|
|
98
|
+
* `aria-busy` fell through to the div until FUT-2619, so CepField's input
|
|
99
|
+
* pointed at nothing while its lookup status lived in a live region beside it.
|
|
100
|
+
*
|
|
101
|
+
* `inputProps` are spread onto the `<input>` AFTER the `aria-describedby` MUI
|
|
102
|
+
* writes for helper text, so a caller's ids REPLACE the helper text's: MUI's
|
|
103
|
+
* `TextField` on its own keeps only one of the two. This joins both, helper
|
|
104
|
+
* text first. The helper id is MUI's `<id>-helper-text` (the parity test pins
|
|
105
|
+
* that spelling), which is why the field's id is fixed here rather than left
|
|
106
|
+
* to the text field.
|
|
107
|
+
*/
|
|
108
|
+
function inputAriaOf(a: {
|
|
109
|
+
id: string;
|
|
110
|
+
helperText: React.ReactNode;
|
|
111
|
+
label?: string;
|
|
112
|
+
describedBy?: string;
|
|
113
|
+
busy?: InputProps['aria-busy'];
|
|
114
|
+
}): Record<string, unknown> {
|
|
115
|
+
const describedBy = [a.helperText ? `${a.id}-helper-text` : undefined, a.describedBy]
|
|
116
|
+
.filter(Boolean)
|
|
117
|
+
.join(' ');
|
|
118
|
+
return {
|
|
119
|
+
...(a.label === undefined ? {} : { 'aria-label': a.label }),
|
|
120
|
+
...(a.describedBy === undefined ? {} : { 'aria-describedby': describedBy }),
|
|
121
|
+
...(a.busy === undefined ? {} : { 'aria-busy': a.busy }),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
92
125
|
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
93
126
|
(
|
|
94
127
|
{
|
|
@@ -108,11 +141,15 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
108
141
|
onFocus,
|
|
109
142
|
onBlur,
|
|
110
143
|
'aria-label': ariaLabel,
|
|
144
|
+
'aria-describedby': ariaDescribedBy,
|
|
145
|
+
'aria-busy': ariaBusy,
|
|
111
146
|
...rest
|
|
112
147
|
},
|
|
113
148
|
ref,
|
|
114
149
|
) => {
|
|
115
150
|
const { testId: dataTestId, rest: props } = splitTestId(rest);
|
|
151
|
+
const generatedId = React.useId();
|
|
152
|
+
const id = props.id ?? generatedId;
|
|
116
153
|
return (
|
|
117
154
|
<StyledTextField
|
|
118
155
|
ref={ref}
|
|
@@ -137,10 +174,11 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
137
174
|
* no accessible name, and a source grep saying "this input is labelled"
|
|
138
175
|
* disagreed with the DOM. The reports search box and the block-title
|
|
139
176
|
* inputs were both named in source and anonymous to a screen reader.
|
|
177
|
+
* `aria-describedby` and `aria-busy` are the same case: `inputAriaOf`.
|
|
140
178
|
*/
|
|
141
179
|
inputProps={{
|
|
142
180
|
'data-testid': dataTestId,
|
|
143
|
-
...(
|
|
181
|
+
...inputAriaOf({ id, helperText, label: ariaLabel, describedBy: ariaDescribedBy, busy: ariaBusy }),
|
|
144
182
|
}}
|
|
145
183
|
InputProps={{
|
|
146
184
|
startAdornment: startAdornment && (
|
|
@@ -150,6 +188,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
150
188
|
}}
|
|
151
189
|
{...SIZE_MAP[size]}
|
|
152
190
|
{...props}
|
|
191
|
+
id={id}
|
|
153
192
|
/>
|
|
154
193
|
);
|
|
155
194
|
},
|
|
@@ -22,7 +22,9 @@ it themes automatically in light/dark.
|
|
|
22
22
|
- **Responsive** — below `railBreakpoint` the rail either stacks above the panel
|
|
23
23
|
(`switcher`) or hands the width to a list→detail drilldown (`drilldown`)
|
|
24
24
|
- **Situation markers** per item — host-resolved, with the meaning in text
|
|
25
|
-
- **Section chip strip** on narrow widths, scrolling itself to the open section
|
|
25
|
+
- **Section chip strip** on narrow widths, scrolling itself to the open section —
|
|
26
|
+
and again when a chip or the strip resizes after mount (a status marker that
|
|
27
|
+
resolves late, a web font), until the visitor scrolls the strip themselves
|
|
26
28
|
- **Agnostic** — no app-specific content; drive it with any `groups` data
|
|
27
29
|
|
|
28
30
|
## The two narrow-width shapes
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Telling the strip's own scrolls from the visitor's (FUT-2606).
|
|
3
|
+
*
|
|
4
|
+
* Split from `SettingsSectionChips.tsx` so the chips stay a component file and
|
|
5
|
+
* the rule for "who moved the strip" can be read in one place.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* How near to where the strip aimed itself it has to come to rest for the scroll
|
|
10
|
+
* to count as the strip's own. A smooth scroll lands on a device pixel, and the
|
|
11
|
+
* target it was given is fractional.
|
|
12
|
+
*/
|
|
13
|
+
const LANDED_SLACK = 1;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Where `scrollend` is missing: how long the strip has to go without a `scroll`
|
|
17
|
+
* before it counts as settled. Generous on purpose — a busy main thread can go
|
|
18
|
+
* several frames between ticks of a smooth scroll, and judging one mid-flight
|
|
19
|
+
* would read the strip's own animation as the visitor's. Too long only delays a
|
|
20
|
+
* re-centre; too short can latch the strip for good.
|
|
21
|
+
*/
|
|
22
|
+
const QUIET_BEFORE_SETTLED_MS = 250;
|
|
23
|
+
|
|
24
|
+
interface VisitorScrollWatch {
|
|
25
|
+
/** True once the visitor has moved the strip themselves. */
|
|
26
|
+
hasScrolled: () => boolean;
|
|
27
|
+
/**
|
|
28
|
+
* True while a scroll the visitor MAY be making has not come to rest yet. The
|
|
29
|
+
* strip must not scroll itself under it: that would yank a drag in progress.
|
|
30
|
+
*/
|
|
31
|
+
isUndecided: () => boolean;
|
|
32
|
+
/** Where the strip last aimed itself, in its own pixels; `NaN` before it has. */
|
|
33
|
+
aimedAt: () => number;
|
|
34
|
+
/**
|
|
35
|
+
* The strip is about to scroll itself to `left`. Records the aim and NOTHING
|
|
36
|
+
* else: it used to disarm too, and a re-centre landing between a visitor's key
|
|
37
|
+
* press (or wheel, or pan) and the first tick of the scroll it causes wiped
|
|
38
|
+
* the arming — their scroll went unclaimed, read as the strip's own. Left
|
|
39
|
+
* armed, the worst a stale arming does is put the strip's OWN next scroll
|
|
40
|
+
* under judgement, which rests on its aim, is not latched, and disarms.
|
|
41
|
+
*/
|
|
42
|
+
beforeOwnScroll: (left: number) => void;
|
|
43
|
+
/** Called once an undecided scroll has come to rest and been judged. */
|
|
44
|
+
onSettled: (listener: () => void) => void;
|
|
45
|
+
detach: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type Listeners = Array<[string, (event: Event) => void]>;
|
|
49
|
+
|
|
50
|
+
/** A wheel's sideways travel: `deltaX`, or `deltaY` with Shift held, as browsers read it. */
|
|
51
|
+
function horizontalDelta(event: Event): number {
|
|
52
|
+
if (!(event instanceof WheelEvent)) return 0;
|
|
53
|
+
return event.deltaX !== 0 ? event.deltaX : event.shiftKey ? event.deltaY : 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface WatchState {
|
|
57
|
+
pointer: boolean;
|
|
58
|
+
touch: boolean;
|
|
59
|
+
nudged: boolean;
|
|
60
|
+
undecided: boolean;
|
|
61
|
+
scrolled: boolean;
|
|
62
|
+
aimed: number;
|
|
63
|
+
quiet: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
+
settled: () => void;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Whether the strip came to rest where it last aimed itself — clamped as the browser would. */
|
|
68
|
+
function restsOnAim(strip: HTMLElement, aimed: number): boolean {
|
|
69
|
+
const reachable = Math.min(aimed, strip.scrollWidth - strip.clientWidth);
|
|
70
|
+
// Written so that `NaN` — the strip never aimed — reads as "not on aim".
|
|
71
|
+
return Math.abs(strip.scrollLeft - reachable) <= LANDED_SLACK;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Tell a scroll the VISITOR made from every other one.
|
|
76
|
+
*
|
|
77
|
+
* The `scroll` event cannot say: the strip's own `scrollTo` fires it, and so does
|
|
78
|
+
* the browser clamping a strip whose content shrank. So a scroll is only a
|
|
79
|
+
* CANDIDATE when it arrives during something only a visitor does:
|
|
80
|
+
*
|
|
81
|
+
* - a finger held on the strip (`touchstart` until `touchend`) — a drag;
|
|
82
|
+
* - a pointer held on it (`pointerdown` until `pointerup`);
|
|
83
|
+
* - a key press, a SIDEWAYS wheel, or the browser taking a pointer over to pan
|
|
84
|
+
* it (`pointercancel`), each armed until the next scroll under judgement has
|
|
85
|
+
* been judged — whoever's it turns out to be.
|
|
86
|
+
*
|
|
87
|
+
* and the verdict waits until the strip comes to REST (`scrollend`, or a quiet
|
|
88
|
+
* spell where the browser has no `scrollend`): it is the visitor's only if the
|
|
89
|
+
* strip rests somewhere other than where it last aimed itself.
|
|
90
|
+
*
|
|
91
|
+
* Judged at rest rather than per `scroll` because the strip's own smooth scroll
|
|
92
|
+
* fires `scroll` for a third of a second, and a visitor who presses Tab or puts
|
|
93
|
+
* a finger on a chip to scroll the PAGE in that window is not scrolling the
|
|
94
|
+
* strip. Every tick of that animation used to count as theirs, the strip stopped
|
|
95
|
+
* re-centring for good, and the open chip could be left clipped (FUT-2606
|
|
96
|
+
* again, by another door). A visitor who really drags or wheels the strip
|
|
97
|
+
* mid-animation cancels it, so the strip rests where THEY left it — and that
|
|
98
|
+
* still counts. The one loss: a visitor who happens to leave it within a pixel
|
|
99
|
+
* of the strip's own aim is not told apart from it, and the strip there is
|
|
100
|
+
* already where it would have put itself.
|
|
101
|
+
*
|
|
102
|
+
* A tap is a pointer held with no scroll under it, so it never counts. A
|
|
103
|
+
* vertical wheel scrolls the PAGE — the strip is `overflowY: hidden` — and is
|
|
104
|
+
* not armed at all.
|
|
105
|
+
*/
|
|
106
|
+
export function watchVisitorScroll(strip: HTMLElement): VisitorScrollWatch {
|
|
107
|
+
const view = strip.ownerDocument.defaultView;
|
|
108
|
+
const hasScrollEnd = 'onscrollend' in strip;
|
|
109
|
+
const state: WatchState = {
|
|
110
|
+
pointer: false,
|
|
111
|
+
touch: false,
|
|
112
|
+
nudged: false,
|
|
113
|
+
undecided: false,
|
|
114
|
+
scrolled: false,
|
|
115
|
+
aimed: Number.NaN,
|
|
116
|
+
quiet: undefined,
|
|
117
|
+
settled: () => undefined,
|
|
118
|
+
};
|
|
119
|
+
const set =
|
|
120
|
+
(key: 'pointer' | 'touch' | 'nudged', value: boolean) =>
|
|
121
|
+
(): void => {
|
|
122
|
+
state[key] = value;
|
|
123
|
+
};
|
|
124
|
+
const settle = (): void => {
|
|
125
|
+
// Only a scroll that was UNDER JUDGEMENT disarms. Chromium can end the
|
|
126
|
+
// strip's own smooth scroll with a `scrollend` when another scroll cuts it
|
|
127
|
+
// off, fired BEFORE that scroll's first `scroll` (seen with an instant one);
|
|
128
|
+
// disarming there would hand the visitor's scroll to nobody.
|
|
129
|
+
if (!state.undecided) return;
|
|
130
|
+
state.undecided = false;
|
|
131
|
+
state.nudged = false;
|
|
132
|
+
if (!restsOnAim(strip, state.aimed)) state.scrolled = true;
|
|
133
|
+
state.settled();
|
|
134
|
+
};
|
|
135
|
+
const onWheel = (event: Event): void => {
|
|
136
|
+
if (horizontalDelta(event) !== 0) state.nudged = true;
|
|
137
|
+
};
|
|
138
|
+
const onScroll = (): void => {
|
|
139
|
+
if (state.pointer || state.touch || state.nudged) state.undecided = true;
|
|
140
|
+
if (!state.undecided || hasScrollEnd) return;
|
|
141
|
+
// Every tick while undecided, armed or not: a quiet spell that began while
|
|
142
|
+
// the strip was still moving would judge it mid-flight, away from its aim.
|
|
143
|
+
clearTimeout(state.quiet);
|
|
144
|
+
state.quiet = setTimeout(settle, QUIET_BEFORE_SETTLED_MS);
|
|
145
|
+
};
|
|
146
|
+
const onStrip: Listeners = [
|
|
147
|
+
['pointerdown', set('pointer', true)],
|
|
148
|
+
['touchstart', set('touch', true)],
|
|
149
|
+
['pointercancel', set('nudged', true)],
|
|
150
|
+
['keydown', set('nudged', true)],
|
|
151
|
+
['wheel', onWheel],
|
|
152
|
+
['scroll', onScroll],
|
|
153
|
+
['scrollend', settle],
|
|
154
|
+
];
|
|
155
|
+
// A gesture can END off the strip, so its end is heard on the window.
|
|
156
|
+
const onView: Listeners = [
|
|
157
|
+
['pointerup', set('pointer', false)],
|
|
158
|
+
['pointercancel', set('pointer', false)],
|
|
159
|
+
['touchend', set('touch', false)],
|
|
160
|
+
['touchcancel', set('touch', false)],
|
|
161
|
+
];
|
|
162
|
+
for (const [type, listener] of onStrip) strip.addEventListener(type, listener, { passive: true });
|
|
163
|
+
for (const [type, listener] of onView) view?.addEventListener(type, listener, { passive: true });
|
|
164
|
+
return {
|
|
165
|
+
hasScrolled: () => state.scrolled,
|
|
166
|
+
isUndecided: () => state.undecided,
|
|
167
|
+
aimedAt: () => state.aimed,
|
|
168
|
+
beforeOwnScroll: (left) => {
|
|
169
|
+
state.aimed = left;
|
|
170
|
+
},
|
|
171
|
+
onSettled: (listener) => {
|
|
172
|
+
state.settled = listener;
|
|
173
|
+
},
|
|
174
|
+
detach: () => {
|
|
175
|
+
clearTimeout(state.quiet);
|
|
176
|
+
for (const [type, listener] of onStrip) strip.removeEventListener(type, listener);
|
|
177
|
+
for (const [type, listener] of onView) view?.removeEventListener(type, listener);
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
}
|
|
@@ -4,6 +4,7 @@ import Box from '@mui/material/Box/index.js';
|
|
|
4
4
|
import { alpha, useTheme, type Theme } from '@mui/material/styles/index.js';
|
|
5
5
|
import React, { useEffect, useRef, type RefObject } from 'react';
|
|
6
6
|
|
|
7
|
+
import { watchVisitorScroll } from './SettingsSectionChips.scroll';
|
|
7
8
|
import { SettingsStatusMarker } from './SettingsStatusMarker';
|
|
8
9
|
import { TOUCH_TARGET } from './SettingsLayout.styles';
|
|
9
10
|
import { rem, sxRem } from '../../../tokens/relative';
|
|
@@ -37,7 +38,38 @@ function prefersReducedMotion(): boolean {
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
*
|
|
41
|
+
* Where the strip has to scroll for `chip`'s centre to sit on its own, clamped
|
|
42
|
+
* to what the strip can scroll.
|
|
43
|
+
*
|
|
44
|
+
* Measured from the two boxes rather than from `chip.offsetLeft`. `offsetLeft`
|
|
45
|
+
* counts from the nearest POSITIONED ancestor, and the strip is not one — nor
|
|
46
|
+
* are the panel and layout around it — so it counted from whatever box the host
|
|
47
|
+
* happened to position, and every target overshot by the strip's offset inside
|
|
48
|
+
* it. The clamp hid that for the last chip; a middle one was simply off-centre.
|
|
49
|
+
* The boxes are both in viewport coordinates, so their difference is the strip's
|
|
50
|
+
* own, whatever the host wraps it in.
|
|
51
|
+
*/
|
|
52
|
+
function centredScrollLeft(strip: HTMLElement, chip: HTMLElement): number {
|
|
53
|
+
const chipBox = chip.getBoundingClientRect();
|
|
54
|
+
const visibleStart = strip.getBoundingClientRect().left + strip.clientLeft;
|
|
55
|
+
const chipCentre = chipBox.left - visibleStart + strip.scrollLeft + chipBox.width / 2;
|
|
56
|
+
const target = chipCentre - strip.clientWidth / 2;
|
|
57
|
+
return Math.max(0, Math.min(target, strip.scrollWidth - strip.clientWidth));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The chip for `id`, walked rather than selected. */
|
|
61
|
+
function findChip(strip: HTMLElement, id: string): HTMLElement | undefined {
|
|
62
|
+
// An id is host data, and a section id carrying a quote would turn a selector
|
|
63
|
+
// string into a thrown SyntaxError — which here would take the whole strip
|
|
64
|
+
// down to centre one chip.
|
|
65
|
+
return Array.from(strip.children).find(
|
|
66
|
+
(node): node is HTMLElement => node instanceof HTMLElement && node.dataset.chipId === id,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Keep the open section's chip in view, however the visitor got here — and
|
|
72
|
+
* however the strip's content settles after it mounted.
|
|
41
73
|
*
|
|
42
74
|
* Keyed on `activeItemId` in an effect rather than done in a click handler:
|
|
43
75
|
* arriving by the desktop rail, by a card, by a pasted link and by the browser's
|
|
@@ -46,9 +78,22 @@ function prefersReducedMotion(): boolean {
|
|
|
46
78
|
* the active chip off the right edge on any list longer than a screen — the
|
|
47
79
|
* visitor is told where they are by an element they cannot see.
|
|
48
80
|
*
|
|
81
|
+
* Centring ONCE is not enough (FUT-2606). A host can widen a chip after mount
|
|
82
|
+
* with the ids unchanged — a status marker whose read resolves late, a web font
|
|
83
|
+
* arriving — and a strip that fitted at mount then overflows with its last chip
|
|
84
|
+
* clipped, because the one centring saw nothing to scroll. So the strip and
|
|
85
|
+
* every chip are watched with a `ResizeObserver`, and a resize re-aims at the
|
|
86
|
+
* active chip when its target has moved.
|
|
87
|
+
*
|
|
88
|
+
* Never against the visitor, though: once they have scrolled the strip
|
|
89
|
+
* themselves, it is theirs until the open section changes
|
|
90
|
+
* ({@link watchVisitorScroll}, which also says why the strip's OWN smooth scroll
|
|
91
|
+
* never counts as theirs). A strip that yanks back mid-drag because a
|
|
92
|
+
* marker loaded is worse than one that does not centre.
|
|
93
|
+
*
|
|
49
94
|
* `itemsKey` rather than `items`: a host almost always rebuilds that array per
|
|
50
95
|
* render (`groups.flatMap(...)`), and depending on it would re-run this on every
|
|
51
|
-
* render and
|
|
96
|
+
* render and reset the visitor's latch with it.
|
|
52
97
|
*
|
|
53
98
|
* The key joins on NUL — as the ESCAPE `'\u0000'`, which it must stay. A
|
|
54
99
|
* literal NUL byte works identically at runtime and makes git classify this file
|
|
@@ -63,25 +108,38 @@ function useCentreActiveChip(
|
|
|
63
108
|
): void {
|
|
64
109
|
useEffect(() => {
|
|
65
110
|
const strip = stripRef.current;
|
|
66
|
-
if (!strip || !activeItemId) return;
|
|
67
|
-
// Walked rather than selected: an id is host data, and a section id carrying
|
|
68
|
-
// a quote would turn a selector string into a thrown SyntaxError — which
|
|
69
|
-
// here would take the whole strip down to centre one chip.
|
|
70
|
-
const chip = Array.from(strip.children).find(
|
|
71
|
-
(node): node is HTMLElement =>
|
|
72
|
-
node instanceof HTMLElement && node.dataset.chipId === activeItemId,
|
|
73
|
-
);
|
|
74
|
-
if (!chip) return;
|
|
111
|
+
if (!strip || !activeItemId) return undefined;
|
|
75
112
|
// Feature-checked, not assumed. `Element.scrollTo` is absent in jsdom and in
|
|
76
113
|
// a few embedded webviews, and an unguarded call there THROWS out of an
|
|
77
114
|
// effect — which React escalates to the nearest error boundary. The whole
|
|
78
115
|
// settings area would go down to centre one chip, and the host's own tests
|
|
79
116
|
// could not render the shell at all. Same reasoning as walking the children
|
|
80
117
|
// instead of building a selector: nothing in here is worth a crash.
|
|
81
|
-
if (typeof strip.scrollTo !== 'function') return;
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
|
|
118
|
+
if (typeof strip.scrollTo !== 'function') return undefined;
|
|
119
|
+
const visitor = watchVisitorScroll(strip);
|
|
120
|
+
const centre = (): void => {
|
|
121
|
+
const chip = findChip(strip, activeItemId);
|
|
122
|
+
// Not while a scroll that may be the visitor's is still moving: a resize
|
|
123
|
+
// mid-drag must not yank the strip. It is re-tried once that scroll rests.
|
|
124
|
+
if (!chip || visitor.hasScrolled() || visitor.isUndecided()) return;
|
|
125
|
+
const left = centredScrollLeft(strip, chip);
|
|
126
|
+
// Only when the target MOVED: a resize that leaves it where it was must not
|
|
127
|
+
// restart a smooth scroll that is already on its way there. The aim is a
|
|
128
|
+
// runtime-computed scroll offset, like the exempt `left` below — not a size.
|
|
129
|
+
if (Math.abs(left - visitor.aimedAt()) < 0.5) return;
|
|
130
|
+
visitor.beforeOwnScroll(left);
|
|
131
|
+
strip.scrollTo({ left, behavior: prefersReducedMotion() ? 'auto' : 'smooth' });
|
|
132
|
+
};
|
|
133
|
+
visitor.onSettled(centre);
|
|
134
|
+
centre();
|
|
135
|
+
// Also feature-checked: without it the strip still centres once, as before.
|
|
136
|
+
const resizes = typeof ResizeObserver === 'function' ? new ResizeObserver(centre) : undefined;
|
|
137
|
+
resizes?.observe(strip);
|
|
138
|
+
for (const child of Array.from(strip.children)) resizes?.observe(child);
|
|
139
|
+
return () => {
|
|
140
|
+
resizes?.disconnect();
|
|
141
|
+
visitor.detach();
|
|
142
|
+
};
|
|
85
143
|
}, [stripRef, activeItemId, itemsKey]);
|
|
86
144
|
}
|
|
87
145
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/HoverCard/HoverCard.tsx"],"sourcesContent":["import Box from '@mui/material/Box/index.js';\nimport Card from '@mui/material/Card/index.js';\nimport CardContent from '@mui/material/CardContent/index.js';\nimport Popover from '@mui/material/Popover/index.js';\nimport { styled, type Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { rem } from '../../../tokens/relative';\nimport { withDefaults } from '../../../utils/withDefaults';\n\nimport { HoverCardContent } from './HoverCard.content';\nimport { useHoverCard } from './HoverCard.hooks';\nimport { arrowSx, cardSx, getAnchorOrigin, getTransformOrigin, sideOf } from './HoverCard.styles';\nimport type { CardStyleFlags } from './HoverCard.styles';\nimport type { HoverCardAnimation, HoverCardPlacement, HoverCardProps } from './HoverCard.types';\n\nconst ArrowContainer = styled('div')<{ placement: HoverCardPlacement; offset?: number }>(\n ({ theme, placement }) => ({ ...arrowSx(theme, placement) }),\n);\n\nconst StyledCard = styled(Card, {\n shouldForwardProp: (prop) =>\n !['customVariant', 'glow', 'pulse', 'animation'].includes(prop as string),\n})<CardStyleFlags>(({ theme, ...flags }) => ({ ...cardSx(theme, flags) }));\n\nconst StyledPopover = styled(Popover, {\n shouldForwardProp: (prop) => prop !== 'customAnimation',\n})<{ customAnimation?: HoverCardAnimation }>(({ customAnimation }) => ({\n // The popover only positions; StyledCard draws the surface.\n '& .MuiPopover-paper': {\n backgroundColor: 'transparent',\n boxShadow: 'none',\n overflow: 'visible',\n ...(customAnimation === 'scale' && { transformOrigin: 'center' }),\n },\n}));\n\nconst DEFAULTS = {\n variant: 'default',\n glow: false,\n pulse: false,\n placement: 'bottom',\n showArrow: false,\n animation: 'fade',\n enterDelay: 700,\n exitDelay: 0,\n loading: false,\n touchEnabled: true,\n disabled: false,\n} satisfies Partial<HoverCardProps>;\n\ntype ResolvedProps = HoverCardProps & Required<Pick<HoverCardProps, keyof typeof DEFAULTS>>;\n\n/** Padding is the variant's own: detailed roomier, minimal tighter. */\nconst CONTENT_PADDING: Record<string, number> = { detailed: 3, minimal: 1.5 };\n\n/**\n * The card is nudged off the anchor on whichever side it sits — `offset` design\n * px (8 unless the caller says otherwise), through the theme's type scale.\n */\nconst offsetMargin = (theme: Theme, placement: HoverCardPlacement, offset: number | undefined) => {\n const side = sideOf(placement);\n const key = ({ top: 'mt', bottom: 'mb', left: 'ml', right: 'mr' } as const)[side];\n return { [key]: rem(theme, offset ?? 8) };\n};\n\n/**\n * The card's cap: 400 design px unless the caller says otherwise. `sx` has always\n * read a number of 1 or less as a fraction of the parent, and that stays so.\n */\nconst capOf = (theme: Theme, maxWidth: number | undefined): string => {\n const cap = maxWidth ?? 400;\n return cap <= 1 && cap !== 0 ? `${cap * 100}%` : rem(theme, cap);\n};\n\nexport const HoverCard = React.forwardRef<HTMLDivElement, HoverCardProps>((props, ref) => {\n const {\n variant, glow, pulse, title, description, avatar, trigger, placement, showArrow,\n animation, enterDelay, exitDelay, maxWidth, loading, loadingComponent, loadingText, touchEnabled,\n offset, disabled, onOpen, onClose, children, dataTestId,\n ...rest\n } = withDefaults(props, DEFAULTS) as ResolvedProps;\n\n const { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers } = useHoverCard({\n disabled,\n touchEnabled,\n enterDelay,\n exitDelay,\n onOpen,\n onClose,\n });\n\n const triggerProps = {\n ...triggerHandlers,\n style: { cursor: disabled ? 'default' : 'pointer' },\n 'data-testid': dataTestId ? `${dataTestId}-trigger` : 'hover-card-trigger',\n };\n\n const triggerElement = trigger ? (\n React.cloneElement(trigger, triggerProps)\n ) : (\n <span {...triggerProps} style={{ display: 'inline-block', ...triggerProps.style }}>\n {children}\n </span>\n );\n\n const padding = CONTENT_PADDING[variant] ?? 2;\n\n return (\n <>\n {triggerElement}\n <StyledPopover\n ref={ref}\n open={isOpen && !disabled}\n anchorEl={anchorEl}\n onClose={handleClose}\n anchorOrigin={getAnchorOrigin(placement)}\n transformOrigin={getTransformOrigin(placement)}\n {...cardHandlers}\n disableAutoFocus\n disableEnforceFocus\n disableRestoreFocus\n customAnimation={animation}\n slotProps={{ paper: { style: { pointerEvents: 'auto' } } }}\n {...rest}\n >\n <Box sx={{ position: 'relative' }}>\n {showArrow && <ArrowContainer placement={placement} offset={offset} />}\n <StyledCard\n customVariant={variant}\n glow={glow}\n pulse={pulse}\n animation={animation}\n data-testid={dataTestId || 'hover-card-content'}\n sx={(theme) => ({ maxWidth: capOf(theme, maxWidth), ...offsetMargin(theme, placement, offset) })}\n >\n <CardContent sx={{ p: padding, '&:last-child': { pb: padding } }}>\n <HoverCardContent\n loadingText={loadingText} variant={variant} title={title}\n description={description}\n avatar={avatar}\n loading={loading}\n loadingComponent={loadingComponent}\n dataTestId={dataTestId}\n >\n {children}\n </HoverCardContent>\n </CardContent>\n </StyledCard>\n </Box>\n </StyledPopover>\n </>\n );\n});\n\nHoverCard.displayName = 'HoverCard';\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/data-display/HoverCard/HoverCard.hooks.ts"],"sourcesContent":["import React from 'react';\n\ninterface HandlerInput {\n isTouchDevice: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n openCard: (event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>) => void;\n closeCard: () => void;\n exitTimeoutRef: React.MutableRefObject<number | undefined>;\n}\n\n/**\n * The pointer and touch handlers, over the open/close pair. Touch gets a\n * long-press because there is no hover to respond to; the card's own handlers\n * keep it open while the pointer is travelling onto it.\n */\nconst useHoverHandlers = ({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n}: HandlerInput) => {\n const [touchTimeout, setTouchTimeout] = React.useState<number>();\n\n /** A touch device with touch disabled has no hover to respond to. */\n const hoverIgnored = isTouchDevice && !touchEnabled;\n\n const cancelTouch = React.useCallback(() => {\n if (touchTimeout) {\n window.clearTimeout(touchTimeout);\n setTouchTimeout(undefined);\n }\n }, [touchTimeout]);\n\n React.useEffect(\n () => () => {\n if (touchTimeout) window.clearTimeout(touchTimeout);\n },\n [touchTimeout],\n );\n\n const triggerHandlers = {\n onMouseEnter: (event: React.MouseEvent<HTMLElement>) => {\n if (hoverIgnored) return;\n openCard(event);\n },\n onMouseLeave: () => {\n if (hoverIgnored) return;\n closeCard();\n },\n onTouchStart: (event: React.TouchEvent<HTMLElement>) => {\n if (!touchEnabled) return;\n if (touchTimeout) window.clearTimeout(touchTimeout);\n setTouchTimeout(window.setTimeout(() => openCard(event), enterDelay));\n },\n // End and cancel do the same thing: the press did not become a long press.\n onTouchEnd: cancelTouch,\n onTouchCancel: cancelTouch,\n };\n\n /** Moving onto the card keeps it open; leaving it starts the exit delay. */\n const cardHandlers = {\n onMouseEnter: () => {\n if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);\n },\n onMouseLeave: closeCard,\n };\n\n return { triggerHandlers, cardHandlers };\n};\n\n/** Escape dismisses the card while it is open. */\nconst useEscapeKey = (active: boolean, onEscape: () => void) => {\n React.useEffect(() => {\n if (!active) return;\n\n const handleEscape = (event: globalThis.KeyboardEvent) => {\n if (event.key === 'Escape') onEscape();\n };\n\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [active, onEscape]);\n};\n\ninterface HoverCardTimingInput {\n disabled: boolean;\n touchEnabled: boolean;\n enterDelay: number;\n exitDelay: number;\n onOpen?: () => void;\n onClose?: () => void;\n}\n\n/**\n * When the card is open, and the delays that decide it.\n *\n * Three timers are in play: the enter delay before opening, the exit delay\n * before closing (so the pointer can travel from the trigger onto the card\n * without dismissing it), and the long-press timer on touch. Each cancels the\n * others' pending work, which is why they live together.\n */\nexport const useHoverCard = ({\n disabled,\n touchEnabled,\n enterDelay,\n exitDelay,\n onOpen,\n onClose,\n}: HoverCardTimingInput) => {\n const [anchorEl, setAnchorEl] = React.useState<HTMLElement | null>(null);\n const [isOpen, setIsOpen] = React.useState(false);\n const [isTouchDevice, setIsTouchDevice] = React.useState(false);\n const enterTimeoutRef = React.useRef<number | undefined>(undefined);\n const exitTimeoutRef = React.useRef<number | undefined>(undefined);\n\n // Probed in an effect, not during render, so SSR and hydration stay intact.\n React.useEffect(() => {\n setIsTouchDevice('ontouchstart' in window);\n }, []);\n\n const openCard = React.useCallback(\n (event: React.MouseEvent<HTMLElement> | React.TouchEvent<HTMLElement>) => {\n if (disabled) return;\n\n if (exitTimeoutRef.current) {\n window.clearTimeout(exitTimeoutRef.current);\n }\n\n const target = event.currentTarget;\n enterTimeoutRef.current = window.setTimeout(() => {\n setAnchorEl(target);\n setIsOpen(true);\n onOpen?.();\n }, enterDelay);\n },\n [disabled, enterDelay, onOpen],\n );\n\n const closeCard = React.useCallback(() => {\n if (enterTimeoutRef.current) {\n window.clearTimeout(enterTimeoutRef.current);\n }\n\n exitTimeoutRef.current = window.setTimeout(() => {\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, exitDelay);\n }, [exitDelay, onClose]);\n\n const handleClose = React.useCallback(() => {\n setIsOpen(false);\n setAnchorEl(null);\n onClose?.();\n }, [onClose]);\n\n const { triggerHandlers, cardHandlers } = useHoverHandlers({\n isTouchDevice,\n touchEnabled,\n enterDelay,\n openCard,\n closeCard,\n exitTimeoutRef,\n });\n\n useEscapeKey(isOpen, handleClose);\n\n React.useEffect(\n () => () => {\n if (enterTimeoutRef.current) window.clearTimeout(enterTimeoutRef.current);\n if (exitTimeoutRef.current) window.clearTimeout(exitTimeoutRef.current);\n },\n [],\n );\n\n return { anchorEl, isOpen, handleClose, triggerHandlers, cardHandlers };\n};\n"],"mappings":"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","names":[]}
|